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
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user