Skip to content

Commit 9be885b

Browse files
authored
Merge pull request #70 from carterscode/feature/ui-overhaul
feat(ui): NavigationView overhaul, Status dashboard, and a GitHub-dark palette
2 parents 127179f + 2ed2924 commit 9be885b

62 files changed

Lines changed: 5238 additions & 1130 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/beta.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: beta
2+
3+
# Manual only. There is deliberately no push trigger and no tag trigger, so this
4+
# cannot fire as a side effect of committing, merging, or tagging anything.
5+
on:
6+
workflow_dispatch:
7+
8+
# Read-only token. This workflow cannot create a release, push a tag, or write to
9+
# the repository even if a step tried to: the GITHUB_TOKEN it is handed has no
10+
# write scope. The beta EXE leaves as a workflow artifact and nothing else.
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: beta-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
beta:
20+
runs-on: windows-latest
21+
# No permissions block here either -- the job inherits the read-only set above.
22+
# release.yml needs contents/id-token/attestations write to publish; this one
23+
# intentionally has none of them.
24+
steps:
25+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
26+
with:
27+
fetch-depth: 0
28+
fetch-tags: true
29+
30+
- name: Resolve beta version
31+
id: ver
32+
shell: pwsh
33+
run: |
34+
# Same shape as dev-build.yml: a numeric base for AssemblyVersion and
35+
# FileVersion (which must stay a pure a.b.c.d), and a descriptive
36+
# InformationalVersion carrying the -beta.<sha> suffix. AppIdentity parses
37+
# the sha back out of it for the in-app BETA marker.
38+
# Stable tags only -- 'v*.*.*' also matches prerelease tags, and [int] on
39+
# a "65-beta" patch component throws.
40+
$latest = (git tag --list 'v*.*.*' --sort=-v:refname | Where-Object { $_ -notmatch '-' } | Select-Object -First 1)
41+
if ([string]::IsNullOrWhiteSpace($latest)) { $latest = 'v0.0.0' }
42+
$parts = ($latest -replace '^v','').Split('.')
43+
$patch = [int]$parts[2] + 1
44+
$base = "$($parts[0]).$($parts[1]).$patch"
45+
$sha = $env:GITHUB_SHA.Substring(0,7)
46+
$full = "$base-beta.$sha"
47+
"base=$base" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
48+
"version=$full" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
49+
"sha=$sha" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
50+
Write-Host "Building beta $full (numeric base $base) from $env:GITHUB_REF_NAME"
51+
52+
- name: Setup .NET 8
53+
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
54+
with:
55+
dotnet-version: '8.0.x'
56+
57+
- name: Restore
58+
run: dotnet restore GamerGuardian.sln
59+
60+
# A beta goes to real testers, so it does not ship without the suite passing.
61+
# This builds the default (non-BETA) flavor, which is also the check that the
62+
# non-beta path still compiles warning-free.
63+
- name: Test
64+
run: dotnet test GamerGuardian.sln --nologo
65+
66+
- name: Publish beta (self-contained, single-file, win-x64)
67+
# Identical publish flags to release.yml, plus -p:Beta=true to define the
68+
# BETA constant and an explicit InformationalVersion for the sha marker.
69+
run: >
70+
dotnet publish src/GamerGuardian/GamerGuardian.csproj
71+
-c Release
72+
-r win-x64
73+
--self-contained true
74+
-p:PublishSingleFile=true
75+
-p:IncludeNativeLibrariesForSelfExtract=true
76+
-p:EnableCompressionInSingleFile=true
77+
-p:Beta=true
78+
-p:Version=${{ steps.ver.outputs.base }}
79+
-p:AssemblyVersion=${{ steps.ver.outputs.base }}.0
80+
-p:FileVersion=${{ steps.ver.outputs.base }}.0
81+
-p:InformationalVersion=${{ steps.ver.outputs.version }}
82+
-o publish
83+
84+
# Fails the run if the update path somehow survived into a beta build.
85+
#
86+
# Scans the managed assembly, NOT publish\GamerGuardian.exe. The shipped EXE is
87+
# a single-file bundle published with EnableCompressionInSingleFile, so its
88+
# assemblies are compressed: scanning the EXE's bytes finds nothing even in a
89+
# stable build. An earlier version of this step did exactly that and passed on
90+
# both flavors, which made it false assurance rather than a check. The DLL
91+
# below is the same assembly that gets bundled into that EXE.
92+
#
93+
# build.yml proves this check can actually fail, by asserting the differential
94+
# against a stable build. Here we only assert the absolute property.
95+
- name: Verify the update path is absent from the beta assembly
96+
shell: pwsh
97+
run: |
98+
$dll = Get-ChildItem -Recurse -Path "src/GamerGuardian/bin/Release" -Filter "GamerGuardian.dll" |
99+
Select-Object -First 1
100+
if (-not $dll) { throw "Could not find the built GamerGuardian.dll to scan." }
101+
$bytes = [System.IO.File]::ReadAllBytes($dll.FullName)
102+
# .NET stores string literals as UTF-16 in the #US heap, but at arbitrary
103+
# byte offsets. Decoding from offset 0 only sees even-aligned literals; an
104+
# odd-aligned one decodes to garbage and is missed. Check both alignments.
105+
$even = [System.Text.Encoding]::Unicode.GetString($bytes, 0, $bytes.Length - ($bytes.Length % 2))
106+
$odd = [System.Text.Encoding]::Unicode.GetString($bytes, 1, $bytes.Length - 1 - (($bytes.Length - 1) % 2))
107+
$pattern = 'api\.github\.com/repos/carterscode/GamerGuardian/releases'
108+
if ([regex]::IsMatch($even, $pattern) -or [regex]::IsMatch($odd, $pattern)) {
109+
throw "BETA build still contains the update-feed URL -- the update path was not compiled out."
110+
}
111+
Write-Host "OK: no update-feed URL in $($dll.FullName)"
112+
113+
- name: List artifacts
114+
shell: pwsh
115+
run: Get-ChildItem -Recurse publish | Select-Object FullName, Length
116+
117+
# The only output. No installer is built, no release is created, no tag is
118+
# pushed, and nothing is published to the update feed.
119+
- name: Upload beta EXE artifact
120+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
121+
with:
122+
name: GamerGuardian-beta-r${{ github.run_number }}-${{ steps.ver.outputs.sha }}
123+
path: publish/GamerGuardian.exe
124+
if-no-files-found: error

