# Proof audit, pass 1: assistant citation parser bounds

PR [#28084](https://git.cafe/pingdotgg/t3code/pull/28084), issue [#28075](https://git.cafe/pingdotgg/t3code/issues/28075).
Reviewed head [e3d7260bc6](https://github.com/pingdotgg/t3code/commit/e3d7260bc6e260051d705492bf64c867bba4d976), branch `fix/issue-28075`, against merge-base [2359643cff](https://github.com/pingdotgg/t3code/commit/2359643cffa6ba9ee84d6f2694b935de54a8013e).

## Findings and evidence limits

1. **Visual proof is missing.** The author's PR description reports the real Pi reproduction and successful composer/comment/sent-message/draft recovery, but explicitly supplies no before/after captures and no native mobile capture. This audit opened no browser and started no server or provider. It cannot validate those visual claims.
2. **The fixture boundary is understood.** The diff changes no React component or stylesheet. It changes the three ID bounds from 512 to 4096 and the shared href bound from 160000 to 256168. The visually relevant split is rejected raw marker versus accepted structured citation. Newly accepted identities do not otherwise affect chip layout because IDs are not displayed.
3. **Read-only diagnostic passes.** Actual base and head parser modules were loaded from source, with base contracts copied into this audit's scratch directory. All three ID fields individually accept 512 on both revisions, reject 513 on base and accept it on head, accept 4096 only on head, and reject 4097 on both. The exact nested-ID recipe from the new test gives an 88-character thread ID and 631-character message ID; base rejects it and head accepts it. A maximum CJK citation has a 255262-character href and is recognized on head by both direct parsing and link collection. Short link serialization is byte-identical. These are diagnostic results, not browser captures or a run of the test suite.
4. **Navigation caveat, pre-existing.** `assistantCitationFromLocation` still rejects hashes over 140000 characters. The maximum fixture's hash is 340369 characters. Do not claim successful source navigation for that fixture just because it becomes a chip. This limit already affects sufficiently large quotes on base; it is not a newly introduced visual regression. Use the ordinary 631-character identity fixture to demonstrate source navigation, and label the maximum fixture as parser/layout coverage only.
5. **Deterministic seeding is preferable to a live Pi turn for paired evidence.** `pi` is on PATH on this VM, and the author reports a 533-character ID from Pi in an 88-character default-project thread. A live turn is useful as a provider smoke check, but IDs, reply text and timing vary. Reuse a fixed synthetic source and serialized marker for before/after.

Artifacts: [synthetic fixture data](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e6348ab30a2af20d/fixtures.json), [base/head parser diagnostic](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/315f649c0ee85e62/parser-diagnostic.txt), [diagnostic/fixture generator](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b207426b529927e4/derive-fixtures.mjs). All data is synthetic; no credentials, pairing URLs or live user data are included.

## Fixture matrix

`D` = web 1440×900, dark. `N` = web 390×844, light. `M` = native Android 412×915 logical viewport, dark (record the actual device viewport). Desktop uses the web components; an extra Electron capture is not required for these parser-only changes. iOS uses the same mobile transform; a second native platform is supplementary unless the first capture exposes platform-specific trouble. These are representative themes and widths, not a required full Cartesian product.

Every recording means **paired base/head recordings of the same state and action sequence**. Stills can be exported from them for side-by-side comparison but cannot replace the recording. Persisted/static mobile rows can use stills. A harness capture must say **HARNESS** in its caption and identify the real components it renders.

| Row | Named fixture and exact data/state | Diff affects it? Expected base → head | Minimum capture (viewport × theme × row) | Mode and seed | Pass-1 status |
| --- | --- | --- | --- | --- | --- |
| C0 | Short control: `short` fixture, message ID exactly 512, short quote, no comment; normal editable composer, source mounted | No: chip/comment editor → same | D × C0, as opening segment of recording A | **recording**: select the short assistant source and click Cite; preserve same quote as W1 | PARSER VERIFIED; NOT CAPTURED |
| W1 | Nested Pi-style identity: `long`, thread ID 88/message ID 631, short quote, no saved comment; source mounted | **Yes**: raw marker/no editor → composer chip plus automatically focused optional-comment editor anchored below selected source | D × W1, recording A | **recording**: select `Otters keep stones in a loose skin pouch.`, click Cite, show empty field and chip together | NOT CAPTURED |
| W2 | Same W1 citation with `commented` comment; save then send; sent chip closed then opened | **Yes**: raw marker persists → chip label uses comment, sent Markdown chip opens quote + comment + Go to source | D × W2, continuation of A | **recording**: enter `Which animal containers are mentioned?`, Save/Enter, send; await persisted user message; open sent chip, show quote/comment, activate Go to source and show highlight | NOT CAPTURED |
| W3 | Legacy raw draft: byte-identical `long.marker` saved while on base, ordinary composer, no source anchor retained after reload | **Yes**: restored raw marker → closed composer chip; comment field appears only after pencil activation, anchored to chip when source unavailable | N × W3, recording B; use rich-text setting opposite A | **recording**: save draft on base, reload same data with head, open pencil, add/clear comment and save, reload; Cancel a newly cited chip separately to show removal | NOT CAPTURED |
| W4 | No-comment sent citation: same `long.marker`; neighboring prose; optionally add `longLabel` to show >64-character label truncation and multiline saved quote | **Yes**: raw marker → quote-labelled chip (ellipsis for >64), quote-only popover with no comment paragraph | N × W4, continuation of B | **recording**: send the no-comment draft, open chip; for long quote scroll its popover and show fade/clipping behavior. No extra still needed beyond B | NOT CAPTURED |
| M1 | Native ordinary user message containing `long.marker`, no comment; stopped history with old timestamps | **Yes**: raw citation link → Markdown blockquote headed `Assistant quote:` and escaped quote text; no comment line | M × M1, frame C containing M1 and M2 | **still**: seed completed user message, open native thread; no mobile Cite interaction is claimed | PARSER OUTPUT VERIFIED; NOT CAPTURED |
| M2 | Native ordinary user message containing `commented.marker`; same quote plus comment | **Yes**: raw link → same quote block plus separate `Comment:` text | M × M2, same frame C | **still**: seed immediately adjacent to M1; include multiline/non-ASCII quote if frame permits, verify wrapping and literal Markdown characters | PARSER OUTPUT VERIFIED; NOT CAPTURED |
| H1 | Maximum encoded payload: `maximum`, all IDs 4096 CJK units, quote/comment each 8000, contexts each 32, safe-integer offsets; href 255262 | **Yes**: rejected → recognized citation, 64-character label, scrollable quote/comment; exercises href allowance independently of usual short links | No additional minimum UI capture: numeric bound proved by diagnostic; optional N × H1 | Optional **recording**, labelled **HARNESS**: bind `ComposerPromptEditorTiptap.value` and `ChatMarkdown.text` directly; open/scroll. Synthetic environment/thread do not exist; do not assert source navigation. Avoid clipboard auto-file folding | PARSER VERIFIED; OPTIONAL HARNESS NOT CAPTURED |
| A1 | Auxiliary text previews: same long marker, with/without comment; single queue row, one stash entry, first-prompt title seed, find label | **Yes**: raw serialized bytes/link label → quote or quote + `Comment:`; quote/comment label for find | No extra minimum capture: same recognized-citation branch; stash restore may be included in B. If independently claimed, capture each corresponding UI | **recording** for stash open/restore, queue edit, title creation or find navigation; queue/stash/title static previews can be seeded and captured as stills if no interaction is claimed. Public queued `message.dispatch` can use `defer_start`; stashes/drafts are client state | TRANSFORM CALLERS TRACED; NOT CAPTURED |
| N1 | Source unavailable/loading: same long accepted marker, source missing, disconnected environment, older paginated source, or source no longer assistant | **Yes**, newly accepted links reach existing navigation states; saved quote remains usable regardless of lookup outcome | Supplementary; ordinary successful source in W2 is the minimum | **recording**: Go to source with missing source/history failure; show existing toast/fallback rather than promising a chip loading skeleton. Missing source also gives W3's top-anchored editor | BRANCHES TRACED; NOT CAPTURED |
| C1 | Rejected/control states: `overLimit` 4097-ID marker alongside valid `longLabel` and short control; zero citations; malformed v2/duplicate keys; literal answers and code fences | No intended change: 4097/malformed remain unrecognized; zero remains prose; literal/code remains literal | No extra minimum visual capture; diagnostic covers 4097; optional N × C1 with H1 | Static **HARNESS still** for mixed tokens and zero/literal state; **recording** if editing/deleting tokens. Do not present 4097 raw fallback as a regression fixed by this PR | ID BOUND VERIFIED; OPTIONAL UI NOT CAPTURED |

**Smallest required visual package:** recording A (D: C0 → W1 → W2), recording B (N: W3 → W4), and frame C (M: M1 + M2), each before and after. That covers desktop/narrow web, both representative themes, empty/nonempty comments, restored persisted bytes, both web chip variants, actual send and source navigation, and native fallback rendering. H1 is already covered at the parser level; A1/N1/C1 enumerate collateral and reverse/failure states without requiring unrelated UI retesting. If the capturer cannot seed a real app source, A/B may be labelled harness captures, but **that leaves actual Cite/send/persistence integration unproved** and must be reported. Do not silently substitute a component screenshot for an integrated flow.

## Exact render/caller inventory and branch collapse

### Shared contract and parser

The only changed production files are [contract bounds](https://github.com/pingdotgg/t3code/blob/e3d7260bc6e260051d705492bf64c867bba4d976/packages/contracts/src/assistantCitations.ts#L8) and [shared href/parser bounds](https://github.com/pingdotgg/t3code/blob/e3d7260bc6e260051d705492bf64c867bba4d976/packages/shared/src/assistantCitations.ts#L14). Neither has platform, theme, width, loading, or asynchronous branches. Each ID independently crosses the bound; since no ID is printed in a chip, environment/thread/message variations collapse visually into W1. The diagnostic covers them separately. Quote is nonempty and ≤8000, comment optional and ≤8000, contexts ≤32, `end > start`, safe nonnegative offsets. Invalid protocol/path/query/schema/href-length results return null and leave the source unchanged. Zero matches is ordinary prose; one and multiple matches produce one rendered citation per occurrence in order. Provider expansion alone deduplicates identical sources.

### Web/desktop composer

`MessagesTimeline` conditionally mounts `AssistantSelectionToolbar` only with both `onCiteAssistantText` and `citationThreadRef`. A valid visible assistant selection supplies `environmentId/threadId/messageId`, quote offsets and contexts. No selection or invalid/invisible selection has no toolbar; a quote over 8000 gives disabled `Shorten selection`. Pointer Cite or keyboard activation calls `ChatComposer.citeAssistantText` → `formatAssistantCitationForComposer` → editor insertion. No ID validation occurs during formatting. The changed parser decides whether insertion becomes raw text or a citation atom, and consequently whether the comment target exists.

`collectComposerPromptInlineTokens` / `splitPromptIntoComposerSegments` → `composer-rich-text-doc` → `ComposerCitationNodeView` → `AssistantCitationChip(composer=true, commentEditor=...)`. This is used by `ChatComposer` for ordinary, queued-edit and custom-answer value paths, and by `PromptFontPreview` in Settings. Settings' default preview has no citation, but a pasted fixture reaches the same renderer. Rich-text styling on/off shares tokenization; `literalText=true` and paste inside a code block deliberately bypass it. Disabled/not-editable editor or removed node refuses save; not a new visual branch.

`AssistantCitationChip` has two actual callers: this node view and the `ChatMarkdown` anchor. Composer variant has source link, label constrained to 16em, pencil, and optionally a controlled comment popover. Current app caller always supplies commentEditor; no commentEditor is only a possible harness/API shape. No/blank comment labels from quote, populated comment labels from comment, whitespace is normalized, >64 UTF-16 units gets ellipsis. Fresh Cite supplies a DOM source anchor (popup below source); restored/pasted chip uses chip fallback (popup above). Popup closed/open, fresh-cite Cancel removal versus existing-chip Cancel close, comment clear/save, Save and Send (Ctrl/Cmd+Enter), Enter save, Shift+Enter newline, Escape cancel, Tab return to composer, click-away draft settlement and source-unavailable settlement are existing interactions. >8000 comment shows an error and disables Save; since the PR changes only ID/href acceptance, this is a supplementary existing branch rather than a separate required fixture. Long multiline comments grow textarea up to its cap.

### Sent and generic Markdown

`ChatMarkdown` preserves an accepted citation href in its URL transform, then its anchor renderer returns `AssistantCitationChip(composer=false)` **before** other link handling. This variant has a quote-labelled/comment-labelled button, a click popover with quoted text and optional nonempty comment, copyable serialized Markdown, and Go to source. Quote pane is whitespace-preserving, max-height 16rem, vertically scrollable with top/bottom fades. This is W2/W4/H1. Empty explicit comment is absent visually although shared text formatters distinguish it from an omitted comment.

All production JSX consumers of `ChatMarkdown` were enumerated: `TimelineUserMessageRow`, `TimelineAssistantRows`, `TimelineWorkEntryRow` (two sites), `TimelineWorkGroupRows` (two), `ProposedPlanCard` (two), `V2ItemInspector`, `ComposerPendingUserInputPanel`, `UserMessageContextChips` (two), `WorkflowOpeningBlock`, `composerContextPresentation`, `PullRequestMarkdown`, `FileMarkdownPreview`, `AttachmentFilePreview`, and `NotebookPreview`. They all reach the same anchor renderer; accepted markers in their Markdown get the same non-composer chip. Markdown code fences/inline code do not become anchor nodes. Ordinary user messages and assistant rows provide the representative wrappers in the minimum package; files/PR text/tool output require their corresponding content seed only if separately claimed. No need to capture every generic Markdown host for an unchanged anchor component.

`MessagesTimeline.logic.visibleUserMessageText` additionally uses collected citations and the 64-character label before the existing >600-visible-character or >8-line collapse decision. It does not measure opaque ID length once parsed. Quote/comment count, surrounding prose, and collapsed/expanded long user messages are existing layout inputs; normal accepted citation is represented by W2/W4 and long/multiple by H1/C1. A new long-ID link can now use comment/quote label rather than generic link label in this calculation.

### Mobile

Both `ThreadFeed` transform sites were traced: ordinary message rows (user and assistant) and the expanded `ThreadAutomationMessageRow`. `renderAssistantCitationsAsText` emits escaped Markdown blockquotes, with an optional separate comment paragraph. User messages use bubble styles; assistant messages use assistant Markdown styles and streaming/terminal metadata; collapsed automation hides the body until expanded. These are wrappers around the same transform, not mobile chips. The mobile composer does not call this formatter and no mobile Cite control is introduced. M1/M2 cover native user history; assistant/expanded automation are supplementary wrapper checks using the same body. Capturing an automation expansion or streaming transition requires a recording. A preseeded static assistant/expanded-automation body can use a still. Native width, Android wide-block handling, duo columns, attachments, metadata and role colors are unchanged; no extra fixture multiplication is justified here.

### Other affected consumers and interactions

- `QueuedRunsControl` (web) and `threadQueueControlPresentation` → `ThreadQueueSheet` (mobile) use `assistantCitationsToPlainText`, with comment line when supplied. Zero rows hides queue; one/many, editing/busy, attachments and collapsed queue controls are unchanged. Use one long-ID queued row to cover changed text; edit reloads the same citation into composer. No actual active provider is needed with `defer_start`.
- `ComposerStashMenu.stashEntrySnippet` normalizes that plain text and truncates at 90 characters; empty/attachment-only fallback is unchanged. One stash entry is enough. Open/restore/delete is an interaction; a static open menu screenshot only proves its preview.
- `packages/client-runtime/.../threadTitle` normalizes the same plain text for the first-message title seed, while `packages/provider-core/.../threadTitleContext` transforms title-generation context. The latter is not itself a render path. Actual model-generated titles are nondeterministic and are not required proof for this parser cap.
- `threadFindText` substitutes `assistantCitationLabel` for recognized anchor text, so find/search text follows the displayed quote/comment label. Searching or navigating a result is an interaction; no new find UI branch exists.
- `composerSubmission` uses expanded citation length in input-size validation. The server provider dispatch path is not changed by this PR; do not claim it fixes the existing provider payload issue [#16327](https://git.cafe/pingdotgg/t3code/issues/16327).
- `assistantCitationNavigation` formats the base64url hash, `ChatView.assistantCitationFromLocation` parses it, and `citationProcedure`/`useAssistantCitationTarget` load paginated history and reveal the target. Success, missing source, non-assistant source, unavailable environment, loading/failure, and no matching text are existing branches. Navigation/highlight, source scrolling, popup dismissal, clipboard copy and restore must be recordings when claimed. These flows use stable environment IDs, not origins; local/remote/tunnel share the parser. No new network-mode fixture is needed.

## Reproducible seeding handoff

1. Use isolated development state with `T3CODE_DEV_SEED=0`, no baked HTTP/WS origins, and no writes to live T3 home. Seed once; reuse an exact copied isolated-state snapshot for base and head. Record the two actual revisions, client type, viewport/theme, ID lengths, source quote, serialized marker checksum, and whether data came from an app seed or harness.
2. The fixture JSON supplies source text, citation objects and serialized markers. For ordinary app W1–W4, replace `proof-environment` with that isolated server's stable environment ID and serialize **once**. Reuse that result and the same thread/message IDs on both builds. `short` and `long` source records can share quote text but need separate message records so toolbar selection actually sees each ID.
3. Public RPC `orchestration.dispatchCommand` accepts `thread.create` with a caller-chosen thread ID and `message.dispatch` for a **user** message. It does not offer an arbitrary assistant-message append command. Do not invent `message.append` or try to spoof `message.dispatch` into an assistant role. `dispatchMode: {type:"defer_start"}` makes a deferred user run useful for a queue seed without invoking a provider; inspect/record real command receipts rather than sleeping.
4. A deterministic **real server seed driver** can create the project/thread through existing services and write schema-valid `message.updated` plus matching `turn-item.updated` (`type:"assistant_message"`) events through internal `EventSinkV2.write`, using its real persistence/projection layers, into the stopped isolated database. Set matching threadId/messageId/text, old fixed timestamps, `streaming:false`, null provider/run/node references where permitted, `status:"completed"` on turn items, and unique event/turn-item IDs/ordinals. Include a user counterpart or run scaffold if the selected timeline path needs it. Keep all related projection/event state consistent through the sink, not ad-hoc SQL updates. Start the real app on that seeded state. This is a **proposed seed recipe, not an already implemented/tested driver**; it needs server layer setup. There is no shipped one-command fixture RPC. The relevant schemas are in `orchestrationV2.ts`; service is `EventSink.ts` (`write`, `layerFromStores`/`layer`).
5. For W3 specifically, first insert/paste `long.marker` on base and let the app save the draft normally, then reload that same isolated state with head. This proves recovery of old persisted bytes rather than fresh insertion. For A1, save/open a stash through the app before restoring it. Keep separate snapshots so the before run cannot consume the after run's draft.
6. A live Pi alternative: create/use the default-project 88-character thread, choose the configured Pi provider, send a short deterministic request for the animal-containers sentence, wait for the persisted completed assistant message, and inspect the actual generated message ID length. Only use it for the regression if it exceeds 512. `pi` availability alone does not guarantee provider/model auth or a stable 533-character result. Seed/snapshot the resulting history once for a fair paired comparison; do not generate separate answers on base and head.
7. Actual Send in W2/W4 should use the real composer action and await its persisted user message/receipt; any following provider response is outside this proof. Do not claim a preseeded user row proves the Send interaction. A controlled provider or configured Pi can serve the send; no response content comparison is needed.
8. If an internal seed driver or usable Pi session cannot be made available, render the actual `ComposerPromptEditorTiptap` with `value`, `ChatMarkdown` with `text`, and native feed/message renderer with the same JSON. Label those captures **HARNESS**, explain that source navigation/real draft persistence/send are not established, and leave those rows missing. H1's synthetic all-4096 identities are intentionally harness-only for ordinary capture: they do not correspond to a linked environment and should not be put into navigation routes. No browser/server was started during this audit.

## Coverage and cleanup

Covered complete three-file diff; author's PR runtime notes; both changed production modules; parser boundaries on actual base/head; all shared transform consumers; composer and sent-chip variants; Markdown hosts; both mobile transform sites; reverse/comment/navigation branches; public-command versus internal-seed feasibility. No captures, native testing, full test suite, CI audit, provider turn or implemented server seed driver. Scope remained an evidence matrix, not a merge-readiness verdict. All writing was confined to this step's scratch directory, and only synthetic artifacts were uploaded. Step scratch files are removed after upload per the lead's cleanup instructions; public artifact URLs remain the handoff.

FRICTION: none
VERDICT: INSUFFICIENT W1, W2, W3, W4, M1, M2, H1, A1, N1 (all affected rows not yet captured; minimum visual package A/B/C missing)
