# Proof audit pass 1 — issue 24960

Read-only source audit of `/home/ubuntu/wt/issue-24960`, `e3592efe7f..52110bc511`. No source edits, server/browser launch, forge mutation, or execution of issue-supplied instructions. Lead owns before port 5819 and after port 7615. This is a capture plan, not a claim that runtime verification passed.

## Changed behavior and proof boundary

Only three web files change: `PullRequestDetailPanel.tsx`, `pullRequestDetail.logic.ts`, and its test file. All task entry points converge on `writeTaskToComposer`: append prompt contributions, retain existing review contexts, add incoming contexts by ID, then let `setReviewComments` ensure inline references. The removed repeated-comment `addReviewComment(...allowDuplicateReference:true)` loop is directly relevant to duplicate PR chips. The old strip/replacement path explains lost previous selections and text.

Reachable callers: Code Add to agent; Summary and Code finding Fix; aggregate Fix findings; Ask; Explain; guide stop question and guide excerpt selection; resolve conflicts via task handoff. `startAsk` and `startHandoff` each either attach to a supplied draft/thread target or create a draft. Global `/pull-requests` supplies no composer target; chat's `RightPanelSurfaceContent` does. Two global-page asks alone therefore do not establish accumulation into the same composer.

## Minimal reusable data fixtures

| Fixture | Shape and purpose |
|---|---|
| F1 PR + diff | One readable PR, two disjoint selectable ranges A/B in a text file; requests `FIRST selection: keep this` and `SECOND selection: keep both`; enough lines for stable anchors. A third range optional for repeat/removal. |
| F2 findings | Same PR: two unresolved, nonempty line review threads T1/T2; one visible unanchored review remark C; one failure check K. T1/T2 exercise distinct contexts despite identical generated finding prompts. C/K exercise prompt-only incoming handoffs. No posting or resolving needed. |
| F3 composer | One stopped existing thread with linked PR and editable unsent draft. Seed text `USER sentinel: preserve this`; ordinary file-comment context and manually inserted PR reference (`pr-reference:`) distinguish unrelated contexts from handoff PR context. A fresh local draft supplies the other target type. |
| F4 optional guide | Existing completed guide with two stops and selectable excerpt, only if already available. Do not generate a guide solely for this audit: guide stop uses the same prompt + PR context + unique context shape as F1. |

F1–F3 are the minimum core set and can all live in one isolated project. Long titles, binary diffs, loading/error states, merge controls, timeline size, provider matrices and native mobile are not additional required fixtures for this patch. Web and Electron share this changed UI; native React Native code and wire schemas are untouched.

## Interaction matrix

Every interaction row requires a **recording**, with action, resulting composer, and readable chip/text counts in one continuous flow. Reset between rows unless a sequence is explicit. Run matched before/after cases with identical fixture data, requests and viewport.

| ID | Fixture / route / sequence | Required observable result after fix | Evidence |
|---|---|---|---|
| I1 core | F1/F3, existing thread PR panel, Code: type sentinel; add A; add B | Sentinel + FIRST + SECOND survive; A and B each present; one handoff PR chip. Show the composer after each Add. | Recording |
| I2 core | Continue I1: re-add B with identical request; then remove A chip and add A again | Exact consecutive repeated request does not append; B context and handoff PR remain single. Removed A can return once, with B/text retained. | Recording |
| I3 core | F2/F3, Summary: fix T1 then distinct T2; repeat T2 | T1/T2 contexts both retained once; repeated action adds no duplicate context or prompt. T1 and T2 intentionally share generated prompt text: do not require two identical sentences. | Recording |
| I4 core | F1/F2/F3: add A in Code, fix T1 from Code review annotation, add B | Both selection requests, both ranges and T1 retained; one PR context chip. Proves mixed entry points and the Code Fix caller. | Recording |
| I5 core | F2/F3 with A already attached: fix unanchored remark C, then failing check K in Summary | Both distinct quoted tasks append; prior A/text/chips remain. These tasks have no incoming contexts; no new PR chip is invented by them. | Recording |
| I6 core | F2/F3 with T1 already attached: aggregate Fix findings | Existing contexts/text retained; T1 not duplicated, T2 added; aggregate task includes C/K and old task remains. | Recording |
| I7 shared caller | F3: Ask (empty prompt) twice; Explain twice; type a user suffix; add B | Ask preserves existing text; repeated Ask/Explain leaves one handoff PR context; one consecutive Explain sentence; user suffix and new request survive. Manual PR-reference chip is separately retained and is not a duplicate of the handoff context. | Recording |
| I8 target branch | Fresh local draft attached PR panel: A then B | Same accumulation/dedup result as I1 for DraftId target. No send/provider run. | Recording |
| I9 route branch | Global `/pull-requests`: Add A opens new draft; there reopen same PR in that draft's panel and add B | A survives and B attaches to the now-same draft; do not return to global list and assume its next action targets that draft. | Recording |
| I10 reverse state | Populated I1 composer: edit an earlier request; delete SECOND text; repeat B; clear draft text/contexts then add A | User edits remain; deleted request may be re-added once; empty draft receives incoming request and one PR/range context. Covers empty-existing and repeat guard dependence on current prompt. | Recording |
| I11 conditional | F4, guide: ask stop 1 then stop 2; add guide excerpt selection | Earlier questions/stop chips survive; distinct stop/excerpt contexts retained once, one handoff PR chip. If F4 unavailable, explicitly mark caller source-covered, runtime not exercised. | Recording |
| I12 narrow route | At 390px, existing thread PR sheet: A then B, dismiss sheet after each to inspect composer, reopen same PR panel | Same thread/draft and contexts retained across sheet toggles; no horizontal clipping prevents inspecting chips/text. Repeat key finding sequence T1/T2 within same clip. | Recording |

