diff --git a/packages/paperclip-runner/docs/design/phase-5-component-plan.md b/packages/paperclip-runner/docs/design/phase-5-component-plan.md new file mode 100644 index 00000000000..5762f7f1ea5 --- /dev/null +++ b/packages/paperclip-runner/docs/design/phase-5-component-plan.md @@ -0,0 +1,297 @@ +# Phase 5 Component and SDK Surface Plan — Reference Console and Reusable Contracts + +Status: **approved** (UX gate PAP-16842, 2026-08-08). This is the binding +UX/component plan for the Phase 5 implementation (PAP-16843). It extends the +approved [Phase 4b interaction map](phase-4b-interaction-map.md) and +[Phase 4b component decision record](phase-4b-component-decisions.md); where +this document is silent, those documents govern. + +Inputs reviewed for this gate: the accepted Phase 4b live console +(`devtools/browser/`, shipped `96092b832d`, corrected `cda1fb03ab`, QA +evidence `09b91b63f7` — 23 accepted screenshots in +`knowledge/evidence/phase-04b/` at 1440×900 and 390×844), the Phase 4b +reducer/protocol contracts (`src/reducer/session-reducer.ts`, +`src/protocol/`), and the live client layer +(`devtools/browser/src/live/{client,protocol,transcript-model,use-live-console}.ts`). + +## 0. Scope and boundary + +- Everything lands under `packages/paperclip-runner/` on branch + `PAP-16679-paperclip-runner`. **Do not modify or import production + `server/`, `ui/`, `packages/db/`, routes, persistence, or legacy + adapters.** +- Extraction is **copy-and-freeze, not move**: the Phase 4b demo console at + `devtools/browser/` and its Phase 1–3 surfaces stay untouched — their + screenshots are frozen QA evidence. The SDK is a new, versioned copy with + stable names. +- The canonical PRP event stream and `SessionSnapshot` reducer remain the + only UI data contract (Phase 4b ground rule §1.1 carries forward + unchanged). + +## 1. Public package surface + +Add subpath exports to `@paperclipai/paperclip-runner`: + +| Subpath | Contents | Dependencies | +|---|---|---| +| `.` | existing node/driver contracts (unchanged) | none | +| `./browser` | framework-free session client: HTTP + SSE transport functions (today's `live/client.ts`), wire types (today's `live/protocol.ts`), transcript model, re-exported reducer | none | +| `./react` | hooks + components (today's `use-live-console.ts` + `components/ui/*` + `live/*` views) | `react`/`react-dom` as **peer** deps only | +| `./styles.css` | token sheet + component styles, scoped under `.pcr-root` | none | + +Rules: + +1. **Zero new runtime dependencies** (carried from Phase 4b §1.3). `react` + and `react-dom` become declared peer dependencies of the `./react` + subpath; nothing else is added. No Tailwind, radix, cva, AI SDK, zod, + shiki, streamdown — same ban list as Phase 4b §6. +2. **Public naming drops `live`/`phase4b`.** Renames (shape unchanged): + `useLiveConsole` → `useRunnerConsole`, `LiveConsole` (interface) → + `RunnerConsole`, `LiveSessionState` → `RunnerSessionState`, + `LiveConnectionStatus` → `ConnectionStatus`, `LiveManifestSummary` → + `ManifestSummary`, `LiveConsoleError` → `RunnerClientError`. + `SessionSnapshot`, `SessionItemSnapshot`, `SessionRequestSnapshot`, + `SequenceGap`, `PrpEvent` are already canonical and keep their names. +3. **The hook contract is frozen as shipped.** `useRunnerConsole` keeps the + accepted `RunnerConsole` shape: `manifests`, `snapshot`, `transcript`, + `connection`, `reconnectAttempt`, `replayParity`, `composer`, + `steeringChips`, `announcement`, `error`, `busy`, thread selection, the + `replay` control group, and the verbs `start/send/steer/interrupt/ + resolve/goal/dropConnection/retryNow/reset/dismissSteeringChip`. The + `announcement` string is a load-bearing accessibility channel (map §10 + A-criteria) and must remain in the public contract. +4. **Semver discipline.** Any change to an exported prop shape, class name, + token name, or keyboard behavior after extraction requires a decision + recorded in this document's follow-ups, not a silent edit. + +## 2. Component contracts + +All components: plain function components, `data-slot` attributes, semantic +class names, every visual value from the token layer. SDK class names and +tokens use the **`pcr-` prefix** (see §4) so they cannot collide in consumer +apps; the frozen `ui-*` demo classes stay behind in `devtools/browser/`. + +Promoted primitives (from Phase 4b REUSE/ADAPT, contracts unchanged): + +| Component | Props typed against | Keyboard contract | +|---|---|---| +| `Button` (default/danger/ghost) | native button props | native | +| `Badge` | tone + children | n/a | +| `Card` | native section props | n/a | +| `Textarea` | native | native | +| `Tabs` | items + selected + onSelect | WAI-ARIA tabs, roving tabindex, arrow keys (map K) | +| `Menu` (menu-button) | fixed item list + onSelect | menu-button pattern: arrows, Home/End, Escape | +| `Dialog` | open/onClose/title/children | native `` `showModal()`: focus trap + Escape | +| `Tooltip` | content mirrored to `aria-describedby` | focus-visible trigger; never the only channel (A5) | +| `Banner` | tone (info/warning/danger/success) + actions | actions are real buttons in tab order | + +Promoted console components: + +| Component | Props typed against | Notes | +|---|---|---| +| `Conversation` | `TranscriptEntry[]` + unseen count | `role="log"`, stick-to-bottom + "jump to latest" as accepted | +| `Message` | `SessionItemSnapshot` | roles: user/assistant/reasoning/tool/system | +| `Composer` | `ComposerState` + submit/steer/stop verbs | Enter submits, Shift+Enter newline; Send/Steer/Stop tri-state per map §1–§3 | +| `ReasoningItem` | `SessionItemSnapshot` | `
` disclosure, auto-open while streaming | +| `ToolItem` | `SessionItemSnapshot` | header/input/output sections, status from canonical events | +| `RequestCard` | `SessionRequestSnapshot` + resolve verb | one card per pending request kind; terminal states preserved (PAP-16839 semantics) | +| `SessionTimeline` (today's `SessionPanel`) | `SessionSnapshot` | timeline, lineage tree, goal state | +| `Inspector` | `SessionSnapshot` + `PrpEvent[]` | Events/Session tabs; copy affordance stays local | +| `ReplayControls` | `RunnerConsole["replay"]` | scrub/step/play; arrow-key step | +| `ConnectionBanner` | `ConnectionStatus` + retry verb | reconnect/backoff states as accepted | + +Layout components (`.pcr-root` shell, three-pane grid, mobile segments) are +exported as the **reference console** (`RunnerConsoleApp`) — see §8. Grid +tracks that hold identifiers must use `minmax(0, 1fr)` with `min-width: 0` +at every level (measured Phase 4b regression; do not rely on CSS reasoning — +verify with `getBoundingClientRect` in the browser tests). + +## 3. Extension points (exactly these five) + +1. **Item body renderer.** `Conversation`/`Message`/`ReasoningItem`/ + `ToolItem` accept optional `renderItemBody?(item: SessionItemSnapshot) => + ReactNode`. Default remains exact plain text. This is where consumers add + markdown or syntax highlighting — which is why `Response` and `CodeBlock` + stay out of the core (§9). +2. **Request detail renderer.** `RequestCard` accepts optional + `renderRequestDetail?(request: SessionRequestSnapshot) => ReactNode` for + the payload section only. Status header, resolution controls, and + terminal-state behavior are not overridable. +3. **Composer slots.** Optional `leadingActions`/`trailingActions` + ReactNode slots in the action row. Submit semantics, tri-state button, + and keyboard behavior are fixed. +4. **Token theming.** Consumers restyle exclusively by overriding `--pcr-*` + custom properties on `.pcr-root` (or a descendant scope). Class names and + DOM structure are not a supported theming API. +5. **Transport injection.** The `./browser` client accepts `{ baseUrl, + fetchImpl?, eventSourceFactory? }` so consumers can add auth headers, + proxies, or test doubles without forking the client. + +Anything not listed is **not** an extension point. No render-prop shells, no +headless mode, no component-swap registry in Phase 5. + +## 4. Design-token rules + +1. The Phase 4b token inventory (color/status pairs, accent, surfaces, type + scale, spacing, radii, shadow, motion, z-index, layout, touch-target) + carries over verbatim, renamed `--background` → `--pcr-background` etc., + declared on `.pcr-root` (not `:root`) in the shipped `./styles.css`. +2. `color-scheme: light` is declared on `.pcr-root`. **Dark mode is a + non-goal** for Phase 5 and is recorded as such (§9). +3. No inline visual values in any SDK component — extend + `check:browser-tokens` to cover the SDK source directories. +4. New tokens are a system change: add to the sheet and record the addition + in this document in the same commit (Phase 4b §5 rule). +5. Motion tokens stay gated by `prefers-reduced-motion`. +6. Shipped default pairs must keep ≥ 4.5:1 contrast (map V1). The package + docs must list the foreground/surface pairs so a consumer who overrides + tokens knows exactly which combinations to re-verify; contrast for + overridden values is the consumer's responsibility and the docs say so. + +## 5. Responsive states + +- Reference console: three-pane (rail / transcript / inspector) at desktop, + single-pane with segment tabs at mobile, exactly as accepted in Phase 4b + (map §0). Canonical review viewports stay 1440×900 and 390×844. +- **Single-tree rule (binding):** one rendered layout selected via a + `matchMedia` hook (breakpoint constant 900px). Never render both layouts + and hide one with CSS — it duplicates every `data-testid`, control, and + ARIA landmark (Phase 4b lesson, recorded in the 4b journal). +- Component minimum widths (document in package docs): `Conversation` and + `Inspector` ≥ 320px, `RequestCard` ≥ 280px, `Composer` ≥ 320px. Components + must not overflow their container at these widths with realistic long + identifiers (verify in-browser, not by CSS inspection). +- Touch targets ≥ `--pcr-touch-target` (2.75rem) for all mobile controls. + +## 6. Keyboard behavior + +Map §10 K1–K9 carry forward as SDK acceptance criteria, owned per component: + +- `Composer`: Enter submits (Send or Steer per state), Shift+Enter inserts + newline, disabled states never trap focus. +- `Tabs`: arrow-key roving tabindex, Home/End. +- `Menu`: menu-button pattern — Enter/Space/ArrowDown opens, arrows cycle, + Escape closes and restores focus to the trigger. +- `Dialog`: native `showModal()` focus trap, Escape closes, focus returns to + the invoking control. +- `ReasoningItem`/`ToolItem`: `
` summary toggles with Enter/Space. +- `ReplayControls`: Left/Right step one event, Space toggles play when the + scrubber has focus. +- Banners: focus is moved to a banner only when it blocks the primary action + (reconnect-failed); informational banners never steal focus. + +## 7. Accessibility acceptance criteria + +Map §10 A1–A7 and V1–V5 are the SDK acceptance bar, verified on the +reference console **and** the mini consumer: + +1. Transcript is `role="log"`; state changes surface through the hook's + `announcement` channel into a single `aria-live="polite"` region. +2. Every interactive control has an accessible name; tooltips mirror to + `aria-describedby` and are never the only channel (A5). +3. Status is never color-only — badge text/icon carries the distinction + (V-criteria). +4. Rebuilt-on-native patterns (dialog, menu, tabs, disclosure) meet the + WAI-ARIA pattern they replace; "the library would have done it" remains + unavailable as an excuse (Phase 4b §1.4). +5. Components keep their ARIA and keyboard contracts under any token + override — theming can never remove semantics. +6. `prefers-reduced-motion` disables all non-essential motion. + +## 8. Reference console and the second SDK consumer + +**Reference console** (`RunnerConsoleApp`): a standalone app entry that +composes only public SDK exports and reproduces the accepted Phase 4b +lifecycle. It is the first consumer and the visual-truth surface for review +screenshots. It must not import from `devtools/browser/` demo internals. + +**Mini consumer** (the tracer-bullet gate): a deliberately minimal second +app (suggested location `examples/mini-consumer/`) that proves the SDK +surface is sufficient and the extension points are real: + +- Imports **only** `@paperclipai/paperclip-runner/browser`, `/react`, and + `/styles.css` — extend `check:forbidden-imports` to fail on any deep or + demo-internal import from either consumer. +- Single-column layout using only `Conversation`, `Composer`, `RequestCard`, + `ConnectionBanner`, and `ReplayControls`. It is intentionally not a second + console: no rail, no inspector, no goal menu. +- Scripted flow (against the fake scripted driver and the real Codex + driver): list manifests → create session → send a turn → steer mid-turn → + interrupt → resolve one command request → set and clear a goal via the + hook verb → drop connection → automatic reconnect with gap recovery → + enter replay and scrub → show `replayParity`. +- Exercises extension points: a custom `renderItemBody` (e.g. wraps assistant + text with a visible marker) and at least one `--pcr-*` token override, + both visible in screenshots. + +## 9. Component decision record — Phase 5 + +Carried ground rules: protocol authority (§1.1), source adaptation over +dependency adoption (§1.2), zero new runtime deps (§1.3), accessibility +parity (§1.4), token gaps are system changes (§1.5). + +**PROMOTE to SDK (copy from accepted Phase 4b, rename classes/tokens to +`pcr-`):** Button, Badge, Card, Textarea, Tabs, Menu, Dialog, Tooltip, +Banner, Conversation, Message, Composer, ReasoningItem, ToolItem, +RequestCard, SessionTimeline, Inspector, ReplayControls, ConnectionBanner, +plus the `useRunnerConsole` hook, transcript model, and browser client. + +**Phase 4b "Phase 5 candidates" — re-decided for Phase 5:** + +| Candidate | Decision | Reason | +|---|---|---| +| `Response` (streamed markdown) | **REJECT in core** | markdown pipelines pull deps and can hide protocol truth; served by the `renderItemBody` extension point instead | +| `CodeBlock` (shiki) | **REJECT in core** | same; `
` + mono token remains the default, consumers plug highlighters via `renderItemBody` |
+| `Context` (token/cost meter) | **REJECT as persistent meter** | usage stays plain data in the Inspector Session tab; a meter overweights cost in a protocol surface |
+| `Command` palette | **REJECT** | goal vocabulary is still five fixed verbs; plain menu wins on Hick's law and zero deps |
+
+**NEW REJECT (Phase 5):**
+
+| Proposal | Reason |
+|---|---|
+| Headless/unstyled distribution | doubles the API surface and QA matrix before there is a second real consumer; token theming covers stated needs |
+| Dark theme | no consuming surface requires it yet; a second palette doubles every V-criterion — record as explicit non-goal, revisit at product integration (Phase 6) |
+| Tailwind/radix port | contradicts the zero-dep rule and the accepted idiom |
+| Toast/notification layer | failures must live in the transcript record, not ephemeral toasts (map §1.5) |
+| Virtualized transcript | no evidence of need at accepted session sizes; adds focus/scroll a11y risk. Revisit with data if real sessions exceed ~2k items |
+
+## 10. Review evidence requirements (binding on PAP-16843)
+
+1. Implementation handoff must attach screenshots at **1440×900 and
+   390×844**: reference console in idle/empty, streaming turn, steering
+   acknowledged, interrupt, request pending, request resolved, goal set,
+   reconnect banner, replay mode, and failed turn; mini consumer in idle,
+   streaming, request pending, reconnect, replay — including one shot each
+   proving the custom `renderItemBody` and the token override.
+2. Real-driver evidence: at least one reference-console screenshot from a
+   live Codex session (Phase 4b QA recipe applies).
+3. The implementer creates a **UX re-review child issue assigned to
+   UXDesigner** with those screenshots before the Phase 5 contract review
+   (PAP-16845) is asked to approve. Code inspection alone is not a UX pass.
+
+## 11. Acceptance criteria for PAP-16843 (UX gate)
+
+- AC1 Subpath exports `.`, `./browser`, `./react`, `./styles.css` exist with
+  the dependency rules of §1; public names per §1.2; hook contract per §1.3.
+- AC2 All §2 components exported with props typed against reducer/protocol
+  shapes; `data-slot` attributes and `pcr-` classes throughout.
+- AC3 Exactly the five §3 extension points, demonstrated in the mini
+  consumer.
+- AC4 Token sheet scoped to `.pcr-root`, `pcr-`-prefixed, light-only,
+  lint-enforced; contrast pairs documented.
+- AC5 Responsive rules of §5 hold, verified by in-browser measurement at
+  both canonical viewports.
+- AC6 Keyboard contracts of §6 and accessibility criteria of §7 pass on both
+  consumers (component + browser tests reference K/A/V ids).
+- AC7 Mini consumer runs the full §8 flow against fake and real drivers with
+  no demo-internal imports (lint-enforced).
+- AC8 Reference console reproduces the accepted Phase 4b lifecycle from
+  public APIs only; Phase 4b demo and Phase 1–3 surfaces are byte-untouched.
+- AC9 Evidence per §10 attached; UX re-review issue created.
+
+## 12. Non-goals
+
+Production `ui/` integration (Phase 6, gated on board acceptance + CTO
+design approval), dark mode, i18n, SSR support (document client-only), npm
+publishing/registry work, and any second event model or client-side session
+cache (map §7.2 stands).
diff --git a/packages/paperclip-runner/docs/index.md b/packages/paperclip-runner/docs/index.md
index 7652376b32c..d6d833d9a38 100644
--- a/packages/paperclip-runner/docs/index.md
+++ b/packages/paperclip-runner/docs/index.md
@@ -27,7 +27,8 @@ Each tutorial starts with an explanation of what the phase is and what the phase
 - [Dated shadcn/ui and AI Elements compatibility note](research/2026-08-07-ui-library-compatibility.md)
 - [Phase 4b live-console interaction map](design/phase-4b-interaction-map.md)
 - [Phase 4b component decision record (shadcn/ui, AI Elements)](design/phase-4b-component-decisions.md)
+- [Phase 5 component and SDK surface plan](design/phase-5-component-plan.md)
 - [Package README](../README.md)
 
-Phase 4b is implemented and ready for Security, CTO, QA, and human review.
-Phase 5 remains uncreated.
+Phase 4b is implemented and accepted. Phase 5 (SDK extraction and reference
+console) has an approved UX component plan; implementation is pending.
diff --git a/packages/paperclip-runner/knowledge/journal/2026-08-08-phase-05-ux-plan.md b/packages/paperclip-runner/knowledge/journal/2026-08-08-phase-05-ux-plan.md
new file mode 100644
index 00000000000..9eeb26563bd
--- /dev/null
+++ b/packages/paperclip-runner/knowledge/journal/2026-08-08-phase-05-ux-plan.md
@@ -0,0 +1,56 @@
+---
+type: Engineering Journal Entry
+title: Phase 5 UX gate — reusable console and SDK component plan
+description: Approved consumer-facing component contracts, extension points, token rules, and the mini-consumer tracer flow for the Phase 5 SDK extraction.
+tags: [native-runner, phase-5, ux, browser, sdk, components, accessibility]
+status: stable
+generated: { by: anthropic/claude, at: 2026-08-08T07:05:00Z }
+entry_kind: phase
+phase: "5-ux"
+---
+
+# Context
+
+Phase 5 generalizes the accepted Phase 4b live console into a versioned,
+package-local SDK surface plus a standalone reference console, proven by a
+second minimal consumer. Before implementation (PAP-16843), the UX gate
+(PAP-16842) had to freeze consumer-facing contracts and re-decide the
+component candidates deferred from Phase 4b.
+
+# Decisions
+
+1. Extraction is copy-and-freeze: the Phase 4b demo console and Phase 1–3
+   surfaces stay byte-untouched as QA evidence; the SDK is a renamed copy
+   under new subpath exports (`./browser`, `./react`, `./styles.css`).
+2. The accepted `useLiveConsole` hook shape is frozen as the public
+   `useRunnerConsole` contract, including the `announcement` accessibility
+   channel. `SessionSnapshot` remains the only UI data contract.
+3. Exactly five extension points: item-body renderer, request-detail
+   renderer, composer action slots, `--pcr-*` token theming, and transport
+   injection. Headless mode and component registries were rejected.
+4. SDK classes and tokens take a `pcr-` prefix scoped to `.pcr-root`,
+   preventing collisions in consumer apps; the demo keeps `ui-*`.
+5. Phase 4b deferred candidates re-decided: `Response`/`CodeBlock` stay out
+   of the core (served by the item-body renderer), the `Context` meter stays
+   rejected (usage remains Inspector data), the command palette stays
+   rejected. New rejections: headless distribution, dark theme, toast layer,
+   virtualized transcript.
+6. The mini consumer is deliberately not a second console: single column,
+   five components, full lifecycle against fake and real drivers, with the
+   extension points visibly exercised.
+
+Full plan: [`docs/design/phase-5-component-plan.md`](../../docs/design/phase-5-component-plan.md).
+
+# Evidence reviewed
+
+Accepted Phase 4b console (`96092b832d`, `cda1fb03ab`) and the 23 accepted
+screenshots in `knowledge/evidence/phase-04b/` (QA `09b91b63f7`) at
+1440×900 and 390×844. No new pixels were judged at this gate; implementation
+screenshots are a binding review requirement (plan §10).
+
+# Gaps / next questions
+
+- Package version number for the first frozen SDK surface is left to the
+  implementer + CTO contract review (PAP-16845).
+- Dark mode and virtualization are recorded non-goals; revisit only with a
+  Phase 6 consuming surface or real session-size data.
diff --git a/packages/paperclip-runner/knowledge/journal/index.md b/packages/paperclip-runner/knowledge/journal/index.md
index bd4b9aca4d5..33916b1ad6c 100644
--- a/packages/paperclip-runner/knowledge/journal/index.md
+++ b/packages/paperclip-runner/knowledge/journal/index.md
@@ -11,3 +11,4 @@
 * [Phase 4b UX gate — interaction map and component decisions](2026-08-08-phase-04b-ux.md) - Approved live-console interaction contract, adapt/reject component record, and keyboard/accessibility acceptance criteria.
 * [Phase 4b protocol and demo-server layer](2026-08-08-phase-04b-protocol-server.md) - Browser-resolved provider requests, control races, goals, lineage, replay, and server-only credentials.
 * [Phase 4b live console browser tracer](2026-08-08-phase-04b-live-console.md) - Reducer-only browser surfaces, adapted components with no new runtime dependencies, interrupt races, reconnect, replay, and the layout and stream failures found on the way.
+* [Phase 5 UX gate — reusable console and SDK component plan](2026-08-08-phase-05-ux-plan.md) - Frozen consumer-facing component contracts, five extension points, pcr- token rules, and the mini-consumer tracer flow for the SDK extraction.