-
Simulator-backed demo data
+
{overview.ok ? overview.context.siteName : "Operations Workbench"}
{overview.ok
@@ -73,6 +78,10 @@ export default async function OverviewPage() {
+ {overview.ok ? (
+
+ ) : null}
+
{!overview.ok ? : null}
{overview.ok ? (
diff --git a/apps/web/e2e/operations-workbench-demo.spec.ts b/apps/web/e2e/operations-workbench-demo.spec.ts
index c122515..f0c65d1 100644
--- a/apps/web/e2e/operations-workbench-demo.spec.ts
+++ b/apps/web/e2e/operations-workbench-demo.spec.ts
@@ -11,6 +11,11 @@ test("walks the simulator-backed Operations Workbench demo path", async ({ page
await page.goto("/");
await expect(page.getByText("Simulator-backed demo data").first()).toBeVisible();
+ await expect(page.getByText("Synthetic local scenario; not real plant data.").first()).toBeVisible();
+ await expect(page.getByRole("region", { name: "Local API connection state" })).toBeVisible();
+ await expect(page.getByText("API target").first()).toBeVisible();
+ await expect(page.getByText("Health", { exact: true }).first()).toBeVisible();
+ await expect(page.getByText("Simulator-backed demo API")).toBeVisible();
await expect(page.getByRole("heading", { name: "Greenville Demo Site" })).toBeVisible();
await expect(page.getByText("Active detections")).toBeVisible();
await expect(page.getByText("Pending recommendations")).toBeVisible();
diff --git a/apps/web/lib/api-client.ts b/apps/web/lib/api-client.ts
index 948ddfc..2f27294 100644
--- a/apps/web/lib/api-client.ts
+++ b/apps/web/lib/api-client.ts
@@ -189,6 +189,9 @@ export function formatApiError(error: unknown): string {
const status = error.status ? ` (${error.status})` : "";
return `${error.message}${status}`;
}
+ if (error instanceof TypeError) {
+ return "The Workbench could not reach the configured FastAPI demo backend.";
+ }
if (error instanceof Error) {
return error.message;
}
diff --git a/apps/web/tests/app-shell.test.mjs b/apps/web/tests/app-shell.test.mjs
index 4957e99..b39865b 100644
--- a/apps/web/tests/app-shell.test.mjs
+++ b/apps/web/tests/app-shell.test.mjs
@@ -26,10 +26,12 @@ test("navigation includes the required demo routes", () => {
assert.match(layout, /Detections/);
assert.match(layout, /Recommendations/);
assert.match(layout, /RCA\/CAPA Draft/);
+ assert.match(layout, /DemoDataBadge/);
});
test("overview page contains manufacturer demo dashboard content", () => {
const overview = readFileSync(join(root, "app/page.tsx"), "utf8");
+ const demoState = readFileSync(join(root, "app/components/demo-state.tsx"), "utf8");
assert.match(overview, /Current demo context/);
assert.match(overview, /Active detections/);
@@ -37,6 +39,12 @@ test("overview page contains manufacturer demo dashboard content", () => {
assert.match(overview, /Most important detection/);
assert.match(overview, /Open detection/);
assert.match(overview, /selectImportantDetection/);
+ assert.match(overview, /ApiConnectionBanner/);
+ assert.match(overview, /API base URL/);
+ assert.match(overview, /API health/);
+ assert.match(demoState, /Local API connected/);
+ assert.match(demoState, /API target/);
+ assert.match(demoState, /Simulator-backed demo API/);
});
test("detections pages contain list and detail content", () => {
@@ -167,11 +175,17 @@ test("RCA CAPA page contains selected detection draft preview", () => {
test("app shell documents configurable API base URL", () => {
const config = readFileSync(join(root, "lib/api-config.ts"), "utf8");
const client = readFileSync(join(root, "lib/api-client.ts"), "utf8");
+ const demoState = readFileSync(join(root, "app/components/demo-state.tsx"), "utf8");
const readme = readFileSync(join(root, "README.md"), "utf8");
assert.match(config, /NEXT_PUBLIC_API_BASE_URL/);
assert.match(client, /apiBaseUrl/);
+ assert.match(client, /configured FastAPI demo backend/);
assert.match(config, /http:\/\/127\.0\.0\.1:8000/);
+ assert.match(demoState, /make demo/);
+ assert.match(demoState, /make api/);
+ assert.match(demoState, /NEXT_PUBLIC_API_BASE_URL/);
+ assert.match(demoState, /Synthetic local scenario; not real plant data/);
assert.match(readme, /NEXT_PUBLIC_API_BASE_URL/);
});
diff --git a/docs/LEARNING_LOG.md b/docs/LEARNING_LOG.md
index 9cd6612..e4c8290 100644
--- a/docs/LEARNING_LOG.md
+++ b/docs/LEARNING_LOG.md
@@ -3323,3 +3323,50 @@ make test
Use the current deterministic rules as the baseline before adding any new
process or quality drift modes, and require each new rule to document its
threshold assumptions and evidence references.
+
+## 2026-05-22 - Workbench demo API connection notice
+
+### What changed
+
+Added a reusable Workbench demo notice and a local API connection banner for
+issue #176. The Overview page now shows the configured FastAPI target, health
+state, and simulator-backed API source, and API failures show presenter-friendly
+recovery steps instead of a terse fetch error.
+
+### Why it was built that way
+
+The issue is a demo-readiness UI task, so the change stays in the existing
+Workbench shell, demo-state components, tests, and runbook. It does not add
+production observability, retry infrastructure, an environment switcher, or any
+new backend behavior.
+
+### How data flows through it
+
+The Overview page still calls `GET /health` through the existing typed API
+client. A successful response feeds the API connection banner. A failed request
+is converted into safe recovery copy that shows the configured
+`NEXT_PUBLIC_API_BASE_URL` target and tells the presenter how to restart the
+local simulator-backed demo.
+
+### How to run it
+
+```bash
+make demo
+make api
+cd apps/web
+npm run dev
+```
+
+### How to test it
+
+```bash
+cd apps/web
+npm test
+npm run lint
+npm run typecheck
+```
+
+### What to learn next
+
+Practice the demo with the API intentionally stopped once, then confirm the
+presenter can recover using only the visible target and recovery guidance.
diff --git a/docs/demo/OPERATIONS_WORKBENCH_DEMO_RUNBOOK.md b/docs/demo/OPERATIONS_WORKBENCH_DEMO_RUNBOOK.md
index e947369..e6f9671 100644
--- a/docs/demo/OPERATIONS_WORKBENCH_DEMO_RUNBOOK.md
+++ b/docs/demo/OPERATIONS_WORKBENCH_DEMO_RUNBOOK.md
@@ -8,8 +8,9 @@ factory context, inspect a detection, read the evidence timeline, review a
governed recommendation, record a human decision, and preview an RCA/CAPA draft.
All content is simulator-backed demo data. The visible Workbench label is
-`Simulator-backed demo data`. It is not production data, a validated audit
-record, an electronic signature, or an industrial writeback workflow.
+`Simulator-backed demo data`, with supporting copy that this is a synthetic
+local scenario and not real plant data. It is not production data, a validated
+audit record, an electronic signature, or an industrial writeback workflow.
## Start The Demo
@@ -42,25 +43,41 @@ http://127.0.0.1:3000
1. Open the overview page and confirm it is labeled `Simulator-backed demo
data`.
-2. Confirm the overview shows Greenville Demo Site, current factory context,
+2. Confirm the overview shows the configured API target, `/health` status, and
+ simulator-backed demo API source.
+3. Confirm the overview shows Greenville Demo Site, current factory context,
active detections, pending recommendations, and the primary detection CTA.
-3. Open `/detections` and confirm the Process Sentinel detection list renders
+4. Open `/detections` and confirm the Process Sentinel detection list renders
from the local API.
-4. Open `/detections/det_fill_weight_gradual_drift`.
-5. Confirm the detection detail shows severity, confidence, status, time
+5. Open `/detections/det_fill_weight_gradual_drift`.
+6. Confirm the detection detail shows severity, confidence, status, time
window, work order, and related assets.
-6. Inspect the evidence timeline and confirm each item shows a readable title,
+7. Inspect the evidence timeline and confirm each item shows a readable title,
timestamp, severity, relevance score, related asset, related batch, related
work order, and source event IDs.
-7. Open the recommendation review page from the detection detail.
-8. Enter a demo reviewer and reason, then approve, reject, or defer the
+8. Open the recommendation review page from the detection detail.
+9. Enter a demo reviewer and reason, then approve, reject, or defer the
recommendation.
-9. Confirm the decision feedback shows reviewer, decision, reason, timestamp,
+10. Confirm the decision feedback shows reviewer, decision, reason, timestamp,
recommendation ID, and updated status.
-10. Open the RCA/CAPA draft preview and confirm the problem statement,
+11. Open the RCA/CAPA draft preview and confirm the problem statement,
evidence summary, recommended containment, CAPA placeholder, and human
review requirement are visible.
+## API Recovery Check
+
+If the API is stopped or `NEXT_PUBLIC_API_BASE_URL` points at the wrong port,
+the Workbench should show an API connection issue panel rather than raw stack
+output. Use the panel to confirm the configured target, then run:
+
+```bash
+make demo
+make api
+```
+
+Restart the Workbench with `NEXT_PUBLIC_API_BASE_URL` only when the API is
+intentionally running on a non-default local port.
+
## Expected Demo IDs
```text