fix(desktop): read the --luke-* roles where CSS still named retired tokens
Summary
#3524 (and #3569 after it) deleted the desktop's compatibility token aliases. PRs that were already open then merged CSS that still reads them. Nothing failed, because a var() naming an undeclared property just makes its declaration invalid, so borders, gaps, padding and colours silently dropped.
- Fix. Every one of the 41 reads on
mainwhen this PR opened, and 10 more that merged since, now reads the--luke-*role its alias pointed at, using #3524's deleted alias block (--space-N→--luke-space-N,--hairline(-strong)→--luke-color-border(-strong),--text-primary/secondary/tertiary→--luke-color-text/-muted/-subtle,--raised→--luke-color-fill-hover,--control-height/gap/radius→--luke-control-*,--switch-on→--luke-color-switch-on,--focus-ring→--luke-color-focus-ring) and #3569's (--focus-ring-width/offset→--luke-focus-ring-width/offset):settings.css: the video avatar's picture choice (#3257, Settings › Experimental › Avatar), not a Diffs group.tasks.css: the Pull request tab header's branch row, stack button and labels (#3268). These use the same replacements #3309 makes to the same lines.tasks.css(merged frommainafter this PR opened): the inline review threads in Files changed (#3245), 10 reads more. This PR's own check caught them in CI, with--section-fill→--luke-color-surfaceand a local--focus-ring-offset: var(--focus-ring-inset)changed to the--luke-focus-ring-*override its neighbours use.desktop.css: the offline mark's size and the sidebar toggle's clearance in the title bar (#3523), which the first regex missed.breadcrumbs.css: this one read--focus-ring-width/-offsetwith literal fallbacks equal to today's values. It now reads the roles with no fallback, the way its sibling components do, so the guard can see it.
- Guard. A new step in
scripts/repository-checks.shtakes every tracked.css,.html,.tsand.tsxfile underapps/andpackages/(tests excluded). It treats a property as declared if it appears as a sheet's--name:or@property --name, or as a script's"--name"literal (setProperty, a style object's key, a constant). A--name-${…}template, such as ChartStyle's--color-${key}, declares the whole family. The step then fails on anyvar(--name)with no fallback that nothing declares, naming the file and line. The only name on the hand-kept list is--default-button-size, which comes from Excalidraw's own sheet. The step takes about 0.5s. Declarations are pooled across apps, so it catches a name that is gone everywhere, which is what a retired token is.- Why not Biome or stylelint: Biome's
noUndeclaredCustomPropertiesis a nursery rule that needs Biome ≥ 2.5.11 (we pin 2.5.8). It runs the project scanner, follows CSS imports only, and cannot see properties set from TypeScript. Stylelint is not in the repo, and its plugin for this has the same blind spot.
- Why not Biome or stylelint: Biome's
Evidence
- The guard on
main's CSS, before the fix: fails with 41 reads (desktop.css×4,settings.css×19,tasks.css×18), each named by file and line. After the fix: passes. ./scripts/repository-checks.sh: passed../scripts/oxlint.sh: 0 warnings.pnpm knip: clean.biome checkon the touched sheets: clean.pnpm check:affected: ran out of memory indiscarded-effect(a TypeScript-only check; this PR changes no TypeScript), so I ran the individual checks above instead.- Screenshots: see Screenshots below. They come from a throwaway esbuild harness with
fakeAppBridgethat renders the real components under the real built stylesheet, before and after, in dark and light. The harness is not committed.
Screenshots
Before, on main, and after, with the built desktop stylesheet. Dark first, then light.
Settings › Experimental › Avatar (picture choice). Before this fix, the faces have no gaps, the chosen face has no ring, the names are the wrong colour, and the stage has no inset or radius.
| Before | After |
|---|---|
![]() | ![]() |
![]() | ![]() |
Pull request tab header (labels, branch row, stack). Before this fix, the label pills have no padding or gap, and the Stack button has no edge, gap or colour.
| Before | After |
|---|---|
![]() | ![]() |
![]() | ![]() |
Invariants
-
./scripts/check.shgreen. (CI) - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot run, or diff explained line by line). (CI) - Every behavior change has, in this PR, the test that fails without it; no change-detector test added. The new repository check fails on
mainwithout the CSS fix. - Gateway envelope goldens unchanged. (CI)
- No new
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside the runtime edges and the shimstools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-run-promise-outside-edges) - No new
setTimeout/setInterval/new Promise/AbortController/fs.watchoutside the filestools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-raw-async-primitives) - No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via
pnpm discarded-effect) - Test count equals the pre-PR count or the delta is listed. Unchanged: the guard is a repository check, not a test.
- Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root
AGENTS.md's "The permanent adaptors" with the reason it stays. - Every
CLAUDE.md/AGENTS.mdsentence naming a changed part is edited; root pair stays byte-identical. -
PRIVACY.mdunchanged. - Package
package.jsondeps match what the sources import by bare specifier;effectviacatalog:. (CI) - Barrel/door rule: no Node-reaching Effect module behind a barrel the renderer or a web function opens. (CI)
Notes
- Blockers or follow-up verification: None. Open PRs #3245, #3294, #3305, #3309 and #3313 fix their own retired tokens. Once this lands, any of them that misses one will fail this check.
Made with Luke
Summary by cubic
Restores the desktop UI's borders, gaps, padding, and colors that silently dropped after #3524 and #3569 retired the CSS token aliases. All var() reads still naming a retired token now read the --luke-* role they pointed at, and a new repository check fails on any var(--name) with no fallback that nothing declares, so the regression can't repeat.
Bug Fixes
settings.css: the video avatar picture choice in Settings › Experimental › Avatar regains its spacing, borders, focus ring, and colors.tasks.css: the Pull request tab's branch row, stack button, and labels regain their gaps, edges, and text colors, and the inline review threads in Files changed (#3245) regain their padding, borders, and surface fill — the new check caught those reads on the merge with main.desktop.cssandbreadcrumbs.css: the offline mark's size, the sidebar toggle's title-bar clearance, and the breadcrumb focus ring are restored; breadcrumbs now read the roles with no fallback, like sibling components, so the check can validate it.
New check
scripts/repository-checks.shfails when avar(--name)has no fallback and no tracked.css,.html,.ts, or.tsxfile underapps/andpackages/declares--name(tests excluded), naming the file and line.- Sheets declare by
--name:or@property --name; scripts only by writing the name (a style string, a style object key, a["--name"] =, orsetProperty), so reads likegetPropertyValuedon't count. - A
--name-${…}template declares the whole family, and declarations are pooled across apps so the check catches a token gone everywhere. - The only hand-listed exception is
--default-button-size, which Excalidraw's own stylesheet sets; the check adds about 0.5s to the run.







