# Proof audit pass 1 — issue 23930

Scope: planned PR, not opened. Worktree `/home/ubuntu/wt/issue-23930`; branch `fix/issue-23930`; [head fe9e13cb1c](https://git.cafe/pingdotgg/t3code/commit/fe9e13cb1cb99da7cd9039e40d374ce277f5d9d4), [base a46f80ee28](https://git.cafe/pingdotgg/t3code/commit/a46f80ee28). Complete two-file diff read: `apps/web/src/markdown-clipboard.ts` and its unit test. No source edits, tests, captures, server starts, pairing-token minting, forge writes, or merge performed. This is a proof plan, not a verification pass.

No visual change expected: selection serialization only. Every **R** row requires a recording of actual selection → native Copy → paste into an unsent plain-text destination, with readable output; a static selected-code screenshot or a synthetic copy event alone cannot prove clipboard behavior. Keep paste unsent, do not execute commands. Show absence of added fences/language/header text and compare exact whitespace separately using escaped clipboard text/length (spaces and NUL cannot be judged from video alone). Also inspect `text/html` on one representative mixed copy; rich paste must retain code structure and omit controls. No recording was made in this pass.

Environment grid **G** = 1440×900 light, 1440×900 dark, 390×844 light, 390×844 dark (CSS pixels). All R rows are planned at G; combine selections into a single clip per grid cell rather than one clip per row. Narrow web is not native mobile. Capture before/base and after/head for changed behavior when capture is authorized; identical rendering is expected. Controls need after-only proof. Any code-derived expectation below remains unverified in a real clipboard.

| ID | Affected path / minimum fixture | Conditional / interaction | Expected proof and risk covered | Evidence / grid |
|---|---|---|---|---|
| R1 | Assistant reply: `Run this command:`, bash block `printf 'hello\\n'`, `Then continue.` | Select code through only `T` of following paragraph; then select entire reply; then prose-before through code ending | After partial copy: `printf 'hello\\n'\n\nT`; full copy retains both paragraphs and block separation, no generated fences or `bash`. Base adds fences in mixed fragment. Exercise code at first, middle and final serializer segment. | **Recording**, G; existing reply supplied, Browser access blocked |
| R2 | Same reply, code-only negative control | Selection entirely within PRE (partial and multiline), then drag through final newline into empty following paragraph/wrapper | PRE-ancestor `range.toString()` and sole-PRE scan remain plain; no newly added fences; exact selected text. Distinguish whole-block newline normalization from raw in-PRE range. | **Recording**, G; existing reply can supply single line; multiline fixture needed |
| R3 | Two blocks (bash plus unlabelled/text or unknown-language block), prose between; include literal backticks/fence-like lines, indentation, tabs, trailing spaces, internal ≥3 newlines, extra ending newline | Select both blocks and prose; start/end selection within code; include empty block variant | Multiple PREs force general serializer; NUL boundaries pair independently and never leak. Code whitespace remains, exactly one final LF per PRE is removed; prose whitespace tidies; language no longer influences selection output. Highlighted spans and plain/fallback PRE must agree. | **Recording**, G; fixture needed; whitespace exact-value check |
| R4 | Code beside empty list item, then a nested code block in ordered/task list and blockquote | Select list marker + block; select nested block plus neighbouring prose | Bare item remains `-\n\npnpm test` (changed test); list/quote prefixes and continuation indentation remain coherent. Parent list/quote cleanup runs before tidy and can collapse internal blank lines: establish actual output rather than assuming global preservation. | **Recording**, G; fixture needed |
| R5 | Open/closed details with summary, prose and code | Expand then select summary/content through code and outside prose; repeat closed summary only | `data-markdown-details` bypasses ordinary traversal into details serializer; details/open marker survives, no generated PRE fences; controls and hidden content do not leak. | **Recording**, G; fixture needed |
| R6 | Prose-only kitchen sink: heading, strong/emphasis/delete, inline code (including backticks), link, image alt/source, HR/BR, table alignment/escaped pipe, ordered/task/nested list, quote | Select rendered content; include prose containing literal triple backticks as text | Global tidy changed even without PRE: old regex treated triple-backtick text as protected spans, new code protects only NUL segments. Confirm ordinary Markdown markers, whitespace cleanup, table and link output remain intended. | **Recording**, G; fixture needed; collapse unchanged serializer branches into one fixture |
| R7 | Mermaid diagram plus prose and ordinary code; source includes meaningful indentation/blank lines | Select inside diagram; separately select across diagram wrapper + prose | Inside-diagram branch still copies trimmed source; mixed wrapper uses unchanged fenced `serializeCodeBlockToMarkdown` via `data-markdown-copy`. New tidy no longer protects these fences, so trailing spaces/≥3 newlines may normalize: explicit regression decision required. Verify HTML diagram becomes source PRE, not SVG/buttons. | **Recording**, G; fixture needed; high-value global-tidy regression control |
| R8 | Inline/display math, file/context/citation chip + normal code and prose | Select inside formula; select across formula/chip and code | Special math ancestor remains TeX; wrapper attributes override child glyphs/labels; mixed selection retains each source exactly once. Skipped header, buttons, SVG, aria-hidden, select-none, sr-only stay excluded; ordinary PRE loses fences only in text/plain. | **Recording**, G; fixture needed; combine with R6 where practical |
| R9 | User message containing mixed code/prose and a resolved context reference | Select reference plus code; repeat without selecting reference | `TimelineUserMessageRow.onBodyCopyCapture` calls payload only when selected context fragment exists; bubbling ChatMarkdown must preserve custom context MIME/encoded HTML and updated plain text. No selected fragment takes ordinary ChatMarkdown path. | **Recording**, G; fixture needed; separate caller branch cannot be inferred from assistant proof |
| R10 | Shared ChatMarkdown outside main assistant: Markdown file/attachment preview; one inline/lineBreaks or parseRawHtml-disabled caller | Open existing surface, select mixed prose/code; inspect only if that surface can render a PRE | Same copy handler must be reachable without chat-only assumptions; inline callers that cannot emit PRE collapse into prose control R6. Include rendering fallback/error PRE if available without mocking production DOM. | **Recording**, G for representative reachable surface; remaining callers mapped below |
| R11 | Same assistant reply's Copy code and Copy message buttons; table copy Markdown/CSV | Click each button and paste, without text selection | Unchanged independent controls: code button writes raw `code`; message button writes supplied source Markdown (fences expected); table actions use their own serializers. Prevent claiming all copy actions now strip fences. | **Recording**, G; assistant controls existing, table fixture needed |
| C1 | No selection/collapsed/empty or skipped-only selection; multiple ranges where browser supports them | Copy without useful selected text; range order and collapsed-range skip | Handler does not override native default for null payload; multiple nonempty ranges join with two LFs, HTML concatenates. No fake claim of multi-range UI support in Chromium; inspect/test independently if unavailable. | Read-only code control now; clipboard interaction recording later if exercised, G |
| S1 | Desktop shared web renderer and native clipboard bridge | Repeat R1 + R2 + R11 in desktop at 1440 light/dark | No Electron changes; DOM serializer shared. Browser proof covers shared logic, not Electron OS clipboard delivery. Native desktop smoke remains required before claiming desktop verified. | **Recording**, 1440 light/dark; not run |
| S2 | Native iOS/Android | Decision: excluded from affected implementation | Mobile uses `SelectableMarkdownText` and native copy/haptic paths, not web DOM serializer. No mobile source/contracts/client-runtime/provider/server changes. 390 web proof does not verify mobile; no mobile build/server warranted for this serializer-only diff. | Source decision; no capture planned |

## Caller inventory and collapse rationale

The only production caller of `serializeRenderedMarkdownFragment` is `chatMarkdownClipboardPayload`. Its two UI entry points are `ChatMarkdown.handleCopy` and `TimelineUserMessageRow.onBodyCopyCapture`; both are represented above. `serializeCodeBlockToMarkdown` remains exported for `MermaidDiagram` (R7). Table Markdown/CSV exports and `mathWrapperOf` remain unchanged; `assistantTextSelection` imports only `mathWrapperOf`, so citation creation is not a new serializer caller.

All direct ChatMarkdown render callers were enumerated by source search: `TimelineAssistantRows`; `TimelineUserMessageRow`; `TimelineWorkEntryRow`; `TimelineWorkGroupRows`; `TimelineV2EventRow`; `V2ItemInspector`; `ProposedPlanCard`; `WorkflowOpeningBlock`; `ComposerPendingUserInputPanel`; `UserMessageContextChips`; `composerContextPresentation`; `PullRequestMarkdown`; `FileMarkdownPreview`; `AttachmentFilePreview`. Collapse assistant/work/event/inspector/plan/workflow/input/popover/PR renderers into the same handler + DOM-shape rows R1–R8; no per-caller serializer configuration exists. File/attachment preview retained as R10 because it is outside chat. User context capture retained as R9 because it is a separate event phase/custom MIME path. Streaming/incremental fenced parsing, Shiki-highlighted PRE, error fallback PRE, hidden Suspense PRE, labelled/unlabelled/text/unknown fences all reduce to selected DOM shape plus skipped visibility; R3 covers this boundary, without requiring six provider fixtures. All providers converge on this renderer; no provider adapter-specific implementation is changed.

Functions/branches covered: removed language resolver (data-language and code language class now irrelevant for PRE text); PRE marker serializer; unchanged sole-block fast path including skip tags/attributes, empty wrappers/LI, second PRE and nonempty-copy overrides; global tidy first/last prose trimming, odd code segments, ordinary whitespace, literal fence text; CODE without PRE (multiline or `.line` versus inline), direct text and element traversal; structural parent serializers; Mermaid/math special ancestors; HTML sanitation; empty/collapsed and multi-range dispatch; native selection copy versus independent button APIs. CODE fragments missing their PRE do not receive NUL protection: include clipped highlighted-line/multiline fragment in R3 and compare whitespace; do not assume the new comment guarantees preservation for every DOM shape.

## Evidence status and FRICTION

- Complete diff inspected; clean worktree confirmed at supplied head. Only two files changed, 29 insertions / 18 deletions. Existing tests use FakeElement and do not prove native selection, MIME data, or OS clipboard output. Added assertion covers code + `T`, and revised assertion covers bare LI + code. No tests were run in this read-only evidence-planning pass.
- `test-t3-app` Browser status was initially unavailable; `preview_open` succeeded for the supplied existing reply URL. A text-only snapshot (no image/save) redirected to `/pair`, showing “Pair with this environment”; console reported **“HTTP Authentication failed; no valid credentials available”**. Dev modules identified the intended worktree. Existing reply contents are user-supplied, not independently observed this pass.
- Did not mint a pairing token or seed a provider reply: those create state and conflict with read-only scope. Authenticated Browser access is needed for later proof execution. No alternate browser attempted, per skill. No captures or actual clipboard verification occurred.
- R1/R2/R11 can reuse the supplied reply once access is restored. Remaining fixtures can be requested in one assistant reply through an unsent-then-authorized UI prompt; no committed repro scratch is needed. Provider generation/fixture writes were not performed here.
- Important proof gaps to resolve: global tidy on fenced Mermaid overrides; nested parent cleanup versus claimed code whitespace preservation; PRE-less clipped CODE; exact edge whitespace/marker leakage; context custom MIME; rich clipboard flavor; desktop native delivery. These are audit targets, not confirmed defects.
- Planned PR has no URL; no PR registration or forge write performed. Matrix upload alone is explicitly authorized.