.github/workflows/build.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,60 @@ jobs:
2626

2727
- name: Test
2828
run: dotnet test GamerGuardian.sln -c Release --no-build --verbosity normal
29+
30+
# beta.yml is workflow_dispatch-only by design, so without this nothing routinely
31+
# proves the BETA flavor still compiles. TreatWarningsAsErrors is on and
32+
# #if-excluded code is invisible to the other compile, so the beta path can rot
33+
# silently between manual dispatches.
34+
beta-compile:
35+
runs-on: windows-latest
36+
steps:
37+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38+
39+
- name: Setup .NET 8
40+
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
41+
with:
42+
dotnet-version: '8.0.x'
43+
44+
- name: Restore
45+
run: dotnet restore GamerGuardian.sln
46+
47+
- name: Build the BETA flavor
48+
run: dotnet build src/GamerGuardian/GamerGuardian.csproj -c Release -p:Beta=true --no-restore -o out-beta
49+
50+
# Differential proof that the update path really is absent from a beta build --
51+
# and, just as importantly, that the check is capable of failing. Asserting only
52+
# "absent in beta" would pass just as happily against a scan that can never
53+
# match anything, which is exactly the trap the previous byte-scan of the
54+
# compressed single-file EXE fell into.
55+
- name: Build the stable flavor for comparison
56+
run: dotnet build src/GamerGuardian/GamerGuardian.csproj -c Release --no-restore -o out-stable
57+
58+
- name: Assert the update path is present in stable and absent in beta
59+
shell: pwsh
60+
run: |
61+
function Test-UpdateUrl($path) {
62+
$bytes = [System.IO.File]::ReadAllBytes($path)
63+
# .NET stores string literals as UTF-16 in the #US heap, but at arbitrary
64+
# byte offsets. Decoding the file as UTF-16 from offset 0 only sees
65+
# literals that happen to start on an even boundary -- an odd-aligned one
66+
# decodes to garbage and is missed. Measured: the BETA marker literal in
67+
# this very assembly is odd-aligned while the update URL is even-aligned,
68+
# so a single-alignment scan is a coin flip. Check both.
69+
$even = [System.Text.Encoding]::Unicode.GetString($bytes, 0, $bytes.Length - ($bytes.Length % 2))
70+
$odd = [System.Text.Encoding]::Unicode.GetString($bytes, 1, $bytes.Length - 1 - (($bytes.Length - 1) % 2))
71+
$pattern = 'api\.github\.com/repos/carterscode/GamerGuardian/releases'
72+
return [regex]::IsMatch($even, $pattern) -or [regex]::IsMatch($odd, $pattern)
73+
}
74+
$stable = Test-UpdateUrl "out-stable/GamerGuardian.dll"
75+
$beta = Test-UpdateUrl "out-beta/GamerGuardian.dll"
76+
Write-Host "stable contains update URL : $stable (expected True)"
77+
Write-Host "beta contains update URL : $beta (expected False)"
78+
79+
if (-not $stable) {
80+
throw "The scan found no update URL in the STABLE build. The check is broken and would pass against anything -- fix the scan, do not trust the beta result."
81+
}
82+
if ($beta) {
83+
throw "The BETA build still contains the update-feed URL. The update path was not compiled out."
84+
}
85+
Write-Host "OK: update path present in stable, absent in beta."

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ bin/
22
obj/
33
out/
44
publish/
5+
publish-beta/
56
.tmp_*/
67
*.user
78
*.suo

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,36 @@ Versions before 1.0.0 are pre-release: features and defaults may still change.
1616

