# Run Page v2 — "control room" observability (Version B, signed off)

audience: AI coding agents first. Imperative, BLUF. Design reference =
`scratchpad/run-page-reinvented.html` (approved mockup) — copy its LAYOUT, HIERARCHY, COPY,
BEHAVIOR. Do NOT copy its CSS verbatim: implement in deck-ui idiom (Tailwind classes +
`packages/deck-ui/src/tokens.css` custom properties). New visual values land as tokens first.

## What this is

New route `/plans/[runId]` — per-run observability page replacing the forensics blob on `/plans`.
Goals (from `../0 DOCS/`): ground-truth distance-to-done (never agent claims), only-actionable
alerts, live, configurable, good recommendations.

## Hard rules

- **No fabricated numbers.** Every rendered value traces to collector panel data or a harness
  control-api response. A metric with no source renders an honest labeled gap state
  ("not recorded yet — needs A8"), NEVER an invented number.
- **Componentization mandate (BLOCKING):** every UI element = a named `@overdeck/deck-ui`
  component OR existing `@platform-modules/*`. NO inline one-off markup in page files. Mockup is
  inline — implementation MUST NOT copy that shape.
- Colocated `.test.tsx` per component (existing deck-ui pattern). Fixtures in
  `packages/deck-ui/src/fixtures/`.
- Both themes (light/dark) via tokens; the app's existing theme mechanism.

## Data sources (verified)

REAL today — wire it:
- `plans` panel (`HarnessPlansPanelData`, `apps/web/src/lib/panel-data.ts`): runId, title, status,
  state, owner, currentTask, tasksTotal/Completed, pendingDecisions, degradedReason, updatedAt,
  waves[].tasks[] (widen `HarnessWaveTask` from `{id}` to full node: status/state/seat/deps/
  attempt/branch — forwarded by `collector/src/adapters/harness.ts` `buildPlansPanel`; forward
  `seq` too).
- `forensics:<runId>` panel: tiles, attribution, runs[] (startTs/endTs), segments[]
  (t0 epoch ms, durMs, cat, taskId?, agentId?). Enough for timeline lanes + playhead + measured
  durations (ETA basis).
- Harness control-api via collector proxy: `GET /runs/:id`, `GET /runs/:id/timeline`,
  `GET+POST /runs/:id/decisions`, `POST .../tasks/:taskId/steer` (collector verb `steer` exists in
  `collector/src/actions.ts`; add a `decision` verb there if absent — collector IS in-repo).
- Live refresh: collector `/events` SSE (~5s snapshots) via existing `useSseStream`.

BLOCKED on harness (spec: `mega-plan-harness/docs/specs/2026-07-18-observability-instrumentation-and-control-api.md`) —
build the component, page renders honest gap state until endpoint lands:
- A8 per-attempt costUsd → cost pill hidden until real.
- A9 executing account slug → "account: not recorded".
- A10 watchdog idle signal (`lastProgressAt`/`idleTimeoutMs`/`alive`) → stuck-agent attention row
  omitted until it lands (never guess idle).
- B5 config read+PATCH → SettingsTable renders "config surface needs B5" state.
- B7 per-wrapper `ratelimits[]` + retry/accounts → Capacity badges show wrapper list with
  "no limit data yet (B7)" tooltip; NO fabricated countdowns; RateLimitDialog fixture-tested only.

## Component inventory (each = own file in `packages/deck-ui/src/`, exported from index.ts)

| Component | Mockup section | Notes |
|---|---|---|
| `RunCommandBar` | sticky top bar | run identity, status chip, Capacity cell, Pause/Kill (wired where verbs exist, else disabled+tooltip), coverage button |
| `WrapperCapacity` | Capacity cell | one badge per wrapper: `cursor ✓ · codex ⏳12:34 · grok ✓`; driven by `ratelimits[]` prop (empty = all healthy); clickable → RateLimitDialog |
| `DistanceToDone` | hero | "N/M tasks truly landed" (landed = journal-verified: gate+review+merge — from task status, never claims), per-task state cells, truth note, ETA from measured segment durations, fix-loop meter (gap state until source exists) |
| `AttentionPanel` + `AttentionRow` | Needs you rail | shared grid column tracks (subgrid — 0px edge spread REQUIRED), severity stripe, grounded `→` recommendation, empty-state contract ("Nothing needs you…") |
| `SwimlaneTrace` + `TraceSegment` | timeline | absolute-time lanes, NOW playhead, wave separators, zoom presets (fit/hour/live), per-wrapper condition bands scoped to affected lanes ONLY, hover tooltip |
| `SegmentDetailDrawer` | segment dialog | when/who/note/evidence (journal seq) + open-agent link |
| `SettingsTable` + `SettingRow` + `LayerLadderEditor` | settings | table: Setting/Value/Decided by/Can I change it?; row expands inline to full layer ladder (engine default→home→repo→plan→this run), editable where legal; NO pills |
| `DecisionDialog` | Decide modal | full decision detail (needs/why/blast_radius/options from `/decisions`); **abort = pending action: 5s UndoToast holds it, POST fires only on expiry; Undo restores row** |
| `RateLimitDialog` | wrapper-scoped resolve | what-happened, scope line, 3 options (wait-is-safe default / retry-now + burns-wait risk note / switch-account picker w/ chain+health+recommendation) |
| `UndoToast` (extend/replace existing toast) | toast | plain mode (3.4s) + actionable mode: message + `Undo (N)` live countdown button, ≥5s persist, focus on button, commits on expiry |
| `DeckTooltip` | data-tipb/tips | pointer-follow tooltip primitive (bold line + muted line) |
| `DataCoveragePanel` | coverage popover | demoted list of not-yet-recorded signals (A8/A9/A10/B7), each linking the spec item |

Reuse (audit BEFORE authoring): `SectionCard`, `SectionHeading`, `KpiTile`, `KvPanel`,
`StaleBadge`, `useSseStream`, `formatDurationMs`. `RunLanes`/`PhaseBars` stay on `/plans`
untouched (this spec does NOT rework them).

## Page assembly

- `apps/web/src/pages/plans/[runId].astro`, `export const prerender = false` (pattern:
  `api/collector/[...path].ts`).
- `apps/web/src/components/plans/PlanRunApp.tsx` — data wiring only (queries, SSE, action posts),
  composes deck-ui components. Zero presentation markup beyond layout grid.
- `/plans` list rows link to `/plans/<runId>`; back affordance on the run page.

## Acceptance (page level)

- `pnpm --filter @overdeck/deck-ui test` green; `pnpm --filter web build` + typecheck green.
- Live app on :31337: `/plans/<real-runId>` renders hero/rail/timeline/settings from REAL panel
  data; every blocked feature shows its labeled gap state; zero fabricated values (grep the
  rendered page for the mockup's demo numbers — none may appear).
- AttentionRow edges: 0px spread across rows (Playwright bounding-box assertion, pattern in
  `scratchpad/verify-multiwrapper.mjs`).
- Abort undo: Playwright assertion pattern in `scratchpad/verify-undo-abort.mjs` (countdown ticks,
  undo restores, commit only after ≥5s).
