Skip to main content

max / balanced_breakfast

css: F5 T — self-declare :root token nature; fix stale token-coverage lint The June intent-layer migration rewrote themes.js to apply theme-common's intents generically (setProperty on each theme.intents key), removing the old per-token COLOR_MAP. The theme-token-coverage lint still grepped themes.js for each token as a quoted literal, so all 28 color tokens read as "unmapped" — a stale check, not dangling tokens. Every :root token now self-declares on its line: /* themed */ (25 intent-layer tokens, verified to match theme-common's emitted intent keys exactly), /* composition */ (3 color-mix deriveds), or /* invariant */ (geometry/scales, already marked). The lint rule now checks for that annotation and drops the obsolete themes.js grep. Frontend lint is clean for the first time since the migration; this unblocks wiring the lint pre-commit gate (phase E). CSS parses; 55 JS tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-12 18:44 UTC
Commit: a3cdd90f80d4bc486fbad7ec80c099f2ecf944e1
Parent: 0bc62d0
2 files changed, +38 insertions, -37 deletions
@@ -73,22 +73,23 @@ hits=$(grep -rnE '\b(window\.)?(confirm|prompt|alert)\s*\(' "$SRC_JS" 2>/dev/nul
73 73 report "no-native-dialogs" "window.confirm/prompt/alert are banned — use BB.ui.show{Confirm,Prompt}Dialog or showToast." "$hits"
74 74
75 75 # 7. theme-token-coverage
76 - # Every :root token in styles.css must either be mapped by themes.js
77 - # COLOR_MAP, derived in applyTheme, or carry an /* invariant */ or
78 - # /* composition */ annotation on the same line. Catches dangling
79 - # tokens that silently fall back to the hardcoded :root value.
76 + # Every :root token must self-declare its nature on its declaration line:
77 + # /* themed */ applied at runtime by themes.js applyTheme(), from the
78 + # shared theme-common intent layer (theme.intents)
79 + # /* composition */ CSS color-mix / var() derived from other tokens
80 + # /* invariant */ fixed geometry/scale; never changes with theme
81 + # themes.js applies the intent layer generically (setProperty on each
82 + # theme.intents key), so there is no per-token map to grep; the annotation
83 + # is the contract. Catches a new token added without deciding what drives it.
80 84 root_tokens=$(awk '/^:root \{/{flag=1; next} /^\}/{flag=0} flag' "$SRC_CSS" \
81 85 | grep -oE -- '--[a-z0-9-]+' | sort -u || true)
82 86 unmapped=""
83 87 for tok in $root_tokens; do
84 - # Allow tokens marked invariant or composition on the declaration line.
85 - if grep -E -- "$tok:[^;]*;.*(invariant|composition)" "$SRC_CSS" >/dev/null 2>&1; then continue; fi
86 - # Mapped directly or referenced as a property in themes.js?
87 - if grep -F -- "'$tok'" "$SRC_JS/themes.js" >/dev/null 2>&1; then continue; fi
88 + if grep -E -- "$tok:[^;]*;.*(themed|invariant|composition)" "$SRC_CSS" >/dev/null 2>&1; then continue; fi
88 89 unmapped="$unmapped$tok"$'\n'
89 90 done
90 91 if [ -n "$unmapped" ]; then
91 - report "theme-token-coverage" "Token in :root is neither themed nor marked invariant/composition." "$unmapped"
92 + report "theme-token-coverage" "Token in :root lacks a /* themed | composition | invariant */ annotation." "$unmapped"
92 93 fi
93 94
94 95 if [ $violations -eq 0 ]; then
@@ -125,40 +125,40 @@
125 125 :root). Defaults below are "flatwhite" (BB's light default) for first
126 126 paint; theme-common resolves every theme incl. the OKLab-derived states,
127 127 shared with audiofiles / GoingsOn / MNW. */
128 - --surface-page: #f1ece4;
129 - --surface-raised: #f7f3ee;
130 - --surface-sunken: #dcd3c6;
131 - --surface-overlay: #e4ddd2;
132 - --content: #605a52;
133 - --content-secondary: #786d5e;
134 - --content-muted: #9a8b78;
135 - --content-on-action: #ffffff;
136 - --action: #4c5361;
137 - --action-hover: #5a616f;
138 - --danger: #ff1414;
139 - --success: #2db448;
140 - --warning: #f2a60d;
141 - --info: #465953;
142 - --border: #93836c;
143 - --border-strong: #84745e;
144 - --focus-ring: #4c5361;
145 - --hover-surface: #dcd3c6;
146 - --category-one: #ff1414;
147 - --category-two: #2db448;
148 - --category-three: #4c5361;
149 - --category-four: #f2a60d;
150 - --category-five: #614c61;
151 - --category-six: #465953;
152 - --overlay: rgba(3, 2, 0, 0.5);
128 + --surface-page: #f1ece4; /* themed */
129 + --surface-raised: #f7f3ee; /* themed */
130 + --surface-sunken: #dcd3c6; /* themed */
131 + --surface-overlay: #e4ddd2; /* themed */
132 + --content: #605a52; /* themed */
133 + --content-secondary: #786d5e; /* themed */
134 + --content-muted: #9a8b78; /* themed */
135 + --content-on-action: #ffffff; /* themed */
136 + --action: #4c5361; /* themed */
137 + --action-hover: #5a616f; /* themed */
138 + --danger: #ff1414; /* themed */
139 + --success: #2db448; /* themed */
140 + --warning: #f2a60d; /* themed */
141 + --info: #465953; /* themed */
142 + --border: #93836c; /* themed */
143 + --border-strong: #84745e; /* themed */
144 + --focus-ring: #4c5361; /* themed */
145 + --hover-surface: #dcd3c6; /* themed */
146 + --category-one: #ff1414; /* themed */
147 + --category-two: #2db448; /* themed */
148 + --category-three: #4c5361; /* themed */
149 + --category-four: #f2a60d; /* themed */
150 + --category-five: #614c61; /* themed */
151 + --category-six: #465953; /* themed */
152 + --overlay: rgba(3, 2, 0, 0.5); /* themed */
153 153
154 154 /* === APP-LOCAL — derived from intents (color-mix) or non-color invariants ===
155 155 BB's component CSS references the intent tokens directly. These few are
156 156 BB-specific: the danger-button hover and yellow highlight (no universal
157 157 intent), the neobrute shadow color, and the neobrute geometry. All derive
158 158 from intents — no color literals. */
159 - --accent-red-hover: color-mix(in oklch, var(--danger) 90%, white); /* .btn-danger:hover */
160 - --accent-yellow-light: color-mix(in oklch, var(--category-four) 82%, white);
161 - --shadow-color: color-mix(in oklch, var(--content-muted) 88%, black);
159 + --accent-red-hover: color-mix(in oklch, var(--danger) 90%, white); /* composition — .btn-danger:hover */
160 + --accent-yellow-light: color-mix(in oklch, var(--category-four) 82%, white); /* composition */
161 + --shadow-color: color-mix(in oklch, var(--content-muted) 88%, black); /* composition */
162 162 --border-width: 2px; /* invariant */
163 163 --radius-xs: 3px; /* invariant — scrollbars, tiny elements */
164 164 --radius-sm: 5px; /* invariant */