diff --git a/THEMES-NOTES.md b/THEMES-NOTES.md index 7f18c088..051d979c 100644 --- a/THEMES-NOTES.md +++ b/THEMES-NOTES.md @@ -103,11 +103,31 @@ was not in tonight's scope. --- +## On verification, and what the screenshots can and cannot tell you + +The byte comparison is **dominated by wallpaper noise**. The dashboard background rotates per page load, so +any screen showing it differs from itself between runs — a control capture of the same build against itself +had 4 of 16 differ, and which 4 changes each time. Between 5 and 6 screens come out byte-identical to the +pre-refactor baseline on any given run; the rest are the wallpaper. + +So the authoritative instrument is `scripts/theme-vardiff.ts`, which compares the COMPUTED value of every +theme variable between the old and new builds in the same colour mode. It reports **all 53 identical**. +That is a much stronger statement than a screenshot: it says the token layer resolves to exactly the values +the old stylesheet produced. + +It also found the one real regression, which no amount of looking would have: six `--duck-*` variables came +out EMPTY in the new build. The utilities were remapped, but `prose.css` reads `var(--duck-teal)` DIRECTLY +in eleven places for chat markdown — links, blockquotes, inline code, checkboxes — and `landing/styles.css` +does the same. Fixed by keeping the legacy names as aliases in every theme, which also protects any plugin +or inline style doing the same thing. They retire with the utility rename. + ## Tools built - `scripts/theme-snapshot.ts` — captures 8 routes × 2 modes, or a pinned theme with `--theme`. Compares two directories byte-for-byte. Its first version silently photographed light twice; it now warns when a light/dark pair is identical. +- `scripts/theme-vardiff.ts` — computed theme variables, old build vs new, side by side. The instrument + that actually proved the extraction faithful. - `scripts/theme-preview-server.ts` — serves this worktree's build against the RUNNING platform's API, so the new frontend can be seen with real data without starting a second platform beside the live one. diff --git a/scripts/theme-vardiff.ts b/scripts/theme-vardiff.ts new file mode 100644 index 00000000..e1b0797b --- /dev/null +++ b/scripts/theme-vardiff.ts @@ -0,0 +1,111 @@ +/** + * Compare the computed theme variables of two running builds. + * + * Screenshots say THAT something changed; this says WHICH VARIABLE changed, which is the question worth + * asking after an extraction. Seven dark screens differed between the old stylesheet and the new one, and + * staring at two near-identical dark screenshots is not a way to find a hue that moved by 2%. + * + * bun scripts/theme-vardiff.ts [oldUrl] [newUrl] + */ +import { chromium } from 'playwright'; + +const token = process.argv[2]; +if (!token) { + console.log(' usage: bun scripts/theme-vardiff.ts [oldUrl] [newUrl]'); + process.exit(1); +} +const oldUrl = process.argv[3] ?? 'http://127.0.0.1:9000'; +const newUrl = process.argv[4] ?? 'http://127.0.0.1:9010'; + +const VARS = [ + '--background', + '--foreground', + '--card', + '--card-foreground', + '--popover', + '--popover-foreground', + '--primary', + '--primary-foreground', + '--secondary', + '--secondary-foreground', + '--muted', + '--muted-foreground', + '--accent', + '--accent-foreground', + '--destructive', + '--destructive-foreground', + '--success', + '--success-foreground', + '--warning', + '--warning-foreground', + '--info', + '--info-foreground', + '--border', + '--input', + '--ring', + '--brand', + '--brand-foreground', + '--brand-muted', + '--radius', + '--cta', + '--cta-foreground', + '--ctahover', + '--naturegreen', + '--naturegreen-foreground', + '--duck-teal', + '--duck-dark', + '--duck-yellow', + '--duck-forest', + '--duck-beige', + '--duck-orange', + '--glass-bg', + '--glass-border', + '--dock-bg', + '--dock-border', + '--dock-tooltip-bg', + '--card-bg', + '--card-grid-color', + '--pixel-grid-color', + '--chart-primary', + '--chart-secondary', + '--status-success', + '--status-warning', + '--status-in-progress', +]; + +const browser = await chromium.launch(); + +async function read(url: string, extra: string): Promise> { + const context = await browser.newContext({ viewport: { width: 1000, height: 700 } }); + await context.addInitScript(`localStorage.setItem('BEARER_TOKEN', ${JSON.stringify(token)}); ${extra}`); + const page = await context.newPage(); + // `commit` rather than `load`: this app holds websockets open, so waiting for a quiet network can hang. + await page.goto(`${url}/jobs`, { waitUntil: 'commit', timeout: 20_000 }); + await page.waitForTimeout(2500); + const values = await page.evaluate((vars: string[]) => { + const cs = getComputedStyle(document.documentElement); + const out: Record = {}; + for (const v of vars) out[v] = cs.getPropertyValue(v).trim(); + return out; + }, VARS); + await context.close(); + return values; +} + +// The OLD build spoke in colour modes; the NEW one in themes. Same appearance, different vocabulary — which +// is the entire refactor, and why the two sides are set up differently here. +const before = await read(oldUrl, `localStorage.setItem('officer-color-mode','dark');`); +const after = await read(newUrl, `localStorage.setItem('officer-theme','duck-dark');`); +await browser.close(); + +let differing = 0; +for (const key of VARS) { + const a = before[key] ?? ''; + const b = after[key] ?? ''; + if (a !== b) { + console.log(` DIFF ${key.padEnd(24)} old=${JSON.stringify(a)} new=${JSON.stringify(b)}`); + differing += 1; + } +} +console.log(differing === 0 ? ' every variable matches' : ` ${differing} of ${VARS.length} differ`); +process.exit(0); diff --git a/src/workspaces/theme/css/themes/duck-dark.css b/src/workspaces/theme/css/themes/duck-dark.css index adb92c23..9eb537f5 100644 --- a/src/workspaces/theme/css/themes/duck-dark.css +++ b/src/workspaces/theme/css/themes/duck-dark.css @@ -28,6 +28,21 @@ --ink: #f1f5f9; --surface-warm: #292524; + /* ── Legacy variable names, for direct `var()` consumers ────────────────────────────────────── + The `duck-*` UTILITIES are remapped in base.css, but a stylesheet reading `var(--duck-teal)` gets + nothing from that — it wants the variable itself. `prose.css` does exactly this eleven times for chat + markdown (links, blockquotes, inline code, checkboxes), and the rename silently emptied all of them. + Caught by diffing computed variables between the old and new builds, not by reading. + + Kept as aliases rather than as values: there is one source of truth above, and these follow it. They + retire when nothing reads them — the same migration as the utility names. */ + --duck-teal: var(--brand-primary); + --duck-dark: var(--ink); + --duck-yellow: var(--brand-accent); + --duck-forest: var(--brand-deep); + --duck-beige: var(--surface-warm); + --duck-orange: var(--brand-warm); + /* ── The page ─────────────────────────────────────────────────────────────────────────────────── */ --page-bg: hsl(222.2 84% 4.9%); --pixel-grid-color: rgba(200, 230, 210, 0.08); diff --git a/src/workspaces/theme/css/themes/duck-light.css b/src/workspaces/theme/css/themes/duck-light.css index 66081e26..9322f3f3 100644 --- a/src/workspaces/theme/css/themes/duck-light.css +++ b/src/workspaces/theme/css/themes/duck-light.css @@ -29,6 +29,21 @@ --ink: #14532d; --surface-warm: #e7d4b5; + /* ── Legacy variable names, for direct `var()` consumers ────────────────────────────────────── + The `duck-*` UTILITIES are remapped in base.css, but a stylesheet reading `var(--duck-teal)` gets + nothing from that — it wants the variable itself. `prose.css` does exactly this eleven times for chat + markdown (links, blockquotes, inline code, checkboxes), and the rename silently emptied all of them. + Caught by diffing computed variables between the old and new builds, not by reading. + + Kept as aliases rather than as values: there is one source of truth above, and these follow it. They + retire when nothing reads them — the same migration as the utility names. */ + --duck-teal: var(--brand-primary); + --duck-dark: var(--ink); + --duck-yellow: var(--brand-accent); + --duck-forest: var(--brand-deep); + --duck-beige: var(--surface-warm); + --duck-orange: var(--brand-warm); + /* ── The page ───────────────────────────────────────────────────────────────────────────────── A complete `background` value, so a theme can be an image or a gradient rather than a colour. */ --page-bg: hsl(0 0% 100%); diff --git a/src/workspaces/theme/css/themes/plain.css b/src/workspaces/theme/css/themes/plain.css index 1d6b63ca..43291928 100644 --- a/src/workspaces/theme/css/themes/plain.css +++ b/src/workspaces/theme/css/themes/plain.css @@ -31,6 +31,21 @@ --ink: #000000; --surface-warm: #ffffff; + /* ── Legacy variable names, for direct `var()` consumers ────────────────────────────────────── + The `duck-*` UTILITIES are remapped in base.css, but a stylesheet reading `var(--duck-teal)` gets + nothing from that — it wants the variable itself. `prose.css` does exactly this eleven times for chat + markdown (links, blockquotes, inline code, checkboxes), and the rename silently emptied all of them. + Caught by diffing computed variables between the old and new builds, not by reading. + + Kept as aliases rather than as values: there is one source of truth above, and these follow it. They + retire when nothing reads them — the same migration as the utility names. */ + --duck-teal: var(--brand-primary); + --duck-dark: var(--ink); + --duck-yellow: var(--brand-accent); + --duck-forest: var(--brand-deep); + --duck-beige: var(--surface-warm); + --duck-orange: var(--brand-warm); + /* ── The page ───────────────────────────────────────────────────────────────────────────────── Flat white and no grid. If a pixel grid or a wallpaper appears under this theme, something is drawing it from outside the theme layer. */