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

73 lines
2.5 KiB
TypeScript

// MissionControl scene: scenario tabs + graph + rails + telemetry strip.
import { useApp, scenarioById } from "../state/store";
import ProcessGraph from "../components/ProcessGraph";
import LeftRail from "../components/LeftRail";
import Inspector from "../components/Inspector";
import Telemetry from "../components/Telemetry";
export default function MissionControl() {
const scenarioId = useApp((s) => s.scenarioId);
const setScenarioId = useApp((s) => s.setScenarioId);
const scenarios = useApp((s) => s.scenarios);
const liveLoading = useApp((s) => s.liveLoading);
const liveError = useApp((s) => s.liveError);
const sc = scenarioById(scenarioId);
return (
<div className="mc">
{liveLoading && (
<div className="mc-banner mc-banner-info">
<span className="spin" /> Fetching live scenarios from demo.flow-master.ai
</div>
)}
{liveError && (
<div className="mc-banner mc-banner-err">
Live mode failed: <span className="mono">{liveError}</span> · showing snapshot
</div>
)}
<div className="mc-strip" role="tablist" aria-label="Scenarios">
{scenarios.map((s) => (
<button
key={s.id}
role="tab"
aria-selected={s.id === scenarioId}
className={`mc-tab${s.id === scenarioId ? " mc-tab-sel" : ""}`}
style={{ ["--accent" as string]: s.family.accent }}
onClick={() => setScenarioId(s.id)}
>
<span className="mc-tab-dot" />
<span className="mc-tab-label">{s.family.label}</span>
<span className={`mc-tab-mark ${s.live ? "is-live" : "is-syn"}`}>{s.live ? "live" : "bp"}</span>
</button>
))}
</div>
<div className="mc-hero">
<div>
<div className="mc-hero-eyebrow">{sc?.family.subtitle}</div>
<h2 className="mc-hero-title">{sc?.defName}</h2>
<div className="mc-hero-sub">{sc?.tagline}</div>
</div>
<div className="mc-hero-kpis">
{sc?.kpis.slice(0, 4).map((k) => (
<div className="mc-kpi" key={k.label}>
<div className="mc-kpi-v">{k.value}</div>
<div className="mc-kpi-l">{k.label}</div>
</div>
))}
</div>
</div>
<div className="mc-body">
<LeftRail />
<main className="mc-main">
<ProcessGraph />
</main>
<Inspector />
</div>
<Telemetry />
</div>
);
}