Skip to content

emrg: gui — fix undefined theme tokens (--fs-small/--bg-1/--bg-hover/--radius-sm) + add theme-integrity guard - #1051

Merged
argszero merged 2 commits into
argszero:masterfrom
how2how2how2-arch:feature/renderer-theme-guard
Aug 27, 2026
Merged

emrg: gui — fix undefined theme tokens (--fs-small/--bg-1/--bg-hover/--radius-sm) + add theme-integrity guard#1051
argszero merged 2 commits into
argszero:masterfrom
how2how2how2-arch:feature/renderer-theme-guard

Conversation

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Summary

Fix 4 undefined theme tokens in the renderer + add a static theme-integrity guard (third host-caught regression → guard, following #1037 boot-chain and #1045 upgrade-prompt).

Background

The v0.2.84 light-theme regression (#1047, rant 2026-08-27T20:33:31) was another host-caught-after-release theming bug: the React shell hardcoded Catppuccin dark hexes instead of using tokens.css theme variables. While auditing the theme layer after that fix, a second, deeper problem surfaced:

4 theme variables are consumed as bare var(--x) (no fallback) but never defined in tokens.css--fs-small, --radius-sm, --bg-1, --bg-hover. A bare var(--x) with an undefined name makes the whole declaration invalid at computed-value time, so those properties silently don't apply: .task-form background is transparent, filetab hover has no background, badge font-size/radius fall back to inherited/0.

Changes

Fixemrg/gui/renderer/css/tokens.css:

  • --fs-small: 11px (below --fs-aux: 12px), --radius-sm: 8px (below --radius-input/btn: 12px) — theme-independent, defined once in :root
  • --bg-1 / --bg-hover — per-theme values in all 4 color blocks: light #eceef2/#e8eaed, dark #2e3138/#2e3138

Guardemrg/gui/test/theme-guard.test.js (2 static tests, node:test):

  1. Every bare var(--x) in renderer CSS must be defined in tokens.css — catches silent invalid declarations (the bug fixed here)
  2. No Catppuccin dark-palette hexes outside tokens.css (comment-stripped) — pins the exact emrg: gui — use theme variables in React shell (fix light theme regression) #1047 regression shape

Negative-state verified: both guards fail against the pre-fix tokens (undefined vars removed) and pass against the fixed file.

Verification

  • GUI npm test — 95 tests (87 pass + 8 skipped), 0 fail
  • Renderer npm run typecheck clean + 445 vitest pass
  • Python uv run pytest tests/ — 1126 passed + 1 skipped; doc-count guard 4/4
  • Import + CLI smoke: OK

Note

GUI test count 93 → 95, Agent.md synced.

…--radius-sm) + add theme-integrity guard
Two-part change hardening renderer theming (rant 2026-08-27T20:33:31 → argszero#1047
light-theme regression class):
FIX: tokens.css lacked 4 variables that layout.css/components.css consume as
bare var(--x) (no fallback): --fs-small (11px), --radius-sm (8px), --bg-1 and
--bg-hover (per-theme values). Bare undefined vars make declarations invalid
at computed-value time — task-form background transparent, filetab hover
dead, badge font-size/radius wrong. All 4 now defined in every theme block
(light default + forced, dark media + forced).
GUARD: emrg/gui/test/theme-guard.test.js — 2 static tests:
1. every bare var(--x) in renderer CSS must be defined in tokens.css
(catches silent invalid declarations — the bug above)
2. no Catppuccin dark-palette hexes outside tokens.css, comment-stripped
(the exact argszero#1047 regression shape: hardcoded theme colors bypassing vars)
Negative-state verified: guard fails on pre-fix tokens, passes on fixed.
GUI tests 93 -> 95, Agent.md synced.

@argszeroargszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Needs fix: guard scan is non-recursive — nested CSS escapes both guards

The core fix is verified and correct: the 4 tokens (--fs-small/--radius-sm/--bg-1/--bg-hover) are genuinely undefined on master and consumed as bare var(--x) in layout.css/components.css → silent invalid declarations. Guard 1 correctly FAILS against pre-fix tokens (verified locally) and both guards pass on the fixed tree (npm test 95: 87 pass + 8 skip; renderer 445/445; typecheck clean; CI test + test-windows green).

However, rendererCssFiles() scans only renderer/css/*.css + renderer/src/*.css (top-level readdirSync). I empirically proved the gap: injecting .x { background: #1e1e2e; } into a new renderer/src/components/Shell.css was not detected by either guard (npm test stayed green), while the same injection into src/shell.css correctly fails guard 2. The React migration is already placing styles under src/components/ — a future scoped CSS file there would silently bypass the guard, recreating exactly the silent-regression class this guard exists to catch.

Suggested fix (Node 20+, CI uses Node 22):

for(constfoffs.readdirSync(srcDir,{recursive: true})){if(typeoff==="string"&&f.endsWith(".css"))files.push(`src/${f}`);}

Directory entries from the recursive read also end with ".css"? No — they don't, so the endsWith filter is sufficient. — cycle cyc20260827-232221

@argszero

Copy link
Copy Markdown
Owner

Maintainer push: resolved the ❌ — rendererCssFiles() now scans src/ recursively (fs.readdirSync(srcDir, { recursive: true }), Node 20+, CI runs Node 22), so CSS in any subdirectory (e.g. src/components/*.css) is covered by both guards.

Verified locally on the updated head (66e4473):

  • Positive: both theme guards pass, GUI npm test 95 (87 pass + 8 skip)
  • Negative (the previously-missed case): injecting #1e1e2e into a new src/components/Nested.css now correctly FAILS guard 2, where the old top-level-only scan silently passed it

Thanks for the solid guard — this closes the coverage gap. CI (test + test-windows) re-triggered on the synchronize event.

@argszeroargszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260827-232221 (1/3). The ❌ (non-recursive guard scan) is resolved via maintainer push 66e4473: rendererCssFiles() now scans src/ recursively. Verified on the new head: both theme guards pass (GUI npm test 95: 87 pass + 8 skip); negative state — #1e1e2e in a nested src/components/Nested.css correctly FAILS guard 2 (previously silently missed). CI test + test-windows green (33087932931), MERGEABLE/CLEAN. Core fix (4 undefined tokens → defined in tokens.css, per-theme values in all 4 blocks) verified correct.

@argszeroargszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260827-233730 (2/3). Head 66e4473 unchanged since cyc20260827-232221 LGTM (1/3, after maintainer fix of the non-recursive scan), CI test + test-windows green (33087932931), MERGEABLE/CLEAN. Core fix (4 undefined tokens + theme-integrity guard) verified.

@argszeroargszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260827-234739 (3/3). Head 66e4473 unchanged since cyc20260827-233730 LGTM (2/3), CI test + test-windows green (33087932931), MERGEABLE/CLEAN. The earlier ❌ (non-recursive scan) was resolved by maintainer push 66e4473 and is followed by clean consecutive ✅s (232221, 233730, 234739) with no ❌ between them. Merging.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@how2how2how2-arch@argszero