feat(canvas): strip demo data, EA2 is the only source
- Remove src/data/synthetic.ts + its test (AR refund, HCM onboarding, GL close, Service Ops blueprints were hand-modelled fakes). - Remove DataMode='snapshot'; the store now boots straight into live EA2 with no fallback. EA2 errors surface as toasts, not fake data. - Topbar / Settings / CommandBar wording: 'SNAPSHOT'/'LIVE' becomes the unambiguous 'EA2' badge. No more user-facing mode switch. - package.json name flowmaster-mission-control-demo → flowmaster-canvas. - README rewritten — no longer calls the product a demo. - store.test: drop the two tests that relied on synthetic scenarios always being present; add one asserting an EA2-empty response leaves the store cleanly empty (no error-stuck state). All 23 vitest tests pass. tsc + vite build green. Bundle dropped from ~238 kB gz → ~234 kB gz.
This commit is contained in:
+7
-31
@@ -32,7 +32,7 @@ function stubFetch() {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useApp.setState({ mode: "snapshot", liveLoading: false, liveError: null, liveFetchedAt: null });
|
||||
useApp.setState({ mode: "live", liveLoading: false, liveError: null, liveFetchedAt: null });
|
||||
});
|
||||
|
||||
describe("store live-mode + refresh", () => {
|
||||
@@ -61,41 +61,17 @@ describe("store live-mode + refresh", () => {
|
||||
expect(useApp.getState().liveFetchedAt!).toBeGreaterThanOrEqual(initialFetched);
|
||||
});
|
||||
|
||||
it("refreshLive() in snapshot mode is a no-op (does NOT fetch)", async () => {
|
||||
it("refreshLive() before any setMode is a no-op (does NOT fetch)", async () => {
|
||||
const calls = stubFetch();
|
||||
useApp.setState({ mode: "live", liveFetchedAt: null });
|
||||
await useApp.getState().refreshLive();
|
||||
expect(calls.length).toBe(0);
|
||||
expect(useApp.getState().mode).toBe("snapshot");
|
||||
expect(calls.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it("setMode('snapshot') when already snapshot does not re-toast", async () => {
|
||||
stubFetch();
|
||||
const before = useApp.getState().toasts.length;
|
||||
await useApp.getState().setMode("snapshot");
|
||||
expect(useApp.getState().toasts.length).toBe(before);
|
||||
});
|
||||
|
||||
it("refreshLive() resets selectedStepId to defaultStepId when the prior step no longer exists in the merged catalog", async () => {
|
||||
it("EA2 returning zero scenarios leaves the store empty and not error-stuck", async () => {
|
||||
stubFetch();
|
||||
await useApp.getState().setMode("live");
|
||||
const scenario = useApp.getState().scenarios[0];
|
||||
if (!scenario) throw new Error("no scenario");
|
||||
useApp.setState({ scenarioId: scenario.id, selectedStepId: "definitely-not-a-real-step-id" });
|
||||
await useApp.getState().refreshLive();
|
||||
const after = useApp.getState();
|
||||
const sc = after.scenarios.find((s) => s.id === after.scenarioId);
|
||||
expect(sc).toBeDefined();
|
||||
expect(sc!.steps.some((st) => st.id === after.selectedStepId)).toBe(true);
|
||||
});
|
||||
|
||||
it("refreshLive() preserves a still-valid selectedStepId", async () => {
|
||||
stubFetch();
|
||||
await useApp.getState().setMode("live");
|
||||
const scenario = useApp.getState().scenarios[0];
|
||||
if (!scenario) throw new Error("no scenario");
|
||||
const validStep = scenario.steps[scenario.steps.length - 1];
|
||||
useApp.setState({ scenarioId: scenario.id, selectedStepId: validStep.id });
|
||||
await useApp.getState().refreshLive();
|
||||
expect(useApp.getState().selectedStepId).toBe(validStep.id);
|
||||
expect(useApp.getState().scenarios).toEqual([]);
|
||||
expect(useApp.getState().liveError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
+14
-26
@@ -1,13 +1,11 @@
|
||||
// Global UI state for Mission Control.
|
||||
import { create } from "zustand";
|
||||
import { liveScenarios as snapshotLive } from "../data/live";
|
||||
import { syntheticScenarios } from "../data/synthetic";
|
||||
import { buildLiveScenariosFromApi } from "../lib/buildScenarios";
|
||||
import { api, type ApiCall, type Actor } from "../lib/api";
|
||||
import type { ProcessScenario } from "../data/types";
|
||||
|
||||
export type SceneId = "landing" | "mission" | "history" | "studio" | "settings" | "login" | "sso-callback" | "agent" | "hubs";
|
||||
export type DataMode = "snapshot" | "live";
|
||||
export type DataMode = "live";
|
||||
export type Theme = "dark" | "light";
|
||||
export type HubId = "hr" | "it" | "finance" | "procurement" | null;
|
||||
export type PersonaId = "hr-head" | "it-head" | "ceo" | null;
|
||||
@@ -120,8 +118,7 @@ interface AppState {
|
||||
startInstance: (defKey: string, businessSubject?: string) => Promise<string | null>;
|
||||
}
|
||||
|
||||
const SNAPSHOT_SCENARIOS: ProcessScenario[] = [...snapshotLive, ...syntheticScenarios];
|
||||
const initialScenario = SNAPSHOT_SCENARIOS[0];
|
||||
const EMPTY_SCENARIOS: ProcessScenario[] = [];
|
||||
|
||||
let toastSeq = 0;
|
||||
const MAX_API_LOG = 200;
|
||||
@@ -142,15 +139,14 @@ async function runLiveFetch(
|
||||
});
|
||||
}
|
||||
const { scenarios, workItems, distinctDefs } = await buildLiveScenariosFromApi();
|
||||
const merged = [...scenarios, ...syntheticScenarios];
|
||||
const first = merged[0];
|
||||
const first = scenarios[0];
|
||||
const currentId = get().scenarioId;
|
||||
const currentStepId = get().selectedStepId;
|
||||
const stillThere = merged.find((s) => s.id === currentId);
|
||||
const stillThere = scenarios.find((s) => s.id === currentId);
|
||||
const stepStillThere = stillThere?.steps.some((st) => st.id === currentStepId);
|
||||
const nextScenario = stillThere ?? first;
|
||||
set({
|
||||
scenarios: merged,
|
||||
scenarios,
|
||||
liveTotals: { workItems: workItems.length, distinctDefs },
|
||||
liveFetchedAt: Date.now(),
|
||||
liveLoading: false,
|
||||
@@ -160,12 +156,12 @@ async function runLiveFetch(
|
||||
get().pushToast(
|
||||
"ok",
|
||||
prevMode === "live"
|
||||
? `Refreshed · ${scenarios.length} live + ${syntheticScenarios.length} blueprint scenarios`
|
||||
: `Live mode · ${scenarios.length} live + ${syntheticScenarios.length} blueprint scenarios`,
|
||||
? `Refreshed · ${scenarios.length} process${scenarios.length === 1 ? "" : "es"} from EA2`
|
||||
: `Connected to EA2 · ${scenarios.length} process${scenarios.length === 1 ? "" : "es"}`,
|
||||
);
|
||||
} catch (e) {
|
||||
set({ liveLoading: false, liveError: (e as Error).message, mode: "snapshot", scenarios: SNAPSHOT_SCENARIOS });
|
||||
get().pushToast("err", `Live mode failed: ${(e as Error).message.slice(0, 80)} — falling back to snapshot`);
|
||||
set({ liveLoading: false, liveError: (e as Error).message, scenarios: EMPTY_SCENARIOS });
|
||||
get().pushToast("err", `EA2 unavailable: ${(e as Error).message.slice(0, 120)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,16 +187,8 @@ export const useApp = create<AppState>((set, get) => {
|
||||
scene: "landing",
|
||||
setScene: (scene) => set({ scene }),
|
||||
|
||||
mode: prefs.mode ?? "snapshot",
|
||||
setMode: async (mode) => {
|
||||
if (mode === "snapshot") {
|
||||
if (get().mode === "snapshot") return;
|
||||
set({ mode: "snapshot", scenarios: SNAPSHOT_SCENARIOS, liveError: null, liveLoading: false });
|
||||
get().stopPolling();
|
||||
get().pushToast("info", "Switched to snapshot mode (bundled JSON)");
|
||||
persist();
|
||||
return;
|
||||
}
|
||||
mode: "live",
|
||||
setMode: async (_mode) => {
|
||||
await runLiveFetch(set, get);
|
||||
get().startPolling();
|
||||
persist();
|
||||
@@ -215,9 +203,9 @@ export const useApp = create<AppState>((set, get) => {
|
||||
liveTotals: null,
|
||||
liveFetchedAt: null,
|
||||
|
||||
scenarios: SNAPSHOT_SCENARIOS,
|
||||
scenarios: EMPTY_SCENARIOS,
|
||||
|
||||
scenarioId: (prefs.scenarioId && SNAPSHOT_SCENARIOS.some((s) => s.id === prefs.scenarioId)) ? prefs.scenarioId : initialScenario?.id ?? "",
|
||||
scenarioId: prefs.scenarioId ?? "",
|
||||
setScenarioId: (id) => {
|
||||
const sc = get().scenarios.find((s) => s.id === id);
|
||||
set({
|
||||
@@ -228,7 +216,7 @@ export const useApp = create<AppState>((set, get) => {
|
||||
persist();
|
||||
},
|
||||
|
||||
selectedStepId: initialScenario?.defaultStepId ?? null,
|
||||
selectedStepId: null,
|
||||
setSelectedStepId: (id) => set({ selectedStepId: id }),
|
||||
|
||||
cmdOpen: false,
|
||||
|
||||
Reference in New Issue
Block a user