1717
## [Unreleased]
1818

19+
## [0.1.65] - 2026-08-07
20+
21+
The biggest visual change since the app was first released: the window has been
22+
rebuilt around a grouped sidebar, and it now has a home screen.
23+
24+
### Added
25+
- **A Status home screen.** Opening GamerGuardian now lands you on Status
26+
instead of the first settings tab. It leads with a single number — how many of
27+
the settings you monitor have drifted from what you asked for — and breaks
28+
that down by section, so you can see at a glance whether anything needs you.
29+
- **A "This PC" summary.** Status shows six cards describing the machine:
30+
processor, graphics, memory, Windows version, displays, and the active power
31+
plan. Each one is context for settings GamerGuardian actually manages — the
32+
CPU behind the power-plan recommendation, the GPU behind hardware-accelerated
33+
scheduling, the Windows build that decides which policies apply.
34+
- **Real memory detail.** The memory card reads your firmware directly and
35+
reports the installed total with its type, plus the module layout and speed —
36+
for example "31.2 GB DDR5" and "2 × 16 GB @ 5600 MT/s". Mismatched sticks are
37+
listed rather than averaged away, because a mismatched pair is worth noticing.
38+
- **Pause monitoring from Status.** Stopping and resuming background checks no
39+
longer means hunting through the settings.
40+
1941
### Changed
42+
- **New navigation.** The row of tabs is gone. Settings are now grouped in a
43+
sidebar under Performance, Privacy, Cleanup and Reference, so related settings
44+
sit together and the window no longer runs out of horizontal room. Everything
45+
that was in the old tabs is still there — nothing was dropped in the move.
46+
- **A GitHub-dark colour scheme.** The app is recoloured to match GitHub's dark
47+
theme: a deep navy-black canvas, cards that actually read as cards, and
48+
GitHub's green, amber and red for status. Light theme is unchanged.
2049
- **Clearer upgrade notes.** The "what's new" text shown when a new version is
2150
offered is now plain-English highlights only — no more raw list of internal
2251
pull-request titles — and Markdown formatting is cleaned up so it reads

0 commit comments

Comments
 (0)