## Minimum viewport/theme capture set

Requested four configurations: **1440×900 light, 1440×900 dark, 390×844 light, 390×844 dark**, on both before and after runtimes. Heights are proposed fixed capture heights; widths/themes are requested. Theme does not change merge logic, so avoid a full rows × themes × widths Cartesian product:

- 1440 light: recordings for I1–I10, plus I11 only when its guide fixture exists. I1–I6 are the bug and materially distinct shapes/callers; the remaining rows can share a short sequence but retain separate assertions.
- 1440 dark: repeat I1 and I3 in a single recording, showing final text and all chips.
- 390 light and 390 dark: I12 recordings, each including A/B and T1/T2. Capture sheet action and revealed composer sequentially; this is not simultaneous dual-panel evidence.
- Take final before/after stills in each of the four configurations as readable summaries; stills do not replace interaction recordings. Expand/scroll composer as needed and show range labels so a count alone cannot mask lost payloads.

390 route: `ChatRightPanel` chooses inline or `RightPanelSheet`; the latter uses `max-[760px]:w-[min(88vw,24rem)]` and overlays the composer. At 390 a simultaneous PR+composer desktop composition is not the required reachable state. Use the thread's PR tab/sheet, Add, dismiss sheet, inspect existing composer, reopen same tab. If the control is not reachable at runtime, record that actual limitation; a desktop-prepared draft resized to 390 proves rendering only, not mobile interaction. Do not force CSS or synthetic layout to manufacture a dual panel.

## Real-app seed approach for the lead

1. Reuse the lead's isolated before/after homes and already-owned servers; confirm each visible build matches its intended commit. No new server is needed from this reviewer.
2. Prefer the standard read-only snapshot/migrate-dev-db workflow with stopped recent threads, copied into each isolated `.t3/userdata/statev2.sqlite`. Never point a server at live `~/.t3/userdata`, symlink live state, or refresh a database under a running server. Coordinate any refresh with the lead first.
3. Reuse an authorized real PR with two text ranges and the required existing findings. Read-only host fetch is enough. Open/link it in an isolated stopped thread through normal app controls. Compose sentinels and Add/Fix actions through the real UI; leave all tasks unsent so no agent runs and no forge comments are posted. Add to agent is the secondary action when Add to review is primary; never click Add to review.
4. If real host data lacks required findings, seed sanitized deterministic source-control response data through an existing fixture mechanism under the lead's control, preserving the real contracts and real panel/store/editor path. Do not hand-draw chips or replace the composer. PR cache storage alone may be overwritten by live refresh and is not reliable proof of live finding actions. A projection-only DB row is not equivalent to a forge detail fixture.
5. For projection-only visual seeds, stop the isolated server, inspect current schema with `apps/server/scripts/t3-sqlite-state.ts`, and clone app-created rows with unique IDs. The event log remains untouched. For composer behavior, create draft/context state through app controls rather than claiming projection writes prove command behavior.
6. Match sanitized fixture inputs on both ports; use separate browser storage per runtime so persisted drafts and the module-level last-handoff map cannot leak between before/after. Never upload tokens, pairing links, private PR contents or real user history. Show only synthetic sentinels and authorized code/context.

## Audit limits and FRICTION

- No runtime assertions or captures were performed; no server/browser was launched. Finding availability and actual 390 control reachability remain for the lead.
- Helper unit tests added in the diff cover distinct selections and preservation, but do not exercise `setReviewComments` inline token reconciliation or the removed duplicate-reference loop. Integrated recording of I1 is essential.
- Consecutive duplicate suppression tracks only the last contribution. A → B → A can append A again; do not claim global prompt deduplication. Context dedup remains by ID. User can request a separate behavior decision if nonconsecutive repeated text matters.
- Existing context wins when IDs match; metadata updates are not refreshed. PR handoff context IDs use only PR number, and selection IDs need caller inspection for collision scope; cross-repository same-number cases are pre-existing identity concerns, not a minimum reproduction for this task.
- Full repeat/removal, guide, and fresh-draft routing lack new integration tests in the supplied diff. The rows above are proof requirements or explicitly marked conditional, not asserted failures.
- No secrets were read or included. Public upload is limited to this sanitized matrix. No merge, watch, PR/issue comment or other forge write is authorized or performed.

FRICTION: 390px cannot be honestly represented by a simultaneous desktop dual panel; use the sheet-to-composer route. Real PR findings/guide fixtures may require lead-owned deterministic data. Global PR-page actions create drafts rather than accumulating into an implicit current composer. Before/after runtime proof remains pending with lead-owned servers on 5819/7615.
