feat(web): mobile-first tokens and controls

44px controls, a separate 24px chip height, one row height, a gutter at
every width, min-width breakpoints, and the shared control and row
vocabulary in ui.ts. check-tokens now also rejects var() names that
nothing defines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-10-24 17:28:22 +01:00
co-authored by Claude Opus 5.5
parent 460b82f039
commit ce6d38d044
4 changed files with 2848 additions and 2302 deletions
+30 -4
View File
@@ -2,6 +2,11 @@
// Design-token gate (issue #45): components consume tokens from :root in
// web/src/style.css, never literal colour or spacing values.
//
// Every `var(--x)` must name a custom property something defines — a token in
// `:root`, a component-local one in the CSS, or one the TS sets with
// `setProperty` — so a mistyped or deleted token fails here rather than
// silently falling back to the browser default.
//
// Allowed colour literals: inside a `:root` block (token definitions) and
// inside `@font-face`. Relative colour derived from a token —
// `oklch(from var(--x) …)` — counts as consuming the token and is allowed
@@ -114,16 +119,37 @@ function checkPlain(source, rel) {
}
const files = [join(webRoot, "index.html"), ...walk(join(webRoot, "src"))];
for (const file of files) {
const rel = relative(webRoot, file);
const source = readFileSync(file, "utf8");
if (file.endsWith(".css")) {
const sources = files.map((file) => ({
rel: relative(webRoot, file),
source: readFileSync(file, "utf8"),
}));
for (const { rel, source } of sources) {
if (rel.endsWith(".css")) {
checkCss(source, rel);
} else {
checkPlain(source, rel);
}
}
const defined = new Set();
for (const { source } of sources) {
for (const match of source.matchAll(/(--[\w-]+)\s*:/g)) {
defined.add(match[1]);
}
for (const match of source.matchAll(/setProperty\(\s*["'](--[\w-]+)["']/g)) {
defined.add(match[1]);
}
}
for (const { rel, source } of sources) {
source.split("\n").forEach((line, index) => {
for (const match of line.matchAll(/var\(\s*(--[\w-]+)/g)) {
if (!defined.has(match[1])) {
errors.push(`${rel}:${index + 1}: unknown token ${match[1]} — nothing defines it`);
}
}
});
}
if (errors.length > 0) {
console.error("check-tokens: components must consume design tokens:\n");
for (const error of errors) {