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:
2026-06-14 00:09:32 +04:00
commit 3ffd0e68a7
45 changed files with 15603 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
// RunHistory scene: cross-scenario timeline.
import { useMemo, useState } from "react";
import { useApp } from "../state/store";
import { Clock, History } from "../components/icons";
const STATUS_FILTERS = ["all", "running", "completed", "errored", "queued"] as const;
type Status = (typeof STATUS_FILTERS)[number];
const STATUS_COLOR: Record<string, string> = {
running: "var(--run)",
completed: "var(--ok)",
done: "var(--ok)",
errored: "var(--block)",
failed: "var(--block)",
queued: "var(--queue)",
};
export default function RunHistory() {
const [filter, setFilter] = useState<Status>("all");
const scenarios = useApp((s) => s.scenarios);
const rows = useMemo(() => {
const out: Array<{ scenarioId: string; scenarioLabel: string; accent: string; live: boolean; run: (typeof scenarios)[number]["runs"][number] }> = [];
for (const s of scenarios) {
for (const r of s.runs) {
if (filter !== "all" && r.status !== filter && !(filter === "completed" && r.status === "done")) continue;
out.push({ scenarioId: s.id, scenarioLabel: s.family.label, accent: s.family.accent, live: s.live, run: r });
}
}
return out.sort((a, b) => (b.run.startedAt || "").localeCompare(a.run.startedAt || ""));
}, [filter]);
const maxDuration = useMemo(() => Math.max(60, ...rows.map((r) => r.run.durationSec)), [rows]);
return (
<div className="rh">
<header className="rh-head">
<div>
<span className="mc-hero-eyebrow"><History size={12} /> Run history</span>
<h2 className="mc-hero-title">All scenarios · all runs</h2>
</div>
<nav className="rh-filters">
{STATUS_FILTERS.map((f) => (
<button key={f} className={`rh-chip${filter === f ? " rh-chip-sel" : ""}`} onClick={() => setFilter(f)}>
{f}
</button>
))}
</nav>
</header>
<div className="rh-list">
{rows.length === 0 && <div className="empty">No runs match the current filter.</div>}
{rows.map((row) => (
<div className="rh-row" key={`${row.scenarioId}-${row.run.id}`}>
<div className="rh-row-id">
<span className="rh-dot" style={{ background: row.accent }} />
<span className="mono">{row.run.shortId}</span>
<span className={`tag ${row.live ? "tag-live" : "tag-syn"}`}>{row.live ? "live" : "bp"}</span>
</div>
<div className="rh-row-scenario">{row.scenarioLabel}</div>
<div className="rh-row-step">{row.run.activeStep ?? "—"}</div>
<div className="rh-row-bar">
<div
className="rh-bar-fill"
style={{
width: `${Math.max(2, (row.run.durationSec / maxDuration) * 100)}%`,
background: STATUS_COLOR[row.run.status] ?? "var(--border-strong)",
}}
title={`${Math.round(row.run.durationSec / 60)}m`}
/>
</div>
<div className="rh-row-meta">
<span className="mono"><Clock size={11} /> {Math.round(row.run.durationSec / 60)}m</span>
<span className={`pill pill-${row.run.status === "completed" || row.run.status === "done" ? "done" : row.run.status === "running" ? "running" : row.run.status === "errored" || row.run.status === "failed" ? "errored" : "queued"}`}>{row.run.status}</span>
</div>
</div>
))}
</div>
</div>
);
}