feat(user-delight) Phase 5 PR-T1: Modern LCD token alignment#166
Merged
Conversation
Closes 9 🔴 + 4⚠️ token-drift findings from `docs/PHASE5-DRIFT-AUDIT.md` § 1 by aligning the shipped Wave 1 visual tokens to the locked Modern LCD spec (`docs/ui/themes/modern-lcd.md` § Visual system). The shipped Wave 1 chrome was structurally on-spec (MudBlazor strict + custom-for-delight per ADR-0026 § 6) but value-drifted to Material defaults: clinical greys instead of warm near-blacks, Material amber instead of arcade amber, default Roboto everywhere instead of Barlow Condensed display + Inter body + JetBrains Mono. PR-T1 lands the foundation Wave 2 components will inherit before they calcify against the wrong tokens. Changes: - PinballTheme.cs — MudPalette aligned to spec (Background #0c0b0e, Surface #161519, TextPrimary #f4f1ea, TextSecondary #9a9590, Primary #ff9a1f, Success #34d96a, Error #ff3b30, Divider #2a282d). Typography H1–H6 use Barlow Condensed (700 primary / 500 secondary); Default + Body1/2 + subtitles + caption use Inter; Roboto stays in the fallback chain. - wwwroot/app.css — :root custom properties for the spec tokens MudPalette can't model (--pw-bg-surface-hi, --pw-accent-mode, --pw-border-glow-* alpha tints) plus stable --pw-* aliases for the rest of the spec palette so custom delight-surface CSS can reference spec names independent of MudBlazor internal classes. Adds --pw-font-display / --pw-font-body / --pw-font-mono stacks, --pw-motion-* duration tokens, font-feature-settings: "tnum" site-wide, and a prefers-reduced-motion override that zeros the motion durations and applies !important animation/transition duration:0ms. - App.razor — replaces the Roboto-only Google Fonts <link> with a single combined load for Barlow Condensed (500;700), Inter (400;500;600;700), JetBrains Mono (400;500), and Roboto (300;400; 500;700) with preconnect hints. CDN delivery is the audit's recipe; self-hosting follow-up tracked separately. - PinballThemeContractTests — new contract test class pins ten PaletteLight slots to spec hex values, asserts Barlow Condensed + Roboto fallback for H1–H6 with FontWeight pinning (700 for H1–H4, 500 for H5–H6), and asserts Inter + Roboto fallback for Default + Body1/2. Mechanically prevents palette drift the next time the theme is touched. MudColor's #RRGGBBAA normalization stripped via RGB-only slice (..7) to compare against the spec's 7-char hex. Out of scope (deferred per audit § 4 / § 6): - Header / footer / nav-link rework (PR-F-chrome-nav-rework + PR-F-chrome-footer follow with file-disjoint scope). - TiltErrorBoundary red-border + display-typography refresh — --pw-accent-refusal token now exists; consuming CSS rule lands with that component's Wave 2 polish. - Theme-state service + theme picker (Wave 3 obligation). - Self-hosted fonts (follow-up task spawned for GDPR / IP-leak posture; PR-T1 ships with Google Fonts CDN per audit recipe). Tests: 1127 → 1137 (+10 PinballThemeContractTests). Web build clean, solution build clean (0 warnings, 0 errors). Local review: 2 🔴 findings (1 fixed in this PR — added --pw-* spec token aliases including --pw-accent-refusal; 1 deferred — Google Fonts CDN → self-hosted, follow-up task spawned), 3⚠️ findings (all fixed: expanded contract test coverage to AppbarBackground/DrawerBackground + H1–H6 FontWeight, corrected misleading "warmer" comment). Mechanical 10-item self-audit: clean (most items N/A for a pure- theming PR; identity verified personal-noreply).
5 tasks
jkeeley2073
added a commit
that referenced
this pull request
May 10, 2026
…o, Roboto) (#172) Removes the Google Fonts CDN dependency from PinballWizard.Web and ships twelve woff2 weights as static web assets under wwwroot/fonts/, with @font-face declarations + font-display: swap in app.css. Why: PinballWizard is a customer-facing showcase / reference app. The LG Munchen I ruling (Az. 3 O 17493/20, January 2022) classified Google Fonts CDN as a GDPR violation because it leaks visitor IPs to Google on every page load. Per CLAUDE.md section Showcase obligations, the demo must not exhibit patterns a privacy-aware prospect would flag. PR-T1's local review (#166) flagged this as a critical finding and deferred the fix to this scoped task because self-hosting expands scope beyond a token-alignment recipe. Provenance: woff2 binaries taken verbatim from the @fontsource v5.2.5 npm packages on jsdelivr (which re-package the canonical upstream font files unmodified). Each family ships its upstream LICENSE.txt with the original copyright holder preserved (Inter Project Authors, Barlow Project Authors, JetBrains Mono Project Authors, Roboto Project Authors -- all OFL 1.1). Coverage: latin subset only, twelve weights matching the families and weights loaded by App.razor (Inter 400/500/600/700, Barlow Condensed 500/700, JetBrains Mono 400/500, Roboto 300/400/500/700). Tests: 1209 (Scraper) + 41 (Web) = 1250 passing, 0 warnings. Local review: 0 critical / 2 minor / 11 ok. Mechanical 10-item self-audit: clean. Identity verified personal-noreply.
jkeeley2073
added a commit
that referenced
this pull request
May 10, 2026
…ransparency surface (#171) Closes the 1 🔴 finding from `docs/PHASE5-DRIFT-AUDIT.md` § 2 by adding the chrome footer that was missing from Wave 1. Coverage-statement copy is locked from `docs/ui/screens/empty-landing.md` § Section 4 and now renders on every screen — not just the empty-landing surface — per the answer-with-citations § Screen zones #5 spec. This is the third (final) PR in the Phase 5 Wave 1 drift-audit fix roadmap. PR-T1 (#166) shipped token alignment; PR-F-chrome-nav-rework (#169) simplifies the header to a single "What we cover" link and relocates Status to the footer added here. Changes: - BrandFooter.razor (NEW) — chrome surface in Components/Layout/. MudBlazor-strict (MudText + MudLink + native <footer> landmark only). Renders the locked coverage statement, the "What we cover →" link to /about styled with Color.Success (= --accent-grounded #34d96a per the PR-T1 spec alignment), the project name, the canonical GitHub link (Target=_blank + rel="noopener noreferrer" tabnabbing protection), and the Status link relocated from the prior 4-link header. - MainLayout.razor — mounts <BrandFooter /> inside <MudLayout> after </MudMainContent>. Two-line edit. - BrandFooterTests.cs (NEW) — 5 bUnit tests pin the footer landmark + aria-label, the locked coverage-statement copy, the "What we cover" link target + label, the GitHub link's tabnabbing-safe attributes, and the relocated Status link. - MainLayoutTests.cs — extended with a drift guard that asserts BrandFooter mounts in the rendered tree (catches a future MainLayout edit silently dropping the footer). The coverage-statement test is the load-bearing one — without it, the next "let's tighten the footer copy" edit can quietly drop the "first-party data on 8 active manufacturers" line that establishes the project's coverage-transparency posture (ADR-0027 § 4). Sequencing note: this branch was forked off origin/main *before* PR #169 (chrome-nav-rework) merges, so the file-disjoint Status link appears in BOTH the existing BrandHeader.razor (header) and the new BrandFooter.razor (footer) until #169 lands. Merge order should be #169 first, then this PR — at which point Status appears once, in the footer, per ADR-0027 § 1. Tests: 55 → 61 passing in Web.Tests (+5 BrandFooterTests + 1 MainLayoutTests drift-guard). Solution build clean (0 warnings / 0 errors). Local review: 0 actionable 🔴 (the 1 🔴 flagged about MudLink silently dropping rel was a false alarm — the bUnit test mechanically verifies rel="noopener noreferrer" reaches the rendered <a>, and that test passed; MudLink supports attribute splatting in this version. The other 🔴 about Status duplication is the merge-order note above, not a code-change concern). 1⚠️ (file-header comment was too long; tightened in this PR — kept the spec authority + ADR citations only, dropped the rationale prose). Mechanical 10-item self-audit: 0 🔴 / 0⚠️ / 5 ✅ / 5 N/A.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes 9 🔴 + 4⚠️ token-drift findings from
docs/PHASE5-DRIFT-AUDIT.md§ 1 by aligning Wave 1 visual tokens to the locked Modern LCD spec (docs/ui/themes/modern-lcd.md§ Visual system). Foundation for Wave 2 delight surfaces — lands before they calcify against Material defaults.PinballTheme.cs— MudPalette aligned to spec (warm near-black bg-base, warm off-white text, arcade amber, atomic green for grounded, saturated red for refusal). Typography H1–H6 → Barlow Condensed (700 / 500); Default + body → Inter; Roboto stays in the fallback chain.wwwroot/app.css—:rootspec-mirror tokens (--pw-accent-*,--pw-bg-*,--pw-text-*,--pw-border-*,--pw-font-*,--pw-motion-*) for the slots MudPalette can't model and for stable Wave-2-consumable names. Addsfont-feature-settings: "tnum"site-wide and aprefers-reduced-motionoverride that zeros motion durations.App.razor— single Google Fonts load for Barlow Condensed + Inter + JetBrains Mono + Roboto withpreconnecthints. (Self-hosting follow-up task spawned for GDPR / IP-leak posture.)PinballThemeContractTests— new contract tests pinning 10 palette slots to spec hex values + H1–H6 family + weight + Default/Body1/Body2 family. Mechanically prevents palette drift on the next theme edit.Scope boundaries
Out of scope (deferred per audit § 4 / § 6):
PR-F-chrome-nav-reworkandPR-F-chrome-footerfollow with file-disjoint scope.TiltErrorBoundaryred-border refresh —--pw-accent-refusaltoken now exists; consuming CSS rule lands with that component's Wave 2 polish.Test plan
dotnet build PinballWizard.slnx— 0 warnings / 0 errorsdotnet test tests/PinballWizard.Web.Tests— 55 / 55 passing (45 prior + 10 new contract tests)git log -1 --format='%an <%ae>'shows94459922+jkeeley2073@users.noreply.github.comPR self-audit
Local review: 2 🔴 findings (1 fixed in this PR — added⚠️ findings (all fixed: expanded contract test coverage to AppbarBackground/DrawerBackground + H1–H6 FontWeight, corrected misleading "warmer" comment).
--pw-*spec token aliases including--pw-accent-refusal; 1 deferred — Google Fonts CDN → self-hosted, follow-up task spawned), 3Mechanical 10-item self-audit: clean (most items N/A for a pure-theming PR; identity verified personal-noreply).