fix(shared): report non-web preview URLs as unsupported protocols#25688

closed6
main
fix/issue-25639
opened13d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 9d ago

Problem

Calling preview_open with about:blank prepended https:// and failed with a misleading parse: https: error. Explicit non-web schemes should be reported as unsupported protocols.

Change

Reject known non-web schemes before adding a default web scheme, using the existing protocol parser and scheme set. Omitted URLs still open blank tabs, and ordinary web URLs and bare host/port inputs retain their normalization.

Scope and approval

Fixes https://github.com/pingdotgg/t3code/issues/25641

Triaged bug via the automated issue flow; GitCafe mirror #25639.

GitCafe: https://git.cafe/pingdotgg/t3code/issues/25639

Verification

  • Regression test fails on the baseline with parse/https: instead of unsupported-protocol/about:; red output.
  • After merging current main, 57 tests passed across shared preview normalization, ServerBrowser, preview MCP handlers, and preview MCP tool definitions.
  • vp run check:changed: formatting, lint, knip, affected typechecks, and adjacent tests.
  • Real dev-server before/after runtime transcript: about:blank changes from parse/https: to unsupported-protocol/about:; omitted URL returns an Idle blank tab in both runs.
  • Evidence index and public reproduction drivers, including case/whitespace, other known schemes, HTTP(S), and bare host/port probes. The changed files are identical to the evidenced implementation after the main merge.

The audited visual matrix is empty: this changes error classification before navigation and has no rendering changes. Desktop and mobile were not launched; their shared normalization path changes without native code changes. Both isolated reviews reported MERGE and the final proof audit reported SUFFICIENT in the prepared handoff.

Models: Claude Opus 5.5 and GPT-6.1-Sol; harnesses: Claude Code and Codex through T3 Code.

Takeover

Taken over from @juliusmarminge. No code changes; merged main (clean, PR diff unchanged) so the Agent context budget check compares against current main, and added evidence and verification below.

Evidence

Before 36d4d711b79c (main), after 2075a09038 (head). Each side ran from its own checkout on a fresh synthetic T3CODE_DEV_SEED=0 database.

Environment: fs-37 Linux x86_64, Node 26.12.1. Electron 44.9.3 under Xvfb, 1280×800, light, DPR 1, clock frozen. Android Pixel 9 emulator (API 36), 1080×2424 portrait light, font scale 1.0. iOS iPhone 17 Pro simulator (iOS 27.2), 402×874 pt portrait light. Mobile used the real dev client with JS from each checkout's Metro; status-bar clocks not frozen.

Server RPC and MCP (real dev servers, raw ordered frames): runtime-transcript.md (before frames, after frames). preview.open / preview.navigate RPC and preview_open / preview_navigate MCP (url and target.url) with about:blank go from parse/https: to unsupported-protocol/about: with no tab created or mutated; mailto:a@b.c and ftp:21, previously normalised to https://mailto:a@b.c/ and https://ftp:21/, are now rejected; viewer-stream navigate rejects silently on both; omitted URL still opens an Idle blank tab; a local HTTP page still loads.

U4 Desktop project action (previewUrl about:blank, then mailto:a@b.c): before.mp4 · after.mp4 · steps before / after

U3 Mobile address bar, submit mailto:a@b.c: before sends a navigate to the normalised HTTPS URL (the before stills show a blank browser area on Android and the previous page under the new address on iOS); after shows "Could not open this address" and keeps the current page. Android before.mp4 · after.mp4; iOS before.mp4 · after.mp4. After dismissal: Android, iOS.

U3 / iOS iPhone 17 Pro / 402×874 portrait lightBeforeAfterDiff0DiffT
Settled stateU3-ios-before.png
36d4d711b79c
U3-ios-after.png
2075a09038
U3-ios-diff0.png
36d4d711b79c → 2075a09038
U3-ios-diff5pct.png
36d4d711b79c → 2075a09038

Diff0: any nonzero RGB difference, amplified to red. DiffT: 5% luminance threshold. Full frames, no masks.

What else changed: Electron terminal scrollback has two extra synthetic true lines from capture retries (visible in both U4 diffs); toast wording and wrap; mobile status-bar clocks; iOS streamed-page vertical placement differs between runs; per-run tab ids, ports and timestamps in protocol output. The web and desktop address bars are unchanged: resolveAddressBarInput already rejected these schemes.

Not captured: direct desktop IPC preview.navigate and raw persisted browser history entries (function-level probe only: probe, results); the environment-port target branch, which bypasses the normaliser.

Drivers and seeds: runtime-driver.mjs, provider-fixture.mjs, seed-visual.mjs, desktop-record.mjs, desktop-launch.sh, mobile-record.py, all scripts. Fixture matrix: proof-matrix.md.

