# Proof audit, pass 1: collapsed markdown table chips

Findings first: the matrix is complete and capturable for this one-rule change. PR context chip labels resolve to the PR number, so a long title is not an overflow fixture; F3 instead uses a long text prefix. The supplied three-row table is the main reproduction, but cannot alone cover the selector's header-cell branch, non-file chips, cell-wide effects on adjacent text, or reversal into the expanded state. No visual correctness or merge-readiness claim is made in this pass; no capture was taken.

Reviewed [issue #25676](https://git.cafe/pingdotgg/t3code/issues/25676), branch `fix/issue-25676`, [head 4e67414bc8](https://git.cafe/pingdotgg/t3code/commit/4e67414bc8739ddf2938bdce40b03a613a19172c), against the supplied immutable [base 71ba6b374c](https://git.cafe/pingdotgg/t3code/commit/71ba6b374c0b0181130c0cc3b3d5aaa3387ada67). The complete diff is eight added lines in `apps/web/src/index.css`. Local `origin/main` now resolves to [5127de8c29](https://git.cafe/pingdotgg/t3code/commit/5127de8c29ede39fc567337cebc4726fbaa16b63); local `main` also differs. Future before captures must use the supplied base, not either moving ref.

## Fixture matrix

All rows have status **READY / uncaptured**. “Affected” means the new selector matches; visible change requires overflow. The rows describe fixture sheets, so related cell shapes appear together rather than requiring one screenshot per cell. `D` = 1440×1000 CSS-pixel web viewport with timeline lane at least 36rem; `N` = 390×844 with lane below 36rem. Measure the actual lane: sidebar/panel width and chat width setting matter more than viewport alone. `L/C` = light, classic design; `D/R` = dark, New design enabled; `L/R` = light, New design enabled. These cover the 0.75rem and 0.875rem table typography rules. Hold font settings/zoom constant between before and after. No theme-specific selector was changed, so no theme Cartesian product is needed. Recording labels concern the behavior being proved; navigation used only to initialize a stable fixture does not require a recording of that setup.

| ID / named fixture | Data and visually distinct cases on the sheet | Diff affects | Minimum capture: viewport × theme/design × row | Real-app seed or labelled harness | Status |
|---|---|---|---|---|---|
| F1 — reported file-chip overflow | Exact three-row issue table using the existing draft paths; one long filename and two fitting controls; non-filename link label `Draft` produces text plus a chip. Ensure the long chip actually crosses its cell's content edge. | Yes, body cells with labels | D × L/C × F1; D × D/R × F1; N × L/R × F1 via F7 | Existing worktree dev app, thread route `/<environmentId>/<threadId>`; ask Codex to return exactly the table without a code fence. Word wrap off **before mount**. | READY / uncaptured |
| F2 — selector and inline-box boundaries | Additional small table: a long chip in `th`; `td` with chip only (basename label), `Draft ` plus chip, chip plus long suffix text, two chips separated by text, and a prefix long enough to consume the cell before the chip. Include first/middle/last column positions to cover asymmetric padding, plus a separate RTL-dominant table (Arabic/Hebrew prose with a file chip) to check the opposite inline edge. Include a long inline-code file path and a non-media file image reference; both also render file chips. | Yes; whole matching cell gets clip, including ordinary prose | D × L/C × F2, including the RTL table and together with F1 if legible; D × D/R × F2 header/prefix smoke | Same provider reply. Use `[basename.md](/absolute/path/basename.md)`, `[Draft](...)`, inline code containing a file path, and `![file](/absolute/path/non-media.md)`. Put file links in header as well as body. | READY / uncaptured |
| F3 — shared label/wrapper variants | Overflowing thread anchor (known long title plus unavailable-ID fallback), citation/popover button with long quote/comment, and user-context chips: PR button after a long text prefix, unresolved dashed span, attachment with name + size, and generic details button. Include fitting versions on the same sheet. PR open/draft/merged/closed change icon/accent, not clipping geometry; actual PR-chip label is computed as a short number, so a long PR title alone does not force overflow. | Yes, any descendant label; a plain external PR URL alone is **not** a ContextChip | D × L/C × F3; capture the distinct anchor/button/span/compound-label shapes together | Thread/citation: same assistant table using `t3-thread://v1/<id>` and serialized `t3-citation` URL. Context variants: send a **user** markdown table via app RPC `orchestration.dispatchCommand` / `message.dispatch` with `context.records` and `attachments`; use `t3-context://v1/<kind>/<contextId>` links and `context: {version: 1, records: [...]}`. Use `dispatchMode: {type: "defer_start"}` when only the rendered user message is needed. No record gives the unresolved chip. A harness rendering production `ChatMarkdown` + production `UserMessageContextReferenceChip` with `TimelineRowCtx` is an explicitly labelled fallback if constructing a particular context record is impractical. Never substitute plain PR URLs for PR context chips. | READY / uncaptured; app or labelled harness |
| F4 — selector exclusions and short/empty controls | Collapsed table with overflowing ordinary text/external URL/ordinary inline code, fitting short chip, empty cell, header-only/no-body table; separate paragraph containing the same long chip outside a table; math-only cell and math + file-chip cell with math rendering on. | No for no-label/expanded/outside cases; yes but no visible delta for fitting chips; math `td` retains overflow-visible/max-width-none exception | D × L/C × F4, same sheet as F2 | Same provider response, including GFM table, optional sanitized raw header-only table, paragraph, and inline math. Math setting on before render. No whole-widget empty/loading/error variant exists in MarkdownTable. | READY / uncaptured |
| F5 — secondary surface / options-only file button | Open the fixture markdown as an uploaded attachment preview (no cwd/threadRef passed to ChatMarkdown); long file link then renders an options button instead of an anchor. Constrain the preview panel, keep table collapsed. | Yes; also verifies scope beyond timeline and tooltip-overwritten chip slot | D × D/R × F5 at a narrow preview-panel width | Upload the fixture `.md` from scratch through the normal composer attachment flow, then open its attachment preview. `AttachmentFilePreview` passes `cwd=undefined`; no environment/thread means no file primary action. File preview and notebook markdown share the same table renderer. | READY / uncaptured |
| F6 — expanded initial state, wide lane | F1/F2/F3 data mounted with word wrap on; chips and adjacent text wrap rather than cell-clip. Wide timeline lane permits table min-width 0, wrapping headers, break-word body cells. | No, expanded selector excludes it | D × L/C × F6 | Set Settings → Appearance → word wrap on, then remount the thread/table. Changing this setting does not retroactively replace MarkdownTable local expanded state. | READY / uncaptured |
| F7 — narrow lane / horizontal overflow | F1–F4 at N; capture collapsed content, scroll table sideways to reveal later columns, expand, scroll expanded table, then return. Narrow expanded lane retains min-content table width and nowrap headers. Include a resize through the 36rem lane boundary. | Yes only while collapsed and label present | **recording**: N × L/R × F7, with an excursion to D; show both ends of horizontal scroll | Same thread, responsive viewport/panel resize; use table's horizontal ScrollArea and expand/collapse button. A web viewport at phone width is not evidence about native mobile. | READY / uncaptured |
| F8 — reversible toggle | Start F1 mounted with word wrap on: expanded → Collapse table cells → Expand table cells. Also start with word wrap off: collapsed → expanded → collapsed. Confirm overflowing chip is clipped, not replaced whole by a cell ellipsis, after each collapse; toggle remains available. | Yes on collapsed segments | **recording**: D × L/C × F8; narrow sequence may be combined into F7 | Same real thread. Use footer toggle; remount once after changing default word wrap. No animation-timing assumption is required. | READY / uncaptured |
| F9 — streaming growth and remeasurement | One cell grows from short plain text/incomplete markdown to a parsed fitting file chip to an overflowing chip; a row is appended and final assistant message settles. Table size can stay constant while content grows. Cover clipping threshold and toggle appearing from initially absent. | Yes once the chip label exists in collapsed state | **recording**: D × D/R × F9 | Record a real Codex reply with a table containing progressively longer links/rows. For precise reproducible token boundaries, label as a **harness recording**: production ChatMarkdown, `isStreaming=true`, successive `text` updates driven by an explicit step control, then `isStreaming=false`; retain real MutationObserver/ResizeObserver. Do not present a static final reply as streaming proof. | READY / uncaptured; app or labelled harness |
| F10 — interaction and copy feedback | On an overflowing visible chip: hover/focus → tooltip/popover, activate the file/thread/details target, return, context-menu where supported. On table footer: open copy menu, copy Markdown and CSV, observe Copied checkmark and reset after 1200ms; exercise copy failure only if claiming the error flow. | Cell clipping/hit area affected; popup/copy implementations unchanged | **recording**: D × L/C × F10; combine with F8 | Normal real-app controls; use an existing draft file and known thread for activation. For unavailable clipboard/error states, use a clearly labelled harness with clipboard rejection; that failure adds no clipping shape and is not a required before/after capture. | READY / uncaptured |

Minimum deliverable set: two core-sheet before/after pairs (D L/C and D D/R, combining F1–F4 where legible), one secondary-surface pair (F5), one wide-expanded control pair (F6), plus recordings for F7, combined F8/F10, and F9. The dark redesigned sheet needs the primary reproduction and header/mixed-content smoke; repeating every F3 color state is unnecessary. Every still/recording comparison uses the same data, viewport, settings, scroll position and immutable base/head. If a sheet is unreadable, split it without dropping its cases. Captures remain future work.

Expected boundaries: clip can cut a chip border/label at the cell edge; it does not promise an internally ellipsized complete chip. A chip entirely beyond a long prefix can remain outside the visible cell. Long trailing prose in a chip-bearing cell loses the cell ellipsis too. F2 explicitly shows those limits rather than promising behavior this diff cannot provide.

## Concrete seed additions

Use the exact table in the audit brief for F1 and the existing drafts directory. For F2, repeat a long file basename such as `bug-02-archive-snapshot-refusal-reports-integrity-mismatch.md` with a longer prefix/suffix if necessary to force overflow at the selected font size. A chip-only cell is `[bug-02-archive-snapshot-refusal-reports-integrity-mismatch.md](/tmp/issue-lead-25676-r5/drafts/bug-02-archive-snapshot-refusal-reports-integrity-mismatch.md)`; a mixed cell uses `[Draft]` with the same destination. Add `after after ...` after it and a second short file chip in separate rows. For RTL, use primarily Arabic/Hebrew text across the table rather than assuming a surrounding application direction setting.

For F3's generic popover, a skill reference can use record `{version:1, kind:"skill", contextId:"proof_skill", label:"Long skill label repeated until it overflows the cell", name:"proof-skill"}` and cell `[Skill](t3-context://v1/skill/proof_skill)`. A missing skill definition retains the chip and falls back to no description. For the unresolved span, use a long markdown label at `t3-context://v1/mention/proof_missing` with no matching record. For a PR chip, use a `review-comment` record with `sectionId:"proof-pr"`, `sectionTitle:"Pull request"`, `filePath:"proof-pr"`, `startIndex:0`, `endIndex:0`, `rangeLabel:""`, `text:""`, `diff:""`, and a valid `pullRequest` metadata object (`number`, `title`, `url`, `headBranch`, `baseBranch`, `state`, `isDraft`). Its metadata makes it a PR summary automatically. The displayed label becomes the PR number regardless of record label/title, so put enough normal text before the reference to force that short atomic button across the edge. A labelled harness may instantiate the production PullRequestChip under ChatMarkdown's reference callback if no local PR metadata is available. Attachment compound labels require an actual uploaded attachment plus a file/image context record pointing to that attachment id; a filename-only record without the attachment falls back to unavailable.

For all later runs, write each driver/capture/log to a new timestamped child of the assigned scratch directory. Do not mutate live state or restart the existing server. Pass 1 supplies recipes only and has not executed them.

## Render-path derivation

Production entry: `ChatMarkdown` → `CHAT_MARKDOWN_COMPONENTS.table` → `MarkdownTable`; no other production caller of MarkdownTable exists. Both GFM and allowed raw HTML tables take that path. `th`/`td` retain their semantic elements. MarkdownTable forwards ordinary table props and children; there are no data-count, provider, network, loading, or error props that select a second table layout.

All production ChatMarkdown callers, including exclusion paths:

| Caller / occurrence | Inputs or branch relevant to reachability/layout | Fixture equivalence |
|---|---|---|
| `TimelineAssistantRows:42` | Assistant text; streaming; cwd/threadRef/skills; optional preserved breaks; header offset; media/template/shell actions | F1–F4, F6–F10 |
| `TimelineUserMessageRow:829` | Empty text returns null; raw HTML off; preserved breaks; context reference callback; threadRef/skills | F3; ordinary tables F1/F2 |
| `TimelineWorkEntryRow:125` | Expanded entry detail or empty string; streaming; preserved breaks; threadRef/skills | Same table layout, narrower parent |
| `TimelineWorkEntryRow:412` | Collapsed reasoning, inline root, raw HTML off, remarkThoughtPreview | Plugin flattens tables to text: excluded |
| `TimelineWorkGroupRows:520,548` | Thought line/entry detail, inline root, raw HTML off, remarkThoughtPreview; active label changes | Tables flattened: excluded |
| `TimelineV2EventRow:105,181` | Event detail, disclosure vs inline event body; absent detail omits markdown; threadRef/skills, preserved breaks | Same renderer, narrow parent |
| `V2ItemInspector:265` | Only reasoning item with nonempty text; preserved breaks, threadRef | Same renderer, narrow parent; tool loading/error/empty output is separate plain UI |
| `ProposedPlanCard:198,207` | Collapsed preview or full displayed plan; nonstreaming; cwd/environment/thread; heading offset; outer height clip/fade | Same table; outer plan fold is not table expansion |
| `ComposerPendingUserInputPanel:238` | Active question text, cwd/threadRef, top padding | Same table in narrow parent |
| `UserMessageContextChips:554,565` | Review comment text when nonempty, raw HTML off/preserved breaks; non-diff fenced content | Comment text can contain table; generated fence cannot |
| `composerContextPresentation:297` | Nonblank draft review-comment text; cwd undefined; popup parent | Same table, options-only file button variant |
| `WorkflowOpeningBlock:220` | Script always wrapped in a JavaScript code fence; outer fold | No table element: excluded |
| `FileMarkdownPreview:26` | cwd/threadRef; imageBaseDir from file directory; mutation id; task edits; max-w-4xl + padding | Same table, file-preview geometry |
| `AttachmentFilePreview:284` | Parsed markdown attachment text; cwd undefined; max-w-4xl + padding | F5, options-only file button |
| `NotebookPreview:119` | Markdown cells only; cwd undefined; raw HTML off; notebook plugin; padding | Same table/button geometry; code/output cells excluded |
| `PullRequestMarkdown:88` | Markdown segments vs generated image segments; cwd, explicit environment and resolved thread/panel refs; autolink plugin; authenticated media | Markdown segment can contain table; ordinary PR anchors remain non-chips unless user-context renderer supplies one |

Link/content branches: file markdown with basename/path label produces just a chip; custom label produces text plus chip. Inline-code file paths and non-media local image references also reach fileLinkChip. File labels include basename, optional duplicate-basename parent suffix, line/column; these reduce to fit/overflow and mixed-content F1/F2. Filesystem availability or editor/preview/remote permissions choose anchor vs options button and available actions; they do not remove the chip label (F5). File icon/type/theme resolve independently and preserve the label selector. Missing file activation is an action error, not an alternate table state.

Thread links with an environment render ThreadContextChip (known shell title/project favicon or supplied-label fallback/message icon); without an environment they render a plain span (F3/F4). Citation URLs render a ContextChipPopover; quotes/comments select the label (capped to 64 characters), full quote in popup (F3/F10). User-context anchor/image references call renderContextReference: mention, skill, thread, image, file/video, terminal, element, review-comment/PR, preview annotation, or unavailable fallback. These reduce to anchor/button/span, single label versus compound attachment label, and popup/tooltip wrapper (F3). Audio references can return null when separately rendered. Missing callback yields plain label span (F4). Normal external links/favicons, PR hover cards/autolinks, media loading/failure UI do not independently contain context-chip-label; cell becomes affected only when it also contains a qualifying chip.

Table state branches: expanded initialized from `getClientSettings().wordWrap`; hasClippedCells initially false and recalculated by cell scrollWidth/clientWidth; zero/short cells omit wrap control, overflowing cells expose it. Measurement temporarily forces collapsed state and restores it plus horizontal scroll, so F7–F9 cover width/content changes. MutationObserver covers streamed children/text; resize observation covers table resize. Wrap toggle sets only local table state. Copy menu has Markdown/CSV paths, copied feedback/reset (1200ms), and failure reporting without changing cell CSS. Tooltip/popover trigger overwrites the outer chip data-slot, while ContextChipLabel retains the marker matched by this diff.

Scope decisions: web local/hosted/remote/tunnel all use this same CSS; no wire, contract, provider, agent/MCP or scheduled-task change. Desktop wraps the web renderer, so the same matrix applies to Electron (issue was reported there); a primary real-client web pass is valid for this code-only pass 1, with desktop smoke desirable in later proof. Native mobile uses a separate renderer and does not import index.css: no affected native-mobile row. Settings Appearance and per-table toggle are the two entry points to collapsed/expanded states. New design changes table font size; width changes container behavior at 36rem. User font/zoom, chat width, split panel, and longer Unicode/basename suffixes move the clipping threshold; use measured fit/overflow cases rather than multiplying them into identical CSS states. rehypeTextDirection sets a top-level table direction from its prose; F2 includes an RTL-dominant table, while MarkdownFileLink isolates its chip left-to-right. No browser-engine-specific mechanism is introduced; do not claim Safari/Firefox verification from Chromium captures.

Inspection only: no edits in the worktree, no forge writes/comments, no app/RPC mutation, no server/browser started/stopped. Disk had 19 GB available at audit start and final check. `git diff --check` passed for the pinned base/head. No tests needed for matrix derivation; no visual claim based on source inspection.

FRICTION: none
VERDICT: SUFFICIENT
