Shad 3ffd0e68a7 Mission Control demo v2
Polished command-center for FlowMaster with two data modes:
- SNAPSHOT: bundled src/scenarios.json from demo.flow-master.ai
- LIVE: in-browser fetch via src/lib/api.ts (dev-login + bearer)

Scenarios:
- procurement, extra-1, extra-2 (live from EA2)
- ar, hcm, gl, service (industry blueprints, same typed shell)

Honesty pass after Oracle review:
- No invented numbers (Telemetry derives SLA + agent acceptance from real data)
- Preview-only actions fire toasts naming the endpoint to wire them
- Blueprint tours framed as 'industry blueprint', not 'we don't have this yet'
- Mode pill + last-fetch age + refresh in topbar
- Dev CORS dodged via vite proxy; production deploys same-origin

18 vitest tests + 26 playwright smoke assertions + DOM layout audit.

Constraint: cross-origin live mode rejected by browser → fall back to snapshot
Rejected: hardcoded SLA % | dishonest demo metrics
Directive: wire preview-only action handlers to /api/runtime/transactions/{id}/actions to ship them for real
Confidence: high
Scope-risk: narrow
Not-tested: production deployment via flowmaster-ops overlay
2026-06-14 00:09:32 +04:00

40 lines
1.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { layoutGraph } from "./layout";
import type { ProcessStep, FlowEdge } from "../data/types";
const linearSteps: ProcessStep[] = [
{ id: "a", name: "A", kind: "start", owner: "system", governs: [], state: "done", actions: [] },
{ id: "b", name: "B", kind: "human", owner: "human", governs: [], state: "running", actions: [] },
{ id: "c", name: "C", kind: "end", owner: "system", governs: [], state: "idle", actions: [] },
];
const linearEdges: FlowEdge[] = [
{ id: "ab", source: "a", target: "b" },
{ id: "bc", source: "b", target: "c" },
];
describe("layoutGraph", () => {
it("returns one position per step", () => {
const positions = layoutGraph(linearSteps, linearEdges);
expect(positions.length).toBe(linearSteps.length);
expect(new Set(positions.map((p) => p.id))).toEqual(new Set(["a", "b", "c"]));
});
it("produces strictly-increasing x positions for a linear chain (LR layout)", () => {
const positions = layoutGraph(linearSteps, linearEdges);
const byId = Object.fromEntries(positions.map((p) => [p.id, p.position]));
expect(byId.a.x).toBeLessThan(byId.b.x);
expect(byId.b.x).toBeLessThan(byId.c.x);
});
it("handles disconnected nodes without crashing", () => {
const positions = layoutGraph(
[
{ id: "x", name: "X", kind: "start", owner: "system", governs: [], state: "idle", actions: [] },
{ id: "y", name: "Y", kind: "end", owner: "system", governs: [], state: "idle", actions: [] },
],
[],
);
expect(positions.length).toBe(2);
});
});