core/docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md
Shay 3c9e7e2ce3
docs(workbench-ui): wave brief pack (W1..W4) (#395)
Production-line brief pack for the workbench-UI implementation wave.
Five briefs in five PRs, gated on ADR-0173 (PR #394) landing first.

Dependency DAG:
  ADR-0173 → W1 → { W2 ‖ W4a ‖ W4b }
                → W3 (after W2 + #393 CompositionClaim handler)

Operator profiles assigned per [[feedback-parallel-dispatch-pattern]]:
  W1  Codex   — Vite/TS/Tailwind/shadcn scaffold from ADR-0162 spec
  W2  Sonnet  — read surfaces (ProposalQueue/Card/Detail + StableJsonViewer)
  W3  Opus    — ratification corridor (load-bearing wrong=0; case 0050 pin)
  W4a Sonnet  — replay surfaces (TraceDrawer/ReplayTheater/Diff)
  W4b Sonnet  — eval surfaces (EvalCenter/LaneList/FailureViewer)

Each brief carries: dispatch line, reads-required-first list,
outcome inventory, hard requirements, tests, deliverables, forbidden
surface. Anti-regression invariants enumerated (wrong=0, ADR-0166
no-new-lanes, partition, case 0050, 127.0.0.1, no remote runtime).

Includes copy-paste dispatch sequence and a `status` zsh trap for
single-line PR monitoring during the wave.

Pain-point retirement matrix maps each of the five scoping-brief
operator pain points to the wave PR that retires it.
2026-05-27 15:35:05 -07:00

740 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Workbench UI Wave — Brief Pack (W1..W4)
**Goal:** Stand up the workbench frontend so the math teaching corridor
is operable by one operator at human-realistic throughput. Implements
the wave shape committed in
`docs/handoff/WORKBENCH-UI-WAVE-SCOPING.md` under the trust boundary
pinned by ADR-0173.
**Blocker.** Every brief here depends on **ADR-0173 W0** landing first
(PR #394). Do not dispatch W1..W4 until that PR merges.
**Bundling rule.** [[feedback-batch-during-research]]. This is
implementation mode, not research — one PR per wave (W4 splits into
two). No spec churn in the implementation PRs; if doctrine needs to
move, it moves in its own ADR PR first.
**Production-line discipline.** [[feedback-production-line-pattern]].
Dispatch lines are copy-paste-ready; each brief opens with a worktree
`add` line; brief pack is the single source of truth.
---
## Dependency DAG
```
ADR-0173 (PR #394)
W1 — Scaffold (one frontend PR; blocks W2/W3/W4)
├──────────┬──────────┐
▼ ▼ ▼
W2 W4a W4b
(read) (replay) (eval)
W3 (ratification corridor — needs W2's queue + detail)
```
**Parallel-safe pairs:** {W2 ‖ W4a ‖ W4b} once W1 lands. W3 is
sequential after W2 because the RatificationCommandPanel is wired to
the ProposalDetailPanel that W2 ships.
**Recommended sequence (single-operator):** W1 → W2 → W3 → W4a → W4b.
**Recommended sequence (parallel, 2+ operators):**
W1 → (W2 ‖ W4a ‖ W4b) → W3.
---
## Operator profiles
Per [[feedback-parallel-dispatch-pattern]]:
| Wave | Profile | Why |
|---|---|---|
| W1 | **Codex** (mechanical) | Vite/TS/Tailwind/shadcn scaffold from ADR-0162 spec; deterministic toolchain bootstrap; minimal judgment. |
| W2 | **Sonnet** (tight-scope) | Frontend work over a fully-specified API contract; TanStack Query + Zustand; semantic color mapping from ADR-0162 ratified enums. |
| W3 | **Opus** (load-bearing wrong=0 surface) | Same profile as CC-2; ratification dispatch with hazard-pin discipline, partition tests, case 0050 pin. |
| W4a | **Sonnet** (tight-scope) | Replay surfaces over existing API routes. |
| W4b | **Sonnet** (tight-scope) | Eval surfaces over existing API routes. |
---
## Brief W1 — Frontend Scaffold
**Operator profile:** Codex (mechanical; Vite/TS/Tailwind bootstrap)
**Branch:** `feat/workbench-ui-w1-scaffold`
**Base:** `origin/main` (post-ADR-0173 merge)
**Style:** Frontend bootstrap. No app routes yet. Tokens + shell + palette + status footer only.
### Dispatch
```bash
git fetch origin main && \
git worktree add /tmp/wt-wb-ui-w1 origin/main && \
cd /tmp/wt-wb-ui-w1 && \
git checkout -b feat/workbench-ui-w1-scaffold
```
### Reads required FIRST
- `docs/decisions/ADR-0160-core-workbench-v1.md` §"Architecture choice" + §"Trust boundary"
- `docs/decisions/ADR-0162-workbench-design-system.md` §"Token namespace", §"Typography", §"Color semantics", §"Motion", §"Keyboard contract", §"Layout shell", §"Component map — v1 must-ship", §"The no-go list", §"Implementation plan — Branch 1"
- `docs/decisions/ADR-0173-workbench-ratification-trust-boundary.md` §"Keyboard contract", §"What the workbench MUST NOT do"
- `docs/handoff/WORKBENCH-UI-WAVE-SCOPING.md` §"Guardrails"
### Outcome
`workbench-ui/` directory at repo root, fully bootstrapped per
ADR-0162 §"Implementation plan — Branch 1":
1. **`workbench-ui/package.json`** — pinned deps:
- React 18.x, ReactDOM 18.x
- Vite 5.x
- TypeScript 5.x
- Tailwind 3.x
- shadcn-style primitives (Radix UI under shadcn)
- TanStack Query 5.x
- Zustand 4.x (state)
- `lucide-react` for icons (bundled, no CDN)
2. **`workbench-ui/vite.config.ts`** — dev server on `127.0.0.1`, no
network proxies to remote hosts, build output to `dist/`.
3. **`workbench-ui/tsconfig.json`** — strict TS.
4. **`workbench-ui/tailwind.config.js`** + **`postcss.config.js`**.
5. **`workbench-ui/src/design/tokens/tokens.css`** — every token
from ADR-0162 §1§4 (color, typography, motion, spacing).
6. **`workbench-ui/src/design/badges/`** — four badge primitives:
- `EpistemicStateBadge` (15 values from `core/epistemic_state.py`)
- `NormativeClearanceBadge` (4 values)
- `ReviewStateBadge` (4 values from `teaching/proposals.py`)
- `GroundingSourceBadge` (6 values)
Each binds color + glyph + text label per ADR-0162 §3. **No color-only encoding.**
7. **`workbench-ui/src/shell/`** —
- `WorkbenchShell.tsx` (the outer layout)
- `TopBar.tsx`
- `LeftNav.tsx`
- `StatusFooter.tsx`
- `CommandPalette.tsx` (focusable via `:` per ADR-0173)
8. **`workbench-ui/src/keyboard/`** —
- `KeyboardHelpOverlay.tsx` populated from ADR-0173 §"Keyboard contract"
- global `useKeyboard()` hook
- `?` opens the help overlay
- `Esc` dismisses
- **No `Cmd`/`Ctrl` chords** (ADR-0173 constraint)
9. **`workbench-ui/src/api/client.ts`** — fetch wrapper for the
existing backend (`http://127.0.0.1:<port>`); typed against
`workbench/schemas.py` shapes via hand-written TS types in
`src/api/types.ts`.
10. **`workbench-ui/dist/`** — added to **root `.gitignore`**.
11. **CI job**`.github/workflows/workbench-ui-build.yml`:
- triggers on PR touching `workbench-ui/**`
- runs `npm ci && npm run build`
- **does not commit `dist/`**
- fails the PR if build fails
### Hard requirements
- **Tokens snapshot test.** A snapshot test pins the computed token
values from `tokens.css` so future PRs cannot drift them silently.
- **Fonts bundled locally** per ADR-0173 Q4 — no CDN font fetch, no
`@import url(...google...)`. License compliance is W1's
responsibility.
- **Icons bundled locally** — `lucide-react` is installed as a
dependency; no remote icon fetches.
- **No app routes yet.** W1 ships the shell; `/proposals`,
`/evals`, `/trace` routes are W2/W3/W4's job.
- **Backend binds 127.0.0.1.** UI verified to refuse cross-origin
requests; CORS configured restrictively in `workbench/server.py`
if not already (read current state first; do not relax).
- **Keyboard help overlay populated from ADR-0173.** Verbatim
bindings table.
- **Accessibility:** no icon-only buttons without `aria-label`; all
badges carry text + shape, not color alone (ADR-0162 no-go list).
- **No-go list cross-check.** Reviewer must verify: no chat-clone
bubbles, no "AI thinking" affordances, no glassmorphism, no
purple-neon-cyberpunk, no graph-builder canvas, no dashboard
splash metrics.
### Tests
- `workbench-ui/src/**/__tests__/*.test.tsx` using Vitest + React
Testing Library
- `shell.test.tsx` — WorkbenchShell renders with TopBar, LeftNav, StatusFooter
- `command_palette.test.tsx``:` focuses palette, `Esc` dismisses
- `keyboard_help.test.tsx``?` opens overlay with ADR-0173 bindings
- `badges.test.tsx` — all four badge primitives render correct
color + glyph + label for every enum value
- `tokens.snapshot.test.ts` — pins computed token bytes
### Deliverables
- `workbench-ui/` directory complete per outcome list
- root `.gitignore` updated (one line: `workbench-ui/dist/`)
- `.github/workflows/workbench-ui-build.yml`
- Tests green; CI workbench-ui-build job green
- No backend changes
- No changes to `core/`, `chat/`, `teaching/`, `field/`,
`generate/`, `algebra/`, `language_packs/`
### Forbidden
- Adding app routes (W2/W3/W4 territory)
- Adding ratification UI (W3 territory)
- Adding any ProposalQueue/Card/Detail components (W2 territory)
- Adding any TraceDrawer/Replay/Eval components (W4 territory)
- Touching backend code (`workbench/api.py`, `workbench/readers.py`, etc.)
- Committing `dist/`
- Remote CDN dependencies (fonts, icons, runtime)
- `Cmd`/`Ctrl` keyboard chords
- Color-only state encoding
- Auto-dismiss toasts for audit events
---
## Brief W2 — Read Surfaces (Proposal Queue)
**Operator profile:** Sonnet (tight-scope frontend over fully-specified API)
**Branch:** `feat/workbench-ui-w2-proposal-queue`
**Base:** `origin/main` (post-W1 merge)
**Style:** Frontend only. Read-only against existing routes.
### Dispatch
```bash
git fetch origin main && \
git worktree add /tmp/wt-wb-ui-w2 origin/main && \
cd /tmp/wt-wb-ui-w2 && \
git checkout -b feat/workbench-ui-w2-proposal-queue
```
### Reads required FIRST
- `workbench/api.py` — routes `/proposals`, `/proposals/{id}`,
`/math-proposals`, `/math-proposals/{id}` (exact payload shapes)
- `workbench/schemas.py``ProposalSummary`, `ProposalDetail`,
`MathProposalSummary`, `MathProposalDetail`, `MathReasoningStep`
- `docs/decisions/ADR-0162-workbench-design-system.md` §"Component map",
§"StableJsonViewer", §"Empty/error/loading state contract"
- `docs/decisions/ADR-0173-workbench-ratification-trust-boundary.md`
§"Keyboard contract" (queue + detail bindings)
- `docs/decisions/ADR-0167-audit-as-teaching-evidence.md` §"Partition guarantees"
### Outcome
Six components + two routes + two stores.
1. **`src/proposals/ProposalQueue.tsx`** — paginated list of
pending proposals. Renders math + cognition in separate tabs
(partition discipline). Default sort: oldest pending first.
Filter chips: state, source kind, replay status.
2. **`src/proposals/ProposalCard.tsx`** — one-row representation:
`proposal_id` (truncated, `CopyableHash`), `EpistemicStateBadge`,
`ReviewStateBadge`, `age_proposals`, `replay_equivalent` icon,
`handler_name` (if math).
3. **`src/proposals/ProposalDetailPanel.tsx`** — full detail view
with `StableJsonViewer` over `evidence`, `replay_evidence`,
`proposed_chain` / `proposed_change_payload`, and
`reasoning_trace_steps` rendered as a step list (not a graph —
ADR-0162 no graph-builder).
4. **`src/json/StableJsonViewer.tsx`** — canonical JSON renderer;
sorted keys; no re-ordering; no syntax-highlight that mutates
bytes; copy-to-clipboard per top-level node.
5. **`src/json/CopyableHash.tsx`** — truncated SHA with full-on-hover
+ copy button.
6. **`src/json/ArtifactLink.tsx`** — typed link to
`/artifacts/{id}` route (lazy-loads the artifact).
7. **`src/routes/proposals.tsx`** — `/proposals` (cognition) +
`/math-proposals` (math) routes; URL state binds to selected
proposal id.
8. **`src/state/proposals.ts`** — Zustand store: queue cache,
selected id, filter chips. TanStack Query owns the fetch.
### Keyboard wires (per ADR-0173)
- `g p` → navigate to `/proposals` (or `/math-proposals` based on
last-viewed tab)
- `j` / `↓` → focus next proposal in queue
- `k` / `↑` → focus previous
- `Enter` → open detail panel for focused proposal
- `Esc` → close detail, return to queue
- `/` → focus filter input
### Hard requirements
- **Partition discipline.** Cognition and math queues are separate
routes, separate Zustand slices, separate component trees.
Math `ProposalCard` cannot render a cognition payload and vice
versa. Verified by component test.
- **`StableJsonViewer` does not alter bytes.** A round-trip
(`json.stringify(parsed)`) of the rendered payload equals the
fetched payload byte-for-byte (modulo whitespace which is rendered
but not stored).
- **No ratification UI.** No `r` keybind. No "ratify" button. No
"accept" or "reject" affordance. That is W3's surface.
- **Empty/error/loading per ADR-0162 §6.** Empty state names what's
missing; error state names the failure; loading state is a
skeleton, not a spinner-soup splash.
- **Replay-state visualization.** `replay_equivalent: true|false|null`
renders three distinct states (passed / failed / not-yet-replayed)
— not collapsed to a two-state truthy/falsy icon. Per scoping
brief pain point #6.
- **`suggested_ratify_cli` rendered (read-only).** Operator can
copy via `y` keybind even though `r` (ratify) is not yet wired.
This unblocks audit-fallback workflows during W2 before W3 ships.
### Tests
- `src/proposals/__tests__/queue.test.tsx` — renders 0, 1, many
proposals; sort/filter behavior
- `src/proposals/__tests__/card.test.tsx` — one card per state
permutation
- `src/proposals/__tests__/detail.test.tsx` — full detail render;
`StableJsonViewer` byte-stability
- `src/proposals/__tests__/partition.test.tsx` — math card refuses
cognition payload; vice versa
- `src/proposals/__tests__/keyboard.test.tsx` — j/k/Enter/Esc/`/`
behavior per ADR-0173 contract
- `src/proposals/__tests__/replay_state.test.tsx` — three distinct
states render distinctly (passed / failed / not-yet-replayed)
- `src/json/__tests__/stable_json.test.tsx` — round-trip
byte-equality on a sample `MathProposalDetail` payload
- **e2e** — `e2e/proposal_queue.spec.ts` — Playwright; start
backend with fixture proposals, open UI, verify the queue
populates and the detail panel renders for one math + one
cognition proposal
### Deliverables
- Components 18 above
- Routes + store wired
- Tests + e2e green
- Workbench backend untouched (read-only against existing routes)
### Forbidden
- Adding ratification UI (W3)
- Adding trace / replay / eval surfaces (W4)
- Backend route changes
- Mutating any existing component from W1
- Mixing cognition and math proposals in one queue/component
---
## Brief W3 — Ratification Corridor (the throughput multiplier)
**Operator profile:** Opus (load-bearing wrong=0 surface; ratification handler discipline; mirror CC-2 rigor)
**Branch:** `feat/workbench-ui-w3-ratification-corridor`
**Base:** `origin/main` (post-W2 merge, **and** post PR #393 / CC-2 merge for CompositionClaim handler availability)
**Style:** Frontend + thin backend dispatch test. Load-bearing.
### Dispatch
```bash
git fetch origin main && \
git worktree add /tmp/wt-wb-ui-w3 origin/main && \
cd /tmp/wt-wb-ui-w3 && \
git checkout -b feat/workbench-ui-w3-ratification-corridor
```
### Reads required FIRST
- `docs/decisions/ADR-0173-workbench-ratification-trust-boundary.md` (entire ADR)
- `docs/decisions/ADR-0167-audit-as-teaching-evidence.md` §"Partition guarantees"
- `docs/decisions/ADR-0168-frameclaim-ratification.md` §"Acceptance gates"
- `docs/decisions/ADR-0169-compositionclaim-ratification.md` §"Acceptance gates"
- `teaching/math_lexical_ratification.py``apply_lexical_claim()`, exception types
- `teaching/math_frame_ratification.py``apply_frame_claim()`, exception types
- `teaching/math_composition_ratification.py``apply_composition_claim()`, exception types
- `workbench/api.py``POST /math-proposals/{id}/ratify` route + `MathRatifyResult` shape
- `tests/test_math_frame_ratification.py::test_case_0050_hazard_pin` (the template)
- `tests/test_math_composition_ratification.py::test_case_0050_hazard_pin` (same template)
### Outcome
One component, one keyboard surface, one e2e per handler.
1. **`src/proposals/RatificationCommandPanel.tsx`** — bottom-sheet
panel attached to the focused proposal in `ProposalDetailPanel`.
- Visible only when a proposal is focused AND in `pending` state
AND `replay_equivalent == true` AND `handler_name` is in the
admitted set (Lexical / Frame / Composition).
- Disabled with a named status message when any precondition
fails (per ADR-0173 §"Keyboard contract" — no silent failures).
- Renders: handler name, claim signature digest, target JSONL
artifact path, "ratify (r)" / "reject with note (x)" / "defer
(d)" / "copy CLI (y)" affordances.
2. **`src/api/ratify.ts`** — typed wrapper around
`POST /math-proposals/{id}/ratify`. On 200, returns
`MathRatifyResult`. On 4xx, surfaces the structured error
verbatim into the status footer (do not translate exception
messages — operator audit fallback depends on raw text).
3. **`src/state/ratify.ts`** — Zustand slice for post-ratify
auto-advance (focus next pending after a successful ratify).
4. **Operator-action telemetry wire** in `workbench/api.py` — emit
`operator_ratify` / `operator_reject` / `operator_defer` events
to the existing `chat/telemetry.py` sink per ADR-0173 Q3. Event
schema:
```json
{"event": "operator_ratify", "proposal_id": "...", "handler": "...", "outcome": "applied|rejected_precondition", "ratifier_kind": "workbench", ...}
```
5. **Ratification record extension**`ratifier_kind: "workbench"`
is written to the JSONL artifact's provenance record. **Audit
forensic field only**; not a permission gate. Verified by test
that strips `ratifier_kind` and confirms replay still works
(per ADR-0161 §5 "Replay invariants").
### Keyboard wires (per ADR-0173)
- `r` → ratify focused proposal (only when preconditions hold;
otherwise no-op + status footer message)
- `x` → reject with note (opens a single-line text input; `Enter`
commits, `Esc` cancels)
- `d` → defer (proposal stays `pending`; operator-defer telemetry emits)
- `y` → copy `suggested_ratify_cli` to clipboard
- Post-ratify: focus auto-advances to next `pending` +
`replay_equivalent==true` proposal
### Hard requirements
These mirror ADR-0173 §"Acceptance gates for W1..W4 → W3" verbatim.
- **Same Python entrypoint.** Every `r` keypress executes the same
`apply_*_claim()` function as the corresponding CLI invocation.
Verified by parametrized test (one per handler) that:
1. constructs a fixture proposal
2. ratifies via the UI (HTTP POST through the API)
3. ratifies an equivalent fixture via the CLI
4. diffs the resulting JSONL artifact rows
5. asserts byte-equal **except for `ratifier_kind`**
- **Case 0050 hazard pin holds end-to-end.** Ratifying any admitted
handler via the UI must not cause case 0050 to admit. Verified
by e2e: ratify a synthetic CompositionClaim under each safe
category, then run `core eval gsm8k_math` (via the existing
`/evals/run` route) and assert case 0050 stays refused.
- **Exception surface verbatim.** `AlreadyRatified`,
`WrongClaimSubType`, `WrongCompositionCategory`,
`EvidenceTampered`, replay-regression errors — all surface
verbatim into the status footer. No translation, no smoothing.
- **`r` is a no-op** on any proposal not in `pending` AND
`replay_equivalent == true` AND admitted handler. The no-op
emits a status-footer message naming the failing precondition.
- **Partition.** Math ratifications cannot mutate cognition
artifacts and vice versa. Verified by test that attempts a
cross-domain ratification and asserts the API returns 400 with a
partition-violation reason.
- **No auto-ratify.** Replay-passed proposals remain `pending` until
an explicit `r` keypress on a focused, expanded detail. Verified
by test: poll the queue with replay-passed fixtures; assert no
state transitions until a keypress arrives.
- **No batch ratification.** Each `r` press ratifies exactly one
focused proposal. Multi-select UI is not introduced.
- **No CORS relaxation.** Backend continues to bind 127.0.0.1
only; cross-origin requests refused. Verified by test.
- **Operator-action telemetry redact-by-default.** Events do not
include surface text or token content. Verified by test:
emit, parse, assert no proposal evidence text leaks.
### Tests
Mirror `tests/test_math_composition_ratification.py` rigor.
- `tests/test_workbench_ratify_lexical.py` — end-to-end UI ratify
→ JSONL artifact byte-diff vs CLI ratify
- `tests/test_workbench_ratify_frame.py` — same shape for FrameClaim
- `tests/test_workbench_ratify_composition.py` — same shape for CompositionClaim
- `tests/test_workbench_ratify_case_0050_hazard_pin.py` — mandatory
- `tests/test_workbench_ratify_partition.py` — math/cognition isolation
- `tests/test_workbench_ratify_no_auto.py` — auto-ratify forbidden
- `tests/test_workbench_ratify_exception_surface.py` — exceptions
surface verbatim
- `tests/test_workbench_operator_telemetry.py` — events emit; no
content leak
- `tests/test_workbench_ratify_idempotent.py` — duplicate ratify
via UI returns `AlreadyRatified`
- `src/proposals/__tests__/ratification_panel.test.tsx` — frontend
unit tests for visibility, keyboard, no-op behavior
- `e2e/ratification_corridor.spec.ts` — Playwright end-to-end for
all three handlers
### Deliverables
- `RatificationCommandPanel.tsx`, `src/api/ratify.ts`,
`src/state/ratify.ts`
- Backend: operator-action telemetry wire in `workbench/api.py`
- `workbench/readers.py` updates if needed for `ratifier_kind`
surfacing (read current state first; minimal diff)
- All tests above, green
- `core test --suite teaching -q` green
- `core test --suite runtime -q` green
- `core eval gsm8k_math` green
- Case 0050 remains refused
### Forbidden
- Admitting a handler not in {Lexical, Frame, Composition}
- Adding auto-ratify of any flavor
- Adding batch / multi-select ratification
- Bypassing handler preconditions in any code path
- Adding remote operator auth, login, or token-bearer
- Relaxing CORS on the backend
- Adding `Cmd`/`Ctrl` keyboard chords
- Mutating `engine_state/*` outside the existing checkpoint path
- Treating `ratifier_kind` as a permission gate
- Adding a parallel telemetry log
---
## Brief W4a — Replay Surfaces
**Operator profile:** Sonnet (tight-scope frontend over existing routes)
**Branch:** `feat/workbench-ui-w4a-replay-surfaces`
**Base:** `origin/main` (post-W1; parallel-safe with W2/W3/W4b)
**Style:** Frontend only. Read-only.
### Dispatch
```bash
git fetch origin main && \
git worktree add /tmp/wt-wb-ui-w4a origin/main && \
cd /tmp/wt-wb-ui-w4a && \
git checkout -b feat/workbench-ui-w4a-replay-surfaces
```
### Reads required FIRST
- `workbench/api.py``/trace/...`, `/replay/...` routes
- `workbench/schemas.py``ReplayComparison`, `ReplayDivergence`,
`ReplayDivergenceSeverity`, `ReplayStatus`
- `docs/decisions/ADR-0160-core-workbench-v1.md` §"Trace Drawer",
§"Replay Theater"
- `docs/decisions/ADR-0162-workbench-design-system.md` §"Component map"
### Outcome
Four components + one route.
1. **`src/trace/TraceDrawer.tsx`** — slide-in drawer showing the
trace for a focused turn / proposal / ratification. Renders
trace hash + step list (not graph). Opens via `g t` keybind.
2. **`src/replay/ReplayTheater.tsx`** — full-width panel hosting
replay comparison + diff.
3. **`src/replay/ReplayComparisonPanel.tsx`** — side-by-side
original-vs-replay hash + equivalent boolean + divergence count.
4. **`src/replay/ReplayDiffViewer.tsx`** — divergence list with
severity coloring (info / warning / failure per ADR-0162); uses
`StableJsonDiffViewer` from new component below.
5. **`src/json/StableJsonDiffViewer.tsx`** — canonical JSON diff;
structural-aware (key/value/array-index), not line-based.
6. **`src/routes/trace.tsx`** — `/trace/:id` route.
7. **`src/routes/replay.tsx`** — `/replay/:id` route.
### Hard requirements
- **Read-only.** No replay-mutation UI. No "re-run replay" button
in v1 (the backend route is read; replay is run server-side by
separate mechanisms).
- **Diff is structural.** `StableJsonDiffViewer` operates on parsed
JSON, not text lines. Byte-equal payloads produce zero
divergences regardless of formatting.
- **Severity rendering.** info / warning / failure render
distinctly without color-only encoding (icon + text + color).
### Tests
- `src/trace/__tests__/trace_drawer.test.tsx`
- `src/replay/__tests__/replay_theater.test.tsx`
- `src/replay/__tests__/replay_diff.test.tsx`
- `src/json/__tests__/stable_json_diff.test.tsx` — round-trip;
byte-equal payloads → zero divergences
- `e2e/replay_theater.spec.ts`
### Forbidden
- Adding replay-mutation routes or buttons
- Line-based diff (must be structural)
- Color-only severity encoding
---
## Brief W4b — Eval Surfaces
**Operator profile:** Sonnet (tight-scope frontend over existing routes)
**Branch:** `feat/workbench-ui-w4b-eval-surfaces`
**Base:** `origin/main` (post-W1; parallel-safe with W2/W3/W4a)
**Style:** Frontend only. Drives existing read-only eval routes.
### Dispatch
```bash
git fetch origin main && \
git worktree add /tmp/wt-wb-ui-w4b origin/main && \
cd /tmp/wt-wb-ui-w4b && \
git checkout -b feat/workbench-ui-w4b-eval-surfaces
```
### Reads required FIRST
- `workbench/api.py``/evals`, `/evals/{id}`, `POST /evals/run` routes
- `workbench/schemas.py``EvalLaneSummary`, `EvalRunResult`
- `docs/decisions/ADR-0160-core-workbench-v1.md` §"Eval Center"
- `docs/decisions/ADR-0166-measurement-capability-sequencing.md`
(do **not** introduce new eval lanes)
### Outcome
Three components + one route.
1. **`src/evals/EvalCenter.tsx`** — top-level eval page.
2. **`src/evals/EvalLaneList.tsx`** — list of available lanes from
`GET /evals`. Each row: lane name, latest run status, version,
read-only badge, description. Clicking a row opens detail.
3. **`src/evals/EvalFailureViewer.tsx`** — for a failed eval run,
list failing cases with their `MathReaderRefusalEvidence` (or
cognition-equivalent) + `ArtifactLink` to the run JSON.
4. **`src/routes/evals.tsx`** — `/evals` route.
### Hard requirements
- **No new eval lanes.** ADR-0166 holds. The UI runs existing
lanes only.
- **`POST /evals/run` is the only mutation surface.** It is
read-only in effect (runs an eval; does not mutate corpus/packs).
Other than that, every interaction is a GET.
- **Failure surfacing tied to evidence.** A failing case must link
to the audit row that produced it (via `ArtifactLink`).
- **No vanity dashboard splash.** ADR-0162 no-go list:
no "total turns" / "active sessions" / pulsing-orb metrics on
the eval landing page.
### Tests
- `src/evals/__tests__/eval_center.test.tsx`
- `src/evals/__tests__/lane_list.test.tsx`
- `src/evals/__tests__/failure_viewer.test.tsx`
- `e2e/eval_center.spec.ts` — list lanes, open one, run via the
read-only route, view results
### Forbidden
- Introducing new eval lanes (ADR-0166)
- Adding splash metrics / "AI cognition theater" panels
- Mutating corpus/packs through the eval surface
---
## Anti-regression invariants (all four briefs)
- `wrong == 0` on `core eval gsm8k_math` — preserved
- ADR-0166 — no new eval lanes
- ADR-0057 replay-equivalence — inherited
- ADR-0167 partition — math/cognition isolation in UI as on server
- Pinned-lane SHAs — should not require updates
- `engine_state/*` — never committed
- Case 0050 hazard — pinned in W3's test suite (mandatory)
- Backend bound to 127.0.0.1 — never relaxed
- No remote runtime dependencies — fonts, icons, telemetry all local
---
## Memory pointers
- [[feedback-batch-during-research]] — implementation mode; one PR per wave
- [[feedback-no-self-dispatch-of-subagents]] — Shay dispatches operators
- [[feedback-production-line-pattern]] — this brief pack pattern
- [[feedback-parallel-dispatch-pattern]] — operator profile mapping
- [[feedback-parallel-agent-worktrees]] — fresh worktree per brief
- [[feedback-wrong-zero-hazard-case-0050]] — W3 mandatory pin
- [[feedback-cleanup-as-you-find]] — apply if W1 finds dead workbench backend stubs
- [[user-circumstances]] — operators on library wifi; local-first matters
- [[milestone-adr-0172-tier1-2026-05-27]] — context for throughput-vs-capability framing
- [[adr-0167-audit-as-evidence-wave]] — parent corridor
---
## What ships when all PRs land
- **W1.** Operator can run `core workbench serve` and open a
bootstrapped UI shell with command palette, keyboard help, and
state badges. Nothing actionable yet.
- **W2.** Operator can read every pending math + cognition
proposal, inspect evidence/replay/reasoning trace, and copy the
suggested ratify CLI for shell fallback.
- **W3.** Operator can ratify a Lexical / Frame / Composition
proposal via `r` keypress in **under 10 seconds per row** for a
row whose evidence they understand. Case 0050 remains refused.
The compounding loop runs at human-realistic throughput.
- **W4a.** Operator can inspect any trace or replay artifact
inline; replay divergences are structurally diffed.
- **W4b.** Operator can run and inspect eval lanes inline; failing
cases link back to their audit-row evidence.
**Together:** the math teaching corridor becomes operable by one
operator without context-switching to the shell. The five operator
pain points from the scoping brief are individually retired:
| Pain point | Retired by |
|---|---|
| HITL burden high | W3 keyboard corridor |
| No keyboard corridor for ratification | W3 `r`/`x`/`d`/`y` bindings |
| Evidence is text-block JSON | W2 `StableJsonViewer` + structured detail |
| Reasoning traces are flat | W2 step-list rendering of `MathReasoningStep` |
| Replay equivalence is opaque hex | W2 three-state replay visualization; W4a `ReplayComparisonPanel` |
| No "stuck queue" diagnosis | W2 filter chips + replay-state visualization |
---
## Copy-paste dispatch lines (when ready)
After ADR-0173 (PR #394) lands, dispatch in order:
```text
# W1 — Codex (mechanical scaffold)
Read docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md §"Brief W1".
git fetch origin main && git worktree add /tmp/wt-wb-ui-w1 origin/main && cd /tmp/wt-wb-ui-w1 && git checkout -b feat/workbench-ui-w1-scaffold
# (wait for W1 merge)
# W2 — Sonnet (read surfaces)
Read docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md §"Brief W2".
git fetch origin main && git worktree add /tmp/wt-wb-ui-w2 origin/main && cd /tmp/wt-wb-ui-w2 && git checkout -b feat/workbench-ui-w2-proposal-queue
# W4a — Sonnet (parallel with W2)
Read docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md §"Brief W4a".
git fetch origin main && git worktree add /tmp/wt-wb-ui-w4a origin/main && cd /tmp/wt-wb-ui-w4a && git checkout -b feat/workbench-ui-w4a-replay-surfaces
# W4b — Sonnet (parallel with W2/W4a)
Read docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md §"Brief W4b".
git fetch origin main && git worktree add /tmp/wt-wb-ui-w4b origin/main && cd /tmp/wt-wb-ui-w4b && git checkout -b feat/workbench-ui-w4b-eval-surfaces
# (wait for W2 merge AND #393 CompositionClaim merge)
# W3 — Opus (ratification corridor; load-bearing wrong=0)
Read docs/handoff/WORKBENCH-UI-WAVE-BRIEF-PACK.md §"Brief W3".
git fetch origin main && git worktree add /tmp/wt-wb-ui-w3 origin/main && cd /tmp/wt-wb-ui-w3 && git checkout -b feat/workbench-ui-w3-ratification-corridor
```
---
## Status zsh trap (operator convenience)
For local dispatch monitoring (mirror the pattern used during the
ADR-0172 Tier 1 wave):
```zsh
status() {
for pr in 394 393; do
gh pr view "$pr" --json number,title,state,mergeStateStatus,statusCheckRollup \
| python3 -c "
import json,sys
d=json.load(sys.stdin)
checks=d.get('statusCheckRollup') or []
from collections import Counter
states=Counter(c.get('conclusion') or c.get('status') for c in checks)
print(f\"#{d['number']} {d['state']:<6} {d['mergeStateStatus']:<16} {dict(states)} — {d['title'][:60]}\")
"
done
}
```
Run `status` to get a single-line summary per gating PR.