Commit graph

9 commits

Author SHA1 Message Date
Shay
de2d5da4f3
feat(workbench): extend TruncatedCell to trace edges and selection rails (#750)
Follow-up to #749, which deliberately scoped TruncatedCell to the
non-virtualized columnar tables and left trace edges + single-column
selection rails as-is. Wire it into those too so every truncated data
cell in the workbench has the same full-content reveal (popover +
copy + modal-for-long).

Component:
- Add `align` prop ("start" | "end"). `end` hugs the right edge and
  right-justifies the display, for right-aligned columns like the trace
  `to_stage` cell. Defaults to "start" (unchanged).

Wiring:
- Trace propagation edges: from_stage / to_stage (to_stage align=end).
- Trace pipeline stage rail: stage label + summary.
- Trace turns rail: prompt + surface excerpts.
- Runs rail: session_id. Replay rail: prompt excerpt. Packs rail:
  pack_id. CORE-Logos pack rail: pack_id. Logos alignment rail: edge
  (rich source→target display, plain-text value for copy/reveal).
- Contemplation run rail: run_id; stage-evidence ids.
- Demos rail: title; scenario trace hash.
- Proposal detail: replay-equivalence hash, evidence hashes;
  proposal chain provenance.

Two row elements that were real <button>s (trace stage rail,
contemplation run row) are converted to <div role="button"> with an
Enter/Space onKeyDown handler — the codebase's established selectable-row
pattern (ProposalTable, RowShell) — so nesting TruncatedCell's button
trigger is valid HTML rather than a button-in-button.

Intentionally left as-is (documented):
- RunsRoute TurnRefRow is an <a>/<Link> row; nesting an interactive
  trigger inside an anchor is invalid HTML. The surface excerpt is
  reachable by following the link.
- Digests everywhere keep DigestBadge (already copy + full-value title).
- Section headings (e.g. the trace stage-detail <h3>) are not data cells.

Evidence: workbench-ui `pnpm build` (tsc -b) green; full vitest suite
535 passed / 60 files, incl. a new align assertion on TruncatedCell.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:11:47 -07:00
Shay
bdb294eac3 feat(workbench): land B3.5-b/c/d/e — calibration evidence subject, B4a leeway gate, docs; runner-reproducible practice artifact
Completes the Wave M B3.5 consolidation slice (b–e), built on #728.

B3.5-b — calibration as a first-class evidence subject (`calibration_class`,
address `calibration:<class_name>`): RightInspector projection + Evidence
Chain Rail semantics (serving-discipline evidence, not runtime truth).

B3.5-c / B4a — nullable `LeewayEvidence` read model threaded through turn,
replay, cognition-proposal, and math-proposal surfaces, with a shared
absence-honest card. B4 is gated correctly: the tuple exists in typed data but
no producer populates it, so the card renders absence (verified: no non-null
producer in workbench/core/chat).

B3.5-d/e — UI-UX-GUIDE.md, b4-leeway-feasibility-gate.md, phase-a-residue-ledger.md.

Practice artifact — earn-it-for-real (runner-reproducible). The committed
`report.json` (additive earns PROPOSE @0.861, 95/5/50) is now emitted by a
deterministic runner rather than copied from the queue. `propose_runner`
gains `regenerate_practice_artifacts()`, which runs ONE sealed `resolve_pooled`
practice pass and writes BOTH report.json (the per-class ledger the calibration
reader consumes) and ratification_queue.json — two projections of one ledger,
coherent by construction and byte-reproducible. `runner.main()` delegates to
it (lazy import, no cycle), so both entry points produce the identical pair.
This closes the gap where a hand-copied report.json agreed with the queue but
no runner produced it. `resolve_pooled` is the aggressive sealed PROPOSE-regime
scorer (proposal-only/HITL, unsafe for serving, legitimate for
attempt-and-eliminate); wrong=5 is the sealed-practice learning signal, NOT the
serving wrong=0. No serving/derivation/reliability_gate source touched; the
practice lane is not in the serving-frozen SHA gate.

Validated:
- python -m pytest tests/test_workbench_{calibration,journal,replay,schemas}.py -> 31 passed
- python -m pytest tests/ -k "workbench or propose or learning_arena or practice"
  -> 190 passed (3 failing tests in test_adr_0175_phase2_practice_lane.py are
  PRE-EXISTING reds on clean origin/main: stale 4/0/46 assertions on build_report,
  which this change does not touch)
- report.json + ratification_queue.json: deterministic (run1==run2) and
  reproduced byte-identically by both `python -m ...runner` and `...propose_runner`
- pnpm build green; 144 UI tests across calibration/leeway/evidence/replay/
  doctrine-gates/routes-docs-drift all pass
2026-06-13 07:36:44 -07:00
Shay
fd0d7f1f6a finish wave r3 ui and trace integrity 2026-06-12 23:25:24 -07:00
Shay
4d8aa1dac9 feat(workbench): the Replay Moment — turn-keyed hero + retire W-026 artifact replay (Wave R3)
First R3 (Theater) piece. Reworks the stale artifact-keyed ReplayRoute into
the turn-keyed hero over the #716 sealed-replay backend, and fully retires
the dead W-026 artifact-keyed machinery on both sides (the NOT_YET_MIRRORED
comment anticipated this).

The hero makes determinism *felt*: pick a journaled turn, CORE re-executes
its prompt in a sealed fresh runtime, and the result renders as a hash
verdict (≡ equivalent / ≠ diverged) + the original/replay DigestBadges + a
leaf diff (critical weighted above informational, each with a ≠ glyph). The
'What this proves' card surfaces comparison_basis + origin_state and states
the honest limit: a divergence means nondeterminism OR origin-state
influence, never rendered on its own as a determinism failure.

Retirement (verified zero serving uses):
- Python schemas.py: removed ReplayComparison/ReplayDivergence/ReplayStatus/
  ReplayDivergenceSeverity; scripts/dump-enums.py drops the two replay enums;
  both snapshots regenerated (deterministic: dump == committed)
- TS: removed ReplayComparison/ReplayDivergence/ReplayDivergenceSeverity;
  added TurnReplayComparison/TurnReplayDivergence (+ basis/origin/severity
  unions); NOT_YET_MIRRORED now empty (every engine schema mirrored)
- badges: removed ReplayStatusBadge + ReplayDivergenceSeverityBadge +
  meta/enums + their enumCoverage cases (hero renders severity inline)
- api: fetchReplayComparison/useReplayComparison -> fetchTurnReplay/
  useTurnReplay (/replay/<turnId>)
- deleted ArtifactList, ReplayComparisonPanel, ReplayDiffViewer,
  ReplayMetadataTable, old replay.test.tsx
- App route /replay/:artifactId? -> /replay/:turnId?; conformance row
  turn-keyed (loading 'Loading turns...', empty 'No turns recorded yet.')

Tests: ReplayRoute.test.tsx (equivalence hero + honesty card, diverged
critical leaf, informational-only label, replace-mode select, j/k spine).
Full vitest 358 green; workbench Python 34 green; both snapshots deterministic.

Follow-up flagged (not in this PR to keep it focused): the artifact query
hooks (useArtifacts/useArtifactDetail/fetchArtifacts*) are now orphaned but
entangled with the still-live artifact EvidenceSubject kind — a separate
cleanup once an Artifacts route or that subject's fate is decided.

Brief: docs/handoff/wave-R3-briefs-2026-06-13.md (all four R3 pieces).
2026-06-12 22:07:34 -07:00
Shay
9344f11415 feat(workbench): design mastery pass — chain rail, typography, doctrine-as-tests (Wave R R1)
- EvidenceChainRail: the spine's seven stages rendered per subject with
  the honesty contract — lit/hollow/dim derive ONLY from carried fields
  (a trace hash lights 'replay' as RECORDED, never as verified); pure
  deriveStages() with meaningfully-fail tests (remove one field -> exactly
  its stage hollows).
- Hash display standard: DigestBadge default 12-char + copy everywhere;
  CopyableHash consolidated away (2 call sites swapped, file deleted).
- Typography: tabular-nums on MetadataTable values + eval metrics;
  text-wrap balance on Panel titles + EmptyState statements.
- Selection unification: new --color-selected-bg/-border tokens; selected
  (persistent) vs focused (transient) now visually distinct across
  Proposals/Replay/Evals lists; focus-ring no longer doubles as selection.
- EmptyState gains a deterministic monochrome inline-SVG glyph.
- Panel adopted in Evals lane list + Proposals queue.
- Doctrine-as-tests: hexScan (no palette literals outside tokens.css/
  generated tokens.ts) + schemaDrift gate (scripts/dump-schemas.py AST
  walk -> schema-snapshot.json -> every dataclass field mirrored in
  types/api.ts; explicit shrink-only NOT_YET_MIRRORED debt list).
  The drift gate caught real drift on first run: ChatTurnResult.turn_id
  existed in Python, missing from TS — now mirrored.

Verified: build green; vitest 36 files / 279 tests EXIT=0; playwright
12/12; dump-schemas.py runs clean.
2026-06-12 13:18:44 -07:00
Shay
c54a942871 feat(workbench): interaction substrate — list nav, virtualization, panel chrome, inspector resize, palette verbs (Wave R R0d)
- useListNavigation: shared j/k/arrows/Home/End/Enter keyboard spine,
  container or window scope, input guard, roving tabindex. Replaces
  ProposalsRoute's bespoke window listener (one pattern app-wide).
- VirtualizedList over @tanstack/react-virtual composing the hook;
  evidence lists render O(viewport).
- Panel: standard header/toolbar/body chrome for routes.
- Inspector resize: Shell center cell becomes SplitPane(main, inspector)
  when open; width persists via guarded storage (SplitPane id).
- Palette action verbs: Command gains kind (navigate|action); Shell
  registers Toggle inspector + Copy evidence link; EvalsRoute registers
  'Run eval lane <lane>' per read-only lane (same POST /evals/run).
- shortcutRegistry: binding sites register what they handle while
  mounted; KeyboardHelp renders FROM the registry — advertising an
  unbound shortcut is structurally impossible. j/k, /, Enter rows
  (removed in R0a as false) return as live registry entries.
- Kbd primitive (front-loaded from R1); SearchInput registers '/';
  SearchInput wired into Proposals + Replay lists with client filters.
- Preview entries for Kbd/Panel/VirtualizedList.

Verified: build green; vitest 33 files / 245 tests EXIT=0 (~5.5s);
playwright 12/12.
2026-06-12 12:48:02 -07:00
Shay
41f4435dc1 feat(workbench): evidence addresses — deep-linkable subjects + inspector URL state
URL = subject: one codec (evidenceAddress.ts) owns the address grammar for
every EvidenceSubject kind, including kinds whose routes are placeholders.
urlToSubject is a total inverse — malformed input yields null, never throws.

- Route params: /trace/:turnId?, /proposals/:proposalId?, /evals/:laneId?,
  /replay/:artifactId? (placeholders keep flat paths)
- ?inspect= carries inspector subject + open state; EvidenceUrlSync restores
  deep links and write-through syncs with replace (selection churn never
  pollutes history); malformed values are dropped from the URL
- Proposals/Evals/Replay restore selection from their param on load, write it
  on selection change (replace), and publish the selected subject to the
  evidence context (identity at once, detail when the query resolves)
- EvidenceSubject.data now optional; inspectors render an honest
  'detail not loaded' state for identity-only subjects
- Cmd+Shift+C copies origin + subjectToUrl(subject); no-op without subject;
  input-focus guard; transient inline 'Copied' on the inspector header
- Fix pre-existing unbounded re-render loop in RightInspector.test.tsx
  (render-phase setSubject); subjects now set from effects
2026-06-12 12:17:57 -07:00
Shay
513f80d5fb fix(workbench): test-runner teardown hardening + frontend CI lane (Wave R R0a)
Full 'pnpm test' previously hung indefinitely (killed at CI walls,
misdiagnosed as teardown slowness). Two root causes found and fixed:

1. RightInspector.test.tsx called setSubject() during render with a fresh
   object every pass -> infinite synchronous render loop (100% CPU spin,
   blocks the event loop so no timeout can fire). Moved into useEffect.
2. Test QueryClients used retry:false but default gcTime (5 min) -> a live
   GC timer per cached query kept workers from exiting. New shared
   createTestQueryClient() with gcTime:0 adopted across all 7 test files.

Hardening so any future leak fails loudly instead of hanging:
- vite.config.ts: testTimeout/hookTimeout 10s, teardownTimeout 5s
- useManagedTimeout hook owns previously-bare setTimeouts in DigestBadge,
  MetadataTable, RatificationCommandPanel (4 sites), CommandPalette focus

First-ever frontend CI lane (.github/workflows/workbench-ui.yml):
path-filtered to workbench-ui/** AND the workflow file itself,
pnpm install --frozen-lockfile + build + vitest, timeout-minutes 15.

Honesty fix: KeyboardHelp no longer advertises j/k, /, Enter (unbuilt
until R0d restores them registry-backed).

Route conformance test (ADR-0162 §6, executable): empty/error/loading
contracts asserted for Chat/Proposals/Evals/Replay; fixed the two gaps
it caught (ReplayRoute bare-div error branch -> ErrorState contract;
ArtifactList empty state gained a next action).

Result: 27 files / 181 tests pass, suite EXITS in 4.9s wall-clock
(was: indefinite hang).
2026-06-12 11:45:23 -07:00
Shay
19506e9f60
feat(workbench/W-031): replay theater (ADR-0160 §Phase 6, deterministic-evidence UX) (#328) 2026-05-26 20:56:31 -07:00