flowmaster-mission-control-.../qa/smoke_blueprint.mjs
Shad dba1eb3328
Some checks failed
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
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)
2026-06-14 02:12:11 +04:00

84 lines
4.0 KiB
JavaScript

// Blueprint canvas smoke covering S1, S2, S3 from the canvas-reimagine
// scenario contract. Run after `vite dev` is up at 127.0.0.1:5173.
import { chromium } from "playwright";
import { mkdirSync } from "node:fs";
const URL = process.env.URL || "http://127.0.0.1:5173";
const OUT = "qa/screenshots";
mkdirSync(OUT, { recursive: true });
function ok(label, cond, detail = "") {
console.log(`${cond ? "✓" : "✗"} ${label}${detail ? " · " + detail : ""}`);
if (!cond) process.exitCode = 1;
}
const b = await chromium.launch({ headless: true });
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
const errors = [];
p.on("pageerror", (e) => errors.push(`pageerror ${e.message}`));
p.on("console", (m) => { if (m.type() === "error") errors.push(`console.error ${m.text().slice(0, 200)}`); });
await p.goto(URL, { waitUntil: "networkidle" });
await p.locator(".sc-card").first().click();
await p.waitForSelector(".mc");
await p.waitForTimeout(800);
await p.screenshot({ path: `${OUT}/blueprint-canvas.png`, fullPage: false });
// S1: paper background + navy hairline grid + square nodes
const frame = p.locator(".bp-frame").first();
ok("S1.a blueprint frame mounted", await frame.isVisible());
const nodeStyle = await p.locator(".bp-node").first().evaluate((el) => {
const cs = getComputedStyle(el);
return {
radius: cs.borderTopLeftRadius,
borderColor: cs.borderTopColor,
fontFamily: cs.fontFamily,
};
});
ok("S1.b node has zero border radius", nodeStyle.radius === "0px", `was ${nodeStyle.radius}`);
ok("S1.c node uses monospace font", /mono|Fira Code/i.test(nodeStyle.fontFamily), `font=${nodeStyle.fontFamily}`);
const canvasBg = await p.locator(".bp-canvas-wrap").first().evaluate((el) => getComputedStyle(el).backgroundColor);
ok("S1.d canvas background is paper #f5f7fb", canvasBg === "rgb(245, 247, 251)", `was ${canvasBg}`);
const gridLines = await p.locator(".react-flow__background").count();
ok("S1.e two grid backgrounds rendered (8px minor + 64px major)", gridLines >= 2, `count=${gridLines}`);
// S2: edges use step (orthogonal) — path commands are only M/L/H/V
const pathD = await p.locator(".react-flow__edge-path").first().evaluate((el) => el.getAttribute("d") || "");
const hasCurve = /[CQcq]/.test(pathD);
ok("S2 edges are orthogonal (no curve commands)", !hasCurve, `d=${pathD.slice(0, 60)}`);
// S3: ruler chrome + corner readout
ok("S3.a top ruler present", await p.locator(".bp-ruler-top").isVisible());
ok("S3.b left ruler present", await p.locator(".bp-ruler-left").isVisible());
const tickCount = await p.locator(".bp-tick.is-major").count();
ok("S3.c major ticks rendered (>= 4)", tickCount >= 4, `major ticks=${tickCount}`);
ok("S3.d corner glyph present", await p.locator(".bp-corner-tl").isVisible());
const readouts = await p.locator(".bp-readout-top .bp-readout-cell").count();
ok("S3.e top readout has >= 6 cells", readouts >= 6, `cells=${readouts}`);
ok("S3.f legend swatches present", await p.locator(".bp-swatch").count() >= 5);
// S5: Studio + Settings + Console still render
await p.locator(".tab", { hasText: /studio/i }).click();
await p.waitForSelector(".studio");
ok("S5.a studio renders", await p.locator(".studio-panel").count() >= 4);
await p.locator(".tab", { hasText: /settings/i }).click();
await p.waitForSelector(".settings");
ok("S5.b settings renders", await p.locator(".quick-users .link-btn").count() >= 1);
await p.locator(".tab", { hasText: /mission/i }).click();
await p.waitForSelector(".bp-frame");
await p.locator(".link-btn", { hasText: /console/i }).first().click();
await p.waitForSelector(".console");
ok("S5.c console renders", await p.locator(".console").isVisible());
// S6 cleanup: take a final picture of the canvas
await p.locator(".link-btn", { hasText: /console/i }).first().click();
await p.waitForTimeout(300);
await p.screenshot({ path: `${OUT}/blueprint-canvas-clean.png`, fullPage: false });
console.log(`\nconsole errors: ${errors.length}`);
errors.slice(0, 5).forEach((e) => console.log(" -", e));
if (errors.length > 0) process.exitCode = 1;
await b.close();