From e39b61800e543bc6dfba9d60bb5d81e3e5b14ab3 Mon Sep 17 00:00:00 2001 From: Aldon Smith Date: Sat, 23 May 2026 17:19:35 -0400 Subject: [PATCH 1/2] feat: add operator console workbench shell --- apps/web/README.md | 11 +- apps/web/app/globals.css | 169 +++++++++++++++--- apps/web/app/layout.tsx | 93 +++++++--- .../web/e2e/operations-workbench-demo.spec.ts | 9 + apps/web/tests/app-shell.test.mjs | 19 ++ docs/LEARNING_LOG.md | 55 ++++++ 6 files changed, 305 insertions(+), 51 deletions(-) diff --git a/apps/web/README.md b/apps/web/README.md index 39de616..9665ebe 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,8 +1,8 @@ # Operations Workbench -Minimal Next.js app shell for the simulator-backed Process Sentinel demo. The -Workbench reads from the local FastAPI backend through a small typed API client -in `lib/api-client.ts`. +Next.js operator-console shell for the simulator-backed Process Sentinel demo. +The Workbench reads from the local FastAPI backend through a small typed API +client in `lib/api-client.ts`. ## Local Startup @@ -76,6 +76,11 @@ is running on a non-default port. ## Routes +The shell uses a persistent sidebar and status strip. Existing Sentinel demo +routes remain available, and the sidebar includes planned navigation slots for +Connections, Protocol Diagnostics, and Tag/Source Browser. Those planned slots +do not add production writeback controls. + - `/` - Factory overview dashboard with site, line, asset, work order, product, active detection count, pending recommendation count, and primary detection CTA - `/detections` - Process Sentinel detection list with summary, severity, diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 01c3dfb..5cfcea9 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -37,7 +37,9 @@ a { text-decoration: none; } -.shell { +.operator-shell { + display: grid; + grid-template-columns: 276px minmax(0, 1fr); min-height: 100vh; } @@ -60,19 +62,21 @@ a { outline: 3px solid #cfe7db; } -.site-header { - border-bottom: 1px solid var(--border); - background: rgb(255 255 255 / 92%); -} - -.header-inner { +.operator-sidebar { + position: sticky; + top: 0; display: flex; - align-items: center; - justify-content: space-between; + height: 100vh; + flex-direction: column; gap: 24px; - width: min(1180px, calc(100% - 32px)); - margin: 0 auto; - padding: 18px 0; + border-right: 1px solid var(--border); + background: #fdfefd; + padding: 22px 18px; +} + +.sidebar-branding { + display: grid; + gap: 14px; } .brand { @@ -99,19 +103,41 @@ a { } .primary-nav { - display: flex; - flex-wrap: wrap; - justify-content: flex-end; + display: grid; + gap: 22px; +} + +.nav-group { + display: grid; + gap: 9px; +} + +.nav-group h2 { + margin: 0; + color: var(--muted); + font-size: 0.72rem; + font-weight: 780; + text-transform: uppercase; +} + +.nav-group-links { + display: grid; gap: 6px; } .nav-link { - border: 1px solid transparent; + display: flex; + min-height: 42px; + align-items: center; + justify-content: space-between; + gap: 10px; + border: 1px solid var(--border); border-radius: 7px; - color: #35423a; + background: var(--surface); + color: var(--text); font-size: 0.9rem; font-weight: 650; - padding: 9px 11px; + padding: 10px 11px; } .nav-link:hover, @@ -122,6 +148,59 @@ a { outline-offset: 2px; } +.nav-link-disabled { + color: var(--muted); + cursor: default; +} + +.nav-link-disabled:hover { + background: var(--surface); +} + +.nav-link-meta { + border: 1px solid var(--border); + border-radius: 999px; + background: var(--surface-muted); + color: var(--muted); + font-size: 0.68rem; + font-weight: 760; + line-height: 1; + padding: 5px 7px; + text-transform: uppercase; +} + +.operator-workspace { + min-width: 0; +} + +.status-strip { + position: sticky; + z-index: 10; + top: 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + border-bottom: 1px solid var(--border); + background: rgb(255 255 255 / 94%); +} + +.status-strip div { + display: grid; + min-width: 0; + gap: 4px; + border-right: 1px solid var(--border); + padding: 12px 16px; +} + +.status-strip div:last-child { + border-right: 0; +} + +.status-strip strong { + overflow-wrap: anywhere; + font-size: 0.9rem; + line-height: 1.25; +} + .page-shell { width: min(1180px, calc(100% - 32px)); margin: 0 auto; @@ -1067,18 +1146,32 @@ code { } @media (max-width: 880px) { - .header-inner, - .hero { - grid-template-columns: 1fr; + .operator-shell { + display: block; + } + + .operator-sidebar { + position: relative; + height: auto; + border-right: 0; + border-bottom: 1px solid var(--border); + } + + .status-strip { + position: relative; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .status-strip div:nth-child(2) { + border-right: 0; } - .header-inner { - align-items: flex-start; - flex-direction: column; + .status-strip div:nth-child(-n + 2) { + border-bottom: 1px solid var(--border); } - .primary-nav { - justify-content: flex-start; + .hero { + grid-template-columns: 1fr; } .api-connection-banner { @@ -1121,8 +1214,7 @@ code { } @media (min-width: 881px) and (max-width: 1240px) { - .page-shell, - .header-inner { + .page-shell { width: min(1100px, calc(100% - 28px)); } @@ -1140,6 +1232,27 @@ code { } @media (max-width: 560px) { + .operator-sidebar { + padding: 18px 16px; + } + + .status-strip { + grid-template-columns: 1fr; + } + + .status-strip div, + .status-strip div:nth-child(2) { + border-right: 0; + } + + .status-strip div { + border-bottom: 1px solid var(--border); + } + + .status-strip div:last-child { + border-bottom: 0; + } + .content-grid { grid-template-columns: 1fr; } diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 653cb5e..b7d9736 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import type { ReactNode } from "react"; import { DemoDataBadge } from "./components/demo-state"; +import { getApiBaseUrl } from "../lib/api-client"; import "./globals.css"; export const metadata: Metadata = { @@ -10,40 +11,92 @@ export const metadata: Metadata = { description: "Simulator-backed Factory Intelligence Platform workbench shell.", }; -const navItems = [ - { href: "/", label: "Overview" }, - { href: "/detections", label: "Detections" }, - { href: "/recommendations", label: "Recommendations" }, - { href: "/rca-capa-draft", label: "RCA/CAPA Draft" }, +const navGroups = [ + { + items: [ + { href: "/", label: "Overview" }, + { href: "/detections", label: "Detections" }, + { href: "/recommendations", label: "Recommendations" }, + { href: "/rca-capa-draft", label: "RCA/CAPA Draft" }, + ], + label: "Sentinel workflows", + }, + { + items: [ + { label: "Connections", status: "Planned" }, + { label: "Protocol Diagnostics", status: "Planned" }, + { label: "Tag/Source Browser", status: "Planned" }, + ], + label: "Protocol operations", + }, ]; export default function RootLayout({ children }: { children: ReactNode }) { return ( -
+
Skip to main content -
-
+
-
- {children} -
+ + +
+
+
+ Mode + Simulator-backed demo +
+
+ API target + {getApiBaseUrl()} +
+
+ Connection policy + Read-only diagnostics +
+
+ Writeback + Disabled +
+
+
+ {children} +
+
diff --git a/apps/web/e2e/operations-workbench-demo.spec.ts b/apps/web/e2e/operations-workbench-demo.spec.ts index 4478410..ed1d504 100644 --- a/apps/web/e2e/operations-workbench-demo.spec.ts +++ b/apps/web/e2e/operations-workbench-demo.spec.ts @@ -14,6 +14,15 @@ test("walks the simulator-backed Operations Workbench demo path", async ({ page await expect(page.getByRole("link", { name: "Skip to main content" })).toBeFocused(); await page.keyboard.press("Enter"); await expect(page.locator("#main-content")).toBeFocused(); + await expect(page.getByRole("navigation", { name: "Primary navigation" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Overview" })).toBeVisible(); + await expect(page.getByText("Connections", { exact: true })).toBeVisible(); + await expect(page.getByText("Protocol Diagnostics", { exact: true })).toBeVisible(); + await expect(page.getByText("Tag/Source Browser", { exact: true })).toBeVisible(); + await expect(page.getByRole("banner", { name: "Workbench status strip" })).toBeVisible(); + await expect(page.getByText("Read-only diagnostics")).toBeVisible(); + await expect(page.getByText("Writeback")).toBeVisible(); + await expect(page.getByText("Disabled", { exact: true })).toBeVisible(); 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(); diff --git a/apps/web/tests/app-shell.test.mjs b/apps/web/tests/app-shell.test.mjs index 12d30da..7813d2f 100644 --- a/apps/web/tests/app-shell.test.mjs +++ b/apps/web/tests/app-shell.test.mjs @@ -24,10 +24,25 @@ test("navigation includes the required demo routes", () => { assert.match(layout, /Skip to main content/); assert.match(layout, /id="main-content"/); + assert.match(layout, /operator-shell/); + assert.match(layout, /operator-sidebar/); + assert.match(layout, /Workbench navigation/); + assert.match(layout, /Operator Console/); + assert.match(layout, /Sentinel workflows/); assert.match(layout, /Overview/); assert.match(layout, /Detections/); assert.match(layout, /Recommendations/); assert.match(layout, /RCA\/CAPA Draft/); + assert.match(layout, /Protocol operations/); + assert.match(layout, /Connections/); + assert.match(layout, /Protocol Diagnostics/); + assert.match(layout, /Tag\/Source Browser/); + assert.match(layout, /aria-disabled="true"/); + assert.match(layout, /Workbench status strip/); + assert.match(layout, /Simulator-backed demo/); + assert.match(layout, /Read-only diagnostics/); + assert.match(layout, /Writeback/); + assert.match(layout, /Disabled/); assert.match(layout, /DemoDataBadge/); }); @@ -234,6 +249,8 @@ test("accessibility baseline covers landmarks, focus, forms, badges, and timelin assert.match(layout, /className="skip-link"/); assert.match(layout, /
Date: Sun, 24 May 2026 08:28:07 -0400 Subject: [PATCH 2/2] style: align workbench shell with demo factory palette --- apps/web/README.md | 14 +- apps/web/app/globals.css | 226 +++++++++--------- apps/web/app/layout.tsx | 8 +- .../web/e2e/operations-workbench-demo.spec.ts | 2 +- apps/web/tests/app-shell.test.mjs | 37 ++- docs/LEARNING_LOG.md | 19 +- 6 files changed, 165 insertions(+), 141 deletions(-) diff --git a/apps/web/README.md b/apps/web/README.md index 9665ebe..35ed52d 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,8 +1,9 @@ # Operations Workbench Next.js operator-console shell for the simulator-backed Process Sentinel demo. -The Workbench reads from the local FastAPI backend through a small typed API -client in `lib/api-client.ts`. +The shell uses a Demo Factory-style steel, dark-nav, and teal-accent palette, +and the Workbench reads from the local FastAPI backend through a small typed +API client in `lib/api-client.ts`. ## Local Startup @@ -76,10 +77,11 @@ is running on a non-default port. ## Routes -The shell uses a persistent sidebar and status strip. Existing Sentinel demo -routes remain available, and the sidebar includes planned navigation slots for -Connections, Protocol Diagnostics, and Tag/Source Browser. Those planned slots -do not add production writeback controls. +The shell uses a persistent sidebar with an embedded status strip instead of a +horizontal top bar. Existing Sentinel demo routes remain available, and the +sidebar includes planned navigation slots for Connections, Protocol Diagnostics, +and Tag/Source Browser. Those planned slots do not add production writeback +controls. - `/` - Factory overview dashboard with site, line, asset, work order, product, active detection count, pending recommendation count, and primary detection CTA diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 5cfcea9..ed47bb7 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -1,17 +1,29 @@ :root { - --background: #f6f7f4; + --background: #e8eef3; --surface: #ffffff; - --surface-muted: #eef2ed; - --text: #18201c; - --muted: #607066; - --border: #d7ded6; - --accent: #176b52; - --accent-strong: #0f4d3b; - --warning: #8b5a10; - --danger: #9f2d35; - --info: #315f9f; - --draft: #5d6470; - --shadow: 0 18px 50px rgb(24 32 28 / 8%); + --surface-muted: #f5f8fa; + --surface-subtle: #f0f5f8; + --text: #16202a; + --muted: #526579; + --muted-2: #6b7d8f; + --border: #d3dee7; + --border-strong: #b7c5d0; + --accent: #0f766e; + --accent-strong: #0b5d57; + --accent-bg: #d7f4eb; + --nav: #101820; + --nav-2: #1d2b36; + --nav-text: #d8e2ea; + --nav-muted: #91a4b3; + --warning: #b45309; + --warning-bg: #fff3d6; + --danger: #b91c1c; + --danger-bg: #fde2e2; + --info: #1e40af; + --info-bg: #dbeafe; + --draft: #475569; + --focus-ring: #9ee1d9; + --shadow: 0 1px 2px rgb(21 32 42 / 4%); } * { @@ -59,7 +71,7 @@ a { .skip-link:focus-visible { transform: translateY(0); - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); } .operator-sidebar { @@ -69,8 +81,9 @@ a { height: 100vh; flex-direction: column; gap: 24px; - border-right: 1px solid var(--border); - background: #fdfefd; + border-right: 1px solid #243541; + background: var(--nav); + color: var(--nav-text); padding: 22px 18px; } @@ -86,18 +99,19 @@ a { } .brand:focus-visible { - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 4px; } .brand-name { + color: #ffffff; font-size: 1rem; font-weight: 760; letter-spacing: 0; } .brand-context { - color: var(--muted); + color: var(--nav-muted); font-size: 0.82rem; font-weight: 560; } @@ -114,7 +128,7 @@ a { .nav-group h2 { margin: 0; - color: var(--muted); + color: var(--nav-muted); font-size: 0.72rem; font-weight: 780; text-transform: uppercase; @@ -131,10 +145,10 @@ a { align-items: center; justify-content: space-between; gap: 10px; - border: 1px solid var(--border); + border: 1px solid transparent; border-radius: 7px; - background: var(--surface); - color: var(--text); + background: transparent; + color: #b7c4cf; font-size: 0.9rem; font-weight: 650; padding: 10px 11px; @@ -142,26 +156,28 @@ a { .nav-link:hover, .nav-link:focus-visible { - border-color: var(--border); - background: var(--surface-muted); - outline: 3px solid #cfe7db; + border-color: #2d4352; + background: var(--nav-2); + color: #ffffff; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } .nav-link-disabled { - color: var(--muted); + color: var(--nav-muted); cursor: default; } .nav-link-disabled:hover { - background: var(--surface); + background: transparent; + color: var(--nav-muted); } .nav-link-meta { - border: 1px solid var(--border); + border: 1px solid #314756; border-radius: 999px; - background: var(--surface-muted); - color: var(--muted); + background: var(--nav-2); + color: var(--nav-text); font-size: 0.68rem; font-weight: 760; line-height: 1; @@ -174,28 +190,29 @@ a { } .status-strip { - position: sticky; - z-index: 10; - top: 0; display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - border-bottom: 1px solid var(--border); - background: rgb(255 255 255 / 94%); + gap: 8px; + margin-top: auto; + border-top: 1px solid #243541; + padding-top: 16px; } .status-strip div { display: grid; min-width: 0; gap: 4px; - border-right: 1px solid var(--border); - padding: 12px 16px; + border: 1px solid #243541; + border-radius: 7px; + background: var(--nav-2); + padding: 10px 11px; } -.status-strip div:last-child { - border-right: 0; +.status-strip .status-label { + color: var(--nav-muted); } .status-strip strong { + color: #ffffff; overflow-wrap: anywhere; font-size: 0.9rem; line-height: 1.25; @@ -208,7 +225,7 @@ a { } .page-shell:focus-visible { - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 6px; } @@ -255,9 +272,9 @@ a { .demo-label { display: inline-flex; width: fit-content; - border: 1px solid #d9c18c; + border: 1px solid #f5d38d; border-radius: 6px; - background: #fff8e7; + background: var(--warning-bg); color: var(--warning); font-size: 0.78rem; font-weight: 760; @@ -269,9 +286,9 @@ a { display: inline-grid; width: fit-content; gap: 4px; - border: 1px solid #d9c18c; + border: 1px solid #f5d38d; border-radius: 7px; - background: #fff8e7; + background: var(--warning-bg); color: var(--warning); line-height: 1.2; padding: 7px 9px; @@ -283,7 +300,7 @@ a { } .demo-notice-copy { - color: #765011; + color: #7a4f0f; font-size: 0.72rem; font-weight: 650; } @@ -297,7 +314,7 @@ a { border: 1px solid var(--border); border-radius: 999px; background: var(--surface-muted); - color: #35423a; + color: #405263; font-size: 0.78rem; font-weight: 760; line-height: 1.05; @@ -312,26 +329,26 @@ a { } .status-badge-success { - border-color: #96c4ae; - background: #e8f5ee; + border-color: #9bd3ca; + background: var(--accent-bg); color: var(--accent-strong); } .status-badge-warning { - border-color: #d9c18c; - background: #fff8e7; + border-color: #f5d38d; + background: var(--warning-bg); color: var(--warning); } .status-badge-danger { - border-color: #d9a1a7; - background: #fff0f1; + border-color: #f2b6b6; + background: var(--danger-bg); color: var(--danger); } .status-badge-info { border-color: #a8bddc; - background: #eef4ff; + background: var(--info-bg); color: var(--info); } @@ -461,7 +478,7 @@ h3 { .primary-action:hover, .primary-action:focus-visible { background: var(--accent-strong); - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -472,8 +489,8 @@ h3 { .secondary-action:hover, .secondary-action:focus-visible { - background: #edf6f1; - outline: 3px solid #cfe7db; + background: #e7f3f1; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -490,10 +507,10 @@ h3 { gap: 18px; align-items: start; margin-top: 18px; - border: 1px solid #b7d4c8; + border: 1px solid #b7ded8; border-left: 4px solid var(--accent); border-radius: 8px; - background: #f3faf6; + background: #e7f3f1; padding: 16px; } @@ -504,7 +521,7 @@ h3 { .api-connection-banner span, .api-connection-details dd { - color: #244337; + color: #21423e; line-height: 1.45; } @@ -519,12 +536,12 @@ h3 { display: grid; gap: 4px; min-width: 0; - border-left: 1px solid #b7d4c8; + border-left: 1px solid #b7ded8; padding-left: 10px; } .api-connection-details dt { - color: #4d665b; + color: var(--muted); font-size: 0.74rem; font-weight: 760; text-transform: uppercase; @@ -622,7 +639,7 @@ h3 { align-items: start; border: 1px solid var(--border); border-radius: 8px; - background: #fbfcfb; + background: var(--surface); padding: 18px; } @@ -671,7 +688,7 @@ h3 { .back-link:hover, .back-link:focus-visible { color: var(--accent-strong); - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 3px; } @@ -699,7 +716,7 @@ h3 { gap: 4px; border: 1px solid var(--border); border-radius: 8px; - background: #fbfcfb; + background: var(--surface); padding: 16px; } @@ -722,7 +739,7 @@ h3 { .state-panel { border: 1px solid var(--border); border-radius: 8px; - background: #fbfcfb; + background: var(--surface); } .data-card { @@ -816,12 +833,12 @@ h3 { .what-this-means { border-left: 4px solid var(--accent); - background: #edf6f1; + background: #e7f3f1; padding: 16px; } .what-this-means p { - color: #244337; + color: #21423e; } .timeline-list { @@ -846,7 +863,7 @@ h3 { border: 3px solid var(--accent); border-radius: 50%; background: var(--surface); - box-shadow: 0 0 0 4px #edf6f1; + box-shadow: 0 0 0 4px #e7f3f1; } .timeline-item article { @@ -883,7 +900,7 @@ h3 { border: 1px solid var(--border); border-radius: 6px; background: var(--surface-muted); - color: #35423a; + color: #405263; font-size: 0.74rem; font-weight: 760; line-height: 1; @@ -892,20 +909,20 @@ h3 { } .evidence-process-signal .evidence-type { - border-color: #9dc8ba; - background: #e7f4ee; - color: #0f4d3b; + border-color: #9bd3ca; + background: var(--accent-bg); + color: var(--accent-strong); } .evidence-quality-result .evidence-type { - border-color: #d6ba76; - background: #fff8e7; + border-color: #f5d38d; + background: var(--warning-bg); color: #7a4f0f; } .evidence-correlation-window .evidence-type { border-color: #b9c1dc; - background: #eef1fb; + background: var(--info-bg); color: #35436f; } @@ -990,17 +1007,17 @@ h3 { } .error-panel { - border-color: #e3b7b7; - background: #fff2f2; + border-color: #f2b6b6; + background: var(--danger-bg); } .error-panel strong { - color: #8a2b2b; + color: var(--danger); } .missing-data-panel { - border-color: #d9c18c; - background: #fffaf0; + border-color: #f5d38d; + background: var(--warning-bg); } .missing-data-panel strong { @@ -1009,8 +1026,8 @@ h3 { .decision-note { border-left: 4px solid var(--accent); - background: #edf6f1; - color: #244337; + background: #e7f3f1; + color: #21423e; line-height: 1.5; padding: 16px; } @@ -1058,7 +1075,7 @@ h3 { .review-form input:focus, .review-form textarea:focus { border-color: var(--accent); - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 1px; } @@ -1081,13 +1098,13 @@ h3 { } .review-actions button:nth-child(2) { - border-color: #8a2b2b; - background: #8a2b2b; + border-color: var(--danger); + background: var(--danger); } .review-actions button:nth-child(3) { border-color: #7a4f0f; - background: #8b5a10; + background: var(--warning); } .review-actions button:disabled { @@ -1096,7 +1113,7 @@ h3 { } .review-actions button:focus-visible { - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -1110,7 +1127,7 @@ h3 { .draft-copy button:focus-visible, .review-actions button:focus-visible { - outline: 3px solid #cfe7db; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -1118,27 +1135,27 @@ h3 { display: grid; gap: 5px; border-left: 4px solid var(--accent); - background: #edf6f1; - color: #244337; + background: #e7f3f1; + color: #21423e; line-height: 1.5; padding: 16px; } .api-panel { border-left: 4px solid var(--accent); - background: #edf6f1; + background: #e7f3f1; padding: 16px; } .api-panel p { margin-bottom: 0; - color: #244337; + color: #21423e; line-height: 1.5; } code { border-radius: 5px; - background: #e4ebe5; + background: #e8eef4; color: var(--accent-strong); font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; font-size: 0.9em; @@ -1158,16 +1175,8 @@ code { } .status-strip { - position: relative; grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .status-strip div:nth-child(2) { - border-right: 0; - } - - .status-strip div:nth-child(-n + 2) { - border-bottom: 1px solid var(--border); + margin-top: 0; } .hero { @@ -1240,19 +1249,6 @@ code { grid-template-columns: 1fr; } - .status-strip div, - .status-strip div:nth-child(2) { - border-right: 0; - } - - .status-strip div { - border-bottom: 1px solid var(--border); - } - - .status-strip div:last-child { - border-bottom: 0; - } - .content-grid { grid-template-columns: 1fr; } diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index b7d9736..452af3f 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -73,9 +73,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { ))} - -
-
+
Mode Simulator-backed demo @@ -92,7 +90,9 @@ export default function RootLayout({ children }: { children: ReactNode }) { Writeback Disabled
-
+ + +
{children}
diff --git a/apps/web/e2e/operations-workbench-demo.spec.ts b/apps/web/e2e/operations-workbench-demo.spec.ts index ed1d504..2fcf2de 100644 --- a/apps/web/e2e/operations-workbench-demo.spec.ts +++ b/apps/web/e2e/operations-workbench-demo.spec.ts @@ -19,7 +19,7 @@ test("walks the simulator-backed Operations Workbench demo path", async ({ page await expect(page.getByText("Connections", { exact: true })).toBeVisible(); await expect(page.getByText("Protocol Diagnostics", { exact: true })).toBeVisible(); await expect(page.getByText("Tag/Source Browser", { exact: true })).toBeVisible(); - await expect(page.getByRole("banner", { name: "Workbench status strip" })).toBeVisible(); + await expect(page.getByRole("region", { name: "Workbench status strip" })).toBeVisible(); await expect(page.getByText("Read-only diagnostics")).toBeVisible(); await expect(page.getByText("Writeback")).toBeVisible(); await expect(page.getByText("Disabled", { exact: true })).toBeVisible(); diff --git a/apps/web/tests/app-shell.test.mjs b/apps/web/tests/app-shell.test.mjs index 7813d2f..331a312 100644 --- a/apps/web/tests/app-shell.test.mjs +++ b/apps/web/tests/app-shell.test.mjs @@ -39,6 +39,7 @@ test("navigation includes the required demo routes", () => { assert.match(layout, /Tag\/Source Browser/); assert.match(layout, /aria-disabled="true"/); assert.match(layout, /Workbench status strip/); + assert.doesNotMatch(layout, /
{ assert.match(layout, /DemoDataBadge/); }); +test("operator shell uses the Demo Factory console palette", () => { + const styles = readFileSync(join(root, "app/globals.css"), "utf8"); + + assert.match(styles, /--background: #e8eef3/); + assert.match(styles, /--surface: #ffffff/); + assert.match(styles, /--text: #16202a/); + assert.match(styles, /--muted: #526579/); + assert.match(styles, /--border: #d3dee7/); + assert.match(styles, /--nav: #101820/); + assert.match(styles, /--nav-2: #1d2b36/); + assert.match(styles, /--accent: #0f766e/); + assert.match(styles, /--warning: #b45309/); + assert.match(styles, /--danger: #b91c1c/); + assert.match(styles, /background: var\(--nav\)/); + assert.match(styles, /background: var\(--nav-2\)/); +}); + 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"); @@ -250,7 +268,7 @@ test("accessibility baseline covers landmarks, focus, forms, badges, and timelin assert.match(layout, /