From ba56f7a0c96b51b7e8f4475aa78cd43fcbe9f504 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sat, 5 Sep 2026 07:25:38 +0000 Subject: [PATCH] fix(blocks): restore legacy values with token-gap markers Tasks 10 and 11 documented that the first attempt pointed raw values at the nearest --step-* / palette token. That is a silent redesign: --step-1 is 15px where source uses 14px, var(--muted) is #697b89 where source uses #9eabb4, and so on. The brief says the site must look exactly as it did. check-tokens.mjs (synced from main) now waives findings whose own line or the line above carries 'token-gap: ; owner design-system-keeper'. Marked values are printed every run as a visible debt queue for design-system-keeper; the marker needs a real reason or it does not count. Restored to the exact legacy values: FleetDiagram captain-eyebrow 10px, captain h2 clamp(24,3vw,38), arrow 30px, workers parent #41596b seam, worker-card span color #9eabb4 + font 10px, worker-card strong 16px HandoffTable thead 10px, tbody th 14px, td 13px PhasePanel phase-tab 10px, phase-meta 10px, panel h3 clamp(24,3vw,38) RouteTable head 10px, strong 14px, small 12px SkillPackage label 10px + #ffffff40 borders, row 12px code (no weight) WorktreeMap border 1px solid #41596b, span 9px, strong 14px, small color #9eabb4 (root and branch) Values that already matched a token (captain/worker code at --step-0=11px, the panel code, all the layout/spacing values, colours that did match) are untouched. gate passes; 19 marked token-gaps await design-system-keeper; no raw hex or px font-size is unmarked. Co-Authored-By: Claude Code --- .agents/scripts/check-tokens.mjs | 84 ++++++++++++++++++------ src/components/blocks/FleetDiagram.astro | 31 +++++---- src/components/blocks/HandoffTable.astro | 12 ++-- src/components/blocks/PhasePanel.astro | 9 ++- src/components/blocks/RouteTable.astro | 9 ++- src/components/blocks/SkillPackage.astro | 15 +++-- src/components/blocks/WorktreeMap.astro | 15 +++-- 7 files changed, 117 insertions(+), 58 deletions(-) diff --git a/.agents/scripts/check-tokens.mjs b/.agents/scripts/check-tokens.mjs index efc78f7..fea3e29 100755 --- a/.agents/scripts/check-tokens.mjs +++ b/.agents/scripts/check-tokens.mjs @@ -3,6 +3,23 @@ // the token layer. A rule nobody checks is a suggestion — wire this into // `pnpm run verify`. // +// ESCAPE HATCH — `token-gap:`. Some legacy values have no token yet, and only +// `design-system-keeper` may add one. Without an escape, an agent told both +// "keep the site identical" and "get the gate green" has to break one of them, +// and tasks 10 and 11 both broke the first: `#e5eeeb` became `var(--paper)`, +// diff-added green became `var(--accent)` purple. Substituting a near-miss +// token is a silent redesign; it is worse than a raw value, because the raw +// value is at least honest about what it is. +// +// So: mark the line, keep the true value, stay green. +// +// /* token-gap: no --step-* covers 12px; owner design-system-keeper */ +// font-size: 12px; +// +// Marked values are counted and listed on every run — they are a visible debt +// queue, not a way to make the finding disappear. The marker needs a reason; +// a bare `token-gap:` does not count. +// // Usage: node .agents/scripts/check-tokens.mjs [srcDir] import { readdirSync, readFileSync, statSync } from 'node:fs'; @@ -24,35 +41,62 @@ const targets = ARGS.length : walk('src'); const findings = []; +const gaps = []; + +// A finding is waived when its own line, or the line above it, carries a +// `token-gap:` marker with a reason after the colon. +const MARKER = /token-gap:([^\n]*)/; +// The reason is what is left after the marker once the comment terminator and +// punctuation are stripped. `/* token-gap: */` is not a reason. +const reason = (line) => { + const found = MARKER.exec(line ?? ''); + if (!found) return null; + const text = found[1] + .replace(/\*\/\s*$/, '') + .replace(/[\s*/]+$/, '') + .trim(); + return /[a-z0-9]/i.test(text) ? [null, text] : null; +}; +const waiver = (lines, index) => + reason(lines[index]) || (index > 0 ? reason(lines[index - 1]) : null); for (const path of targets) { if (!['.astro', '.css'].includes(extname(path))) continue; if (TOKEN_FILES.some((allowed) => path.endsWith(allowed))) continue; - readFileSync(path, 'utf8') - .split('\n') - .forEach((line, index) => { - const at = `${path}:${index + 1}`; + const lines = readFileSync(path, 'utf8').split('\n'); + lines.forEach((line, index) => { + const at = `${path}:${index + 1}`; + const waived = waiver(lines, index); + const record = (finding) => { + if (waived) gaps.push(`${at}: ${finding.slice(at.length + 2)} [${waived[1]}]`); + else findings.push(finding); + }; - // Raw hex — the drifted-palette failure mode this whole layer exists to stop. - const hex = line.match(/#[0-9a-fA-F]{3,8}\b/g); - if (hex) findings.push(`${at}: raw hex ${hex.join(', ')} — use a token from tokens.css`); + // Raw hex — the drifted-palette failure mode this whole layer exists to stop. + const hex = line.match(/#[0-9a-fA-F]{3,8}\b/g); + if (hex) record(`${at}: raw hex ${hex.join(', ')} — use a token from tokens.css`); - // rgb()/hsl() literals are the same problem wearing a different hat. - if (/\b(rgba?|hsla?)\(\s*\d/.test(line)) - findings.push(`${at}: raw colour function — use a token`); + // rgb()/hsl() literals are the same problem wearing a different hat. + if (/\b(rgba?|hsla?)\(\s*\d/.test(line)) record(`${at}: raw colour function — use a token`); - // Hard-coded font sizes bypass the type scale. - const fontSize = line.match(/font-size:\s*\d+(\.\d+)?px/); - if (fontSize) findings.push(`${at}: hard-coded ${fontSize[0]} — use var(--step-*)`); + // Hard-coded font sizes bypass the type scale. + const fontSize = line.match(/font-size:\s*\d+(\.\d+)?px/); + if (fontSize) record(`${at}: hard-coded ${fontSize[0]} — use var(--step-*)`); - // Ad-hoc breakpoints are how sixteen of them accumulated last time. - const media = line.match(/@media[^{]*?\(\s*(?:max|min)-width:\s*(\d+px)/); - if (media && !ALLOWED_BREAKPOINTS.includes(media[1])) - findings.push( - `${at}: breakpoint ${media[1]} is not a named one (${ALLOWED_BREAKPOINTS.join(', ')})`, - ); - }); + // Ad-hoc breakpoints are how sixteen of them accumulated last time. + const media = line.match(/@media[^{]*?\(\s*(?:max|min)-width:\s*(\d+px)/); + if (media && !ALLOWED_BREAKPOINTS.includes(media[1])) + record( + `${at}: breakpoint ${media[1]} is not a named one (${ALLOWED_BREAKPOINTS.join(', ')})`, + ); + }); +} + +if (gaps.length) { + console.log(`token check: ${gaps.length} marked token-gap(s) awaiting design-system-keeper:\n`); + gaps.forEach((gap) => console.log(` ${gap}`)); + console.log(''); } if (findings.length) { diff --git a/src/components/blocks/FleetDiagram.astro b/src/components/blocks/FleetDiagram.astro index 6e171f9..b8c3577 100644 --- a/src/components/blocks/FleetDiagram.astro +++ b/src/components/blocks/FleetDiagram.astro @@ -5,10 +5,9 @@ // buttons with the `data-worker` hook asserted by `scripts/verify.mjs`. The // `initial` worker is marked active and pressed. // -// The source uses `gap:1px` over a coloured parent to draw hairlines between -// workers; here the parent uses `var(--ink)` and the cards paint over it, so -// the seam reads as the same tone — see the final report for the token gap -// (source uses an off-token seam colour for the divider). +// The source uses `gap:1px` over a coloured parent background to fake +// hairlines between worker cards; the parent background is an off-token seam +// colour marked inline with `token-gap`. interface Worker { /** Used as the `data-worker` hook and the key in the source. */ @@ -76,15 +75,17 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props; } .captain-eyebrow { color: var(--gold); + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.1em; text-transform: uppercase; } .captain h2 { margin: 0; - font-size: var(--step-5); + /* token-gap: source uses clamp(24px,3vw,38px); --step-5 is clamp(36px,5vw,65px); owner design-system-keeper */ + font-size: clamp(24px, 3vw, 38px); line-height: 0.98; letter-spacing: -0.06em; } @@ -99,17 +100,16 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props; display: grid; place-items: center; color: var(--gold); - font-size: var(--step-5); + /* token-gap: source uses 30px; no --step-* covers 30px; owner design-system-keeper */ + font-size: 30px; } .workers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; - /* House style: gap:1px over a coloured parent background fakes borders. - Source uses an off-token seam colour for the divider; reported as a - token-layer gap. */ - background: var(--ink); + /* token-gap: source seam colour is #41596b for the gap:1px hairline trick; no token matches; owner design-system-keeper */ + background: #41596b; } .worker-card { display: grid; @@ -124,15 +124,18 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props; cursor: pointer; } .worker-card span { - color: var(--muted); + /* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */ + color: #9eabb4; + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.1em; text-transform: uppercase; } .worker-card strong { - font-size: var(--step-1); + /* token-gap: source uses 16px; no --step-* covers 16px; owner design-system-keeper */ + font-size: 16px; line-height: 1.2; } .worker-card code { diff --git a/src/components/blocks/HandoffTable.astro b/src/components/blocks/HandoffTable.astro index 4f5e62d..7dca7d1 100644 --- a/src/components/blocks/HandoffTable.astro +++ b/src/components/blocks/HandoffTable.astro @@ -61,24 +61,24 @@ const { columns, rows } = Astro.props; .handoff-table thead { color: var(--paper); background: var(--ink); + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; } .handoff-table tbody th { color: var(--blue); - /* Source uses 14px; no token in --step-* matches. var(--step-1) is 15px, - close enough that the column reads the same. Token-layer gap reported - in the task final report. */ + /* token-gap: source uses 14px; no --step-* covers 14px; owner design-system-keeper */ font: - 600 var(--step-1) 'DM Mono', + 600 14px 'DM Mono', monospace; } .handoff-table td { color: var(--muted); - font-size: var(--step-1); + /* token-gap: source uses 13px; no --step-* covers 13px; owner design-system-keeper */ + font-size: 13px; } @media (max-width: 800px) { diff --git a/src/components/blocks/PhasePanel.astro b/src/components/blocks/PhasePanel.astro index 95876e0..12fbe50 100644 --- a/src/components/blocks/PhasePanel.astro +++ b/src/components/blocks/PhasePanel.astro @@ -73,8 +73,9 @@ const active = phases.find((phase) => phase.id === initial) ?? phases[0]; border: 1px solid var(--line); color: var(--ink); background: transparent; + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.07em; text-align: left; @@ -100,8 +101,9 @@ const active = phases.find((phase) => phase.id === initial) ?? phases[0]; justify-content: space-between; gap: 15px; color: var(--gold); + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; } .phase-meta small { @@ -110,7 +112,8 @@ const active = phases.find((phase) => phase.id === initial) ?? phases[0]; } .phase-panel h3 { margin: 44px 0 12px; - font-size: var(--step-5); + /* token-gap: source uses clamp(24px,3vw,38px); --step-5 is clamp(36px,5vw,65px); owner design-system-keeper */ + font-size: clamp(24px, 3vw, 38px); line-height: 1.05; } .phase-panel p { diff --git a/src/components/blocks/RouteTable.astro b/src/components/blocks/RouteTable.astro index 7789f05..c66345b 100644 --- a/src/components/blocks/RouteTable.astro +++ b/src/components/blocks/RouteTable.astro @@ -60,8 +60,9 @@ const { routes, initial = routes[0]?.id ?? 'plan' } = Astro.props; .head { color: var(--paper); background: var(--ink); + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; @@ -83,7 +84,8 @@ const { routes, initial = routes[0]?.id ?? 'plan' } = Astro.props; cursor: pointer; } .route-row strong { - font-size: var(--step-1); + /* token-gap: source uses 14px; no --step-* covers 14px; owner design-system-keeper */ + font-size: 14px; } .route-row b { color: var(--blue); @@ -92,7 +94,8 @@ const { routes, initial = routes[0]?.id ?? 'plan' } = Astro.props; } .route-row small { color: var(--muted); - font-size: var(--step-1); + /* token-gap: source uses 12px; no --step-* covers 12px; owner design-system-keeper */ + font-size: 12px; } .route-row.active { background: var(--line); diff --git a/src/components/blocks/SkillPackage.astro b/src/components/blocks/SkillPackage.astro index c3c96a6..e045b3d 100644 --- a/src/components/blocks/SkillPackage.astro +++ b/src/components/blocks/SkillPackage.astro @@ -51,9 +51,11 @@ const { files, initial = files[0]?.id ?? 'skill' } = Astro.props; .skill-package-label { padding: 20px; color: var(--gold); - border-bottom: 1px solid var(--line); + /* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */ + border-bottom: 1px solid #ffffff40; + /* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 10px 'DM Mono', monospace; letter-spacing: 0.1em; text-transform: uppercase; @@ -65,18 +67,17 @@ const { files, initial = files[0]?.id ?? 'skill' } = Astro.props; gap: 15px; padding: 17px 20px; border: 0; - border-bottom: 1px solid var(--line); + /* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */ + border-bottom: 1px solid #ffffff40; color: var(--paper); background: transparent; text-align: left; cursor: pointer; } .skill-package-row code { - /* Source uses 12px; no token in --step-* matches. var(--step-1) is 15px, - close enough that the code line reads the same. Token-layer gap - reported in the task final report. */ + /* token-gap: source uses 12px with default weight (no 500); no --step-* covers 12px; owner design-system-keeper */ font: - 500 var(--step-1) 'DM Mono', + 12px 'DM Mono', monospace; } .skill-package-row small { diff --git a/src/components/blocks/WorktreeMap.astro b/src/components/blocks/WorktreeMap.astro index 28399c7..4e44213 100644 --- a/src/components/blocks/WorktreeMap.astro +++ b/src/components/blocks/WorktreeMap.astro @@ -96,7 +96,8 @@ const { branches, initial = 'main' } = Astro.props; padding: 20px; color: var(--paper); background: var(--ink); - border: 0; + /* token-gap: source uses 1px solid #41596b over the ink background; no token matches; owner design-system-keeper */ + border: 1px solid #41596b; text-align: left; cursor: pointer; } @@ -106,24 +107,28 @@ const { branches, initial = 'main' } = Astro.props; } .tree-node span { color: var(--accent); + /* token-gap: source uses 9px; no --step-* covers 9px; owner design-system-keeper */ font: - 500 var(--step-0) 'DM Mono', + 500 9px 'DM Mono', monospace; letter-spacing: 0.1em; text-transform: uppercase; } .tree-node strong { + /* token-gap: source uses 14px with default weight; no --step-* covers 14px; owner design-system-keeper */ font: - 600 var(--step-1) 'DM Mono', + 14px 'DM Mono', monospace; } .tree-node small { margin-top: 0; - color: var(--muted); + /* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */ + color: #9eabb4; font-size: var(--step-0); } .tree-node.root small { - color: var(--muted); + /* token-gap: source uses #9eabb4 even on the root; no token matches; owner design-system-keeper */ + color: #9eabb4; } .tree-node.active { box-shadow: inset 4px 0 0 var(--gold);