Every scene now reads as the same FM doctrine: paper canvas + navy
frame + amber accent, 1px hairlines, square edges, monospace
uppercase labels, no glass, no shadows, no gradients.
WHAT CHANGED
- src/index.css rewritten end-to-end. Doctrinal hex tokens declared
at :root, legacy --bg/--surface/--text/--primary/--border aliases
repointed at doctrine values so existing component classes inherit
the blueprint palette without per-component churn. Global
* { border-radius: 0 } + box-shadow strip-out. All low-opacity
tints via color-mix(in srgb, var(--bp-navy) X%, transparent) so no
rgba() literals survive.
- Topbar redone as a 44px instrument strip: navy brand-lock with
amber mark, uppercase mono tabs, amber-on-paper selected tab,
square link buttons, mode pill with currentColor border.
- Mission Control hero + scenario tab strip + KPI cards retoken
with amber underline on selected.
- Left rail: 1px-bordered KPI grid, queue cards stack as a single
bordered list, agent supervision actions span the full width.
- Inspector: tabbed nav with amber selected, hairline-separated
fields, square rule + run + evidence cards.
- Command palette: paper bg, amber-bordered selected item, mono
caps headings.
- Live API console: paper drawer, mono call rows, amber filter chip,
color-tokenised METHOD_COLOR map.
- Toaster: left-border accent on paper surface.
- Telemetry: navy gauges, mono row, square tick.
- Studio + Settings: shared studio-grid layout — paper panels in a
1px navy grid with no margins between, mono inputs.
- Run History: mono table rows with amber selected filter chip.
- Landing: mono hero, square brand mark, stats strip as one
bordered row, scenario cards in a 1px grid (no glow, no shadow,
no gradient).
- Family accents in synthetic.ts and buildScenarios.ts retoken to
doctrine hex. src/scenarios.json snapshot patched in place.
- Console.tsx METHOD_COLOR map → var(--bp-muted/info/amber/err).
AUDIT
- qa/palette_audit.mjs upgraded: scans 31 source files (.ts/.tsx/
.json + index.css), catches rgb()/rgba()/hsl()/hsla() literals,
and refuses named CSS colors (white/red/blue/...) as background/
color/fill/stroke/border values. Hex regex uses (?![0-9a-fA-F])
lookahead so deploy-id text like '#d3f1a' is not a false positive.
Result: 0 non-doctrinal literals anywhere in src/.
- qa/smoke.mjs + qa/smoke_blueprint.mjs hasText matches converted
to case-insensitive regex because the doctrine uppercases every
user-visible label via text-transform: uppercase.
- qa/snap_all_scenes.mjs captures 9 fresh 1440x900 screenshots
in qa/screenshots/v4/ (landing, mission procurement, mission AR
blueprint, inspector raw, command palette, studio, settings,
run history, mission live with console).
VERIFY
- tsc -b clean
- vite build green (CSS 41 KB / 8 KB gz, JS 851 KB / 230 KB gz)
- vitest 5 files / 24 tests green
- main smoke 27/27, 0 console errors
- blueprint smoke 15/15, 0 console errors
- palette audit clean (31 files)
ORACLE-REVIEWED
Round 1 PASS with <promise>VERIFIED</promise>. Two non-blocking
audit hardening notes landed in this same commit: scan JSON,
catch rgb/hsl/named CSS colors.
Confidence: high
Scope-risk: moderate (theme sweep, all scenes touched)
Not-tested: pixel-level visual diff vs prior theme (Oracle could
not inspect images this round; relied on programmatic checks)
42 lines
2.5 KiB
JavaScript
42 lines
2.5 KiB
JavaScript
// Take a fresh screenshot of every scene with the blueprint shell.
|
|
import { chromium } from "playwright";
|
|
import { mkdirSync } from "node:fs";
|
|
mkdirSync("qa/screenshots/v4", { recursive: true });
|
|
const URL = process.env.URL || "http://127.0.0.1:5173";
|
|
const b = await chromium.launch({ headless: true });
|
|
const p = await b.newPage({ viewport: { width: 1440, height: 900 } });
|
|
await p.goto(URL, { waitUntil: "networkidle" });
|
|
await p.waitForTimeout(400);
|
|
await p.screenshot({ path: "qa/screenshots/v4/01-landing.png" });
|
|
await p.locator(".sc-card").first().click();
|
|
await p.waitForSelector(".mc"); await p.waitForTimeout(800);
|
|
await p.screenshot({ path: "qa/screenshots/v4/02-mission-procurement.png" });
|
|
await p.locator(".mc-tab", { hasText: /accounts receivable/i }).click();
|
|
await p.waitForTimeout(700);
|
|
await p.screenshot({ path: "qa/screenshots/v4/03-mission-ar-blueprint.png" });
|
|
await p.locator(".itab", { hasText: /^raw/i }).click(); await p.waitForTimeout(200);
|
|
await p.screenshot({ path: "qa/screenshots/v4/04-inspector-raw.png" });
|
|
await p.keyboard.press("Meta+k"); await p.waitForSelector(".cmd"); await p.waitForTimeout(200);
|
|
await p.screenshot({ path: "qa/screenshots/v4/05-command-palette.png" });
|
|
await p.keyboard.press("Escape"); await p.waitForTimeout(100);
|
|
await p.locator(".tab", { hasText: /studio/i }).click();
|
|
await p.waitForSelector(".studio"); await p.waitForTimeout(300);
|
|
await p.screenshot({ path: "qa/screenshots/v4/06-studio.png" });
|
|
await p.locator(".tab", { hasText: /settings/i }).click();
|
|
await p.waitForSelector(".settings"); await p.waitForTimeout(300);
|
|
await p.screenshot({ path: "qa/screenshots/v4/07-settings.png" });
|
|
await p.locator(".tab", { hasText: /runs/i }).click();
|
|
await p.waitForSelector(".rh"); await p.waitForTimeout(300);
|
|
await p.screenshot({ path: "qa/screenshots/v4/08-run-history.png" });
|
|
await p.locator(".tab", { hasText: /mission/i }).click(); await p.waitForTimeout(400);
|
|
await p.locator(".link-btn", { hasText: /console/i }).first().click();
|
|
await p.waitForSelector(".console");
|
|
await p.locator(".mode-toggle").first().click();
|
|
await p.waitForFunction(() => Array.from(document.querySelectorAll(".mode-pill")).some(el => el.textContent?.trim() === "LIVE"), { timeout: 15000 });
|
|
await p.waitForTimeout(2000);
|
|
await p.screenshot({ path: "qa/screenshots/v4/09-mission-live-with-console.png" });
|
|
const fs = await import("node:fs/promises");
|
|
const list = await fs.readdir("qa/screenshots/v4");
|
|
console.log(`captured ${list.length} screenshots: ${list.sort().join(", ")}`);
|
|
await b.close();
|