core/docs/handoff/WORKBENCH-UI-WAVE-SCOPING.md
Shay 76051d6ac5
docs(workbench-ui): scoping brief for UI wave (W0..W4) (#392)
Names the wave shape before any implementation. Audits what doctrine
has already settled (ADR-0160 stack pins + read-only trust boundary;
ADR-0162 design system + 15-component must-ship list + no-go list),
catalogs the current state (backend complete, frontend zero), surfaces
the five operator pain points sourced from the 2026-05-27 demo and the
CompositionClaim brief pack, and proposes a five-wave sequence:

  W0 (docs)  — Trust-boundary ratchet ADR (ADR-0173 working title)
               admitting operator ratification through the workbench,
               scoped to existing Tier 1.5 handlers (Lexical / Frame /
               Composition) and pinning the keyboard contract.
  W1 (base)  — workbench-ui/ scaffold per ADR-0162 Branch 1.
  W2 (read)  — ProposalQueue + ProposalCard + ProposalDetailPanel.
  W3 (act)   — RatificationCommandPanel + handler dispatch (the
               throughput multiplier).
  W4 (verify)— TraceDrawer + ReplayTheater + EvalCenter.

Guardrails enumerated: CLAUDE.md docs discipline, ADR-0162 no-go list,
determinism, trust boundary, wrong==0, case 0050 hazard pin,
keyboard-first, accessibility, local-first, no engine_state writes
outside the checkpoint path.

Open questions (in-process vs out-of-process ratification, single- vs
multi-operator, telemetry path, font/icon bundling, build artifact
location) listed for W0 resolution. No code in this brief.
2026-05-27 15:09:58 -07:00

18 KiB
Raw Blame History

Workbench UI — Wave Scoping Brief

Status: Scoping (no code in this brief; names the wave shape and the prerequisite ADRs/decisions before any implementation moves) Date: 2026-05-27 Author: Shay Parent doctrine: ADR-0160, ADR-0162 Related: ADR-0161 (proposal-review trust boundary), ADR-0167 (teaching-corridor), ADR-0168 / 0168.1 (FrameClaim handler), ADR-0169 / 0169.1 (CompositionClaim doctrine), ADR-0172 (math contemplation corridor)


Goal

Stand up the workbench frontend so the math teaching corridor is operable by a single operator at human-realistic throughput. The current bottleneck is not capability — Tier 1 + Tier 1.5 shipped FrameClaim and (pending) CompositionClaim handlers — it is operator ergonomics. Every audit refusal we cannot ratify quickly is a learning event the engine never gets.

This brief does not commit code. It names:

  1. what doctrine has already settled,
  2. what the current operator pain points are,
  3. what needs a fresh decision (ADR or scoping note) before any implementation,
  4. the proposed wave shape (sequence + dependency DAG),
  5. the guardrails the wave must not violate,
  6. the acceptance criteria for the wave overall.

What doctrine has already settled

The frontend has substantial pre-existing doctrine. The scoping brief must respect, not relitigate, the following.

ADR-0160 — Workbench v1

  • Frontend stack pinned: React + Vite + TypeScript, TanStack Query, Zustand/Jotai, Tailwind + shadcn primitives, Monaco where structured JSON inspection is necessary.
  • No Electron. No heavy design system. No plugin marketplace.
  • Local-first, deterministic backend. Backend already exists at workbench/ (stdlib HTTP server, routes for chat/proposals/ math-proposals/ratify/evals/replay/trace).
  • Trust boundary v1 = read-only by default. Accepting proposals, rejecting proposals as durable state, and pack/corpus mutation were explicitly forbidden in v1.

ADR-0162 — Design System

  • Token namespace, typography, color semantics all bound to ratified enums (EpistemicState, NormativeClearance, ReviewState, grounding source). No invented color states.
  • Keyboard contract. Workbench is keyboard-first.
  • Layout shell committed.
  • Component map v1 must-ship (15 components): WorkbenchShell, TopBar, LeftNav, StatusFooter, CommandPalette, ChatTurnList, ChatTurnCard, ResponseEvidenceStrip, TraceDrawer, four state badges, ReplayTheater, ReplayComparisonPanel, ReplayDiffViewer, ProposalQueue, ProposalCard, ProposalDetailPanel, RatificationCommandPanel, EvalCenter, EvalLaneList, EvalFailureViewer, StableJsonViewer, StableJsonDiffViewer, ArtifactLink.
  • No-go list: no chat-clone bubbles, no "AI thinking" affordances, no glassmorphism, no purple-neon-cyberpunk, no graph-builder canvas, no dashboard splash metrics, no color-only state encoding.
  • Implementation plan — Branch 1 (pre-W-027) names a single PR scope: workbench-ui/ directory at repo root with pinned deps and the tokens CSS. No app routes yet.

These two ADRs already constitute the design phase. The wave is implementation, not re-design.


Current state — backend ready, frontend zero

  • workbench/api.py exposes the routes ADR-0160 anticipates: /health, /runtime/status, /artifacts, /proposals, /math-proposals, /math-proposals/{id}/ratify, /evals, /evals/run, /chat/turn, /trace/..., /replay/....
  • workbench/server.py is a stdlib ThreadingHTTPServer. No remote network dependency for cognition.
  • workbench/schemas.py defines ProposalSummary, ProposalDetail, MathProposalSummary, MathProposalDetail, MathRatifyResult, EvalLaneSummary, EvalRunResult, ReplayComparison, ReplayDivergence — the JSON contract the UI will consume.
  • MathProposalDetail already carries suggested_ratify_cli and handler_name, which means the dispatch corridor is already end-to-end on the server side.

What does not exist yet:

  • workbench-ui/ directory (ADR-0162 Branch 1 deliverable — not landed).
  • Any HTML/CSS/JS asset.
  • Any client-side state management.
  • Any keyboard binding implementation.

So the wave starts from zero on the client and from a complete contract on the server.


Operator pain points (evidence)

Sourced from the 2026-05-27 end-to-end demo and the CompositionClaim brief pack:

  1. HITL burden is high. Architecturally clean rejection of handler-mismatched proposals still costs the operator a full round-trip (read row → reason about category → reject → move on). At 47 audit rows × multiple proposal categories, this is the gating cost on the compounding loop.
  2. No keyboard corridor for ratification. Operator currently constructs the suggested CLI by reading MathProposalDetail.suggested_ratify_cli from JSON output and pasting into a shell — a four-step context-switch per row.
  3. Evidence is text-block JSON. MathReaderRefusalEvidence ships the audit-row context but reading it from raw JSON requires the operator to mentally render the source sentence, the bound slots, and the missing-operator pair.
  4. Reasoning traces are flat. MathProposalDetail.reasoning_trace_steps carries the step-by-step decomposer hypothesis, but with no visualization the operator cannot quickly see where the chain would diverge from a correct admission.
  5. Replay equivalence is a string field. replay_equivalence_hash is a meaningful invariant but currently surfaces as opaque hex — the operator has no eyes-on confirmation that a proposal genuinely replays.
  6. No "stuck queue" diagnosis. The queue of pending proposals does not currently visualize why an item is pending (replay passed but operator hasn't reviewed? replay didn't run? evidence incomplete?). All three states render identically.

These are not capability problems. They are throughput problems with a deterministic fix.


What needs a fresh decision

Two doctrine updates are prerequisites. Both should land before any frontend code so the wave doesn't burn cycles on a moving target.

D1 — Trust-boundary ratchet (new ADR)

Problem. ADR-0160 v1 explicitly forbids "accepting proposals" in the UI. That was correct when no ratification handlers existed server-side. Tier 1.5 changed the world:

  • teaching/math_lexical_ratification.py::apply_lexical_claim (W2-D, merged)
  • teaching/math_frame_ratification.py::apply_frame_claim (PR #389, merged)
  • teaching/math_composition_ratification.py::apply_composition_claim (PR-β, pending)

Each handler is replay-gated, hazard-pinned (case 0050), partition- tested, idempotent, and append-only on a reviewed JSONL artifact. The original ADR-0160 prohibition was a safety stance against mutation paths that did not yet have proper gates. Those gates now exist.

Decision needed. Either:

  • (a) A new ADR (working title: ADR-0173 — Workbench Ratification Trust Boundary) that explicitly admits operator ratification through the workbench, scoped to the three existing handlers and gated by their existing replay/hazard/partition pins; or
  • (b) A scoping note that re-affirms ADR-0160 v1 read-only and defers ratification UI to a v2.

Recommendation: (a). Otherwise the wave reduces to "render proposals you cannot act on," which does not retire the operator- burden pain point at all.

Acceptance: ADR-0173 must enumerate exactly which handlers are admitted, which are not, what mutation paths are still forbidden (corpus, packs, engine_state outside checkpoint path, frame/lexical registry edits outside the handler), and how the UI surfaces the proposal-review trust boundary visually (e.g. RatificationCommandPanel must distinguish "replay passed, you may ratify" from "replay didn't run, ratify is disabled").

D2 — Component-set ratification (sub-decision in D1 or its own scoping note)

ADR-0162's "v1 must-ship" component list pre-dates the ratification handlers. Specifically, RatificationCommandPanel was named as must-ship but its semantics were not pinned because no handler existed. With handlers live, we need to fix:

  • which proposal kinds the panel can drive (LexicalClaim, FrameClaim, CompositionClaim — and the deferred SUB_TYPE_FOR_OPERATOR entries remain disabled with a clear "no handler yet" affordance);
  • whether the panel executes the suggested CLI in-process via the existing apply_*_claim() handlers, or merely renders the CLI for the operator to copy/run (the brief recommends in-process execution under D1's trust boundary, because copy/paste-to-shell is itself an operator-burden axis);
  • the keyboard binding contract (e.g. j/k to navigate proposals, r to ratify the focused proposal, x to reject, ? for help — must be pinned in D1 or a sibling note before implementation so every panel shares one mental model).

Recommendation: fold D2 into D1 as a single ADR.


Proposed wave shape

Five waves, ordered. Each wave is one PR or one tight stack, not a sprawling branch.

W0 (docs):    Trust-boundary ratchet ADR + component-set update
                │
                ▼
W1 (UI base): workbench-ui/ scaffold per ADR-0162 Branch 1
              (Vite, TS, Tailwind, shadcn primitives, tokens CSS,
               shell, command palette, status footer — no routes yet)
                │
                ▼
W2 (read):    ProposalQueue + ProposalCard + ProposalDetailPanel
              (math + cognition proposals, read-only;
               StableJsonViewer for evidence/replay payloads;
               EpistemicState / ReviewState / TraceHash badges)
                │
                ▼
W3 (act):     RatificationCommandPanel + handler dispatch
              (keyboard corridor: j/k navigate, r ratify, x reject;
               in-process apply_*_claim() execution via API;
               replay-state visualization;
               post-ratify auto-advance to next pending)
                │
                ▼
W4 (verify):  TraceDrawer + ReplayTheater + ReplayComparisonPanel +
              ReplayDiffViewer + EvalCenter + EvalLaneList +
              EvalFailureViewer
              (the audit/replay surfaces that close the loop;
               StableJsonDiffViewer for replay divergences)

Why this order

  • W0 is unblock-everything. Code written against a stale trust boundary either gets thrown out (if doctrine tightens) or builds in a constraint that the rest of the wave can't honor (if doctrine loosens unevenly).
  • W1 is the substrate. ADR-0162 already named it; ship it once, every later wave assumes it.
  • W2 is the minimum legible workbench. If we ship W0-W2 only, the operator can already inspect every pending proposal at human speed — a real throughput improvement even without ratification UI.
  • W3 is the throughput multiplier. The keyboard ratification corridor is the entire reason this wave exists. It must land before W4 because trace/replay surfaces are audit tools, not throughput tools — and we're throughput-bound, not audit-bound.
  • W4 closes the loop. Once an operator has ratified a proposal, TraceDrawer + ReplayTheater + EvalCenter let them verify the effect immediately rather than dropping back to the CLI.

What ships in each wave

Wave One-line description Approximate PR count Approximate test surface
W0 Trust-boundary ratchet ADR + (sub-decision) component-set update 1 docs PR 0 (docs only)
W1 workbench-ui/ scaffold + tokens + shell + palette + status footer 1 frontend PR smoke (build green, tokens snapshot, shell renders)
W2 ProposalQueue + ProposalCard + ProposalDetailPanel + StableJsonViewer + badges 1 PR (math) + 1 PR (cognition) OR 1 bundled component tests + 1 e2e against backend fixtures
W3 RatificationCommandPanel + keyboard corridor + handler dispatch 1 PR per handler OR 1 bundled PR (LexicalClaim + FrameClaim + CompositionClaim) one e2e per handler ratifying a fixture proposal → checking the JSONL artifact change
W4 TraceDrawer + ReplayTheater + ReplayDiff + EvalCenter 2 PRs (replay surfaces + eval surfaces) one e2e per surface

This is the shape, not the exact PR boundaries — operators running W2/W3/W4 should bundle per the project's existing "batch-during-research" pattern when CI cycle time argues for it.


Guardrails (must hold across every wave)

Each is non-negotiable. A PR that violates one is reworked, not waived.

  1. CLAUDE.md docs discipline. No standalone HTML artifacts as substrate (CSS regen ordering / SVG element ordering break determinism). The workbench is allowed to render HTML because it is a read-only view of deterministic JSONL artifacts, not a substitute for them.
  2. ADR-0162 no-go list. Reread every PR. No chat-clone bubbles, no "AI thinking" theater, no glassmorphism, no dashboard splash pages, no graph-builder canvas, no auto-dismiss toast for audit events, no color-only state encoding.
  3. Determinism preserved. Every UI state derived from a deterministic backend payload. No client-side randomness that affects rendered content. (Loading skeletons are fine; "shuffle proposal queue" is not.)
  4. Trust boundary respected. W2 ships read-only. W3 ships ratification only for handlers admitted by D1's ADR. Pack / corpus / engine_state mutation paths remain forbidden through the UI.
  5. wrong == 0 invariant unchanged. No UI wave touches the server-side handlers' hazard pins. Every UI PR runs the affected server suites green (core test --suite teaching -q, core test --suite runtime -q).
  6. Case 0050 protected. UI must not introduce a fast-path that bypasses the handler's pre-conditions. Ratifying through the UI must call the same apply_*_claim() function as the CLI, carrying the same evidence, raising the same exceptions.
  7. Keyboard-first. No mouse-only interactions for any v1 must- ship component. (Mouse is supported; mouse-only is not.)
  8. Accessibility. No icon-only buttons without accessible labels; no color-only state encoding (badges carry text + shape, not color alone). Pinned in ADR-0162 §"The no-go list" — restated here because it's the easiest guardrail to drift on.
  9. No remote network dependencies for cognition. Workbench backend stays local-first. UI may fetch from http://127.0.0.1:<port> only. No CDN-loaded fonts/icons in v1; bundle locally.
  10. No engine_state writes outside the existing checkpoint path (ADR-0146/0150).

Anti-regression invariants

Inherited from the broader teaching corridor — listed so every wave PR can self-check.

  • wrong == 0 on core eval gsm8k_math preserved.
  • ADR-0166 — no new eval lanes introduced by UI work.
  • ADR-0057 replay-equivalence — inherited unchanged.
  • ADR-0167 partition — cognition and math proposal surfaces remain partitioned in the UI as on the server.
  • Case 0050 hazard pin holds across every W3 ratification path.
  • engine_state/* never committed.
  • Pinned-lane SHAs should not require updates due to UI work; if one moves, the UI PR is doing something it shouldn't.

Open questions (resolve in W0)

  1. In-process ratification vs out-of-process dispatch. D1 recommends in-process via the existing API, but if there's a reason to keep the UI strictly read-only and dispatch ratification through the CLI (e.g. an audit-trail concern), W0 must name it.
  2. Single-operator only vs multi-operator-aware. The current handler architecture is single-operator (no proposal locking, no review-state contention). W0 should affirm or deny that v1 stays single-operator.
  3. Telemetry path. The backend chat/telemetry.py JSONL sink exists. W0 should pin whether the UI emits its own operator- action events (e.g. "operator-ratify", "operator-reject", "operator-defer") into the same sink, or into a separate workbench-events log.
  4. Font / icon bundling. ADR-0162 pins typography but doesn't commit on whether to bundle Inter / a system stack. W0 should resolve so W1 doesn't introduce a runtime fetch.
  5. Build artifact location. Does workbench-ui/dist/ get committed, gitignored, or served from a CI step? W0 should commit. (Recommendation: gitignored; built locally; CI verifies the build is green but does not commit the artifact.)

Acceptance criteria (whole wave)

The wave is "done" when:

  • A new operator can run core workbench serve (existing CLI) and open the UI in a local browser.
  • Pending audit refusals from audit_brief_11.json appear as a navigable queue.
  • An operator can ratify a LexicalClaim, FrameClaim, or CompositionClaim proposal via keyboard in under 10 seconds per row for a row whose evidence they understand.
  • The ratification produces the same JSONL artifact change as invoking the corresponding apply_*_claim() from the CLI — byte-equal where possible, semantic-equal otherwise.
  • core test --suite teaching -q, core test --suite runtime -q, and core eval gsm8k_math remain green throughout.
  • Case 0050 remains refused after end-to-end UI ratification of any pending proposal.
  • The five operator pain points listed above are individually retired (each can be pointed at one shipped component).

Memory pointers


Next step

If approved, W0 docs PR is the immediate output: ADR-0173 (working title: Workbench Ratification Trust Boundary) authored against the five operator pain points above, the existing handlers as scope, and the keyboard contract as pinned semantics. Once W0 lands, W1 (the ADR-0162 Branch 1 scaffold) can dispatch.