# Proof audit 1: clipboard in html_render

Issue [#27402](https://git.cafe/pingdotgg/t3code/issues/27402). Read-only inspection of `fix/issue-27402`, head [8f66a27e8d](https://git.cafe/pingdotgg/t3code/commit/8f66a27e8d63292f6bdaf1e3a546debaa93797f5), against main [1cf14a498f](https://git.cafe/pingdotgg/t3code/commit/1cf14a498fa27712a305097012c6f8f920a6da33). No captures exist for this pass; baseline failures in inline and full-screen were reported by the lead, not independently captured here.

## Findings

- Four render contexts need proof. `HtmlRenderDocument` covers inline, dialog, and explicitly flagged attachment previews. `GuestHtmlRender` owns a separate iframe and must be exercised separately. Both add only `allow="clipboard-write"`; there is no layout change.
- A still cannot prove the change. Record a real click, the page's status transition, and a paste of the actual clipboard text into an unsent composer or other neutral text field. A success label alone is weaker than verifying the clipboard contents. Before each run, replace the clipboard with a different sentinel so a previous successful run cannot supply a false positive.
- The attachment branch is code-reachable but has no identified production producer of `ChatFileAttachment.htmlRender = true` in this revision. Ordinary uploaded/workspace HTML does not select the changed component. R3 is explicitly a harness capture unless a real producer is demonstrated.
- The guest route requires Labs > Guest links, a valid T3 Connect hostname, a newly created guest invite, and a redeemed guest session. Plain localhost with no Connect configuration cannot seed that route through `guestShare.create` (it returns `connect_unavailable`). Do not present an owner frame or a bare HTML URL as guest proof.
- The new browser test drives a real click in a Blob-backed `HtmlRenderDocument`, with a headless clipboard-write permission grant. It does not exercise the real asset response/CSP, wrappers, guest session, or guest iframe. This pass inspected it but did not run tests or a browser.

## Shared fixture and capture contract

Publish one short page in a real Codex turn through the isolated dev server's `html_render` tool. Use title `Clipboard proof 27402`, `<pre id="code">echo hello-27402</pre>`, a visible Copy button that calls `navigator.clipboard.writeText(document.querySelector('#code').textContent)` directly from its click handler, and `<output id="status">idle</output>`. Set output to `copied` only after the promise resolves; on rejection show `failed: <name> <message>`. Keep code, button, and status visible without scrolling. Use the returned real attachmentId, title, height, and optional measured heights; do not fake a tool output for R1/R2/R4.

Minimum capture cells: **1440 x 900 CSS px, Chromium, dark theme, R1-R4, both revisions**. Eight row/revision segments; segments may share recordings (in particular R1 followed by R2). Use the same viewport, fixture HTML, theme, and browser permission setup for before and after. Baseline should show the permissions-policy NotAllowedError after Copy; fixed should show `copied` and paste exactly `echo hello-27402`. If a row behaves differently, record and report it rather than manufacturing the expected state. All four rows are **recording**, never still. No additional light/narrow/zoom/title-length/count permutations are required for an unconditional iframe permission change. Local owner rows use a secure localhost origin; the real guest row supplies the HTTPS/remote route. Plain insecure HTTP is outside this clipboard proof.

If headless Chromium requires its normal context-level clipboard-write grant, use the identical grant on main and head, disclose it, and keep iframe sandbox and Permissions Policy enforcement enabled. Do not disable browser security or grant a different policy to the child frame.

| ID / named fixture | Render path and required data | Diff affected | Seed in the real app / harness label | Minimum viewport x theme / medium | Required recording and expected result | Status |
| --- | --- | --- | --- | --- | --- | --- |
| R1 — Owner inline, loaded short page | `HtmlRenderTimelineRow` -> `HtmlRenderFrame` -> `HtmlRenderDocument`; one valid `HtmlRenderReference`, working environment/thread reference and asset URL, page loaded with live height | Yes: shared iframe permission | **Real app:** real Codex turn through isolated dev server calls `html_render` with shared fixture; open its thread and bring the inline frame into view | 1440 x 900, dark; **recording**, main + head | Start at idle; click the page's Copy; show failure on main, `copied` on head; paste clipboard into an unsent composer to verify exact contents | **PENDING CAPTURE** both revisions; main failure reported only |
| R2 — Owner full-screen, default zoom | R1 -> Open full screen -> `HtmlRenderDialog` -> fresh `HtmlRenderDocument`; same asset and short title, zoom 1 | Yes: shared iframe permission in a separately mounted document | **Real app:** reuse R1's real turn; hover/focus frame, click Open full screen; use the actual dialog, not the inline frame behind it | 1440 x 900, dark; **recording**, main + head; may follow R1 in same video | Record opening dialog, idle -> Copy -> failure/copied; close dialog and paste without sending; verify exact clipboard on head. Close/reopen resets the page if another attempt is needed | **PENDING CAPTURE** both revisions; main failure reported only |
| R3 — Flagged html_render attachment, rendered panel | `RightPanelSurfaceContent` -> `FilePreviewPanel` attachment branch -> `AttachmentFilePreview` -> `BrowserDocumentFrame` (`pdf=false`, `htmlRender=true`) -> `HtmlRenderDocument`; valid HTML attachment id/name/MIME/size and threadRef; rendered mode on | Yes, only when flag is true; ordinary HTML/PDF/source modes bypass it | **LABELLED HARNESS:** reuse R1's real published attachment. In the dev client import `/src/rightPanelStore.ts`, then call `useRightPanelStore.getState().openAttachment(threadRef, {type:'file', id:attachmentId, name:'Clipboard proof 27402.html', mimeType:'text/html', sizeBytes:actualBytes, htmlRender:true})`. This seeds client panel state; retain the real server asset URL/CSP. No production flag producer found. If Vite module access is unavailable, mount `AttachmentFilePreview` with those props in an explicitly labelled harness | 1440 x 900, dark; **recording**, main + head | Show the attachment panel and rendered page; click the page's Copy, show failure/copied, verify paste. Do not use the toolbar's Copy contents action. A normal uploaded HTML attachment is not equivalent | **PENDING HARNESS CAPTURE** both revisions; production entry path unproven |
| R4 — Redeemed guest, loaded shared page | `/guest` -> `GuestPage` -> `GuestSession` -> `GuestThread` timeline override -> `GuestHtmlRender`; valid shareId, HTML-render row itemId, same reference; iframe `/api/guest/render?share=<id>&item=<id>` | Yes: distinct guest iframe permission, separate from shared owner component | **Real guest flow:** on isolated environment with active T3 Connect hostname enable Guest links in Settings > Labs; use thread action Share with guest (RPC `guestShare.create({threadId})`); open returned `/guest#<credential>` in a separate guest context, enter guest name/redeem, and locate R1's real tool item. Keep invite/session secrets out of uploaded evidence. If Connect/DNS prevents this, label any mounted `GuestHtmlRender` capture **HARNESS**, with the real-flow limitation explicit | 1440 x 900, dark; **recording**, main + head; real guest route is HTTPS | From redeemed guest timeline, click page Copy; show failure/copied and paste into the guest's unsent message field. Verify exact clipboard text. Neither an owner-frame recording nor navigation directly to the HTML asset proves this row | **PENDING CAPTURE** both revisions; Connect/guest flow not exercised |

## Callers, data, branches, and why the matrix stops here

- **All production direct callers:** `HtmlRenderDocument` is called by `HtmlRenderFrame`, `HtmlRenderDialog`, and `BrowserDocumentFrame`. `HtmlRenderFrame` is called by `HtmlRenderTimelineRow`; `HtmlRenderDialog` only by `HtmlRenderFrame`. `GuestHtmlRender` only by the timeline override in `GuestThread` in `GuestPage`. The guest override bypasses the owner frame. `BrowserDocumentFrame` is called by `AttachmentFilePreview` and `WorkspaceBrowserPreview` in `FilePreviewPanel`.
- **Attachment callers:** `AttachmentFilePreview` is called by `FilePreviewPanel` for attachments (forwards `attachment.htmlRender`), by its workspace document/binary branch (no flag), and by `ComposerFilePreviewDialog` (no flag). WorkspaceBrowserPreview also supplies no flag. BrowserDocumentFrame selects PDF first, then htmlRender, otherwise FileDocument. Thus PDF, ordinary workspace/uploaded HTML, composer file preview, and HTML source view do not receive the new allow attribute. The attachment branch requires a relativePath and attachment; the panel store supplies relativePath from attachment.name. This is why R3 needs the explicit flag rather than ordinary file upload.
- **Data/layout:** `HtmlRenderReference` contains attachmentId, title, height, optional width/height samples. Inline/guest sizing chooses reported live content height (80..20000), otherwise measured-neighbor heights with caps, otherwise legacy height (80..2000). Width comes from ResizeObserver. Counts of HTML rows do not alter policy; use one. Short vs long/empty titles affect fallback/truncation/file naming; not the permission. A changed attachmentId/key remounts; renewed src keeps the initial document. Dialog is 92vw with viewport-limited height and zoom 0.5..2 in 0.25 steps; panel is flex-sized. One loaded short page per container covers the changed behavior without generating a layout matrix for unchanged code.
- **Lifecycle/error branches:** inline asset pending has no iframe; initial asset failure shows Unable to load; success mounts it. Full-screen opens with a sufficiently fresh URL or refreshed URL; refresh failure shows Page unavailable. HtmlRenderDocument loaded=false/true changes color scheme only; onLoad posts current theme/shortcuts. Attachment loading, URL/text failures + Retry, source toggle, text truncation, large-text, archive/SQLite, fonts, Office, Markdown/Mermaid/table, audio/video/image, unsupported file, download saving, optional close/remove/origin/size controls bypass or surround the changed frame. Guest checking/name/invalid/connecting/ended/failed-session and empty/no-html timeline contain no guest HTML iframe; only a redeemed live thread with an HTML row exercises the change. No loading, expired-link, retry, or time-delay capture is needed to prove clipboard permission.
- **Interactions:** the changed interaction is the page's Copy click and writeText promise. Full-screen open/close is included in R2. Unchanged zoom, resize, theme updates (fragment then postMessage), source/render toggle, save/retry/close/remove, and content-height messages do not select another permission value. Owner link opens, modified external opens, allowlisted Cmd/Ctrl shortcuts (Mac vs other), focused/recent-activation gating, image expand/close and fullscreen callbacks are unchanged; guest link open and height messages are unchanged. No extra rows for those features.
- **Surfaces:** web and Electron consume the same web iframe components; this is a web proof matrix and does not claim native Electron clipboard permission verification. Native mobile uses its separate HtmlRenderWebView and AttachmentDocumentBody, not either changed component. Providers only need to publish the same html_render artifact; Codex is the real seed requested here. No provider adapter, wire contract, or native-mobile change occurs. Width/theme/platform do not branch the new allow attribute. Insecure remote HTTP and browser engines that do not support this Permissions Policy feature are not established by Chromium evidence.

## Audit limits and friction

No browser/computer use, dev server, emulator, lease, code edit, test run, push, comment, PR, or checkout change was performed by this reviewer. This is fixture derivation, not behavioral verification. The lead owns the checkout and scratch parent; retain this file if upload fails, as requested. An issue read via the origin-selected GitCafe CLI failed with `SERVER_ERROR / ConnectionRefused`; issue behavior context therefore comes from the brief. All local caller tracing was completed within the 45-minute budget.

Upload attempt failed: `ghdrop upload --json` returned `Transport error (POST .../files)`, exit 1. Raw output remains in `ghdrop-upload.log` beside this file. No public URL was returned. The requested fallback matrix and unuploaded raw log are deliberately retained; no reviewer-owned running services or checkout exist to clean up. Final free disk check: 15 GB.

FRICTION:
- infra | ghdrop upload failed with a transport error; GitCafe issue read failed with ConnectionRefused | working external connectivity from the review VM.
- brief | attachment-preview context implies a production entry path, but this revision has no identified producer of htmlRender=true | identify a real entry path or explicitly accept a labelled panel-state harness.

VERDICT: INSUFFICIENT R1-R4 before/after recordings still to capture; R3 must be labelled harness unless a production entry path is demonstrated.
