feat(theme): promote industrial blueprint to the whole shell

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)
This commit is contained in:
2026-06-14 02:12:11 +04:00
parent 2edba020ef
commit dba1eb3328
9 changed files with 1300 additions and 9156 deletions
+50 -38
View File
@@ -1,9 +1,8 @@
// Doctrine palette audit (S6).
// 1. Every hex color inside the INDUSTRIAL BLUEPRINT CANVAS CSS section
// must be one of the doctrinal tokens.
// 2. Blueprint TSX files must not introduce hardcoded hex/rgb literals —
// they should reference --bp-* tokens via var(...).
import { readFileSync } from "node:fs";
// Doctrine palette audit (shell-wide).
// The whole app is now blueprint-native: NO source file may contain a
// hardcoded hex/rgb color outside the 11 doctrinal tokens.
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
const DOCTRINE = new Set([
"#f5f7fb", "#e8edf5", "#d5dde9",
@@ -15,42 +14,55 @@ const DOCTRINE = new Set([
function fail(msg) { console.log("✗", msg); process.exitCode = 1; }
function pass(msg) { console.log("✓", msg); }
const css = readFileSync("src/index.css", "utf8");
const idx = css.indexOf("INDUSTRIAL BLUEPRINT CANVAS");
if (idx < 0) { fail("blueprint CSS block not found"); process.exit(1); }
const cssBlock = css.slice(idx);
const cssHexes = cssBlock.match(/#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}/g) || [];
const cssOffending = cssHexes.filter((h) => !DOCTRINE.has(h.toLowerCase()));
if (cssOffending.length === 0) {
pass(`CSS palette: ${cssHexes.length} hex tokens, all doctrinal`);
} else {
fail(`CSS non-doctrinal hex tokens: ${cssOffending.join(", ")}`);
function walk(dir, exts) {
const out = [];
for (const e of readdirSync(dir)) {
const p = join(dir, e);
const s = statSync(p);
if (s.isDirectory()) { out.push(...walk(p, exts)); continue; }
if (exts.some((x) => p.endsWith(x))) out.push(p);
}
return out;
}
const cssRgba = cssBlock.match(/rgba?\([^)]*\)/g) || [];
if (cssRgba.length === 0) {
pass("CSS has no raw rgba() literals (alpha goes through color-mix tokens)");
} else {
fail(`CSS raw rgba() literals: ${cssRgba.join(", ")}`);
}
const tsxFiles = [
"src/components/ProcessGraph.tsx",
"src/components/BlueprintFrame.tsx",
const FILES = [
"src/index.css",
...walk("src", [".ts", ".tsx", ".json"]),
];
let tsxOffending = 0;
for (const f of tsxFiles) {
// CSS color keywords that are not allowed (besides the ~doctrinal greys we
// already use via tokens). The doctrine forbids "hardcoded color drift", so
// we reject named colors anywhere outside test fixtures.
const FORBIDDEN_NAMED = new Set([
"white", "black", "red", "green", "blue", "yellow", "orange", "purple",
"pink", "brown", "gray", "grey", "silver", "gold", "violet", "magenta",
"cyan", "lime", "teal", "navy", "maroon", "olive", "aqua", "fuchsia",
]);
let violations = 0;
for (const f of FILES) {
if (f.endsWith(".test.ts") || f.endsWith(".test.tsx")) continue;
const src = readFileSync(f, "utf8");
const hex = (src.match(/#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}/g) || []).filter(
(h) => !DOCTRINE.has(h.toLowerCase()),
);
const rgba = src.match(/rgba?\(\s*\d+/g) || [];
if (hex.length || rgba.length) {
fail(`${f}: hardcoded color literals: hex=${hex.join(",") || "none"} rgba=${rgba.join(",") || "none"}`);
tsxOffending += hex.length + rgba.length;
// Match a hex color only when it is NOT followed by another hex digit
// (so a literal like `#d3f1a` inside a deploy id is not mis-matched as `#d3f`).
const hex = (src.match(/#[0-9a-fA-F]{6}(?![0-9a-fA-F])|#[0-9a-fA-F]{3}(?![0-9a-fA-F])/g) || [])
.filter((h) => !DOCTRINE.has(h.toLowerCase()));
const rgb = src.match(/\b(?:rgba?|hsla?)\(\s*\d+/g) || [];
const named = [];
for (const w of FORBIDDEN_NAMED) {
const re = new RegExp(`\\b(?:background|color|fill|stroke|border)[^;:]*:\\s*${w}\\b`, "gi");
const m = src.match(re);
if (m) named.push(...m);
}
if (hex.length || rgb.length || named.length) {
fail(`${f}: hex=${hex.join(",") || "none"} rgb=${rgb.join(",") || "none"} named=${named.length ? named.join("; ") : "none"}`);
violations += hex.length + rgb.length + named.length;
}
}
if (tsxOffending === 0) pass(`TSX palette: ${tsxFiles.length} blueprint TSX files clean (no hardcoded color literals)`);
if (process.exitCode === 1) console.log("\nPalette audit FAILED");
else console.log("\nPalette audit PASSED");
if (violations === 0) {
pass(`palette clean: ${FILES.length} files scanned, 0 non-doctrinal color literals`);
console.log("\nPalette audit PASSED");
} else {
console.log(`\nPalette audit FAILED · ${violations} non-doctrinal literals`);
}