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:
co-authored by
Claude Opus 5.5
parent
460b82f039
commit
ce6d38d044
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user