Verification log
  • Mode: evidence-only, no code changes. Head 2075a09038 = 36d4d711b79c merged into ad98157e87; vp run check:changed passed (format, lint, knip, typecheck of 31 packages, 24 tests in packages/shared/src/preview.test.ts).
  • Proof audit pass 1: 16-row matrix, SUFFICIENT. Captured 10/16 rows; U1, U2 and U5 are unchanged controls; R8 breadth, H1 and H2 are function-level only.
  • Proof audit pass 2 (report): INSUFFICIENT for U3 (before frames: Android blank, iOS stale page), U4 (diffs include unrelated terminal scrollback), and R8 breadth / R9 environment-port / H1 / H2 (function-level only). R1–R7 runtime rows SUFFICIENT. Recapture owed.
  • CI: Build/Typecheck/Check green on ad98157e87; Agent context budget failed there only because main has since cut mcpToolListJsonBytes (54964 → 42328), fixed by the main merge. On 2075a09038: Agent context budget and Native fingerprint diff green; Build/Typecheck/Test suite in progress at handoff.
  • Unverified surfaces: none.
  • Model and harness: Claude Opus 5.5 (lead, Claude Code) and GPT-6.1-Sol (proof audit, capture, Codex) through T3 Code.

Checks

21 successful · 18 skipped
All checks passed
2075a09

Added 4 commits

  • fix(shared): preview URLs reject about:blank as an unsupported protocol49ad3bf
  • Merge remote-tracking branch 'origin/main' into fix/issue-256397bfb5b6
  • test(shared): preview URLs reject about: as an unsupported protocol (red)a778b77
  • Merge main into fix/issue-25639d00eeb8
Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 576a40fa530d0883068377aec83332ffbfb42629 and 7bfb5b6676956ac5d85296e3c4844a805091b419.

External image Files selected for processing: 2
  • packages/shared/src/preview.test.ts
  • packages/shared/src/preview.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Walkthrough

normalizePreviewUrl now detects known non-web schemes before adding a default web scheme. It reports those inputs as unsupported-protocol errors. A test verifies the error for about:blank.

Changes: Preview URL normalization

Layer / File(s)Summary
Detect and test unsupported schemes
packages/shared/src/preview.ts, packages/shared/src/preview.test.ts
The normalizer rejects recognized non-web schemes with an unsupported-protocol error before URL rewriting. A test checks the error for about:blank.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~5 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related PRs

Suggested reviewers: bmdavis419

Merge Risk: External image Minimal · up to 7bfb5

The requested unsupported-scheme behavior is implemented without an established regression in supported preview flows.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the main change: reporting non-web preview URLs as unsupported protocols. It uses a conventional commit format.
Linked Issues checkPassedIssue #25641 requires about:blank to open as a blank tab or return unsupported-protocol: about:. The PR chooses rejection. normalizePreviewUrl now parses an explicit scheme before prefixing and …
Out of Scope Changes checkPassedThe diff changes only shared preview URL normalization and its regression test. The implementation uses the existing protocol parser and known non-web scheme set. The additional scheme handling preven…
Description checkPassedThe pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, expected behavior, implementation, linked issue, regression tes…
Full details: Linked Issues check

Explanation

Issue #25641 requires about:blank to open as a blank tab or return unsupported-protocol: about:. The PR chooses rejection. normalizePreviewUrl now parses an explicit scheme before prefixing and throws PreviewUrlNormalizationError with reason: "unsupported-protocol" and protocol: "about:". preview.test.ts adds a regression test for this behavior. Existing empty-input handling still supports omitted URLs through the existing caller flow.

Full details: Out of Scope Changes check

Explanation

The diff changes only shared preview URL normalization and its regression test. The implementation uses the existing protocol parser and known non-web scheme set. The additional scheme handling prevents the same misclassification for related non-web protocols. No unrelated product behavior or files are changed.

Full details: Description check

Explanation

The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, expected behavior, implementation, linked issue, regression test, focused checks, runtime evidence, and limitations. It also identifies the models and harnesses used.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

Approvability

Verdict: Approved at 7bfb5b6

Macroscope's review found this PR approvable — This is a narrowly scoped shared URL-normalization fix with a regression test. It only improves the error classification for explicit non-web schemes such as about:blank and leaves normal preview URL handling unchanged.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

No code changes detected at 2075a09. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded21.6 KiB21.6 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages110 (0.0%)8✅

Baseline: 36d4d71 · PR result: 2075a09 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into fix/issue-25639ad98157

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into fix/issue-256392075a09

Closing: current main already covers this as of 5754121c23. If you still see it on a build that includes that commit, reply and we'll reopen.