docs(workbench): ADR-0162 — Workbench Design System v1 (proposed) (#293)

The design substrate that W-027..W-031 will inherit.  Pins tokens,
typography, motion, semantic state mapping, the StableJsonViewer
trust-surface invariants, empty/error/loading contracts, the
keyboard-first contract, the five-region shell, the v1 component map,
and an explicit no-go list — before any frontend code exists.

Headline decisions:

- Semantic tokens only.  `--color-surface-base`, not `--color-zinc-900`.
- Inter (UI) + JetBrains Mono (hash/JSON/trace), self-hosted.
- Badges bound 1:1 to ratified Python enums:
  EpistemicState (15), NormativeClearance (4), ReviewState (4),
  grounding source (6).  No aspirational badges; adding an enum
  value to the engine without a badge fails the test.
- Motion: reveals structure, not cognition.  Allowed set is small
  and tokenised; reduced-motion collapses everything to instant.
- StableJsonViewer ships six tested invariants (deterministic order,
  lossless strings, no semantic auto-format, copy-path as JSON
  Pointer, structural diff, large-doc / oversize safety).
- Every route ships empty / error / loading states from day one,
  each following an explicit contract.  No empty-empty, no
  "Thinking…", no indefinite shimmer.
- Five-region shell; routes may collapse the right inspector but
  not the top bar, left nav, or status footer.
- v1 must-ship component map is narrower than the vision; named
  follow-ups are anticipated but not committed.

No-go list is explicit: no chat-clone styling, no animated cognition
theater, no glassmorphism, no purple gradients, no accept buttons,
no dashboard soup, no color-only encoding.

Status: proposed.  Implementation lands in Branch 1
(workbench-ui/ scaffold + design tokens + StableJsonViewer +
badges + empty/error/loading + a /preview page) before W-027
starts.

Scope discipline: docs-only.  No code, no UI, no API changes.
This commit is contained in:
Shay 2026-05-26 10:38:47 -07:00 committed by GitHub
parent ddb94d35c4
commit 8a24256ac5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,604 @@
# ADR-0162 — Workbench Design System (v1)
**Status:** Proposed
**Date:** 2026-05-26
**Author:** Shay
**Parent:** [ADR-0160 — CORE Workbench v1](./ADR-0160-core-workbench-v1.md)
**Companions:** [ADR-0161 — HITL async queue](./ADR-0161-hitl-async-queue.md), [ADR-0153 — TurnEvent.trace_hash back-stamp](./ADR-0153-turn-event-trace-hash-backstamp.md), [ADR-0159 — Contemplation-quality eval](./ADR-0159-contemplation-quality-eval.md)
---
## Context
ADR-0160 named the Workbench's product doctrine — read-only first, calm
by default, infinite depth, replay before persuasion, no accept/reject in
v1. W-026 landed the read-only API with typed schemas and closed-set
error codes.
What the cascade still lacks is a **ratified design substrate**. Without
one, every downstream branch (W-027 frontend shell, W-028 chat + trace
drawer, W-029 proposal queue, W-030 eval center, W-031 replay theater)
will hand-roll its own tokens, badge mappings, motion rules, JSON-viewer
behavior, and empty/error/loading states. Five branches' worth of
silent drift is exactly the failure mode ADR-0160 forbids ("no dashboard
soup").
This ADR pins the design substrate **before** any frontend code exists.
Tokens, typography, motion, semantic state mapping, the
`StableJsonViewer` invariants, empty/error/loading contracts, keyboard
behavior, and the explicit no-go list are doctrine, not styling. They
are the trust surface the visual layer sits on.
The principle behind every call below is the same as ADR-0161's: select
the **narrowest commitment** that still names testable invariants. This
ADR ships zero implementation; it commits the contract that W-027 will
honor and that every later branch will inherit.
---
## Product north star (one paragraph)
The Workbench should feel like Linear's calm + Raycast's speed +
GitHub PR review's auditability + Xcode Instruments' precision —
operating a deterministic cognition engine with full traceability. Not
a chat clone. Not a SaaS dashboard. Not animated AI theater. The
beauty comes from structural truth, hierarchy, responsiveness, and
precision; from operational sovereignty, not decoration.
Every meaningful surface must be able to answer six questions: *What
happened? Why was it allowed? What evidence supports it? Can it be
replayed? Did it mutate anything? Who/what has authority to ratify the
next step?* A panel that cannot answer one of those questions does not
ship in v1.
---
## Decision summary
| Concern | Decision |
|---|---|
| Token namespace | Semantic, not literal. `--color-surface-base`, not `--color-zinc-900`. Tokens are CSS custom properties + a typed TS export. |
| Theme | Dark default. Light theme deferred to v2. No system-theme auto-switch in v1. |
| Typography | Inter (UI), JetBrains Mono (hash/JSON/trace). System fallback chain pinned. |
| Color semantics | Bound to ratified `EpistemicState` (15) and `NormativeClearance` (4) enums in `core/epistemic_state.py`. No badge color without an enum binding. |
| Motion | Allowed: drawer/palette/expand/diff/route transitions. Forbidden: "thinking" animations, particle effects, glowing networks, avatar pulses, shimmer-everywhere. |
| `StableJsonViewer` | Deterministic, lossless, copyable, diffable. Trust surface, not a code block. Six tested invariants. |
| Empty / error / loading | Every route ships all three. Empty includes a next action. Error includes reproducer + mutation status. Loading uses specific labels. |
| Keyboard | Keyboard-first. `⌘K` palette, `Esc` closes overlays, `Enter` opens selection, focus-visible everywhere, no color-only encoding. |
| Layout shell | Five-region grid: TopBar / LeftNav / MainSurface / RightInspector / StatusFooter. Routes may collapse the rail but not the bar/nav/footer. |
---
## Decision detail
### 1. Token namespace and theme
Tokens live in `workbench-ui/src/design/tokens/` as CSS custom
properties (`tokens.css`) plus a typed TypeScript mirror
(`tokens.ts`). The TS export is generated from the CSS to keep one
source of truth.
**Naming rule.** Token names are **semantic**, not literal. A consumer
reads what the value *means*, not what shade it is:
```text
--color-surface-base
--color-surface-raised
--color-surface-overlay
--color-border-subtle
--color-border-strong
--color-text-primary
--color-text-secondary
--color-text-mono
--radius-sm --radius-md --radius-lg
--space-1 .. --space-12
--shadow-panel --shadow-floating
--motion-fast --motion-base --motion-slow
--motion-ease-out --motion-ease-spring
```
Forbidden: `--color-blue-500`, `--color-zinc-900`. Literal-named tokens
leak palette decisions into consumers and prevent palette evolution.
**Theme.** Dark by default. Light theme is deferred to a follow-up
ADR. No system-theme auto-switching in v1 (avoids first-render flash
and keeps screenshots stable for the audit trail).
### 2. Typography
UI: **Inter** with the system stack as fallback (`-apple-system,
"Segoe UI", Roboto, "Helvetica Neue", sans-serif`).
Mono: **JetBrains Mono** with fallback (`"SF Mono", "Menlo",
"Consolas", monospace`). Used for every surface that carries
audit-significant text: `trace_hash`, `proposal_id`, JSON, file paths,
CLI commands, digests.
Hierarchy is pinned by token, not by class proliferation:
```text
--font-size-display --font-size-h1 --font-size-h2
--font-size-body --font-size-meta --font-size-mono
--line-height-tight --line-height-base
--font-weight-regular --font-weight-medium --font-weight-semibold
```
No more than two weight steps per surface. Density without clutter.
### 3. Color semantics — bound to ratified enums
This is the load-bearing call. Every status badge color **must** be
bound to an enum value that the engine already emits. No "aspirational"
badge colors. No two badges may share a color. This keeps the badge
layer honest and audit-grade.
#### 3a. EpistemicState (15 values from `core/epistemic_state.py`)
| State | Badge label | Token | Felt meaning |
|---|---|---|---|
| `decoded` | Decoded | `--color-state-decoded` (cool blue-green) | Engine reconstructed the answer in canonical form |
| `decoded_unarticulated` | Decoded (silent) | `--color-state-decoded` (muted) | Reconstructed but no surface emitted |
| `verified` | Verified | `--color-state-verified` (cool green) | Cross-checked against reviewed evidence |
| `evidenced` | Evidenced | `--color-state-evidenced` (teal) | Direct evidence supports the surface |
| `evidenced_incomplete` | Evidenced (partial) | `--color-state-evidenced` (muted) | Evidence partial; surface qualified |
| `inferred` | Inferred | `--color-state-inferred` (indigo) | Composed from existing chains, not directly evidenced |
| `unverified_possible` | Unverified | `--color-state-unverified` (blue-gray) | Plausible but unverified |
| `unverified_novel` | Unverified (novel) | `--color-state-unverified` (warm) | New territory, no prior evidence |
| `perceived` | Perceived | `--color-state-perceived` (cyan) | Input registered, not yet grounded |
| `contradicted` | Contradicted | `--color-state-contradicted` (red) | Evidence falsifies the surface |
| `ambiguous` | Ambiguous | `--color-state-ambiguous` (violet) | Multiple coherent readings |
| `undetermined` | Undetermined | `--color-state-undetermined` (muted gray) | Insufficient evidence to choose |
| `scope_boundary` | Out of scope | `--color-state-scope` (slate) | Outside the engine's accepted domain |
| `computationally_bounded` | Bounded | `--color-state-bounded` (orange) | Computation hit a deliberate ceiling |
| `epistemic_state_needed` | Needs review | `--color-state-needed` (amber) | Engine declined to self-classify |
Clicking any badge opens a popover explaining the state, citing the
ADR that pinned it, and listing the evidence that produced the
classification.
#### 3b. NormativeClearance (4 values from `core/epistemic_state.py`)
| State | Badge label | Token |
|---|---|---|
| `cleared` | Cleared | `--color-clearance-cleared` (cool green) |
| `violated` | Violated | `--color-clearance-violated` (red) |
| `unassessable` | Unassessable | `--color-clearance-unassessable` (muted gray) |
| `suppressed` | Suppressed | `--color-clearance-suppressed` (muted red) |
#### 3c. ReviewState (4 values from `teaching/proposals.py`, per ADR-0057 / ADR-0161)
| State | Badge label | Token |
|---|---|---|
| `pending` | Pending | `--color-review-pending` (amber) |
| `accepted` | Accepted | `--color-review-accepted` (cool green) |
| `rejected` | Rejected | `--color-review-rejected` (muted red) |
| `withdrawn` | Withdrawn | `--color-review-withdrawn` (muted gray) |
#### 3d. Grounding source (6 values from cold-start-grounding lane)
| State | Badge label | Token |
|---|---|---|
| `teaching` | Teaching | `--color-grounding-teaching` (cool green) |
| `pack` | Pack | `--color-grounding-pack` (teal) |
| `vault` | Vault | `--color-grounding-vault` (indigo) |
| `partial` | Partial | `--color-grounding-partial` (muted teal) |
| `oov` | Out of vocab | `--color-grounding-oov` (orange) |
| `none` | Not grounded | `--color-grounding-none` (slate) |
**Color-only encoding is forbidden.** Every badge carries its label;
the color is reinforcement. Accessibility and screenshot-audit fidelity
both depend on this.
### 4. Motion
The motion rule is one line: **motion reveals structure, not cognition.**
Allowed motion (each with a single token, no per-surface bespoke easing):
| Motion | Duration | Easing |
|---|---|---|
| Drawer open/close | `--motion-base` (180ms) | `--motion-ease-out` |
| Command palette enter/exit | `--motion-fast` (120ms) | `--motion-ease-out` |
| Trace row expand | `--motion-base` (180ms) | `--motion-ease-out` |
| Diff highlight pulse | `--motion-fast` (120ms) | `linear` (one cycle, no loop) |
| Route transition | `--motion-base` (180ms) | `--motion-ease-out` |
| Skeleton loading shimmer | `--motion-slow` (1200ms) | `linear`, ≤2 cycles before falling back to static "Loading…" label |
Forbidden:
- "thinking…" pulses, dots, or orbs
- particle effects
- glowing neural-network animations
- avatar movement
- glassmorphism / frosted-glass surfaces
- shimmer applied to anything that is not a loading skeleton
- animated graph or chain construction "theater"
- background gradients that move
- any animation that loops indefinitely
Reduced-motion (`prefers-reduced-motion: reduce`) must collapse every
allowed motion to instant. No exceptions.
### 5. `StableJsonViewer` — trust surface invariants
This is the component that, if done casually, silently erodes the audit
invariant. It is **doctrine, not styling**. Six invariants, each
testable:
1. **Deterministic key ordering.** Keys render in the order the source
serialized them. When the source is a dict, sort lexicographically.
When the source is an already-serialized JSON string, preserve
appearance order. Never silently re-sort.
2. **Lossless string preservation.** No smart quotes. No whitespace
stripping. No unicode normalization. No HTML entity coercion.
What you see is what is on disk.
3. **No semantic auto-format.** Numbers display as the source typed
them (`1e-6` ≠ `0.000001`). Booleans, null, and integer/float
distinctions are preserved.
4. **Copy paths.** Right-click (and a keyboard shortcut) yields the
JSON Pointer path to the selection (e.g., `/scenes/3/detail/proposed_chain/object`).
This is the audit-trail handle.
5. **Diff mode.** Given two values, render side-by-side with
structural alignment. Highlight only the leaf fields that changed.
Color-only encoding forbidden — changed leaves carry a glyph
(`≠ added`, `≠ removed`, `≠ changed`).
6. **Large-document safety.** Virtualize rendering above 1,000 leaf
nodes. Above 16 MiB raw bytes, refuse to inline-render and surface a
"Open in external viewer" affordance with a copy-path button — same
ceiling as the W-026 read API.
The viewer must also display the SHA-256 digest of the rendered source
as a `--font-mono` badge. Clicking the digest copies it.
### 6. Empty / error / loading state contract
Every route ships **all three** states from day one. This is acceptance
criterion #9 from the vision doc, lifted into doctrine here.
#### Empty
Every empty state must contain:
- A one-line statement of what is absent.
- A **next action** — either a CLI command to copy, a route to navigate
to, or a runtime config to inspect.
Example:
```text
No pending proposals.
Run: core demo learning-arc
```
Never empty-empty. Never just a "—".
#### Error
Every error state must surface:
- **What failed** (one sentence)
- **Whether state was mutated** (`No corpus mutation occurred.` is a
load-bearing line per CLAUDE.md doctrine)
- **A reproducer** — the CLI command or curl that triggers it
- **Whether retry is safe**
Example:
```text
Replay failed before comparison.
No corpus mutation occurred.
Reproduce:
uv run core demo learning-arc --json
Retry: safe
```
#### Loading
Loading labels are specific, never "Thinking…":
```text
Loading trace…
Computing replay…
Reading proposal log…
Running eval lane…
Comparing artifacts…
```
The skeleton shimmer caps at two cycles (per §4) and then collapses to
the static label. Indefinite shimmering is forbidden.
### 7. Keyboard contract
The Workbench is keyboard-first. Baseline:
- `⌘K` / `Ctrl+K` opens the command palette from any route
- `Esc` closes any overlay (drawer, palette, popover)
- `Enter` activates the focused item (open, expand)
- `↑` / `↓` traverse lists; `←` / `→` traverse adjacent panels
- `?` opens the keyboard-shortcut cheat sheet
- Every interactive element has `focus-visible` styles drawn from
`--color-focus-ring`
- `Tab` order matches visual order; explicit `tabIndex` only where the
visual order conflicts with the semantic order
- Every drawer/dialog announces a `role` and an `aria-label`
- No interactive surface is reachable only by mouse
The command palette must support, at minimum, fuzzy search across:
```text
Run eval lane
Open proposal
Replay trace
Compare run
Inspect pack
Search trace hash
Copy ratification command
Open latest contemplation report
```
### 8. Layout shell
The shell is a five-region grid, named consistently across routes:
```text
+---------------------------------------------------------------+
| TopBar (command palette / context / status) |
+---------+-------------------------------------------+---------+
| | | |
| LeftNav | MainSurface | Right |
| | | Inspect |
| | | |
+---------+-------------------------------------------+---------+
| StatusFooter (runtime / replay / mutation mode) |
+---------------------------------------------------------------+
```
Routes may **collapse** the RightInspector but not hide the TopBar,
LeftNav, or StatusFooter. Removing the persistent surfaces breaks the
"always know what runtime you're talking to" affordance that ADR-0160
required.
LeftNav contents are pinned and ordered:
```text
Chat
Trace
Replay
Proposals
Evals
Runs
Packs
Vault / Recall
Audit Log
Settings / Runtime
```
No giant sidebar. No collapsible sub-trees in v1.
StatusFooter surfaces three signals at all times:
- `mutation_mode` from `GET /runtime/status` (`read_only` or
`runtime_turn`) — color-encoded **and** labeled
- `git_revision` (short SHA, mono)
- `checkpoint_revision` (short SHA, mono) — turns amber when
`revision_warning: true` per ADR-0157
### 9. Component map — v1 must-ship vs follow-up
The vision doc lists ~30 components. The honest v1 scope is narrower.
This ADR commits to **must-ship** for W-027..W-031; everything else is
named here so the design system anticipates it, but ships in a follow-up.
**Must ship in v1 (W-027..W-031):**
```text
WorkbenchShell
TopBar LeftNav StatusFooter
CommandPalette
ChatTurnList ChatTurnCard
ResponseEvidenceStrip
TraceDrawer
EpistemicStateBadge GroundingSourceBadge NormativeClearanceBadge ReviewStateBadge
TraceHashBadge CopyableHash
ReplayTheater ReplayComparisonPanel ReplayDiffViewer
ProposalQueue ProposalCard ProposalDetailPanel
RatificationCommandPanel
EvalCenter EvalLaneList EvalFailureViewer
StableJsonViewer StableJsonDiffViewer ArtifactLink
```
**Follow-up after v1 milestone:**
```text
TraceStepList (inline trace step navigator)
ProposalChainViewer (chain visualization)
SourceProvenancePanel (deep provenance graph)
PackInspector CorpusInspector VaultRecallInspector
MetricGateTable RegressionDiffPanel
```
Pinning the v1 component set keeps the implementation branches honest.
### 10. The no-go list
Explicit, because doctrine drift is what produces "AI dashboard" UIs:
- No chat-clone styling. No avatar bubbles. No "AI is thinking…"
affordances.
- No animated cognition theater (particles, glowing networks, pulsing
orbs).
- No glassmorphism / frosted glass.
- No purple gradients, no neon accents, no cyberpunk styling.
- No live "hallucinated" chain or graph construction animations.
- No node-graph builder in v1.
- No workflow automation canvas in v1.
- No "accept proposal" button anywhere in v1 (per ADR-0160 + ADR-0161 §2).
- No dashboard-soup pages.
- No vanity green-dashboard eval views.
- No "active session count" / "total turns" splash metrics on a landing
page.
- No system-tray notifications, no toasts that auto-dismiss audit
events.
- No icon-only buttons without accessible labels.
- No color-only state encoding.
---
## Implementation plan — Branch 1
Pre-W-027 deliverable. Single PR titled `feat(workbench-ui): design
system v1 (ADR-0162)`:
1. `workbench-ui/` directory created at repo root with `package.json`,
`vite.config.ts`, `tsconfig.json`. Pin React 18, Vite 5, TS 5.x,
Tailwind 3.x, shadcn primitives. No app routes yet.
2. `workbench-ui/src/design/tokens/tokens.css` — every token from §1§4.
3. `workbench-ui/src/design/tokens/tokens.ts` — typed TS mirror, generated
from `tokens.css` at build time (a `scripts/generate-tokens.ts` reader).
4. `workbench-ui/src/design/components/StableJsonViewer/` — full
implementation honoring all six invariants in §5, with tests.
5. `workbench-ui/src/design/components/EpistemicStateBadge/` and the
four other badge primitives — each generated from the enum tables in
§3 so the badge set is provably exhaustive.
6. `workbench-ui/src/design/components/EmptyState/`, `ErrorState/`,
`LoadingState/` — primitives the route screens compose.
7. `workbench-ui/preview/` — a Vite route exposing every primitive on
one page so the design baseline is reviewable in a browser. Not
shipped to end users; it is the operator's "Storybook lite".
8. Frontend tests: token presence, badge-set exhaustiveness against the
Python enums (parsed from `core/epistemic_state.py` and
`teaching/proposals.py` at build time), `StableJsonViewer` invariants
16, reduced-motion collapse, keyboard contract for the palette and
drawers.
9. Docs update: `docs/workbench/design-system.md` linking the ADR,
`docs/workbench/README.md` runbook addition (`cd workbench-ui &&
pnpm preview`).
**Crucially: no app shell, no routes, no API client yet.** Branch 1 is
*only* the substrate. W-027 (frontend shell) is the first branch that
consumes it.
---
## Acceptance criteria
This ADR is ratifiable when:
1. Every token in §1§4 is a CSS custom property with a matching typed
TS export, and a test fails if either drifts.
2. Every enum value in §3a§3d has exactly one badge component, and a
test parses the Python enums and asserts 1:1 coverage. Adding an
enum value to the engine without adding a badge fails the test.
3. `StableJsonViewer`'s six invariants each have a test:
- byte-identical round-trip on a synthetic source,
- copy-path returns a valid JSON Pointer,
- diff highlights only changed leaves and adds the glyph,
- large-document virtualization triggers above 1,000 leaves,
- oversize-document refusal triggers above 16 MiB,
- SHA-256 digest matches the source bytes.
4. Every primitive component renders an `EmptyState` / `ErrorState` /
`LoadingState` snapshot test from a fixture and asserts the
"next action" / "reproducer" / "specific label" rules in §6.
5. `prefers-reduced-motion: reduce` collapses all motion to instant in a
Playwright test.
6. The command palette is reachable via keyboard from every primitive's
preview page (Playwright).
7. The preview page (`pnpm preview`) renders every primitive without
network access (offline-safe, deterministic).
---
## Out of scope
This ADR is the design substrate. It does not commit to:
- the app shell or routes (W-027)
- chat or trace UI (W-028)
- proposal queue UI (W-029)
- eval center UI (W-030)
- replay theater UI (W-031)
- a backend API beyond what W-026 already exposes
- light theme / system-theme switching
- internationalization
- multi-tenant or remote operation
- mobile layouts (Workbench is a desktop tool; the mobile path for
ratification stays the GitHub mobile app + workflow_dispatch per
ADR-0161 §2)
- a frontend authentication layer (deferred to a separate ADR; v1 is
local-only, unauthenticated, per ADR-0160)
- Storybook / Chromatic — the `/preview` route is the substitute
- Figma component mirroring — design tokens are the source of truth, not
a Figma file
---
## Consequences
### Positive
- Five downstream branches inherit one substrate. No silent drift
between W-027 and W-031.
- The badge layer is provably exhaustive against the engine's ratified
enums — adding a new `EpistemicState` value cannot ship without a
badge.
- `StableJsonViewer` becomes a tested trust surface, not a code-styling
choice. Replay-equivalence claims gain a UI-side audit handle.
- The no-go list closes the "AI dashboard drift" failure mode by name,
not by hope.
- Empty / error / loading contracts ship from day one. The "we'll add
empty states later" failure mode is forbidden.
### Negative
- Branch 1 is overhead before any user-visible UI exists. ~1 day's
work that shows nothing in the chat surface. Worth it.
- Tying badges to enums means engine refactors that rename or remove
enum values cascade into the UI. This is the intended behavior, not
a cost — the UI cannot diverge from the engine's self-classification.
- The 16 MiB `StableJsonViewer` ceiling matches the W-026 API ceiling.
If either ceiling moves, the other must move with it.
### Risks
- Token bikeshedding. Mitigation: tokens are semantic-only. The
conversation is about *what the surface means*, not what shade it is.
- Inter / JetBrains Mono licensing. Both are OFL-licensed; bundling is
permitted. Self-hosted, not fetched at runtime.
- Component scope creep. The §9 component map names what ships in v1
vs follow-up; deviation requires a separate ADR.
---
## Cross-references
- [ADR-0160 — CORE Workbench v1](./ADR-0160-core-workbench-v1.md) — product doctrine; this ADR is its design substrate.
- [ADR-0161 — HITL async queue](./ADR-0161-hitl-async-queue.md) — the proposal-queue UI's semantic contract (admits ≠ ratifies).
- [ADR-0153 — TurnEvent.trace_hash back-stamp](./ADR-0153-turn-event-trace-hash-backstamp.md) — `TraceHashBadge` consumes this.
- [ADR-0157 — Revision-mismatch warning](./ADR-0157-revision-mismatch-warning.md) — `StatusFooter` consumes this.
- [ADR-0159 — Contemplation-quality eval](./ADR-0159-contemplation-quality-eval.md) — `EvalCenter` reads against this lane shape.
- [`core/epistemic_state.py`](../../core/epistemic_state.py) — the enum tables in §3a§3b are bound to this file.
- [`teaching/proposals.py`](../../teaching/proposals.py) — `ReviewState` for §3c.
- [`workbench/schemas.py`](../../workbench/schemas.py) — typed dataclasses the UI mirrors as TS discriminated unions.
- [CLAUDE.md](../../CLAUDE.md) — deterministic replay, exact recall, proposal-only learning; this ADR encodes those into the UI substrate.
### Memory cross-references
- [[thesis-decoding-not-generating]] — the UI must reveal the engine
*decoding* (badges, replay, trace hashes), not staging a *generating*
performance (animation theater). Every motion / animation choice is
measured against this.
- [[feedback-address-critiques-dont-waive]] — the vision-doc gaps
(StableJsonViewer underdefined, empty-states absent, badge taxonomy
outrunning the engine) are addressed here, not deferred.
- [[feedback-adr-cross-reference-discipline]] — every badge color is
bound to a ratified enum. No badges without a Python source of
truth.
- [[user-circumstances]] — the operator works from a tent / library, on
intermittent connectivity. Local-only, offline-safe, no fonts or
assets fetched at runtime. The `/preview` page must render with the
network disabled.