Problem
The fork-sync regression class documented in remoteclaw/hq#57 (post-mortem) and demonstrated by #2501 (CSS class drift after v2026.3.13-1 sync) likely affects more files than the two surfaces visible so far. The Control UI sidebar nav and topbar were the symptoms a user happened to notice — but every CSS file the fork has synced from upstream could carry similar drift, and there's no evidence other component surfaces are clean.
We need a one-shot audit pass to find ALL existing CSS-class drift across the UI, before fixing anything beyond #2501. Without this, fixes happen reactively as users encounter unstyled components — slow and incomplete.
Goal
Produce a complete inventory of every CSS class referenced from ui/src/**/*.{ts,tsx,html} template strings that has no matching rule in any imported CSS file. Cluster findings by likely sync-wave origin so fixes can be batched.
Scope
In:
- All
ui/src/**/*.ts and ui/src/**/*.tsx files (template-string class references)
- All
ui/src/styles/**/*.css files (class definitions in selectors)
ui/src/styles.css import graph (only files actually loaded count)
- Top-level
class="..." and class=${"..."} literals (Lit template syntax)
- BEM-style classes including double underscores and double hyphens (
block__elem, block--mod)
Out (for first pass):
- Dynamic class composition (e.g.,
${cond ? "a" : "b"} template-string interpolation that produces class names) — too many false positives; revisit if the simple pass is clean
- Classes referenced from
:host, attribute selectors, or other non-class="" mechanisms
- Classes from
@create-markdown or other vendored UI (out of fork ownership)
Approach
A standalone script at scripts/audit-css-class-drift.mjs (or .sh if simpler):
-
Parse class references: regex-match all class="([^"]*)" and class=${"([^"]*)"} literals across ui/src/**/*.{ts,tsx}. Tokenize values on whitespace. Result: a map of class-name → [file:line, file:line, ...] references.
-
Parse class definitions: scan all ui/src/styles/**/*.css files; extract class names from selectors (handle nested compound selectors like .foo .bar, .foo.bar, .foo > .bar). Result: a set of defined classes.
-
Compute orphans: references - definitions set difference.
-
Cluster findings: for each orphan, run git log -p ui/src/styles/<likely-css-file>.css | grep '<orphan>' to find when the rule was last present. Group orphans by the commit that removed them (likely a sync commit).
-
Output report at .tmp/audit-css-drift.md:
# CSS Class Drift Audit (YYYY-MM-DD)
**Total orphans**: N
**Clusters**: K (by removing-commit)
## Cluster 1: removed in <commit-hash> (sync v2026.3.13-1)
- `.nav-group` — used at `ui/src/ui/app-render.ts:256, 272, 278`
- `.nav-label` — used at `ui/src/ui/app-render.ts:258, 279`
- ... (full enumeration)
## Cluster 2: removed in <commit-hash> (...)
...
## Recommended actions per cluster
...
Acceptance criteria
Commit message
test(ui): add CSS class drift audit script — surface fork-side references with no CSS definition
Estimated effort
1-2 hours: ~30 min to write the script, ~15 min to run + verify, ~30-60 min to triage + write the report + file follow-up issues per cluster.
References
- First-instance fix:
#2501 (nav-section + topbar drift)
- Pattern post-mortem:
remoteclaw/hq#57
- Risk model:
remoteclaw/hq#59
- Sync workflow companion check:
remoteclaw/hq#58
- Sync rename detection:
remoteclaw/hq#63
- Build-time gate (depends on this audit succeeding): companion remoteclaw issue
Problem
The fork-sync regression class documented in
remoteclaw/hq#57(post-mortem) and demonstrated by#2501(CSS class drift after v2026.3.13-1 sync) likely affects more files than the two surfaces visible so far. The Control UI sidebar nav and topbar were the symptoms a user happened to notice — but every CSS file the fork has synced from upstream could carry similar drift, and there's no evidence other component surfaces are clean.We need a one-shot audit pass to find ALL existing CSS-class drift across the UI, before fixing anything beyond #2501. Without this, fixes happen reactively as users encounter unstyled components — slow and incomplete.
Goal
Produce a complete inventory of every CSS class referenced from
ui/src/**/*.{ts,tsx,html}template strings that has no matching rule in any imported CSS file. Cluster findings by likely sync-wave origin so fixes can be batched.Scope
In:
ui/src/**/*.tsandui/src/**/*.tsxfiles (template-string class references)ui/src/styles/**/*.cssfiles (class definitions in selectors)ui/src/styles.cssimport graph (only files actually loaded count)class="..."andclass=${"..."}literals (Lit template syntax)block__elem,block--mod)Out (for first pass):
${cond ? "a" : "b"}template-string interpolation that produces class names) — too many false positives; revisit if the simple pass is clean:host, attribute selectors, or other non-class=""mechanisms@create-markdownor other vendored UI (out of fork ownership)Approach
A standalone script at
scripts/audit-css-class-drift.mjs(or.shif simpler):Parse class references: regex-match all
class="([^"]*)"andclass=${"([^"]*)"}literals acrossui/src/**/*.{ts,tsx}. Tokenize values on whitespace. Result: a map ofclass-name → [file:line, file:line, ...]references.Parse class definitions: scan all
ui/src/styles/**/*.cssfiles; extract class names from selectors (handle nested compound selectors like.foo .bar,.foo.bar,.foo > .bar). Result: a set of defined classes.Compute orphans:
references - definitionsset difference.Cluster findings: for each orphan, run
git log -p ui/src/styles/<likely-css-file>.css | grep '<orphan>'to find when the rule was last present. Group orphans by the commit that removed them (likely a sync commit).Output report at
.tmp/audit-css-drift.md:Acceptance criteria
scripts/audit-css-class-drift.mjs(or equivalent).tmp/audit-css-drift.md(gitignored)fix(ui)issue with the renames tableCommit message
Estimated effort
1-2 hours: ~30 min to write the script, ~15 min to run + verify, ~30-60 min to triage + write the report + file follow-up issues per cluster.
References
#2501(nav-section + topbar drift)remoteclaw/hq#57remoteclaw/hq#59remoteclaw/hq#58remoteclaw/hq#63