Frontend Revamp — Phase-by-Phase Execution Prompts

Companion to: docs/reports/FRONTEND_UI_IMPROVEMENT_PLAN.md Target app: miz-oki-command-center-ui/ (Next.js 15 App Router, React 19, TypeScript, Tailwind, Zustand, React Query) Date: August 5, 2026 How to use this document: Each phase below is written as a self-contained, execution-ready prompt. Copy the entire "PROMPT" block for a phase into a fresh agent session (or hand it to an engineer) and it carries everything needed: mission, context, technical spec, visual/style spec, explicit non-goals, acceptance gates, and verification commands. Phases are ordered by dependency — do not start a phase until the previous phase's exit gate is green.


Table of Contents

  1. Phase Map & Dependencies
  2. Shared Context Block (paste into every prompt)
  3. Phase 0 Prompt — Safety Net & Ground Truth
  4. Phase 1 Prompt — Wiring Correctness (One Gateway)
  5. Phase 2 Prompt — Consolidation & Dead-Code Removal
  6. Phase 3 Prompt — Rendering & Performance
  7. Phase 4 Prompt — UX Overhaul & Design System
  8. Phase 5 Prompt — Expansion Surfaces
  9. Cross-Phase Style Guide (referenced by every prompt)
  10. Prompt-Writing Conventions Used Here

Phase Map & Dependencies

flowchart LR
    P0["Phase 0<br/>Safety Net<br/>(tests, strictness,<br/>convention files)"]
    P1["Phase 1<br/>Wiring Correctness<br/>(one gateway client,<br/>kill hardcoded URLs)"]
    P2["Phase 2<br/>Consolidation<br/>(dead code, route merge,<br/>state cleanup)"]
    P3["Phase 3<br/>Rendering & Perf<br/>(RSC, streaming,<br/>bundle diet)"]
    P4["Phase 4<br/>UX Overhaul<br/>(nav, palette, design<br/>tokens, feedback kit)"]
    P5["Phase 5<br/>Expansion<br/>(ORACLE, governance,<br/>mobile, realtime hub)"]

    P0 --> P1 --> P2 --> P3 --> P4 --> P5

    style P0 fill:#7c2d12,color:#fff
    style P1 fill:#9a3412,color:#fff
    style P2 fill:#b45309,color:#fff
    style P3 fill:#4d7c0f,color:#fff
    style P4 fill:#0f766e,color:#fff
    style P5 fill:#1d4ed8,color:#fff
