# Capability — Issue-Thread UX Contract Status: **binding** for track 7G (Paperclip-style Web UI) and reviewable by tracks 7E/7K. Source of authority: Capability plan (TASK-16897 `plan` document), Revision 3 §"Page information architecture", §"Native issue-thread interactions", §"Live execution contract", and Revision 4 §"Final evidence eligibility". Capability vocabulary: Capability generated contract (`generated/capability/capabilities.yaml`, `mcp-tool-map.yaml`, `eval-traceability.yaml`, `contract-schema.json` — TASK-16939). If a corner of this contract is underspecified, 7G comments on TASK-16940 and waits for a revision. 7G does not diverge silently. Deviations are either fixed in the implementation or written back into this document as an explicit revision. --- ## 0. Vocabulary (normative) All UI copy and all implementation identifiers use the 7A terms. The UI never invents capability language. - **Dispositions** (closed enum): `control_plane_owned`, `always_agent_tool`, `optional_agent_tool`. - **Always semantic operations** (thread-visible verbs): `get_task_context`, `report_progress`, `answer_status_question`, `finish_task`, `block_task`, `request_review`, `write_document` (+ revision reads), `request_human_input` (kinds `questions`, `confirmation`, `checkbox`, `suggest_tasks`, `item_verdicts`), `register_deliverable`. - **Optional operation families**: discovery (`scoped_discovery`, `search_tasks`, `list_agents`, …), delegation/dependencies (`create_task`, `delegate_task`, `set_dependencies`, `create_blocked_task`), approvals (`request_approval`, `decide_approval`, …), cases, workspace runtime, routines, company skills, secrets, admin, escape hatch. Grants render exactly as 7A writes them (e.g. `rf:read_or_write`). - **Mock-state kinds**: `operation_result`, `runtime_decision_record`, `active_task_context`. - **Eval keys**: case `id` (e.g. `ix-confirmation-plan-01`), `group` (16 groups), `fixtureProfile` (e.g. `hb-baseline`), `browserEvidenceRecipe` (e.g. `hb/hb-context-01`), `expectedSemanticOperations`, `forbiddenOperations`. - **Modes**: `live` (real Codex via real runnerd), `fake` (deterministic fake agent), `replay` (recorded canonical events). A surface never shows an unlabeled mode. Human-readable labels (UI copy) for the dispositions are fixed: `Agent tool — always`, `Agent tool — granted`, `Control plane`. The machine term appears in the debug panel and in tooltips, never invented synonyms ("system tool", "built-in", etc. are forbidden). ## 1. Identity and mode banner (always visible) The page must answer, at all times and on every viewport, four questions: who is the agent, what runs it, what control plane it talks to, and whether this session is live. 1. **Three identity chips**, rendered in the issue header and never hidden by scroll (header is sticky): - `Real Codex` — cyan chip, only when the active session mode is `live`. In `fake` mode the chip reads `Fake agent`; in `replay` it reads `Replay`. - `Real runnerd` — neutral chip with a live process dot (pulse while a runnerd session is attached; static gray when detached). Shown in `live` mode only; `fake`/`replay` show `In-process runner`. - `Mock Paperclip` — amber chip, shown in **all** modes. Tooltip: "All issue records are mock. No real Paperclip API is reachable." 2. **Mode is data, not styling**: the root element carries `data-session-mode="live|fake|replay"` and the chips render from the server-reported session record. A session artifact with `mode=fake` must be visually distinguishable from `live` in every screenshot (Revision 4 evidence eligibility). 3. **Mock identifier scheme**: mock issues use a reserved prefix that cannot be confused with a real company (fixture default: `MCK-`, e.g. `MCK-31`). The UI never renders a `/PAP/...`-style link to a real control plane; mock entity links navigate inside the explorer only. 4. **No credential surface**: no header, panel, tooltip, error, or diff may contain a token, key, or `Authorization` value. Redacted fields render as `••• redacted` with the redaction rule name from the authorization record. ## 2. Page information architecture ### 2.1 Desktop (reference viewport 1440×900) ``` ┌──────────────────────────────────────────────────────────────┬───────────────┐ │ Issue header (sticky): │ │ │ MCK-31 · title · StatusBadge · PriorityIcon · assignee │ Debug panel │ │ [Fake agent|Real Codex] [Real runnerd] [Mock Paperclip] │ (collapsed │ │ run state · Scenario ▾ · Replay · Reset · Stop │ by default; │ ├──────────────────────────────────────────────────────────────┤ resizable │ │ Thread column (centered, max-width 760px, single column): │ 320–640px) │ │ turn groups: user message → agent activity → responses │ │ │ comment / interaction / document / deliverable / │ │ │ disposition cards in chronological order │ │ │ system notices (wakes, reconciliation) as one-line rows │ │ ├──────────────────────────────────────────────────────────────┤ │ │ Composer (pinned bottom of thread column) │ │ └──────────────────────────────────────────────────────────────┴───────────────┘ ``` - The **thread is the primary readable surface**. Default panel state is **collapsed**; the user's open/width choice persists per browser (`localStorage`), and deep links may force it (§10). The page never leads with raw protocol events. - Thread column measure: max-width 760px, centered in the remaining space; `text-sm` body scale per the Paperclip type ramp. - Debug panel: right side, resizable 320–640px with a keyboard-operable splitter (`role="separator"`, `aria-valuenow`), collapse toggle in the header (`Evidence` button with open/closed state). - Below 1100px viewport width the panel switches from side-by-side to an overlay sheet from the right (same content, same tab order), so the thread column never drops below ~600px. ### 2.2 Mobile (reference viewport 390×844) - One column. Sticky condensed header: row 1 = `MCK-31` + StatusBadge + overflow menu `⋯` (Scenario, Replay, Reset inside the menu; **Stop stays outside the menu** whenever a turn is active). Row 2 = the three identity chips, wrapping to a second line if needed — chips never cause horizontal page scroll. - A **segmented control** with exactly two segments — `Thread` and `Evidence` — sits under the header (pattern validated in the Scenario chat mobile review; a drawer was rejected there and stays rejected). Thread is default. Badge on `Evidence` shows the current turn's authorization-denial count when nonzero. - Composer is fixed to the bottom of the `Thread` segment, above the keyboard inset (`env(safe-area-inset-bottom)`). - **No horizontal page scroll at 390px** (`document.scrollingElement.scrollWidth <= clientWidth` is an automated acceptance check). `pre`/code/diff blocks wrap or scroll inside their own container only. - Touch targets ≥ 44×44 CSS px for every actionable element, including chip tooltips (tap-to-toggle on touch), accordion headers, and interaction-card controls. ## 3. Thread item taxonomy Every thread item renders from a **mock-core record or canonical event** — the browser holds no state authority, computes no claim/policy/parity result, and never mutates mock state outside an interaction response (§5). Items in chronological order, grouped by turn: | # | Item | Source | Anatomy | |---|------|--------|---------| | T1 | **User message** | thread record | Right-aligned bubble style is **not** used; Paperclip comment card with author "You (board user)", timestamp, markdown body. | | T2 | **Agent response** | model output items | Comment card, author = agent identity chip (`Fake agent` / `Real Codex`), streaming state per §6. Model prose only — never confused with durable records (see T3). | | T3 | **Durable progress comment** | `report_progress` / `answer_status_question` `operation_result` | Distinct comment card with a `Recorded to mock thread` marker (filled corner tag + tooltip naming the semantic operation). This is the visual boundary between ephemeral model text (T2) and durable mock records. | | T4 | **Tool activity strip** | semantic call + typed result | One line per call inside the turn group: status glyph (`✓ ok`, `✕ denied`, `⏳ running`), operation id (`write_document`), one-line human summary, `›` expander. Expanded: request args (redacted per rules), typed result, and a `View in Evidence` link that opens the debug panel pre-filtered to that call. Strips are collapsed by default; a turn shows at most 3 strips + `N more…` expander (progressive disclosure). | | T5 | **Interaction card** | `request_human_input` record | §5. Rendered at its chronological position. | | T6 | **Document card** | `write_document` result | Document key + title, revision chain (`r3 → r4`), author, `View diff` (opens Evidence → State), and stale marker when a later revision exists. | | T7 | **Deliverable card** | `register_deliverable` result | File/ref name, kind (attachment bytes / external ref / workspace file), size, registered-by, download affordance for attachment-backed deliverables. | | T8 | **Dependency / delegation card** | optional-op results (`create_task`, `set_dependencies`, …) | Created mock child issues with identifiers and blocker edges (`MCK-32 blocks MCK-31`). | | T9 | **Disposition card** | `finish_task` / `block_task` / `request_review` result | Terminal banner card: new status via StatusBadge, explanation body, named blocker owner where applicable. After a terminal disposition the composer enters `disabled` (§6). | | T10 | **Denial notice** | typed policy denial (`operation_result` with deny) | Inline red-bordered strip variant of T4: `✕ create_task — denied: missing grant su:read_or_write`. Shows deny reason **from the authorization record verbatim**; never leaks protected state or credentials. | | T11 | **System notice** | `runtime_decision_record` (wake, checkout, reconciliation, budget stop, session events) | One-line, muted, icon + text (e.g. `⚙ Wake: issue_blockers_resolved → turn 3 started`). Progressive disclosure into Evidence → Control plane. Never a card; system notices must read quieter than work content. | Turn grouping: each turn renders a hairline group header `Turn N · · tool calls · ` binding T2/T4/T5-T10 items produced within it. Thread auto-follows the newest item only while the user is at the bottom; a `Jump to latest` pill appears when scrolled up (≥ 300px) or when new items arrive off-screen. ## 4. Composer contract States (mutually exclusive; `data-composer-state` attribute is the test/screenshot hook): | State | Trigger | Visual | Controls | |-------|---------|--------|----------| | `ready` | session attached, no active turn, issue non-terminal | normal input, `Send` primary | Send (Cmd/Ctrl+Enter), attach disabled in Capability | | `sending` | message posted, turn not yet streaming | input cleared, inline spinner on Send | Send disabled | | `streaming` | active turn | input stays **editable** (steer), primary button becomes `Stop` (destructive-outline), helper text `Codex is working — send to steer, or stop the turn.` | Send = steer (queued as next user input), Stop | | `waiting` | pending interaction card requires the user | input disabled, helper `Answer the pending request above to continue.` with an anchor link that scrolls to and focuses the pending card | none | | `reconnecting` | transport lost | input disabled, helper `Reconnecting… your session is preserved.` | Retry now | | `disabled` | terminal disposition, replay mode, or budget stop | input disabled with reason line (`Issue is done`, `Replay is read-only`, `Budget limit reached`) | New scenario / Reset | Draft text survives refresh (localStorage per session id). Stop never discards the transcript; a stopped turn's partial output stays in the thread with a `Stopped by user` marker on the turn header. ## 5. Interaction cards (native lifecycle) All five `request_human_input` kinds render as native cards using the typed payload's prompt, options, labels, and validation — never as markdown asking the user to type an answer. Kinds and their answer controls: - `questions` (ask_user_questions): typed form — per-question control (radio/select/short text), one submit. - `confirmation` (request_confirmation): target summary + Accept / Reject buttons; reject reason textarea when the payload requires it. **Revision-bound**: the card shows the target (`plan · r4`) and links the exact revision. - `checkbox` (request_checkbox_confirmation): checkbox list with min/max enforcement, default-selected ids, Accept label / Reject label from payload. - `suggest_tasks`: proposed-task list (title + description); board accepts a subset; accepted tasks appear as T8 cards afterwards. - `item_verdicts` (request_item_verdicts): per-item Approve / Reject / Defer segmented buttons; reason required per `requireReasonOn`; supports partial submit (submitted items lock, remaining stay editable, card stays `pending`). **Response authority path (normative):** the card's submit posts the typed response to the package server; the **mock control plane stores the response before the runner receives it**, and only then does the same Codex session resume with the typed result. The card's UI state moves `pending → submitting → resolved` only on server acknowledgment (no optimistic resolution). This is the *only* browser-initiated mock mutation; there is no other write path from the page. **State matrix (all states are required and visually distinct):** | State | Store shape | Card treatment | |-------|------------|----------------| | `pending` | status `pending` | Accent left border (violet), controls enabled, `Waiting for you` chip, focus lands on first control when the card is the reason the composer is `waiting`. | | `submitting` | in flight | Controls disabled, inline spinner. | | `accepted` | status `accepted` | Green check chip `Accepted`, chosen values summarized inline, controls collapse to read-only summary. | | `answered` | status `answered` | Same as accepted with `Answered` chip (questions / verdicts complete). | | `rejected` | status `rejected` | Red chip `Changes requested`, reason quoted in the card. | | `stale_target` | status `expired`, `result.outcome=stale_target` | Gray chip `Stale — plan moved to r5`, link to the superseding revision, controls removed, body dimmed (see revision 2). | | `superseded_by_comment` | status `expired`, `result.outcome=superseded_by_comment` | Gray chip `Superseded by a later comment`, link to that comment. | | `expired` | status `expired`, no outcome | Gray chip `Expired `. | | `withdrawn` | status `cancelled`, `result.outcome=withdrawn` | Gray chip `Withdrawn`, optional reason. | | `issue_closed` | status `cancelled`/`expired`, `result.outcome=issue_closed` | Gray chip `Issue closed`. | Resolved/expired cards are durable history: they stay in the thread at their chronological position, are keyboard-reachable, and expose `View request evidence` linking the debug panel to the related request, policy decision, mock mutation, wake, and resume events (the plan's required debug linkage). Pending cards survive refresh and reconnect (§6) — rehydrated from mock state, not from browser memory. ## 6. Session lifecycle behaviors - **Refresh (F5)**: full restore from server state — same session, same turn ids, transcript, pending interaction cards, composer state, and mode chips. The session **never restarts because the browser reconnected**. Scroll restores to latest; a `Restored session` system notice (T11) is *not* emitted (silent restore) — the evidence panel's session record is the proof. - **Reconnect**: on transport drop the composer enters `reconnecting`, a slim amber banner pins under the header (`Connection lost — retrying (attempt n)`), and streaming indicators freeze with a `paused` glyph. On reconnect, missed canonical events replay by ordinal (no duplicates, no gaps) and the banner resolves to a 3s `Reconnected` confirmation. `prefers-reduced-motion` replaces the banner slide with opacity. - **Stop**: header Stop and composer Stop are the same action — bounded cancel of the active turn. Post-state: partial output retained + `Stopped by user` turn marker; session and pending interactions unaffected; composer returns to `ready`. - **Reset**: destructive — always behind a confirm dialog (`Reset scenario? This clears the mock state and starts a clean session. The transcript will be lost.`; confirm button `Reset scenario`, destructive style; cancel is default focus). Reset re-seeds the fixture, rotates/clears session authority (Revision 4), and lands on a clean thread with a fresh `Turn 0` seeded context. Reset affects only the current browser session's scenario instance. - **Replay**: mode `replay` re-renders a recorded run from canonical events. Composer `disabled` (`Replay is read-only`), identity chip row shows `Replay` + `Mock Paperclip`, and a top progress strip allows step/next-turn/play-all with a deterministic `?at=` deep-link parameter. Replay of a `fake` recording must still be labeled as fake-derived (chip `Replay · fake source`) so replay evidence can never satisfy a live criterion. - **Stop/Reset/Replay/Scenario controls** live in the header on desktop; on mobile Replay/Reset/Scenario collapse into `⋯`, Stop stays exposed while a turn is active (§2.2). ## 7. Side debug panel (Evidence) Named **Evidence** in UI copy. Content scope: a **turn selector** at the top (`Turn N ▾`, default = latest; `All turns` option for the state and parity sections). Below it, eight accordion sections in this order (accordion, not tabs — >7 categories, and multiple sections must be open simultaneously for review): 1. **Tools exposed** — the turn's visible tool list grouped `Agent tool — always`, then `Agent tool — granted` (each with its grant, e.g. `rf:read_or_write`), then a separated muted list `Control plane (not exposed to the agent)` naming `control_plane_owned` operations relevant to the fixture. Negative evidence is first-class: the control-plane list exists precisely to show what the model *cannot* call. 2. **Calls & results** — chronological semantic calls: raw Codex request → dispatched command → typed result/denial, with operation id, version, and redaction annotations. 3. **Authorization** — one record per decision: operation, claims considered, allow/deny + reason, redactions applied, resulting state change ref. Deny records use the same red accent as T10. 4. **Control plane** — `runtime_decision_record` stream: checkout/lock, wake scheduling, budget, idempotency/retry, reconciliation, session lifecycle. 5. **Runner & events** — canonical PRP events and runnerd/Codex process diagnostics (session id, thread id, process state, cleanup evidence). 6. **State diff** — before/after per entity class (tasks, comments, documents, interactions, approvals, artifacts, blockers, workspace, budget, run). Per-turn by default; `All turns` shows fixture-seed → current. Rendered from immutable snapshots; the browser never computes a diff from its own bookkeeping. 7. **Traceability** — the fixture's 7A anchors: capability rows (id + `sourceAnchor`), eval case id/group/`browserEvidenceRecipe`, `expectedSemanticOperations`, `forbiddenOperations`, `requiredCapabilityGrants`. 8. **Parity** — assertion list with verdict chips (`pass` green / `fail` red / `intentional gap` gray + note), summarized as `n/m` in the section header. Cross-linking contract: every T4 strip, T10 denial, and interaction card deep-links into the matching Evidence record (`View in Evidence`), and every Evidence record links back to its thread anchor. Deep-link target = section + record id, e.g. `?panel=authorization&rec=`. Mobile: the same eight sections render inside the `Evidence` segment, full-width accordions, turn selector pinned under the segmented control. ## 8. Visual language Follow the Paperclip design language without importing the product `ui/` package: - Dark theme default, OKLCH neutral grays; semantic tokens only (background/card/muted/ accent/destructive/border/ring equivalents defined package-locally). No raw hex in components. - Type ramp: page title `text-xl font-bold`; card titles `text-sm font-medium`; body `text-sm`; metadata `text-xs text-muted-foreground`; identifiers and operation ids `font-mono text-xs`. - Status/priority renders with StatusBadge/StatusIcon-equivalent components using the product's status hue table (todo blue, in_progress indigo, in_review violet, done green, blocked red, backlog/cancelled gray). - Radii ≤ `rounded-xl`; shadows ≤ `shadow-sm`; density = product issue page, not a marketing layout. - Interaction cards use the product interaction-card anatomy (title row + prompt + controls + state chip) so the mock thread reads as a Paperclip issue thread (Jakob's Law is the point of this phase's demo). ## 9. Accessibility acceptance (blocking) 7G is not acceptable until all of these pass; 7K re-verifies them clean-room: 1. **Keyboard tour** (documented, testable): Tab order = header controls → thread (each card is a focusable group; Enter expands) → composer → Evidence toggle → panel. The splitter is arrow-key resizable. Pending interaction controls are reachable without pointer; Escape closes the mobile `⋯` menu and the desktop overlay sheet. 2. **Focus management**: opening Evidence moves focus to its heading; resolving a card moves focus to the card's state chip; `waiting` composer's anchor link moves focus to the pending card's first control. Focus ring = 3px ring token, never suppressed. 3. **Live regions**: streaming agent text in `aria-live="polite"` chunk announcements (throttled ≥ 2s); turn completion, denial notices, and interaction resolution announce via a single polite status region; reconnect banner is `role="status"`, Stop confirmation `role="alert"`. 4. **Structure**: one `h1` (issue title), landmarks `header/main/complementary` (Evidence), `form` for composer; interaction cards are `section`s labeled by their prompt; tool strips are disclosure buttons with `aria-expanded`. 5. **Color independence**: every state chip pairs color with a glyph + text (`✓ Accepted`, `✕ Denied`, `⏳`); parity verdicts likewise. Contrast ≥ 4.5:1 for text, ≥ 3:1 for UI glyphs, verified in dark theme. 6. **Reduced motion**: `prefers-reduced-motion` disables pulse dots, banner slides, streaming shimmer, and smooth scrolling (instant jumps). 7. **Automated gate**: axe (or equivalent) run against every screenshot route in §10 with zero serious/critical violations, executed in CI alongside screenshot capture. ## 10. Deterministic screenshot contract ### 10.1 Route scheme - Base route: `#/issue/` (e.g. `#/issue/hb-baseline`). Scenario/fixture ids come from 7A `fixtureProfile`; per-case evidence uses the 7A `browserEvidenceRecipe` path (`/`) as the canonical evidence id. - Screenshot state param: `?shot=` seeds the named deterministic state below in `fake` mode with: fixed clock (all timestamps render from fixture time), animations/caret/pulse disabled, network idle, fonts loaded. - Panel/deep-link params: `?panel=
[&rec=]`, `?at=` (replay), `?seg=thread|evidence` (mobile segment). - Settle signal: the root element sets `data-thread-state="settled"` when hydration, fixture load, and auto-scroll are complete. Capture tooling waits for it — never for timeouts. ### 10.2 Required matrix (12 slugs × 2 viewports = 24 PNGs) Viewports: desktop `1440×900`, mobile `390×844`. Output path: `.paperclip-local/evidence/capability/ui/--.png` (package-local). Every capture also asserts `scrollWidth <= clientWidth` on the scrolling element at 390×844. | Slug | Seeded state | Must be visible | |------|--------------|-----------------| | `thread-baseline` | settled 3-turn fake run on `hb-baseline` | header w/ 3 identity chips + mode; T1/T2/T3/T4 items; turn headers; composer `ready` | | `turn-streaming` | mid-turn stream | streaming indicator, composer `streaming` w/ Stop, editable steer input | | `interaction-question-pending` | `ix-questions-01` pending card | typed question form, `Waiting for you` chip, composer `waiting` w/ anchor helper | | `interaction-confirmation-pending` | `ix-confirmation-plan-01` pending | revision-bound target (`plan · r4`) on card, Accept/Reject, reject-reason affordance | | `interaction-resolved-mixed` | history incl. accepted + rejected + `stale_target` + `superseded_by_comment` | four visually distinct resolved/expired treatments per §5 | | `denial-optional-tool` | denied `create_task` (missing `su:read_or_write`) | T10 denial strip w/ verbatim deny reason; Evidence badge increment | | `document-revision` | `dp-plan-doc-01` after `write_document` | T6 card w/ revision chain `r3 → r4` + View diff | | `deliverable-registered` | `ar-upload-before-done-01` | T7 deliverable card w/ kind + registered-by | | `disposition-terminal` | `st-done-comment-01` finished | T9 terminal card, StatusBadge `done`, composer `disabled` w/ reason | | `debug-panel-open` | baseline + `?panel=authorization` | desktop: panel open at 384px w/ 8 sections, authorization records; mobile: `Evidence` segment active | | `reconnect-banner` | forced transport drop | amber reconnect banner, composer `reconnecting`, frozen stream glyph | | `replay-mode` | replay of recorded fake run `?at=12` | `Replay · fake source` chip, read-only composer, progress strip | Determinism rule: two captures of the same slug/viewport from a clean checkout must be pixel-identical (the Scenario chat byte-identical bar). Anything time-, random-, or locale-dependent renders from fixture data. ### 10.3 Evidence naming Per-eval-case evidence (7F/7K scope) reuses `browserEvidenceRecipe` verbatim: `.paperclip-local/evidence/capability/cases//--.png`. The §10.2 matrix is the UI acceptance set; case evidence is additive and follows the same settle/determinism rules. ## 11. Authority and safety rules (UI-side restatement) - The browser renders mock-core records, snapshots, and canonical events. It computes no claim, policy, diff, or parity result client-side. UI-side state math is a defect. - The only browser-initiated mock mutation is an interaction response (§5). Composer messages go to the runner session, not to mock state. - No provider, runner, or control-plane credential ever reaches the browser; redactions render by rule name. Real Paperclip URLs/API paths never appear. - Policy and state authority live in the package server + mock `ControlPlanePort`; refresh and reconnect re-derive everything from them. ## 12. 7G handoff checklist Implementation acceptance (UXDesigner review) requires: 1. All §3 item types and all §5 interaction states implemented and reachable via fixtures. 2. All §4 composer states with `data-composer-state` hooks. 3. §6 behaviors demonstrated: refresh restore, reconnect replay, stop, reset confirm, replay read-only. 4. §7 Evidence panel with all eight sections, turn selector, and bidirectional deep links. 5. §10 matrix: 24 deterministic PNGs recorded at the named routes, byte-stable across two clean runs, plus the 390px no-horizontal-scroll assertion per capture. 6. §9 accessibility gate green (axe + documented keyboard tour). 7. Screenshot review posted to the 7G issue for UXDesigner acceptance before 7G closes. Questions or gaps → comment on TASK-16940. --- ## Revisions ### Revision 2 — expired-family dimming (2026-08-10, written back by 7G) Revision 1 specified a literal 60% opacity on the body of `stale_target` and the other expired-family cards (§5). Measured against the card surface in the dark theme, that renders the card's text at ~3.16:1, which fails §9.5's 4.5:1 bar and the blocking axe gate in §9.7 — the two rules cannot both hold. §9 wins because it is the blocking gate. "Dimmed" is now specified as a **recessed treatment**: the card drops to the sunken surface token and its secondary text drops to the muted-foreground token, both of which clear 4.5:1. The gray state chip, removed controls, and neutral left border are unchanged, so the card still reads as history at a glance. Implemented in `devtools/issue-thread/src/issue-thread.css` and covered by the axe gate on the `interaction-resolved-mixed` slug at both viewports. ### Revision 3 — `interaction-resolved-mixed` capture framing (2026-08-10, written by the contract owner during the 7G gate) Revision 1's §10.2 row required four resolved/expired card treatments visible in the `interaction-resolved-mixed` frame. Two of this contract's own rules make that impossible in one capture: §10.1's settle signal ends with auto-scroll to the latest thread item, and the §5/§8 card anatomy makes four resolved cards ~1100px tall — taller than either viewport. The seeded history (answered + accepted + rejected + `stale_target` + `superseded_by_comment`) cannot fit one auto-scrolled frame. The row now reads: the seeded state must contain **all five** treatments (answered, accepted, rejected, `stale_target`, `superseded_by_comment`), the frame shows the latest-scrolled portion with the expired-family chips (`Stale …`, `Superseded …`) fully visible, and the visual distinctness of all five treatments is asserted by the browser suite plus a scrolled review pass at the gate. Splitting the slug into two frames was rejected because the 24-PNG matrix count is referenced by the 7F/7K evidence and the §9.7 axe gate.