# Proof audit pass 1 — fixture matrix

PR [#25688](https://git.cafe/pingdotgg/t3code/pull/25688), branch `fix/issue-25639`. Requested head [ad98157](https://git.cafe/pingdotgg/t3code/commit/ad98157e8718083836cfda13dff8f3a6a44ad36d), base [2ad853a](https://git.cafe/pingdotgg/t3code/commit/2ad853ab211935a414cdf3c2f5004db635ae04bc).

## Findings

1. The earlier claim that the visual matrix is empty is too broad. `about:blank` has no new rendered result in the web address bar or native mobile address bar, but other inputs caught by the same guard do. Base `normalizePreviewUrl("mailto:a@b.c")` returns `https://mailto:a@b.c/`; head throws unsupported-protocol/mailto:. Base `ftp:21` returns `https://ftp:21/`; head rejects it. Mobile calls the normalizer directly and displays its existing generic alert only when normalization throws. Therefore these inputs change from sending a navigation command to displaying an alert. This is an intended rejection path, not an identified implementation bug.
2. The indirect project-action path is also affected. `resolveDiscoveredServerUrl` returns normalized HTTPS for the mailto fixture on base and returns the original input from its catch on head. `useChatTerminalActions` passes this into `openUrlInPreview` and renders a `Could not open preview` toast on RPC failure. For `about:blank`, both revisions return the original string from the resolver, but the RPC error text changes, so this toast's description changes. Use a supported desktop client for this fixture because the script path checks `isPreviewSupportedInRuntime()`.
3. Ordinary web/Electron address-bar submission goes through `resolveAddressBarInput`, which already rejected whitespace-free known non-web schemes before the PR. Its `catch {}` suppresses either error. No new visible error, loading state, or layout results for `about:blank`, mailto, or ftp:21. Malformed authority inputs such as `ftp://[` change classification internally but remain silently rejected. Inputs with interior whitespace can remain search queries; this PR does not change that resolver branch.
4. Existing runtime proof covers only `preview.open` RPC with `about:blank` and omitted URL, at older revisions, in summarized rather than raw frames. It is not an after-change MCP tool result, not `preview.navigate`, not the viewer stream, and not native client proof. Earlier breadth probes are direct function probes, not runtime transcripts or UI captures. Neither supplied artifact covers the full matrix below.
5. No rendering component changed in the diff. Every direct normalizer caller was traced. No new CSS, theme, width, count, or loading branches were added. Existing caller branches are collapsed below according to whether the changed normalizer can affect their observable result.

## Evidence classification and status

`READY` means fixture and route are derived and ready for the capturer; it does not claim a capture exists. `CONTROL` means unchanged observable behavior. `HARNESS` means an artificial raw input unavailable from a normal app-produced state; label any capture accordingly. Every interactive/time-dependent visual row is explicitly `recording`. Runtime request/response transcripts do not need a screen recording; preserve ordered raw frames and request ids instead.

| ID / named fixture | Data and entry path | Diff effect / expected base → head | Required evidence; minimum viewport × theme | Real-app seeding / driver | Status / earlier coverage |
|---|---|---|---|---|---|
| R1 RPC open, explicit blank | `preview.open({threadId,url:"about:blank",runtime:"server"})` | `PreviewInvalidUrlError`: parse/https: → unsupported-protocol/about:, length 11; no opened session | Real dev-server **RPC error payload transcript**, before/after; viewport/theme N/A | Isolated stopped test thread; authenticate normally; send Effect RPC over `/ws?orchestrationProtocol=2`; correlate response id and list sessions before/after | READY; earlier summary covers older revisions only |
| R2 RPC navigate, explicit blank | Existing non-server-runtime session; `preview.navigate({threadId,tabId,url:"about:blank"})` | Same classification change; no navigated event or snapshot mutation | Real dev-server **RPC error payload transcript**, before/after; N/A | Create session without URL and omit runtime (server-runtime navigate is control-gated for valid inputs); navigate via RPC; compare list results, ignoring timestamps only where expected | READY; missing earlier |
| R3 MCP open, explicit blank | Valid agent/thread capability; `preview_open({url:"about:blank"})` | Agent-visible execution error retains normalizer message: parse/https: → unsupported-protocol/about:; this is not the RPC `PreviewInvalidUrlError` payload | Real dev-server **MCP tool result transcript**, before/after; N/A | Use scoped MCP session for isolated thread, no existing tab required; capture complete tool result/error and no tab creation | READY; earlier mentions original error only, no paired MCP result |
| R4 MCP navigate, both URL shapes | Owned tab; `preview_navigate({tabId,url:"about:blank"})` and `{tabId,target:{kind:"url",url:"about:blank"}}` | Both resolveNavigationUrl branches expose changed error message; tab remains unchanged | Real dev-server **MCP tool result transcript**, before/after; N/A | Open blank tab with omitted URL under same agent session; retain explicit tabId; capture each result and status | READY; missing earlier |
| R5 Omitted URL / blank success | RPC `preview.open({threadId,runtime:"server"})`; MCP `preview_open({reuseExistingTab:false})` | Unchanged success: Idle/null page URL. An omitted URL is distinct from explicit about:blank and empty string | Real dev-server **RPC + MCP success transcripts**; N/A | Isolated thread; record returned tabId/status; close only created tabs after evidence | CONTROL; earlier RPC summary only |
| R6 Accepted non-web-looking raw input now rejected | `mailto:a@b.c`, `ftp:21`; direct RPC open and owned-tab MCP navigate | Base normalizes to HTTPS credentials/host-port and can start navigation; head rejects before navigation. A base network/DNS failure is distinct from normalization rejection | Real dev-server **RPC/MCP runtime transcripts**; N/A; do not require network success | Reuse R1/R4 drivers with these inputs; include normalized URL or resulting session state on base; avoid real credentials | READY; earlier after-only function probes, no runtime coverage |
| R7 Viewer stream direct invalid navigation | Controlled server viewer; send `{type:"navigate",url:"about:blank"}`, then mailto fixture | About: same silent rejection/control broadcast; mailto: base attempts navigation, head silently rejects. Error classification is swallowed by viewer catch, not sent as an RPC error | **Viewer protocol runtime transcript**, before/after, ordered input/control/status; N/A | Open owned isolated server tab, attach normal viewer stream, takeControl, send input. Raw mailto input is a protocol fixture; web address bar itself pre-rejects it | READY; missing earlier; raw input driver, no claim of UI-originated web mailto command |
| R8 Known-scheme and error-shape breadth | `ABOUT:blank`, `  about:blank  `, `ftp://[`, `ftp://example.com`, plus one representative of each of all 14 set entries | Case folds; original raw length retained; malformed known-authority parse error now unsupported; valid ftp:// already unsupported. New guard removes parsing cause from newly rejected inputs | **Supplemental function probe**, not runtime/UI evidence; N/A | Import pinned base/head shared module from owning package, no app seed needed. List known schemes: about, blob, chrome, data, file, ftp, javascript, mailto, sms, ssh, tel, view-source, ws, wss | READY; earlier covers case/outer whitespace/mailto/ftp:21, not full breadth or malformed authority |
| R9 Unchanged normalization controls | `https://example.com/path?q=1`, HTTP local fixture, `localhost:3000`, `devbox:8080`, `example.com:8080`, empty/whitespace, invalid HTTPS port, unknown numeric `custom:123`, environment-port target | Web/bare host normalization unchanged; empty and malformed HTTPS retain errors; unknown numeric scheme-like host remains accepted; environment-port branch bypasses normalizer | Supplemental function probe plus one **real runtime HTTP local success**; N/A | Local static page on a capturer-owned port and isolated thread; environment-port target uses that same port | CONTROL; earlier after-only probes cover HTTPS and three host inputs, not all controls |
| U1 Web/Electron ordinary address bar | Live tab with control, enter `about:blank`, mailto fixture, `ftp:21`, malformed ftp://[, interior-whitespace query | Same silent rejection for first four; query remains search. `resolveAddressBarInput` pre-rejects ordinary schemes. No error UI change | No required capture. If retained as proof, **recording** from typing through submit and unchanged tab; one 1280×800 light desktop viewport suffices | Open Browser panel in real web/Electron thread, obtain control for server stream. Address-bar focus command/keybinding reaches same handler | CONTROL; no earlier capture needed; ordinary web input is not the changed UI row |
| U2 Mobile about rejection / disabled input | iOS/Android, one streaming controlled server tab; submit about:blank. Also streaming false or canControl false | Existing `Could not open this address` / `Enter an http or https URL.` alert unchanged for about. Disabled address input cannot submit | No required capture; optional **recording**, native viewport, one theme | Native BrowserPreview route `{environmentId,threadId,tabId}`, normal connected server; take control. No arbitrary URL in route is needed | CONTROL; earlier native clients unlaunched |
| U3 Mobile newly rejected navigation | iOS and Android; one server tab, streaming and canControl true; enter `mailto:a@b.c`, submit | Base sends HTTPS navigate command and resets draft; head resets draft and opens generic alert without navigating | **UI recording before/after**, one native phone portrait viewport × light × iOS and Android (two paired recordings). Include input, submit, alert, dismiss, unchanged current URL | Same real BrowserPreview route as U2; keep a local known page loaded, take control, edit Address field. No database injection or harness required. ftp:21 collapses to same visible alert combination | READY; genuinely affected UI; absent earlier |
| U4 Desktop project action error text / rejection | One action, name `Preview proof`, benign terminal command `true`, `autoOpenPreview:true`, `previewUrl:"about:blank"`; repeat with mailto fixture | About toast description parse/https: → unsupported/about:. Mailto: base opens normalized HTTPS tab, head returns original raw input and fails RPC, displaying `Could not open preview` toast | **UI recording before/after**, one 1280×800 light Electron desktop viewport (one recording per revision can include both inputs). Show trigger through toast/tab outcome; capture promptly before toast expires | Create action in real Project Action editor/Settings with preview URL and auto-open; run from project action control in isolated thread. Settings edits data; runtime action aliases feed same runProjectScript handler. Choose client where isPreviewSupportedInRuntime is true | READY; genuinely affected UI; absent earlier |
| U5 Local server cards / regular recents / move tab | Configured or discovered valid HTTP(S) loopback URL; stored canonical HTTPS recent entry; move an existing normal tab server↔desktop | Existing about rejection swallowed; normal entries remain normalized; configured server merge accepts only valid HTTP(S) loopback URLs. Existing tab snapshots store normalized web URLs, so canonical recents/move do not reintroduce raw mailto | No required capture; optional interaction proof is **recording**, one desktop viewport/theme | Normal local server discovery or previously opened HTTP(S) tab. Do not claim configured server list can naturally contain mailto: it is filtered out | CONTROL; no earlier visual proof needed |
| H1 Raw persisted history compatibility | Artificial localStorage `t3code:browser-history:v1` with project key and one raw `mailto:a@b.c`, timestamp valid, title `Synthetic raw history`; optional second canonical HTTPS entry | Base hydrate normalizes raw entry to HTTPS and strips credentials; head drops it, affecting history count/empty state. Canonical history written by the app before this PR already contains HTTPS and stays | **Labelled harness recording** only if visualized; raw migration/function probe is sufficient for this artificial input. One desktop viewport/theme | Inject synthetic localStorage only in isolated client profile before hydration; normal production history producer cannot persist this raw value because recordVisit normalizes first. One raw entry tests empty vs nonempty, one canonical companion tests retained list | HARNESS; absent earlier; not a required real-user screenshot |
| H2 Desktop direct IPC normalization | Existing desktop tab; `desktop preview.navigate(tabId,"about:blank")`; repeat mailto fixture | About: inner cause classification changes, outer `PreviewOperationError` operation/message stays generic. Mailto now fails before Loading/update/loadURL. Normal web address bar does not reach this with these raw inputs | Labelled **IPC/runtime transcript**, not a screenshot. No required native visual capture beyond U4 | Real Electron IPC driver via existing previewBridge; or clearly labelled service harness if no runnable desktop. Do not present shared function probe as IPC execution | READY for IPC driver / HARNESS if service-only; absent earlier |

## Caller and render branch inventory

- Shared module: empty check runs first; new explicit known-scheme branch; existing authority-vs-prefix choice, loopback HTTP default, parse error, non-HTTP(S) protocol check, success. `resolveAddressBarInput` authority/empty fast path, no-whitespace explicit scheme guard, host detection, search fallback are all covered by R8/R9/U1.
- Server Manager: open truthy URL yields Loading only after normalization; omitted/empty-string open yields Idle; navigate normalizes before session lookup/control check and mutation. R1/R2/R5/R6. No theme/viewport/count can change these errors.
- ServerBrowser: open present-vs-omitted URL; navigate explicit url vs target.url vs environment-port; viewer navigate under control vs no control; broker translates thrown normalizer error to automation execution failure. R3/R4/R5/R6/R7/R9. Tab reuse, profile, readiness, reveal, viewport and multiple-tab selection do not change the guard; use one owned tab to avoid unrelated gates.
- Web PreviewView: submit uses address resolver; discovered/recent click chooses direct normalizer for streamed server/no bridge versus resolver for desktop bridge; move-to-desktop also uses resolver. U1/U5/H1. Its unchanged visual branches include requested/active/no tab, Idle/Loading/Success/LoadFailed/download, native/server-stream/desktop-placeholder surface, stream pending/control disabled, fill/device toolbar, profile badge, recent/local-server empty/list, desktop overlay, visible/hidden and normal width/theme. Invalid URLs throw before any branch state can change; do not multiply these unaffected combinations into captures.
- browserTargetResolver: normalize failure returns original raw; non-loopback returns normalized; loopback direct primary/WSL vs remote private-network rewrite/public-gateway error fallback. Only formerly accepted raw known schemes change the normalize-vs-return-raw result; project action U4 is a reachable consumer. Loopback transport modes are R9/U5 controls.
- browserHistoryStore: normalize returns sanitized HTTPS or null; hydrate/migrate/merge, recordVisit, title lookup/update, remove and pending visits all flow through it. Normal saved entries are canonical web URLs; artificial raw entries are H1. One raw entry vs raw+canonical covers empty/list distinction; length cap/title length/history limits/sort/grouping are unchanged.
- Desktop Manager: normalize inside attempt before tab state mutation or loadURL; rejected input produces generic operation error with changed cause, H2. Web address handler is U1; indirect action is U4. Pending/unregistered versus registered webview follows normalization and is not a distinct rejected-input fixture.
- Mobile: route selects requested/latest tab; no tabs/loading/close, single/multiple tab menus, active/foreground/PiP streaming, PDF vs streamed page, iOS sheet/header vs Android app bar; address ready is streaming && canControl. Only enabled address submit runs normalizer; all catches have fixed generic alert text. U2/U3 cover rejection-equivalent and newly rejected cases. Native platform alert presentation differs, so U3 names both platforms; extra themes/landscape/PDF/tab counts do not exercise this guard differently.
- No provider adapters or scheduled-task rendering change. Agent calls use the MCP path, independent of provider. Local/relay/tunnel do not alter normalization; a remote runtime transcript may replace the local one if it records the same destination server and RPC frames.

## Minimum capture set and acceptance

- Required visual set: U3 iOS portrait light before/after recording; U3 Android portrait light before/after recording; U4 Electron 1280×800 light before/after recording (include both project-action inputs). No still-only row proves an interaction or a transient toast. A platform that cannot be launched must remain explicitly missing, or be replaced by an honestly labelled harness and its limitation accepted by the lead.
- Required non-visual set: R1–R7 real runtime transcripts; R9 one real HTTP local success; H2 real IPC transcript or clearly labelled harness limitation. R8/R9 direct normalization breadth is supplemental. These can be bundled into a single credential-free artifact with per-row sections, revisions, input, response, request id, tab state and runtime metadata. Test assertions and direct function probes are not replacements for these runtime results.
- H1 is a labelled synthetic-data compatibility row; ordinary app persisted history is U5, unchanged. No UI capture is required for U1/U2/U5.
- Capturer must use isolated state and exact supplied commits, or provide caller/transport equivalence in addition to shared-file equivalence. Never use live userdata read-write. No pairing URL, auth ticket, token or real customer data in upload.

## Earlier evidence assessment

[Earlier runtime transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5aa0d5fc3e378650/runtime-transcript.md) has R1 and RPC half of R5, summarized on base 4034b5f6b5/head 49ad3bf500de6ffaf32b96f74b12da7af06ffe57. It merely states an independent original MCP error, not a paired after result. [Earlier evidence index](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/bc4fd61d7f0eebf0/evidence-index.md) points to case/whitespace/mailto/ftp/HTTPS/host probes, explicitly function-only, with native clients unlaunched and an uncorrelated first-message RPC driver. It claims 0/0 visual rows; U3/U4 contradict that scope claim.

This audit independently confirmed zero diff for both changed files from the primary transcript's before revision to requested base, and after revision to requested head; also from earlier PR head 7bfb5b6676956ac5d85296e3c4844a805091b419 to requested head. This establishes shared implementation equivalence only. It does not supply missing caller captures or prove old transports equal current transports.

This audit ran a credential-free base/head shared-module probe (JSONL and driver retained alongside this matrix). It confirms the classifications and success→rejection values above; it is explicitly not app/runtime/capture proof. No tests or clients were launched in this read-only pass.

## Budget and friction

Audit began 2026-10-27 14:26:56 UTC; budget check returned 15:29:11 UTC. Investigation stopped at that check; artifact preparation/upload is the handoff only. All direct normalizer callers and the indirect project-action consumer were covered; no browser/server capture was attempted, and this is not a full merge-readiness/code audit.

FRICTION:
- infra | Shared review worktree HEAD moved from requested ad98157 to f47c362 during inspection | Freeze the lead checkout during read-only audit or supply an immutable source snapshot; switched all subsequent relevant reads to git show of requested SHA.
- tool | Large batched tool output truncated, requiring smaller reads | Bound aggregate tool output or return one file per read.
- tool | Node 26 stripTypeScriptTypes rejects mode transform; probe rerun with strip and succeeded | Use strip mode for erasable TypeScript.
- infra | Later fetch of earlier RPC driver failed DNS resolution; main transcript/index/supplement were read successfully | Stable artifact-host DNS; no driver behavior was independently verified.

VERDICT: SUFFICIENT
