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:
parent
ddb94d35c4
commit
8a24256ac5
1 changed files with 604 additions and 0 deletions
604
docs/decisions/ADR-0162-workbench-design-system.md
Normal file
604
docs/decisions/ADR-0162-workbench-design-system.md
Normal 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
|
||||
1–6, 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.
|
||||
Loading…
Reference in a new issue