paperclip/packages/paperclip-runner/docs/design/capability-scenario-explore...

550 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Capability Interaction Map — Scenario Explorer and Screenshot Acceptance Spec
Status: **approved UX contract for 7F implementation** (TASK-16899, 2026-08-09).
Owner: UXDesigner. Implementer: 7F (browser scenario explorer), integrating
7C mock adapter output and 7E parity output.
Companion records: [Capability contract](../capability-contract.md),
`spec/capability/eval-traceability.yaml`, the Capability plan (TASK-16897),
[Live console interaction map](live-console-interaction-map.md),
[SDK component decisions](sdk-component-decisions.md).
(annotated desktop + mobile renders in `.paperclip-local/evidence/capability/`).
This map is the interaction contract for the Capability browser scenario
explorer. The explorer is a **read surface over records the runtime already
produced**. It renders scenario metadata from the generated 7A traceability
manifest, run artifacts from the 7C mock control-plane adapter, and parity
results from the 7E conformance suite. The UI never owns state, policy,
credentials, or execution decisions: it does not compute tool exposure, does
not evaluate claims, does not apply redaction, and holds no Paperclip or
provider credential. Every allow/deny, redaction, and state transition shown
on screen must arrive as a record emitted by the mock core.
## 0. Naming, vocabulary, and canonical labels
- Product name in the UI: **Scenario Explorer** (header: “Capability scenario
explorer — mock control plane”).
- UI copy uses the canonical term **task** (`DESIGN.md` principle 7). Eval
case IDs, fixture IDs, route paths, JSON payloads, and source anchors are
machine values and stay verbatim (`issues`, `i-0102`, `MCK-102`) in
monospace — never “translated.”
- The three dispositions render with exactly these labels, everywhere:
**Control plane** (`control_plane_owned`), **Always tool**
(`always_agent_tool`), **Optional tool** (`optional_agent_tool`). The
traceability panel and all badges must use the 7A contract values as their
source; no UI-local re-classification.
- The five assertion classes render as: **Invariant**
(`control_plane_invariant`), **Tool contract** (`agent_tool_contract`),
**Authorization** (`authorization_policy`), **Multi-hop**
(`combined_multi_hop`), **Restraint** (`restraint_no_call`).
- Parity statuses: **Pass**, **Fail**, **Intentional gap**, **Not run**.
Each has an icon + text label; color alone never encodes status (WCAG
color-independence).
## 1. Shell and information architecture
Package-local Vite entry `examples/scenario-explorer/` with script
`demo:scenarios` (default port 4183; 4182 belongs to the Standalone demo). It runs
from static assets plus checked-in fixtures with **no Paperclip services and
no network dependency** in fake-agent mode. Reuse the frozen `0.1.2` SDK
surface (`@paperclipai/paperclip-runner/react` + `./styles.css`) through its
five approved extension points; do not fork the token layer.
Desktop layout (≥ 64rem), one React tree (SDK finding — never render two
trees):
- **Left rail (`--pcr-rail-width`, 17rem):** scenario picker — search,
facet filters, case list grouped by eval group.
- **Center column (fluid, max `--pcr-center-width`, 48rem):** run view —
the chronological scenario transcript and the run header (verdict banner,
mode toggle, run/replay controls). Primary surface, strongest hierarchy.
- **Right rail (`--pcr-inspector-width`, 24rem):** inspector with four tabs
— **Context · Authorization · State diff · Parity**. Open by default on
desktop (unlike the 4b protocol inspector: here the inspector carries
first-class acceptance evidence, not diagnostics).
Mobile (< 64rem): single column behind a segmented control
(`Scenarios · Run · Inspect`), same pattern as the Live console console. The
segmented control is sticky at the top; the Run segment is the default when
a case is selected via route. Touch targets ≥ `--pcr-touch-target` (2.75rem).
Density: diagnostic-console dense, like 4b. All spacing, color, type, radius,
motion from `--pcr-*` tokens only (`DESIGN.md` principles 2–4; `pnpm
check:token-gates` applies).
## 2. Scenario picker and filters (left rail)
**Source of truth:** the generated scenario index derived from
`spec/capability/eval-traceability.yaml` (all 106 cases, 16 groups) plus the
latest parity results artifact from 7E. The picker consumes these; it never
re-derives group membership or dispositions.
Contents, top to bottom:
1. **Search input** — substring match on case ID and title. Mono rendering
of matched IDs. Debounce ≤ 150ms (Doherty).
2. **Facet filters** (collapsible group, `--pcr-facet-height` baseline):
- **Group** — the 16 eval groups (`hb 5 · co 6 · st 8 · cm 6 · se 4 ·
su 4 · bl 5 · dp 3 · ix 9 · ap 6 · ar 4 · er 9 · rf 22 · mh 4 · rs 3 ·
wk 8`), each with its case count.
- **Case** — direct case-ID picker (combobox) for jump-to-case.
- **Disposition** — Control plane / Always tool / Optional tool, from
`primaryDisposition`.
- **Role** — actor role of the fixture (`ic`, `manager`, `reviewer`,
`board_user`, …) from the scenario index (§9 data contract).
- **Claim** — required grants from `requiredGrants` (e.g.
`approval:read`), rendered mono.
- **Parity status** — Pass / Fail / Intentional gap / Not run, from the
parity artifact.
Facets are AND-combined across facets, OR-combined within a facet. Every
facet value shows its live result count; zero-count values stay visible
but disabled (recognition over recall — the vocabulary stays learnable).
An active-filter chip row with per-chip remove and one **Clear filters**
affordance sits above the list (forgiveness).
3. **Case list** — grouped by eval group with sticky group headers. Each row:
case ID (mono, `--pcr-font-size-xs`), title (one line, truncated with
full text in a tooltip), disposition badge, parity status dot+icon.
Selected row uses `--pcr-accent-surface` with a `--pcr-accent` left rule.
Keyboard: the list is a single-select listbox (`role="listbox"`,
`aria-activedescendant`); Up/Down move, type-ahead jumps, Enter selects and
moves focus to the run header. Facets are disclosure buttons +
checkbox groups. Filter state mirrors into the route (§7) so any filtered
view is linkable; filter state is never stored as protocol state.
Empty results: “No scenarios match these filters.” + Clear filters button —
never a bare void.
## 3. Run view (center column)
### 3.1 Run header
- Case ID (mono) + title, group badge, disposition badge, assertion-class
badges.
- **Verdict banner** (SDK `Banner`): overall parity verdict for the last
completed run — Pass (success surface), Fail (danger surface), Not run
(muted). The banner names the assertion counts (“12 assertions · 12 pass”)
and deep-links the Parity tab.
- **Mode control:** `Fake agent` (default) / `Codex (bounded)` segmented
control. Fake mode is always available offline. Codex mode appears only
when the local relay is reachable; otherwise the option renders disabled
with the reason (“provider relay not running — see tutorial §4”). The
browser never holds a provider credential; Codex traffic goes through the
same server-side relay boundary proven in SDK.
- **Run / Re-run** button and replay controls (SDK `replay-controls`).
Deterministic fixtures mean re-run in fake mode reproduces the identical
timeline; state diffs and parity always describe the displayed run.
### 3.2 Scenario transcript
**Source of truth:** the ordered run artifact timeline emitted by the mock
core (§9). Items render in artifact order; the UI never reorders, merges, or
re-times entries.
The transcript interleaves two visually distinct channels (Similarity +
Common Region — a stranger must tell them apart in two seconds):
1. **Agent channel** — model turns and semantic tool calls/results. Aligned
with the standard conversation layout (SDK `conversation`/`message`/
`tool-item`). A semantic tool call renders collapsed by default with a
one-line summary: operation ID (mono) + disposition badge + one-phrase
outcome (“`finish_task` · Always tool · committed”). Expansion is the
item-body / request-detail extension point (§8) and shows: operation ID
and version, argument JSON, result JSON, idempotency behavior, claims the
call required, and redaction chips (§5) — exactly the descriptor fields
from the 7D catalog, never re-labeled.
2. **Control-plane channel** — actions the runtime performed with **no
tool** (checkout, wake routing, budget stops, reconciliation, blocker
wake scheduling…). These render full-width on a `--pcr-muted` surface
with a “Control plane” badge and a gutter glyph distinct from tool items,
and are indented from the agent lane. Copy states the actor plainly:
“Control plane checked out task MCK-102 — no agent tool exists for this.”
Each entry expands to the decision record (inputs considered, resulting
state change, audit reference).
Failed/denied entries render in place on `--pcr-danger-surface` with the
exact typed denial (§5) — failures are part of the record, never toast-only.
Restraint/no-call scenarios (`rs-*`, `er-*` restraint cases) must remain
legible when the correct behavior is *absence*: after the final agent turn
the transcript appends a system note “No further operations — N forbidden
operations, none invoked” which deep-links the Parity tab’s forbidden list.
Restraint evidence must never depend on an empty screen.
PRP events stay behind a per-item “Protocol events” disclosure (same nested
pattern as SDK `debugEvents`) — the semantic view is primary; raw
protocol stays inspectable but secondary (progressive disclosure).
Auto-follow, jump-to-latest, streaming affordances, and reduced-motion rules
are inherited verbatim from the Live console map §1 — do not redesign them.
## 4. Inspector tabs (right rail)
All four tabs are SDK `Tabs`; the active tab mirrors into the route (§7).
### 4.1 Context
Answers “who ran, under what authority, with which tools” before any
transcript reading (mental model first):
- **Actor block:** actor role, task mode, fixture ID (mono), wake payload
summary (reason + trigger), budget posture.
- **Capability grants:** the scenario’s `requiredGrants` plus every grant
the fixture actually issued, rendered as mono chips.
- **Tool exposure list** — the acceptance-critical view. Three labeled
sections, populated **only** from the mock core’s exposure record:
1. **Always tools (N)** — always-catalog operations visible this run;
2. **Optional tools (N)** — visible optional operations, each with the
grant that unlocked it (“`decide_approval` — via `approval:decide`”);
3. **Control plane — no tool (N)** — control-plane-owned capabilities
relevant to this scenario, explicitly listed so reviewers can verify
absence (negative evidence must be visible, not implied).
- **Traceability panel:** source anchor (file:line, mono), skill elements
(file · section · lines), evidence IDs, legacy MCP mapping row if any —
verbatim from the 7A contract.
### 4.2 Authorization
Chronological table of every authorization record the mock core emitted:
operation (mono), claims considered, decision (**Allow** / **Deny** with
icon + text), reason string, redactions applied, resulting state change
reference. Row expansion shows the full record JSON. Denied rows use
`--pcr-danger-surface`; a count chip on the tab surfaces denials without
opening it (“Authorization · 1 deny”). Selecting a row scrolls/flashes the
corresponding transcript entry (uniform connectedness across panels).
### 4.3 State diff
Before/after immutable snapshot diff for the displayed run, grouped by the
ten entity domains in the plan: **tasks, comments, documents, interactions,
approvals, artifacts, blockers, workspace, budget, run**. Each domain is a
collapsible section headed by a change summary (“tasks · 1 changed”,
“comments · 2 added”); unchanged domains collapse to a single muted row
(“unchanged”) so change carries the visual weight (Von Restorff). Rows show
entity ID (mono) + field-level changes as `field: before → after`, with
added/removed/changed markers as icon + label, not color alone. The diff is
computed by the runtime/test layer and delivered as data; the UI performs no
diffing of its own. A “final state” toggle shows the complete after-snapshot
JSON per domain behind a disclosure.
### 4.4 Parity
The eval acceptance surface:
- **Verdict header** repeating the banner verdict.
- **Assertion list:** one row per conformance assertion — assertion class
badge, human-readable expectation, Pass/Fail, and on fail an expected vs
observed block (mono, side-by-side at desktop, stacked at mobile).
- **Forbidden operations:** each `forbiddenSemantics` entry with “never
invoked ✓” or the violating transcript reference.
- **Intentional gaps:** entries marked as intentional exclusions render
under their own heading with the recorded rationale — visually distinct
from Fail (a gap is a decision, not a defect).
- **Legacy baseline note:** the case’s skill-present/skill-absent reference
note, verbatim, muted — labeled “reference baseline, not the pass
condition.”
## 5. Credentials, redaction, and denial rendering
- Fake mode performs zero network I/O; Codex mode talks only to the local
relay; no route, storage key, or bundle string may contain a Paperclip or
provider credential. `localStorage` may hold UI preferences only
(inspector tab, filter collapse state) — never run artifacts, grants, or
anything from a fixture.
- Redacted values arrive pre-redacted from the mock core. The UI renders a
redaction chip: `•••` + “redacted” label + the redaction-rule name in a
tooltip. The UI must not receive-and-hide secrets; if a raw secret ever
reaches the browser payload, that is a 7C/7D defect, and the explorer
additionally fails closed by rendering nothing for fields flagged
redacted.
- A **denied** tool call renders the typed policy denial: operation, the
missing claim, and the denial reason — and never any protected state. An
**absent** tool simply does not appear in exposure lists; the Context tab’s
control-plane section is the affordance proving absence is intentional.
- The generic escape hatch (`paperclipApiRequest` successor), when a test
scenario grants it, renders with a persistent `--pcr-warning-surface`
“test-only escape hatch” banner on every call card (plan §4: visually
flagged).
## 6. Accessibility, responsive, empty/error states
Accessibility (WCAG POUR; inherits all Live console/5 baseline rules):
- Landmarks: `nav` (picker), `main` (run view), `complementary`
(inspector); one `h1`; group headers are `h2`/`h3` in order.
- Full keyboard operation: picker listbox (§2); `F6`/documented shortcut
cycles the three regions; tabs follow the WAI-ARIA tabs pattern
(arrow-key activation); every disclosure is a real `button`. Visible
focus ring via `--pcr-ring` everywhere.
- Live regions: run progress announces via `aria-live="polite"`
(“Scenario run settled — verdict pass”); denial entries announce
assertively once.
- Reduced motion: with `prefers-reduced-motion`, streaming reveal, banner
transitions, and scroll-flash connect cues render instantly/statically
(SDK rule).
- Contrast from the existing token pairs only; parity/authorization
statuses always icon + text.
Responsive: content-driven single breakpoint at 64rem (§1). At mobile the
inspector tabs become full-width stacked sections inside the Inspect
segment; expected-vs-observed blocks stack; the transcript keeps every
descendant intrinsically shrinkable (`min-w-0` discipline) so no horizontal
scrollbar appears at 390px (SDK finding).
States that must be designed, not defaulted:
| State | Surface | Rendering |
| --- | --- | --- |
| No scenario selected | Center + inspector | Explorer intro card: corpus stats (106 cases · 16 groups), “Pick a scenario” pointer at the rail, three example deep links. No dead panels. |
| Scenario selected, not run | Run view | Header + context render from the index; transcript area shows a primed empty state (“Run to produce the deterministic timeline”) with the Run button duplicated inline (Fitts). Diff/Parity tabs show “No run yet.” |
| Run in progress | All | Progress in the header (indeterminate, reduced-motion safe); transcript streams; tabs show live counts; controls disable with reasons. |
| Run failed (harness error) | Run view | Danger banner with the exact diagnostic + “Re-run”; partial timeline stays visible and labeled partial. Never silently reset. |
| Parity fail | Banner/Parity | Fail is a first-class rendered state (danger banner + failing assertion rows) — the explorer’s job is showing it, not avoiding it. |
| Denied/absent tool | Transcript/Context | Per §5. |
| Missing/invalid fixture or artifact | Shell | Error card naming the artifact path and the regenerate command (`pnpm generate:capability-inventory` / 7E command). No blank screen. |
| Codex relay unavailable | Mode control | Disabled option + reason; fake mode remains fully usable. |
## 7. Deterministic screenshot routes
Hash routing (static hosting, no server rewrites):
```
#/ explorer home (picker + intro)
#/case/<case-id> scenario selected, not run
#/case/<case-id>?run=fake deterministic fake run, auto-executed
#/case/<case-id>?run=fake&view=<transcript|context|authorization|diff|parity>
#/?group=<g>&disposition=<d>&role=<r>&claim=<c>&parity=<p>&q=<text> filtered picker
```
Determinism contract for screenshot/CI use:
- `run=fake` auto-runs the scenario to completion on load; when the run and
all panels are settled the shell sets `data-run-state="settled"`
(Playwright wait target). Until then it is `pending`.
- Fake runs render **fixture time only** — no wall-clock timestamps,
durations, or locale-dependent formatting anywhere in fake mode; ordering
is by artifact sequence. Two loads of the same route are pixel-identical
at the same viewport (modulo font rasterization).
- `view=` selects the inspector tab (desktop) or segment+tab (mobile);
`view=transcript` closes nothing — it just guarantees focus/scroll starts
at the transcript top.
- Stable `data-testid`s (minimum set): `scenario-picker`, `facet-<name>`,
`case-row-<id>`, `run-header`, `verdict-banner`, `mode-toggle`,
`transcript`, `transcript-item-<seq>`, `cp-action-<seq>`,
`inspector-tab-<name>`, `exposure-always`, `exposure-optional`,
`exposure-control-plane`, `authorization-row-<seq>`, `diff-domain-<name>`,
`parity-assertion-<n>`, `redaction-chip`.
## 8. Package-local implementation guidance (7F)
- **Reuse first.** Shell, transcript, and inspector compose the frozen SDK:
`RunnerConsoleApp` is *not* the shell (this is not a chat console);
compose `conversation`, `message`, `tool-item`, `tabs`, `card`, `badge`,
`banner`, `menu`, `dialog`, `tooltip`, `replay-controls` from
`@paperclipai/paperclip-runner/react`. Semantic call detail uses the
item-body renderer (ext point 1); control-plane detail uses the
request-detail renderer contract (ext point 2); theming via scoped
`--pcr-*` overrides (ext point 4). Do not add SDK exports or break the
`0.1.2` freeze — explorer-only components live in the explorer entry.
- **New explorer-local components** (system proposals, one job each):
`ScenarioPicker` (search + facets + listbox), `ExposureList`,
`AuthorizationTable`, `StateDiff` (domain sections + field rows),
`ParityPanel`, `RedactionChip`, `ChannelEntry` (control-plane transcript
entry). Each uses tokens only; any genuinely missing token (e.g. a
`--pcr-diff-*` pair if muted/success/danger surfaces prove insufficient)
is proposed in the 7F PR as a token addition, never inlined.
- **Data contract consumed (owned by 7C/7E, not the UI):**
- *Scenario index* (generated from `eval-traceability.yaml`): `id`,
`title`, `group`, `fixture`, `actorRole`, `taskMode`,
`primaryDisposition`, `requiredGrants[]`, `assertionClasses[]`,
`sourceAnchor`, `skillElements[]`, `evidenceIds[]`,
`forbiddenSemantics[]`. If `actorRole`/`taskMode` are not yet in the
manifest, 7C adds them to the index generator — the UI must not infer
them from prose.
- *Run artifact* (per scenario × mode): ordered `timeline[]` entries
discriminated as `semantic_call` / `semantic_result` /
`control_plane_action` / `protocol_event_ref`; `exposure` (always /
optional-with-grant / control-plane lists); `authorizationRecords[]`
(operation, claims considered, decision, reason, redactions, state-change
ref); `snapshots.before` / `snapshots.after` per entity domain;
`parity` (assertions with class/expected/observed/status, intentional
gaps with rationale, legacy baseline note, verdict).
- **Authority rule (binding):** every rendered fact traces to one of those
records. Grep-able heuristics, UI-side claim math, or re-deriving
dispositions from case IDs are defects. If a view needs a fact the records
lack, extend the record upstream.
- **`DESIGN.md` alignment:** tokens-only values; one component per job;
hierarchy by structure not decoration (domain sections and channels use
surface + position, minimal borders); status vocabulary systematic (§0
labels everywhere); machine values (IDs, anchors, grants, JSON) in
`--pcr-font-mono`; canonical copy “task.”
- **Testing hooks 7F must ship:** component tests for the four inspector
tabs and both transcript channels; keyboard-path tests (listbox, tabs,
region cycle); reduced-motion test; the §7 route determinism test
(two loads → identical settled DOM); accessibility assertions (landmarks,
names, contrast tokens); screenshot capture script for §9.
## 9. Screenshot acceptance matrix
Evidence lands in `.paperclip-local/evidence/capability/` as
`<slug>-<viewport>.png`, at both **1440×900** and **390×844**, captured from
the §7 routes after `data-run-state="settled"`. These twelve shots are the
UX acceptance set for 7F (they also cover the plan’s human-checkpoint
scenario families: heartbeat, planning, blockers, approvals, artifacts,
interactions, manager, multi-hop, restraint).
| # | Slug | Route | Expected visible evidence |
| --- | --- | --- | --- |
| 1 | `explorer-home` | `#/` | Picker with all 16 group facets and counts summing to 106; intro card with corpus stats; no dead panels. |
| 2 | `picker-filtered` | `#/?group=ix&disposition=always_agent_tool&parity=pass` | Active filter chips; filtered count visible; zero-count facet values disabled but legible; Clear filters affordance. |
| 3 | `hb-heartbeat` | `#/case/hb-scoped-wake-01?run=fake&view=transcript` | Control-plane channel entries for scoped-wake routing and checkout (“no agent tool exists for this”), then `get_task_context` agent call; pass banner. |
| 4 | `wk-planning` | `#/case/wk-plan-directive-01?run=fake&view=transcript` | `write_document` call with revision fields, revision-bound `request_human_input` (confirmation), review-parked disposition; no re-plan loop. |
| 5 | `bl-blockers-diff` | `#/case/bl-create-blocked-01?run=fake&view=diff` | State diff: tasks domain shows the new task created blocked; blockers domain shows first-class `blockedByIssueIds`; unchanged domains collapsed. |
| 6 | `ap-approval-denied` | `#/case/ap-mcp-gate-01?run=fake&view=authorization` | Authorization table with a Deny row (missing claim + reason, danger surface, no protected state); “Authorization · 1 deny” tab chip; transcript shows the typed denial card. |
| 7 | `ar-artifacts` | `#/case/ar-upload-before-done-01?run=fake&view=transcript` | `register_deliverable` completing **before** `finish_task` in artifact order; deliverable metadata in the expanded call card. |
| 8 | `ix-interaction` | `#/case/ix-checkbox-01?run=fake&view=transcript` | `request_human_input(kind=checkbox)` call card with options payload; control-plane wake/continuation entry; result inspection entry. |
| 9 | `rf-manager` | `#/case/rf-api-mgr-heartbeat-01?run=fake&view=context` | Context tab: manager role; optional discovery tools listed **with their unlocking grants**; control-plane “no tool” list populated; traceability panel showing source anchor + skill elements. |
| 10 | `mh-multihop` | `#/case/mh-subtask-tree-01?run=fake&view=parity` | Multi-hop assertion rows (child creation, chained blockers) all Pass; forbidden operations section showing “never invoked ✓”. |
| 11 | `rs-restraint` | `#/case/rs-question-only-01?run=fake&view=parity` | Restraint verdict: answer-only transcript with the “No further operations” note; parity forbidden list showing zero state writes; Pass banner. |
| 12 | `rs-secret-redaction` | `#/case/rs-secret-hygiene-01?run=fake&view=transcript` | Redaction chips (`•••` + rule name) in call/result cards; comment content free of the key; parity Pass. |
Mobile captures use the same routes; the segmented control must show the
correct active segment (`Run` for transcript shots, `Inspect` for tab shots)
and no horizontal scrollbar may be present.
Acceptance procedure: UXDesigner reviews all 24 images against this matrix
before 7F is accepted (plan §6: “UXDesigner supplies the interaction map and
reviews screenshots before implementation acceptance”). Any deviation is
either fixed or written into this document as a revision — silent divergence
fails the gate.
## 10. Out of scope / rejected
- No second app shell, no Tailwind/Radix, no dark mode, no markdown
renderer, no command palette, no transcript virtualization — SDK
rejections stand.
- No editing affordances of any kind: fixtures, grants, and policies are
changed in code/YAML, not in the browser. The mode toggle and run/replay
controls are the only mutating-looking controls, and they mutate only the
in-browser demo run.
- No parity “re-judging” in the UI: the Parity tab renders 7E output; it
never recomputes assertions.
- No live-Paperclip or ACPX affordances; the future binding boundary stays a
docs note, not a UI surface.
## 11. Revisions recorded during 7F implementation
Per §9, deviations are written here rather than left silent. All of the
following were found while building the explorer (TASK-16904) and are now part
of the contract.
1. **Timeline entry kinds extended.** §8 listed `semantic_call`,
`semantic_result`, `control_plane_action`, and `protocol_event_ref`. The
agent channel also needs plain model turns and the restraint note §3.2
requires, so the artifact adds `agent_message` and `system_note`. Protocol
event references ride on `semantic_result.protocolEventRefs` rather than
standing as their own entry, keeping the semantic view primary.
2. **Parity is computed by the runtime, not the browser.**
§4.4 and §10 forbid the UI from judging assertions, and they do: the
package-local scenario runner (`src/scenarios/scenario-parity.ts`) produces the
verdict from the traceability expectations and the recorded artifact. The
conformance suite's own per-case result is carried through verbatim in a separately
labelled "Capability conformance suite" block and is never merged into, or
used to overrule, the assertion list. When no conformance report is bundled the block
reads **Not run**.
3. **Actor role, task mode, and scenario claims are declared, not inferred.**
§8 assigned `actorRole`/`taskMode` to the scenario index generator; it shipped
without them. They are now declared in `src/scenarios/scenario-index.ts` as an
explicit fixture/case profile table, alongside the catalog claims each
scenario grants. A claim that cannot unlock any operation in the scenario's
task mode or for its actor role is dropped from the profile, so no scenario
renders a grant chip that unlocks nothing.
4. **The mobile section switcher is a `radiogroup`, not a `tablist`.** §1 asked
for the Live console segmented control. The Live console console renders it only in
compact mode; the explorer keeps one CSS-driven tree, so a `tablist` would
put two tablists in the document and model the three landmarks as tabpanels.
The three regions stay `nav`/`main`/`complementary`, and the only `tablist`
on the page is the inspector's.
5. **Two token-layer overrides, scoped to the explorer.** The SDK gives every
direct `span` child of a disclosure summary `min-width: 0`; a `Badge` is
also a `span` and sets `white-space: nowrap`, so at 390px badges were
squeezed below their own content and pushed the page into horizontal scroll.
The SDK badge also applies `text-transform: capitalize`, which renders
"Optional Tool" and "Not Run" against the §0 vocabulary. Both are corrected
in `examples/scenario-explorer/src/explorer.css` under `.pcr7-shell`; the
frozen `0.1.2` sheet is unchanged. **These are SDK defects worth fixing at
the next SDK revision, not explorer quirks.**
6. **Only the case list scrolls in its own box.** The rails are otherwise in
page flow. §9 evidence is captured full-page, and an inner scroll container
on the rail clipped exactly what the matrix asks to see (all 16 group facets
and their counts). The 106-row case list keeps a bounded height so the page
stays navigable.
7. **Shot 2's route uses `parity=not_run`.** §9 specified
`#/?group=ix&disposition=always_agent_tool&parity=pass`. Parity status is a
property of a *run*, and the picker route runs nothing, so `parity=pass`
selects zero scenarios on a cold load. The recorded route filters on
`not_run`, which is the honest cold-load state and still demonstrates
chips, live counts, disabled zero-count values, and Clear filters.
The following revisions were recorded at the 7F UX gate (TASK-16905,
2026-08-09) after reviewing all 24 captures and a live keyboard/responsive
session at both viewports.
8. **Shot 8's continuation evidence is split across surfaces.** In
`ix-checkbox-01` the continuation appears as the Context tab's
control-plane exposure entry (`route_wake` — "Interaction continuations
are routed by the control plane, not requested again") plus the parked-note
system entry — not as transcript entries. No fixture in the corpus scripts
the continuation-consuming side: only the ten §9 cases have bespoke
fake-agent plans, `ix-checkbox-result-01` falls back to a generic script
that re-raises `request_human_input` (contradicting its own premise), and
`inspect_operation_result` is catalogued but never invoked corpus-wide.
Accepted for 7F because the request side and routing authority are fully
evidenced; the continuation-side fixture upgrade is a filed follow-up, not
a silent gap.
9. **Forbidden-operations lists are empty corpus-wide.** All 106 cases record
empty `forbiddenSemantics`, so §9 rows 10–11's "never invoked ✓" rows
cannot render from real fixture data. Restraint is proven by state-diff
absence plus the intentional-gap rationale, and the Parity tab says so
explicitly ("This case records no forbidden operations; restraint is
proven by the absence of state writes in the diff"). The populated-list
path (`never invoked ✓` / violating transcript reference) exists in
`inspector-panels.tsx` and is unit-tested; the explanatory empty state is
accepted as the §9 rendering.
10. **Keyboard deltas, accepted as interim with a filed follow-up.** The
picker listbox uses selection-follows-focus (arrows select immediately —
a valid listbox pattern the map did not anticipate); type-ahead and
Enter-moves-focus-to-run-header are not implemented; `F6` region cycling
is replaced by a "Skip to run view" skip link plus landmark navigation;
denial entries do not announce assertively (only a fail verdict does).
§7's `inspector-tab-<name>` testids are absent (the frozen SDK `Tabs`
exposes roles, not testids) — tests target tabs by accessible name.
An unknown case ID in the route falls back silently to the home view
rather than a named error card.
11. **Redaction chips render the rule name inline**, not in a tooltip:
`••• redacted <rule>`. Inline beats hover-only disclosure (works on
touch, visible in captures); recorded as the new §5 contract.
12. **Evidence captures expand disclosures.** The recorder opens every
`main`/`aside` disclosure before capturing so argument payloads, decision
records, and final-state JSON are visible in the §9 images. The live
defaults are collapsed exactly as §4.3 specifies (verified in the gate's
live session); the expanded captures are a deliberate evidence choice,
not the default density.
13. **UX-gate keyboard follow-up.** The explorer handles `F6` for region
cycling when the browser delivers that key to the page. Chromium reserves
bare `F6` for browser chrome, so the documented in-page alternative is
`Control+Period`; `aria-keyshortcuts` exposes both bindings. Either binding
cycles picker (`nav`) → run (`main`) → inspector (`complementary`), with
`Shift+F6` cycling in reverse where bare `F6` is available. The skip link
remains unchanged.