Phase Theme Risk if skipped Hard dependency
0 Safety net Every later phase ships regressions invisibly none
1 Wiring correctness Auth-locked services keep failing from the browser; SSR breaks on hostname changes 0 (tests catch the migration)
2 Consolidation Bundle stays bloated; every future change is made in 2–3 duplicate places 1 (canonical client must exist before deleting duplicates that reference old paths)
3 Rendering & performance TTI stays poor; force-dynamic blocks all caching 2 (can't optimize routes that are about to be deleted)
4 UX overhaul Users keep drowning in 100+ undifferentiated nav entries 3 (nav rebuild should target the final route set)
5 Expansion New surfaces built on old foundations inherit all the debt 4 (new pages consume the design system)

Shared Context Block

Paste this block verbatim at the top of every phase prompt. It gives the executing agent the ground truth it needs without re-deriving it.

CONTEXT — MIZ OKI Command Center UI (read before doing anything)

Repo: mediaintelligence/MIZOKICloudRun (monorepo). The frontend is
miz-oki-command-center-ui/ — Next.js 15.5 App Router, React 19, TypeScript,
Tailwind CSS + Radix UI, Zustand (store/ with slices), React Query, Socket.io,
SSE via hooks/useSSE.ts. It deploys to Cloud Run as
miz-oki-command-center-ui via cloudbuild.yaml (root build context — the ROOT
.gcloudignore governs the upload; the lockfile must stay allowlisted).

Measured inventory (2026-08): 155 page.tsx files, 210 API route handlers,
243 component files, 65 hooks, 135 lib modules, ~391 hardcoded *.run.app URL
literals, 16 app/boss* directory variants (7 archived), zero test files,
zero loading.tsx/not-found.tsx/global-error.tsx.

Known constraints you MUST respect:
1. next.config.mjs currently sets eslint.ignoreDuringBuilds and
   typescript.ignoreBuildErrors — the CI has NO type gate. Any TS cleanup
   must be measured with a local delta-tsc against the current error
   baseline (record the count first; ship only zero-genuinely-new).
2. Navigation safety: all dynamic router.push()/redirects MUST go through
   toSafeUrl() from lib/safe-navigation.ts. ESLint no-restricted-syntax
   rules exist for this — do not weaken them.
3. Backend services are IAM-locked Cloud Run services. Browser code cannot
   mint OIDC tokens — every backend call from the client must go through a
   same-origin Next.js API route (BFF) that attaches identity tokens
   server-side (lib/service-auth.ts pattern, authedFetch).
4. Boss Agent chat is served by the boss-agent-adk service through
   app/api/boss/chat/route.ts. Do not point chat at any other service.
5. The repo has an auto-merge bot: pushing a cursor/* or claude/* branch is
   a de-facto merge to main within minutes. Keep every push shippable.
6. Do NOT touch app/api/stream + app/api/streaming SSE routes' contract
   without updating hooks/useSSE.ts and providers in the same commit.
7. The design canon governance applies to "# MIZ OKI 3.5/" (marketing site)
   — that folder is OUT OF SCOPE for all of these phases. Never edit it.

Phase 0 Prompt — Safety Net & Ground Truth

Why this phase exists (explanation)

Every later phase moves or deletes code. Today the app has zero automated tests, builds that ignore both TypeScript and ESLint errors, and none of the App Router convention files that catch failures at runtime (loading.tsx, not-found.tsx, global-error.tsx). That combination means a migration mistake in Phase 1 or 2 would ship to production silently. Phase 0 builds the tripwires first: a minimal but real test harness, a recorded type-error baseline with a delta gate, error/loading conventions, and a CI job that actually fails. Nothing in this phase changes user-visible behavior — it only makes future change safe and measurable.

Visual outcome

Users see three small but real improvements: skeleton loading states instead of white flashes on slow routes, a branded 404 page instead of the Next.js default, and a graceful full-app error screen with a "reload" action instead of a blank crash.

flowchart TD
    subgraph BEFORE["Before Phase 0"]
        A1["Route transition"] --> A2["White flash / layout jump"]
        A3["Bad URL"] --> A4["Default Next.js 404"]
        A5["Root render crash"] --> A6["Blank screen"]
    end
    subgraph AFTER["After Phase 0"]
        B1["Route transition"] --> B2["Branded skeleton via loading.tsx"]
        B3["Bad URL"] --> B4["Branded not-found.tsx with nav back"]
        B5["Root render crash"] --> B6["global-error.tsx: message + reset()"]
    end
    style BEFORE fill:#450a0a,color:#fff
    style AFTER fill:#052e16,color:#fff

PROMPT — copy from here

[PASTE SHARED CONTEXT BLOCK]

MISSION: Build the safety net for the miz-oki-command-center-ui frontend so
all later refactors are measurable and reversible. No user-facing behavior
changes except the three convention screens described below. Work on a new
branch; keep commits small and topical.

TECHNICAL SPEC — do these in order:

1. TYPE BASELINE (do this FIRST, before any edit):
   - Run `npx tsc --noEmit` in miz-oki-command-center-ui and save the full
     error list to docs/reports/frontend-tsc-baseline-<date>.txt. Record
     the count in the PR description.
   - Add an npm script "typecheck": "tsc --noEmit" and a
     "typecheck:delta" script (node script) that diffs current errors
     against the committed baseline file and exits non-zero only on
     GENUINELY NEW errors (set-diff on file:line-normalized messages).
   - Do NOT flip strict:true or remove ignoreBuildErrors yet — that is a
     later step gated on the baseline shrinking.

2. TEST HARNESS:
   - Add Vitest + React Testing Library + @testing-library/jest-dom.
     Config: vitest.config.ts with jsdom environment, path aliases mirrored
     from tsconfig paths, and setupFiles for jest-dom.
   - Write the first REAL tests (not placeholders):
     a. lib/safe-navigation.ts — toSafeUrl() rejects objects, absolute
        externals, javascript: URIs; passes through safe relative paths.
     b. One API route handler test: app/api/boss/chat/route.ts — mock the
        upstream fetch, assert the route validates the body shape and
        returns 4xx on malformed input (not 500).
     c. One component smoke test: render the navigation sidebar with a
        minimal config and assert external links get target="_blank".
   - Add "test" and "test:watch" npm scripts.

3. CONVENTION FILES (App Router):
   - app/loading.tsx: full-width skeleton (see VISUAL SPEC).
   - app/not-found.tsx: branded 404 with links to / and the command palette
     hint. All links via toSafeUrl or static hrefs.
   - app/global-error.tsx: 'use client', renders its own <html><body>,
     shows error.digest when present, and a reset() button. Keep the
     component dependency-free (no store, no providers — it must render
     when everything else is broken).
   - Route-group loading.tsx for the 3 heaviest sections (boss, knowledge
     graph, dashboards) with skeletons that match those layouts' shapes.

4. CI GATE:
   - Add .github/workflows/frontend-ci.yml (or extend the existing
     frontend-guard workflow — check first; it currently targets apps/web,
     retarget or add a job for miz-oki-command-center-ui): jobs for
     `npm ci`, `npm run lint` (warnings allowed, errors fail),
     `npm run typecheck:delta`, `npm run test`. Path-filter to
     miz-oki-command-center-ui/**.
   - The workflow must NOT run the full Next build (too slow/flaky for
     PR gate); the Cloud Build deploy already builds.

5. GUARDRAIL AUDIT (report only, no fixes yet):
   - Grep for router.push(, window.location.href =, redirect( and produce
     docs/reports/frontend-nav-safety-audit-<date>.md listing every call
     site that does NOT flow through toSafeUrl. This becomes Phase 1/2
     input. Do not fix them in this phase.

VISUAL / STYLE SPEC:
   - Skeletons: use existing Tailwind theme tokens only. Base block:
     rounded-lg bg-muted/60 animate-pulse. Compose per-layout: a header bar
     (h-8 w-48), a filter row (h-9 w-full max-w-md), then a grid of cards
     (h-32) matching the real page's grid columns. NEVER use spinners for
     route-level loading — spinners are reserved for in-component actions.
   - not-found.tsx: centered column, max-w-md, an icon from lucide-react
     (CircleSlash or Compass), text-2xl font-semibold heading "Page not
     found", one sentence of body copy in text-muted-foreground, and two
     actions: primary Button → "/", ghost Button → "Open command palette
     (⌘K)" (the palette itself ships in Phase 4 — link to / until then and
     leave a TODO with the Phase 4 ticket reference).
   - global-error.tsx: dark-neutral full screen (bg-zinc-950 text-zinc-50
     hardcoded — do NOT depend on the Tailwind config being alive), an
     AlertTriangle icon, the message "Something went wrong at the root of
     the app", the digest in a <code> block, and a single solid button
     calling reset(). Inline styles are acceptable here; resilience beats
     purity in this one file.
   - Tone of all copy: calm, specific, no exclamation marks, no blame
     ("We couldn't find that page" not "Oops!!").

NON-GOALS (do not do these in Phase 0):
   - No dependency upgrades beyond the test tooling.
   - No fixing of the ~391 hardcoded URLs (Phase 1).
   - No deletion of any route or component (Phase 2).
   - No flipping strict TypeScript or removing ignoreBuildErrors.

ACCEPTANCE GATES (all must pass before merging):
   [ ] `npm run test` green with ≥3 real test files.
   [ ] `npm run typecheck:delta` green (zero new errors vs baseline).
   [ ] Visiting a garbage URL shows the branded 404.
   [ ] Throwing in app/layout children (temporary test) shows global-error.
   [ ] CI workflow runs and fails when a test is deliberately broken
       (prove it once in the PR, then revert the break).
   [ ] Baseline + audit reports committed under docs/reports/.

VERIFICATION COMMANDS:
   cd miz-oki-command-center-ui
   npm run test
   npm run typecheck:delta
   npm run lint
   npm run dev  # manual check: /, /nonexistent-url, slow route skeleton

Phase 1 Prompt — Wiring Correctness (One Gateway)

Why this phase exists (explanation)

The review found ~391 hardcoded *.run.app URL literals spread across components, hooks, and lib modules, plus components calling backends directly with fetch/axios. This breaks in three ways: (1) browser calls to IAM-locked Cloud Run services fail because browsers can't mint OIDC tokens — the documented D19/Wave-3 lesson; (2) any Cloud Run hostname change requires a 391-site grep-and-pray; (3) there is no single place to attach auth, tracing, retries, or timeouts. Phase 1 builds one typed gateway client and routes every backend call through the same-origin BFF (app/api/**), where lib/service-auth.ts attaches identity tokens server-side. It also replaces the in-memory conversation store in the chat route with a durable store, because in-memory state on Cloud Run evaporates per-instance.

Target architecture

flowchart LR
    subgraph Browser
        C["Components / Hooks"] --> GW["lib/api/gateway.ts<br/>ONE typed client<br/>(zod-validated, traced)"]
    end
    subgraph NextServer["Next.js server (BFF)"]
        GW -->|"same-origin /api/*"| R["app/api/** route handlers"]
        R --> SA["lib/service-auth.ts<br/>OIDC mint + audience=origin"]
        R --> CFG["lib/config/services.ts<br/>SINGLE service URL registry<br/>(env-driven)"]
    end
    subgraph CloudRun["IAM-locked Cloud Run"]
        SA --> B["boss-agent-adk"]
        SA --> M["moa / moe / cells"]
    end
    X["❌ component → fetch('https://…run.app')"] -.forbidden by lint rule.-> B
    style X fill:#7f1d1d,color:#fff
    style GW fill:#1d4ed8,color:#fff
    style CFG fill:#0f766e,color:#fff

PROMPT — copy from here

[PASTE SHARED CONTEXT BLOCK]

MISSION: Make every backend call in miz-oki-command-center-ui flow through
one typed gateway client and the same-origin BFF. Eliminate direct
browser→run.app calls. Replace in-memory API-route state with a durable
store. Phase 0's tests and delta-tsc gate are your regression net — run
them after every batch of changes.

TECHNICAL SPEC:

1. SERVICE REGISTRY (single source of URLs):
   - Create/finish lib/config/services.ts: a typed map
     { bossAgent, moaController, moeRouter, cell(n), … } resolved from env
     vars with the documented production URLs as defaults. Export
     getServiceUrl(name) and a serviceNames const union type.
   - This module is SERVER-ONLY (import 'server-only' at top). The browser
     must never see raw service URLs.

2. GATEWAY CLIENT (browser side):
   - Create lib/api/gateway.ts: a thin typed client exposing
     get/post/stream methods that ONLY hit same-origin /api/* paths.
     Features, in this order of importance:
       a. zod schema parameter per call → parse response, return typed data
          or a discriminated { ok:false, error } — never throw raw fetch
          errors into components.
       b. AbortController + default 30s timeout (override per call).
       c. Correlation header (x-request-id: crypto.randomUUID()) echoed by
          the BFF into upstream calls for tracing.
       d. streamSSE(path, handlers) helper that wraps EventSource with
          reconnect + backoff, replacing ad-hoc EventSource usage.
   - Add a React Query integration layer lib/api/queries.ts exporting
     typed hooks (useBossHealth, useMcpTools, useCellStatus, …) built on
     the gateway. New code uses hooks; old code migrates incrementally.

3. BFF HARDENING (server side):
   - Every app/api/** route that proxies a backend must: use
     getServiceUrl() (no literals), attach OIDC via the canonical
     service-auth helper with audience = service ORIGIN (scheme://host —
     never a path; this is the documented D17b failure mode), validate the
     inbound body with zod, and propagate x-request-id.
   - Migrate the generic proxy pattern to
     app/api/orchestration/[svc]/[...path]/route.ts (exists — extend it)
     with an explicit allowlist of svc values from the registry. Reject
     unlisted services with 404. NEVER proxy arbitrary hostnames.

4. KILL HARDCODED URLS (the 391):
   - Work from the Phase 0 audit + `grep -rn "run\.app" --include='*.ts*'`.
     Triage each hit into: (a) browser code → replace with gateway call to
     an /api/* path (create the BFF route if missing); (b) server
     code → replace with getServiceUrl(); (c) dead code → list for Phase 2
     deletion, do NOT fix (leave a `// PHASE2-DELETE` marker comment).
   - Add ESLint no-restricted-syntax rules: forbid string literals matching
     /run\.app/ in components/, hooks/, app/(?!api) — allow only in
     lib/config/services.ts and tests.

5. DURABLE CHAT STATE:
   - app/api/boss/chat/route.ts keeps conversation history in a module-
     level Map — per-instance and lost on scale-to-zero. Replace with
     Firestore (collection ui_conversations, doc per conversation_id, TTL
     field) using the existing server-side Firebase Admin setup
     (FIREBASE_PROJECT_ID env is already wired in cloudbuild). Fall back to
     the in-memory Map ONLY when Firestore init fails, and surface
     storage_mode in the route's response metadata so degradation is
     visible, never silent.

6. TESTS (extend the Phase 0 harness):
   - gateway.ts: timeout fires abort; zod failure returns { ok:false };
     x-request-id set.
   - services.ts: unknown service name is a type error (compile-time) and
     runtime throw.
   - orchestration proxy: unlisted svc → 404; listed svc → upstream called
     with Authorization header (mock the minter).

VISUAL / STYLE SPEC (this phase is mostly invisible; make failures visible):
   - Error surfaces: when the gateway returns { ok:false }, components must
     render the shared <InlineError> pattern — a compact rose-tinted row
     (border-l-2 border-rose-500 bg-rose-500/10 px-3 py-2 text-sm) with the
     human message and a retry button when a refetch fn exists. Build this
     one component now in components/ui/inline-error.tsx; Phase 4 will
     absorb it into the feedback kit.
   - Degraded-mode indicator: when chat storage_mode !== 'firestore', show
     an amber dot + "memory-only session" tooltip near the chat header.
     Subtle (h-2 w-2 rounded-full bg-amber-400), not a banner.
   - No layout changes, no restyling of existing pages in this phase.

NON-GOALS:
   - Do not delete duplicate routes/components (Phase 2).
   - Do not convert pages to Server Components (Phase 3).
   - Do not redesign navigation (Phase 4).

ACCEPTANCE GATES:
   [ ] grep -rn "run\.app" in components/ hooks/ app/(non-api) returns ONLY
       `// PHASE2-DELETE`-marked dead files (count recorded in PR).
   [ ] New ESLint rules active and passing.
   [ ] Chat survives a simulated instance restart (conversation persists —
       prove with a test against the Firestore emulator or a mocked store).
   [ ] All Phase 0 + new tests green; typecheck:delta green.
   [ ] Manual smoke: Boss chat round-trip, one cell status widget, one SSE
       stream reconnects after a killed connection.

VERIFICATION COMMANDS:
   cd miz-oki-command-center-ui
   grep -rn "run\.app" components hooks app --include='*.ts*' | grep -v PHASE2-DELETE | wc -l   # must be 0
   npm run test && npm run typecheck:delta && npm run lint

Phase 2 Prompt — Consolidation & Dead-Code Removal

Why this phase exists (explanation)

The app has 16 app/boss* directory variants (7 explicitly archived), 155 pages, 210 API routes, and 243 components — far more surface than the product actually exposes. Duplicates mean every fix is applied to one copy and silently missed in others, and they bloat both the build and every grep. Phase 2 is a disciplined shrink: verify each candidate is truly dead (zero importers, zero nav references, zero inbound links), delete it in its own commit (git-recoverable), and merge near-duplicates into one canonical implementation. This phase is where the // PHASE2-DELETE markers from Phase 1 get resolved. The measured-before-migrated rule from the platform's SRDAL migration applies: measure the inventory before deleting; never trust a name alone.

Consolidation flow

flowchart TD
    S["Candidate surface<br/>(page / route / component)"] --> Q1{"Referenced by nav config,<br/>any import, any redirect,<br/>or external deep link?"}
    Q1 -- no --> Q2{"Archived variant or<br/>PHASE2-DELETE marked?"}
    Q2 -- yes --> DEL["DELETE in its own commit<br/>(subject lists the path)"]
    Q2 -- no --> HOLD["Log in keep-list with reason"]
    Q1 -- yes --> Q3{"Duplicate of a<br/>canonical surface?"}
    Q3 -- yes --> MERGE["Merge: port unique features into<br/>canonical, add redirect() shim,<br/>then delete duplicate"]
    Q3 -- no --> KEEP["Keep — record as canonical"]
    style DEL fill:#7f1d1d,color:#fff
    style MERGE fill:#b45309,color:#fff
    style KEEP fill:#14532d,color:#fff

PROMPT — copy from here

[PASTE SHARED CONTEXT BLOCK]

MISSION: Shrink miz-oki-command-center-ui to its canonical surface. Delete
verified-dead pages, API routes, components, hooks, and lib modules; merge
near-duplicates; leave redirect shims for any URL that ever shipped in a
nav config. Every deletion must be provably safe and individually
recoverable from git history.

TECHNICAL SPEC:

1. INVENTORY FIRST (commit the evidence before deleting anything):
   - Build docs/reports/frontend-consolidation-ledger-<date>.md with one
     table per category (pages, api routes, components, hooks, lib):
     path | importer count | nav reference? | verdict (KEEP/MERGE/DELETE) |
     evidence. Generate importer counts with a script (ts-prune or a
     madge/grep pass), not by eyeball. Commit the ledger BEFORE the first
     deletion commit so review can challenge verdicts.

2. BOSS SURFACE CONSOLIDATION (the flagship merge):
   - Enumerate all app/boss* directories. Identify THE canonical boss
     surface (the one referenced by config/navigation.ts — verify, don't
     assume). For each variant: diff against canonical; port any unique,
     still-wired feature into canonical; then delete the variant. Variants
     whose URL appeared in a shipped nav config get a permanent
     redirect() shim page (a 5-line page.tsx calling redirect('/boss/…'))
     — mirror of the platform's 307-shim convention.

3. API ROUTE MERGE:
   - Collapse per-service one-off proxy routes into the Phase 1
     orchestration/[svc]/[...path] allowlisted proxy wherever the route
     adds no logic beyond forwarding. Routes WITH logic (validation,
     shaping, memory) stay. Target: measurably fewer route handlers;
     record before/after counts in the ledger.
   - Resolve every // PHASE2-DELETE marker from Phase 1: delete the file
     or justify keeping it in the ledger.

4. STATE & HOOK CLEANUP:
   - Zustand: enumerate store slices; delete unconsumed selectors/slices;
     merge globalStore.ts / global.ts / app-store.ts overlap into ONE
     store entry point with slices (document the winner in the ledger).
   - Hooks: 65 hooks — same treatment. Merge useSSE/useMultiSSE overlap
     into the Phase 1 gateway streamSSE-backed hook. Delete hooks with
     zero importers.
   - Dependencies: run depcheck; remove packages with zero imports (verify
     each against next.config/scripts before removal — e.g. WASM tooling
     is used by build scripts, not imports). Record removals + resulting
     node_modules/bundle size delta.

5. MECHANICAL RULES:
   - One logical target per commit ("remove app/boss-v2 (0 importers,
     archived 2026-03)"), so any single deletion is trivially revertable.
   - Never delete and refactor in the same commit.
   - After each batch: npm run test && npm run typecheck:delta — the
     Phase 0 net must stay green at every commit, not just at the end.
   - If ANY verdict is uncertain, downgrade to KEEP + note. Deleting late
     is cheap; resurrecting a half-remembered feature is not.

VISUAL / STYLE SPEC:
   - Redirect shim pages must be invisible (immediate server redirect(),
     no flash content).
   - Where a merge changes a page's internal layout, preserve the
     canonical page's visual hierarchy — ported features arrive as new
     tabs/sections styled with existing patterns, never as pasted-in
     foreign-styled blocks. If the variant's styling was better, take that
     decision to Phase 4 as a note in the ledger instead of restyling now.

NON-GOALS:
   - No rendering-strategy changes (Phase 3).
   - No new features or redesign (Phases 4–5).
   - No touching app/api/stream|streaming contracts.

ACCEPTANCE GATES:
   [ ] Consolidation ledger committed with evidence for every verdict.
   [ ] app/boss* count reduced to canonical + shims only.
   [ ] Page / route / component / hook counts measurably reduced —
       report before/after in the PR (target: pages ≤ ~100, routes ≤ ~150,
       but the ledger's evidence rules over any fixed number).
   [ ] Zero PHASE2-DELETE markers remaining in the tree.
   [ ] All tests + delta-tsc green on every commit in the PR.
   [ ] Manual smoke of the 10 most-used surfaces (boss chat, KG, cells,
       dashboards, settings) after the final deletion commit.

VERIFICATION COMMANDS:
   cd miz-oki-command-center-ui
   grep -rn "PHASE2-DELETE" --include='*.ts*' | wc -l          # must be 0
   find app -name page.tsx | wc -l                             # report
   find app/api -name route.ts | wc -l                         # report
   npm run test && npm run typecheck:delta && npm run lint

Phase 3 Prompt — Rendering & Performance

Why this phase exists (explanation)

The root layout exports dynamic = 'force-dynamic', which opts the entire app out of static and cached rendering, and nearly every page is a 'use client' component — so the App Router is being used as a client-side SPA with extra steps. Data arrives via a mix of polling and SSE for the same resources, duplicating load. Heavy visualization libraries (Cytoscape, D3, react-force-graph, ReactFlow) are imported statically, landing in the shared bundle even for users who never open a graph. Phase 3 makes rendering intentional: server-render what is server-renderable, stream what is slow, lazy-load what is heavy, and pick exactly one freshness mechanism per resource.

Rendering decision model

flowchart TD
    P["Page or section"] --> Q1{"Needs per-request data<br/>or user session?"}
    Q1 -- no --> STATIC["Static / revalidated RSC<br/>export const revalidate = N"]
    Q1 -- yes --> Q2{"Data slow (>300ms)<br/>or independent sections?"}
    Q2 -- yes --> STREAM["RSC + Suspense boundaries<br/>stream sections as ready"]
    Q2 -- no --> Q3{"Interactive after load?<br/>(charts, editors, chat)"}
    Q3 -- yes --> ISLAND["Server shell + client island<br/>next/dynamic({ ssr:false }) for<br/>heavy viz libs"]
    Q3 -- no --> RSC["Plain Server Component"]
    style STATIC fill:#14532d,color:#fff
    style STREAM fill:#0f766e,color:#fff
    style ISLAND fill:#1d4ed8,color:#fff
    style RSC fill:#334155,color:#fff

PROMPT — copy from here

[PASTE SHARED CONTEXT BLOCK]

MISSION: Make rendering and data-freshness intentional in
miz-oki-command-center-ui. Remove the global force-dynamic, convert
server-renderable surfaces to RSC with streaming, lazy-load heavy
visualization bundles, and enforce one freshness mechanism per resource.
Measure everything: this phase's currency is before/after numbers.

TECHNICAL SPEC:

1. MEASURE FIRST:
   - Add @next/bundle-analyzer behind ANALYZE=true. Record baseline:
     shared First Load JS, per-route JS for the 10 heaviest routes, and a
     Lighthouse run (performance + a11y) on /, boss chat, and the KG page.
     Commit the numbers to docs/reports/frontend-perf-baseline-<date>.md.

2. UN-FORCE-DYNAMIC:
   - Remove `export const dynamic = 'force-dynamic'` from app/layout.tsx.
     The mobile-detection headers() usage forces dynamic implicitly —
     replace it: move device detection to CSS container/media queries
     where it drives layout, or isolate the headers() read into a small
     server component that wraps only the parts that truly need it.
   - Add per-route segment config ONLY where needed:
     `export const revalidate = 30` for dashboard-ish pages,
     `dynamic = 'force-dynamic'` only on truly per-request pages (chat).
     Every remaining force-dynamic must carry a one-line comment saying
     why.

3. RSC CONVERSION (top-10 routes by traffic/weight, from the ledger):
   - Pattern per page: page.tsx becomes a Server Component that fetches
     initial data server-side via the service registry (server-side can
     mint OIDC directly), then renders <ClientSection initialData={…}>
     islands for interactivity. React Query hydrates from initialData —
     no double-fetch on mount.
   - Wrap independent slow sections in <Suspense fallback={<SectionSkeleton/>}>
     so the shell streams immediately.

4. BUNDLE DIET:
   - Convert every static import of cytoscape, d3, react-force-graph,
     reactflow, jsplumb, monaco/editor-like modules to
     next/dynamic(() => import(...), { ssr:false, loading: Skeleton }).
   - Audit lucide-react and date libs for per-icon/function imports.
   - Re-run the analyzer; record deltas. Target: shared First Load JS
     down ≥30% vs baseline; graph libs absent from non-graph routes
     (verify in the analyzer treemap).

5. ONE FRESHNESS MECHANISM PER RESOURCE:
   - Build the inventory: for each live resource (cell status, KG stats,
     boss health, run streams…) record current mechanisms (poll interval,
     SSE topic, socket event). Pick ONE per resource:
       * high-frequency push (tokens, run progress) → SSE via gateway
         streamSSE;
       * periodic snapshots (health, stats) → React Query with
         refetchInterval and visibilitychange pause;
     Delete the loser. providers.tsx must stop double-subscribing
     (currently connects a global SSE stream AND sets interval refreshes
     for overlapping data).
   - Pause all polling when document.hidden; resume + refetch on focus
     (React Query's refetchOnWindowFocus covers most).

VISUAL / STYLE SPEC:
   - Every Suspense fallback uses the Phase 0 skeleton language — shaped
     like the content it replaces (same grid, same heights). No spinners,
     no "Loading…" text at route level.
   - Streaming must not cause layout shift: skeletons reserve the same
     min-height as the settled content (measure the settled section and
     set min-h accordingly). CLS on the 10 converted routes must be < 0.1
     in Lighthouse.
   - Lazy-loaded viz containers show a bordered placeholder with the
     library name and a subtle shimmer (border border-dashed
     border-muted-foreground/30 rounded-xl) so users understand a heavy
     module is arriving — perceived intent beats blank space.

NON-GOALS:
   - No navigation/IA changes (Phase 4).
   - No new pages (Phase 5).
   - Do not migrate chat streaming off its current SSE route.

ACCEPTANCE GATES:
   [ ] Root layout no longer force-dynamic; every remaining force-dynamic
       is commented with a reason.
   [ ] Shared First Load JS reduced ≥30% vs committed baseline.
   [ ] Graph libraries absent from non-graph route bundles (analyzer
       screenshot in PR).
   [ ] Freshness inventory committed; zero resources with two mechanisms.
   [ ] Lighthouse performance ≥ 80 on /, boss chat, KG page (report all
       three before/after).
   [ ] All tests + delta-tsc green.

VERIFICATION COMMANDS:
   cd miz-oki-command-center-ui
   ANALYZE=true npm run build        # record treemap
   npm run test && npm run typecheck:delta
   npx lighthouse http://localhost:3000 --preset=desktop   # after npm start

Phase 4 Prompt — UX Overhaul & Design System

Why this phase exists (explanation)

The product exposes 100+ navigation entries with equal visual weight, has no global search or command surface, and grew page-by-page so spacing, cards, states, and feedback differ across sections. Power users navigate by memory; new users are lost. Phase 4 rebuilds the experience around four pillars: progressive disclosure (a role-aware home + curated nav with an "everything" escape hatch), a global ⌘K command palette (navigate, run tools, search entities), a design-token pass (one spacing/color/typography scale enforced by lint), and a unified feedback kit (one toast, one inline-error, one empty-state, one confirm pattern used everywhere). This is the most visible phase — it should feel like a new product without moving any backend contract.

Experience model

flowchart TB
    subgraph HOME["Role-aware Home"]
        H1["'What needs my attention'<br/>alerts, pending approvals,<br/>degraded services"]
        H2["Pinned surfaces<br/>(user-chosen, drag to reorder)"]
        H3["Recent activity<br/>(runs, chats, decisions)"]
    end
    subgraph NAV["Navigation"]
        N1["Curated sidebar:<br/>≤7 top groups"]
        N2["'All surfaces' directory page<br/>searchable, tagged"]
    end
    subgraph PALETTE["⌘K Command Palette"]
        K1["Go to page…"]
        K2["Run MCP tool…"]
        K3["Search entities<br/>(campaigns, cells, decisions)"]
        K4["Recent + pinned actions"]
    end
    HOME --- NAV --- PALETTE
    style HOME fill:#0f766e,color:#fff
    style NAV fill:#1d4ed8,color:#fff
    style PALETTE fill:#7c3aed,color:#fff

PROMPT — copy from here

[PASTE SHARED CONTEXT BLOCK]

MISSION: Rebuild the miz-oki-command-center-ui experience layer: design
tokens, feedback kit, navigation IA, role-aware home, and a global ⌘K
command palette. All work targets the post-Phase-2 canonical route set and
the post-Phase-3 rendering model. Backend contracts do not change.

TECHNICAL SPEC:

1. DESIGN TOKENS (foundation — do first):
   - Consolidate the Tailwind theme into explicit tokens in
     tailwind.config: a 4px-base spacing scale; a semantic color layer
     (bg-surface, bg-surface-raised, text-primary, text-muted, border-line,
     accent, success/warning/danger/info) mapped to the existing palette;
     a type scale (display/h1/h2/h3/body/caption with fixed line-heights);
     radius tokens (sm 6px, md 10px, lg 16px); z-index scale; motion
     durations (fast 120ms, base 200ms, slow 320ms) + a single easing.
   - Codify in components/ui/: Button (primary/secondary/ghost/danger ×
     sm/md/lg), Card, Badge/Status-pill, Input/Select/Combobox (Radix),
     Tabs, Table shell, Drawer, Modal, Tooltip. Reuse and normalize
     existing Radix-based components rather than adding a new library —
     the dependency set stays as-is.
   - Enforcement: an ESLint rule (or stylelint-style grep check in CI)
     flagging raw hex colors and arbitrary px values in className outside
     components/ui/**. Existing violations get a tracked allowlist file
     that must only shrink.

2. FEEDBACK KIT (one way to say each thing):
   - <Toast/> (single provider, 4 intents, action slot, auto-dismiss with
     hover-pause), <InlineError/> (absorb Phase 1's), <EmptyState/>
     (icon + one-line explanation + primary action), <ConfirmDialog/>
     (destructive actions require it; danger button on the right),
     <StatusDot/> (healthy=emerald, degraded=amber, down=rose,
     unknown=zinc — with accessible labels, never color-only).
   - Migrate the 20 most-used surfaces to the kit; grep-and-replace ad-hoc
     alert()/inline-div patterns as encountered; ledger the rest.

3. NAVIGATION IA:
   - Rewrite config/navigation.ts as a typed registry where each entry has
     { id, title, href, icon, group, tags[], roles?[], pinnedByDefault? }.
   - Curated sidebar: ≤7 top-level groups (e.g. Home, Boss Agent,
     Intelligence, Operations, Data & KG, Governance, Settings). Groups
     collapse; current section stays expanded; active item gets a left
     accent bar (border-l-2 accent), not a filled background.
   - "All surfaces" page (/directory): searchable, tag-filtered grid of
     every registered page — the escape hatch that lets the sidebar stay
     small. External links (Agent IDE) keep target=_blank + the external
     icon convention.

4. COMMAND PALETTE (⌘K / Ctrl-K):
   - Use cmdk (already a dependency — verify; add if absent). Sources:
     (a) the navigation registry (fuzzy "go to"); (b) MCP tools via the
     existing tools list endpoint — "Run tool…" opens a param form drawer,
     never fire-and-forget for mutating tools (reuse ConfirmDialog);
     (c) entity search endpoint if present — otherwise ship nav+tools
     first and leave entities as a registered follow-up; (d) recent items
     (localStorage, last 10).
   - Palette is globally mounted in providers, lazy-loaded on first open,
     fully keyboard navigable, and closes on route change.

5. ROLE-AWARE HOME:
   - Replace the current landing with three stacked zones (see diagram):
     attention (pending approvals, degraded StatusDots, error-rate
     alerts — sourced from existing health/status endpoints via the
     gateway), pinned surfaces (user-pinnable from the directory page;
     persist in localStorage now, profile doc later), recent activity
     (runs/chats/decisions from existing endpoints).
   - Zones stream independently (Suspense) and each has a real
     EmptyState ("Nothing needs your attention — all systems nominal").

VISUAL / STYLE SPEC:
   - Overall character: calm operations console. Dense but breathable —
     12/16/24px rhythm, generous section gaps (32px+), max content width
     1440px with fluid gutters. Dark theme is primary (the fleet is
     monitored at night); verify every token pair passes WCAG AA in dark
     AND light.
   - Typography: keep the existing font stack; enforce the scale. Numbers
     in tables/metrics use tabular-nums.
   - Color discipline: neutrals dominate; accent color is for interactive
     affordances ONLY; intent colors (success/warning/danger) are for
     state ONLY, never decoration. Charts get a fixed 8-color categorical
     ramp that harmonizes with the tokens.
   - Motion: sidebar collapse, drawer, palette open = base 200ms ease-out;
     respect prefers-reduced-motion (disable non-essential transitions).
   - Every interactive element: visible focus ring (ring-2 ring-accent
     ring-offset-2 on dark), hit target ≥ 40px, aria labels on icon-only
     buttons. Add eslint-plugin-jsx-a11y and fix new violations (existing
     ones join the shrink-only allowlist).
   - Empty/error/loading states are DESIGNED states, not afterthoughts:
     each kit component has a Storybook-style demo page at /directory/kit
     (internal) showing all variants — this doubles as the living style
     guide.

NON-GOALS:
   - No backend/API changes beyond consuming existing endpoints.
   - No new product surfaces (Phase 5).
   - No theme-system rewrite (extend the existing Tailwind setup).

ACCEPTANCE GATES:
   [ ] Token layer merged; raw-hex/arbitrary-px lint active; allowlist
       committed and CI-enforced as shrink-only.
   [ ] Feedback kit shipped + adopted on top-20 surfaces (list them in
       the PR).
   [ ] Sidebar ≤7 groups; /directory lists every registered page;
       zero pages reachable ONLY by memorized URL.
   [ ] ⌘K palette: navigate + run-tool flows work end-to-end; mutating
       tools always confirm.
   [ ] New home renders attention/pinned/recent zones with live data,
       streaming independently.
   [ ] Keyboard-only walkthrough of home → palette → boss chat → a
       dashboard succeeds; axe/jsx-a11y clean on new code.
   [ ] All tests + delta-tsc green; add tests for the nav registry
       (unique ids/hrefs) and palette actions.

VERIFICATION COMMANDS:
   cd miz-oki-command-center-ui
   npm run test && npm run typecheck:delta && npm run lint
   npm run dev   # manual: ⌘K flows, keyboard-only pass, /directory, /directory/kit

Phase 5 Prompt — Expansion Surfaces

Why this phase exists (explanation)

With the foundation sound (typed gateway, canonical routes, intentional rendering, design system), the frontend can finally expose platform capabilities that exist in the backend but have no UI today: the ORACLE/Intent platform (cells 33–36 with eight intent_* tools live), the governance layer (decision proposals, approvals, authorizations, audit replay), a unified realtime operations hub, and a first-class mobile experience. Each surface below is written as its own mini-prompt so they can be built in parallel by different agents once Phase 4 merges — they share the design system and gateway, not each other's code.

Expansion map

flowchart LR
    GW["Typed Gateway + BFF<br/>(Phase 1)"] --> A["5A · Intent / ORACLE<br/>console"]
    GW --> B["5B · Governance &<br/>Approvals console"]
    GW --> C["5C · Realtime Ops hub"]
    GW --> D["5D · Mobile-first pass"]
    DS["Design System<br/>(Phase 4)"] --> A & B & C & D
    style A fill:#7c3aed,color:#fff
    style B fill:#b45309,color:#fff
    style C fill:#0f766e,color:#fff
    style D fill:#1d4ed8,color:#fff

PROMPT 5A — Intent / ORACLE Console

[PASTE SHARED CONTEXT BLOCK]

MISSION: Build the Intent/ORACLE console at /intelligence/intent — the
first UI over the live intent platform (cells 33–36; eight intent_* tools
served by the boss agent; consent-gated, fail-closed).

TECHNICAL SPEC:
- BFF routes under app/api/intent/* proxy the boss agent's intent_* tools
  (intent_status, intent_score_get, intent_cohort_query,
  intent_transitions_recent, intent_explain, intent_taxonomy_list,
  intent_consent_stats, intent_incrementality_report) through the
  orchestration allowlist. Zod-type every response.
- Pages: overview (pipeline status, hourly scoring-run freshness, consent
  stats), cohort explorer (filterable table + score distribution chart),
  identity drilldown (score history sparkline + transitions +
  intent_explain panel), taxonomy viewer (topics × domains grid,
  deny-list surfaced read-only).
- Poll-free: overview uses React Query refetchInterval 60s (hourly
  pipeline — faster polling is theater); explorer/drilldown fetch on
  demand.

VISUAL / STYLE SPEC:
- Scores are CALIBRATED PROBABILITIES: render as 0–1 with two decimals +
  a horizontal micro-bar, never as percentages with hype styling. Neutral
  ramp for magnitude; intent colors only for state (consent denied =
  rose, backfilled provenance = amber badge).
- Every metric block carries its claim label chip ("built,
  pre-benchmark") in text-caption text-muted — truth-in-UI mirrors the
  platform's claim discipline, and the AUUC/ranking-gate caveat from the
  Phase-A report is shown verbatim in the incrementality panel's info
  popover.
- Consent-denied cohorts render as EmptyState with the fail-closed
  explanation, never as zeros that look like data.

GATES:
[ ] All eight tool surfaces reachable and typed; error/empty/loading
    states designed; identity IDs displayed as shp_*/em_* hashes only
    (never raw PII); tests for the zod layer; a11y pass.

PROMPT 5B — Governance & Approvals Console

[PASTE SHARED CONTEXT BLOCK]

MISSION: Build /governance — a console over the governed decision pathway
(envelope → passport → policy → decision → approval → authorization →
audit replay) served by the service-* fleet through the BFF.

TECHNICAL SPEC:
- BFF routes proxy: pending approvals (tenant-scoped), decision detail
  (DEL score, passport checks, rejected alternatives, policy_version),
  approve/reject actions (require ConfirmDialog + typed reason), audit
  replay fetch by decision_id.
- The approval action is the ONLY mutating call; everything else is
  read-only. Idempotency: send a client-generated request id; disable the
  button while in flight; surface 409 (already consumed) as a designed
  state, not an error toast.

VISUAL / STYLE SPEC:
- Decision detail reads like a case file: header (DEL score as a large
  numeral vs threshold, pass/fail pill), passport checks as a vertical
  checklist (check = emerald check icon, fail = rose x, soft-fail = amber
  tilde), rejected alternatives in a collapsed section, full JSON behind
  a "raw" tab in a monospace scroll area.
- Approvals demand gravity: the approve dialog restates amount/action/
  bounds in a bordered summary block and requires a typed confirmation
  for amounts over a configured threshold. Danger-red is reserved for
  reject/rollback.
- Audit replay renders as a numbered vertical timeline with monospace
  hashes, each step linkable (#seq anchors).

GATES:
[ ] Approve/reject round-trip works against a mocked BFF in tests; 409
    and tenant-mismatch render designed states; timeline deep-links work;
    a11y pass including focus-trap in dialogs.

PROMPT 5C — Realtime Operations Hub

[PASTE SHARED CONTEXT BLOCK]

MISSION: Build /operations/live — one hub that answers "is the platform
healthy RIGHT NOW" by unifying the fleet-health, cell-status, and
run-stream data the app already receives but scatters across pages.

TECHNICAL SPEC:
- One multiplexed SSE subscription via the gateway's streamSSE (topics:
  health, runs, alerts) feeding a dedicated Zustand slice; React Query
  snapshots hydrate the initial state. No polling on this page.
- Panels: fleet grid (every service as a StatusDot card with revision +
  latency), live run feed (streaming rows, virtualized list —
  react-window or CSS content-visibility), alert lane (governance holds,
  SLO breaches).
- Failure-class rule from the platform docs is ENCODED: unauth 403 =
  healthy+locked (emerald with a lock glyph), authed 404 = up/wrong-path
  (amber), 503 = real error (rose), timeout = cold start (zinc pulse).
  Never render a locked service as an outage.

VISUAL / STYLE SPEC:
- Wall-display friendly: readable at 2m — metric numerals text-3xl
  tabular-nums, high-contrast dots with shape+label redundancy, an
  optional ?kiosk=1 mode that hides nav chrome.
- New feed rows enter with a 200ms fade + background flash
  (accent/10 → transparent); the feed NEVER auto-scrolls while the user's
  pointer is inside it (pin-to-bottom resumes on leave).
- Timestamps: relative ("32s ago") with absolute on hover; a stream-health
  indicator in the page header (connected/reconnecting with backoff
  countdown).

GATES:
[ ] Kill the SSE connection in devtools → reconnect with backoff and a
    visible reconnecting state, no data loss on resume (snapshot refetch).
[ ] 500-row feed scrolls at 60fps (virtualized).
[ ] Failure-class rendering matches the table above (unit-test the
    classifier fn).

PROMPT 5D — Mobile-First Pass

[PASTE SHARED CONTEXT BLOCK]

MISSION: Make the five surfaces an operator actually needs on a phone
work excellently at 390×844: Home, Boss chat, approvals, live ops,
directory. Everything else must be usable (no horizontal scroll, no
broken layouts) but is not optimized.

TECHNICAL SPEC:
- Replace the server-side header sniffing removed in Phase 3 with pure
  responsive CSS (container queries where supported, min-width media
  queries otherwise). One source of breakpoints in the Tailwind config
  (sm 640 / md 768 / lg 1024 / xl 1280) — kill ad-hoc pixel checks in JS
  (window.innerWidth reads move to a single useViewport hook if truly
  needed).
- Navigation on <lg: sidebar becomes a bottom tab bar (5 slots: Home,
  Boss, Approvals, Live, More→directory drawer). Palette gets a floating
  search button (bottom-right, above the tab bar).
- Chat on mobile: full-height flex column, composer pinned above the
  keyboard (dvh units + visualViewport listener), message list uses
  overscroll-behavior: contain.

VISUAL / STYLE SPEC:
- Touch targets ≥44px; swipe-to-dismiss on drawers/toasts; tables
  collapse to card lists (each row becomes a Card with label/value
  pairs) — never pinch-zoom tables.
- Test matrix: iPhone 14 (390), small Android (360), iPad portrait (768).
  Screenshot all five surfaces × three widths into the PR.

GATES:
[ ] Zero horizontal scroll at 360px on all routes (automated check via
    Playwright viewport sweep if available, else manual sweep recorded).
[ ] The five target surfaces pass a thumb-only walkthrough.
[ ] Lighthouse mobile performance ≥ 75 on Home and chat.

Cross-Phase Style Guide

Every prompt above references these rules; they are the constitution for all visual work.

Voice & copy

Rule Do Don't
Calm and specific "Cell 28 has returned 503 for 4 minutes" "Oops! Something broke!!"
State-first "3 approvals waiting" "You have some pending items"
Honest degradation "memory-only session" amber dot silently losing chat history
Claim discipline "built, pre-benchmark" chips on metrics implying benchmarked performance

Color semantics (token layer)

Token Meaning Never used for
accent interactive affordances (links, focus, primary buttons) status, decoration
success / emerald healthy, passed, approved "good numbers" styling
warning / amber degraded, soft-fail, backfilled, memory-only attention-grabbing marketing
danger / rose down, failed, destructive actions, consent-denied emphasis of non-destructive items
zinc neutrals structure, text, unknown states conveying state on their own

State rendering (the "four states" rule)

Every data surface designs all four states before it ships:

stateDiagram-v2
    [*] --> Loading: mount
    Loading --> Loaded: data
    Loading --> Error: failure
    Loaded --> Empty: zero items
    Error --> Loading: retry
    note right of Loading: shaped skeleton,\nnever spinner at route level
    note right of Error: InlineError + retry,\nhuman message
    note right of Empty: EmptyState with\nexplanation + action

Accessibility floor (all phases)

Engineering conventions (all phases)


Prompt-Writing Conventions Used Here

For anyone extending this document with new phase prompts:

  1. Explanation before prompt. Each phase opens with why it exists in prose, so the executing agent (or reviewer) can challenge the spec instead of following it blindly.
  2. Shared context is pasted, not linked. Agents in fresh sessions have no memory; the context block travels with every prompt.
  3. Technical and visual specs are separate sections. They are reviewed by different people and fail in different ways.
  4. Non-goals are explicit. Scope creep between phases is the biggest schedule risk; each prompt names what it must not do.
  5. Gates are checkboxes with verification commands. "Done" is a command output, not a feeling.
  6. Diagrams are Mermaid, in-repo. They version with the code and render on GitHub.

End of document. Companion plan: docs/reports/FRONTEND_UI_IMPROVEMENT_PLAN.md.

← All docsView source on GitHub →