fix(preview): WSL-only desktop renders browser tabs natively again#24238

closed2
main
GitHub fork: fix/wsl-primary-streams-browser-tabs
opened20d agobyKKaizer303
View on GitHubTwo-way sync enabledPR last imported 11d ago

Problem

On the Windows desktop app in WSL-only mode, the Browser panel never loads a page. Typing a URL and pressing Enter resets the address bar to "Search or enter URL", and the pane stays on the "Local servers" list with no error (#17009). Before 0.0.46 the panel worked natively there, element picker included.

The WSL primary is started through wsl.exe, which drops the extra file descriptors that carry the desktop browser channel. Its server therefore ran every tab on its own headless page, while the desktop still mounted a local server-driven <webview> for those tabs: typed URLs went to the webview, the server never heard about them, and its snapshot stayed Idle.

Change

The desktop browser channel now reaches a backend that cannot inherit its fds over a WebSocket, so a WSL-only primary renders its tabs natively again. Native rendering also requires that channel to exist, so a primary without one streams instead of mounting a dead webview.

  • contracts: bootstrap field desktopBrowserSocketSecret, the socket path and its hello frame, and the optional environment capability desktopBrowser.
  • desktop: the WSL-only primary's bootstrap carries a random 32-byte secret, delivered over stdin with the rest of the bootstrap. Once the backend is ready, the main process dials /api/desktop-browser on the backend's URL with the secret in a request header, then carries DesktopBrowserHost events and commands as frames, reconnecting with backoff while the backend runs. A WSL backend running beside Windows gets no secret, since only the primary's tabs render natively.
  • downloads: the server's Playwright asks for downloads in a directory on its own disk. For a WSL backend the relay now maps that directory to its \\wsl.localhost\<distro>\... path, so Chromium writes the file where the server reads it.
  • server: DesktopBrowserChannel serves either transport. The socket route checks the secret before upgrading (timing-safe compare, 401 otherwise), and returns 404 on servers that were not given one. The newest connection replaces and closes the previous one, whose frames are then ignored; when a connection closes, its tabs detach, and the desktop announces them again when it reconnects. desktopBrowser is advertised whenever either transport is configured.
  • web: rendersServerTabNatively also requires the primary's desktopBrowser; ElectronBrowserHost, PreviewView and ThreadPreviewMiniPlayer all decide through it.

Primaries that inherit the fds (Windows, macOS, Linux) keep the existing pipe transport unchanged.

Scope and approval

Fixes #17009, a triaged bug. The maintainer diagnosis lists both parts: gating native rendering on the server actually having the desktop channel, and carrying "the browser channel over a transport that survives wsl.exe, such as a loopback socket, so WSL tabs render natively too." Reporters confirmed the WSL-only setup and the symptoms in later comments. #17926 implemented only the gate and was closed by its author; #22877 marks a fallback tab headless only when the channel is available, so it does not cover a primary that never has one.

Verification

  • Live on Windows 11 with WSL2 Ubuntu (mirrored networking), vp run dev:desktop from this branch, Settings → Connections → WSL backend: Ubuntu with WSL only on. The only backend was WSL Node running apps/server/dist/bin.mjs --bootstrap-fd 0.
    • With only the capability gate (this PR's first revision), google.com loaded but was streamed from a headless Chrome inside WSL, without the element picker.
    • With the socket channel, the panel renders a native <webview> (no stream canvas) with the full toolbar. Picking the Google logo with the element picker sent it to the agent, which identified it. Asking the agent to search Google ran the search in the same native tab on screen through the preview tools, and no chrome-headless-shell process ran in WSL.
    • The Electron main process held the channel connection to the WSL backend, and an upgrade request to /api/desktop-browser without the secret got 401.
  • Tests, 112 passing across 12 files:
    • DesktopBrowserChannel.test.ts (new): only the bootstrap secret is authorized; a connected socket carries tabs and commands, and its tabs detach when it disconnects; a reconnecting desktop closes the socket it replaces, whose later frames are ignored and which gets no commands.
    • CdpRelay.test.ts: a WSL server's download directory is mapped to its \\wsl.localhost path, both for Chromium and for the saved-file lookup.
    • ServerEnvironment.test.ts: desktopBrowser with fds, with the socket secret, and absent without either.
    • DesktopBackendConfiguration.test.ts: only the WSL-only primary's bootstrap gets the secret.
    • ElectronBrowserHost.browser.test.tsx: the primary's server tab mounts a webview with the capability and none without it.
    • Existing ServerBrowser, DesktopBackendManager, DesktopBrowserHost and WSL path tests still pass.
  • vp run check:changed: format, lint, knip, graphql and the tests beside the change pass. Its parallel typecheck step cannot start tsc on this WSL host ("Device or resource busy"), so I ran tsc --noEmit per package: contracts, server, desktop, web and mobile report no errors.
  • Not checked live: an agent download in a WSL tab (covered by the relay test only); WSL NAT networking mode, where the desktop dials the same httpBaseUrl the renderer already uses but native tabs resolve localhost through WSL's localhost forwarding, as they did before 0.0.46; a packaged installer build; and the unchanged fd transport on macOS and Linux beyond the existing tests.

Checks

5 successful · 8 skipped
All checks passed
462eb40

Added 1 commit

  • fix(preview): stream a WSL-only primary's browser tabs instead of a dead webviewf19cc24
Synced from GitHub

Approvability

Verdict: Approved at f19cc24

Macroscope's review found this PR approvable — This is a focused WSL-only preview bug fix that advertises an existing browser-channel capability and uses the existing streaming fallback when that channel is unavailable. The additive contract change and targeted tests keep the scope and compatibility risk low.

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

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between f19cc242e4674e4e28f9e385d7c41d870f836a40 and 462eb40dd19082a86d6243600717a3b31cef34c7.

📒 Files selected for processing: 20
  • apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
  • apps/desktop/src/backend/DesktopBackendConfiguration.ts
  • apps/desktop/src/backend/DesktopBackendManager.ts
  • apps/desktop/src/preview/CdpRelay.test.ts
  • apps/desktop/src/preview/CdpRelay.ts
  • apps/desktop/src/preview/DesktopBrowserHost.ts
  • apps/desktop/src/wsl/wslPathParsing.ts
  • apps/server/src/cli/config.ts
  • apps/server/src/config.ts
  • apps/server/src/environment/ServerEnvironment.test.ts
  • apps/server/src/environment/ServerEnvironment.ts
  • apps/server/src/preview/DesktopBrowserChannel.test.ts
  • apps/server/src/preview/DesktopBrowserChannel.ts
  • apps/server/src/preview/ServerBrowser.test.ts
  • apps/server/src/server.ts
  • apps/web/src/browser/ElectronBrowserHost.browser.test.tsx
  • apps/web/src/browser/previewRuntime.ts
  • packages/contracts/src/desktopBootstrap.ts
  • packages/contracts/src/desktopBrowser.ts
  • packages/contracts/src/environment.ts
🚧 Files skipped from review as they are similar to previous changes: 3
  • packages/contracts/src/environment.ts
  • apps/web/src/browser/ElectronBrowserHost.browser.test.tsx
  • apps/web/src/browser/previewRuntime.ts

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

📝 Walkthrough

The server and desktop app add a secret-authenticated WebSocket browser channel for cases where browser file descriptors are unavailable. The server reports desktop browser availability, the desktop app maps WSL download paths, and the web app uses the reported capability to select native rendering for server-runtime tabs.

Changes: Desktop browser channel and tab routing

Layer / File(s)Summary
Define and publish channel availability
packages/contracts/src/*, apps/server/src/config.ts, apps/server/src/cli/config.ts, apps/server/src/environment/*
The bootstrap and environment contracts include the socket secret and desktopBrowser capability. The server reads the secret and reports the capability when browser file descriptors or the secret are configured.
Authorize and serve browser sockets
apps/server/src/preview/DesktopBrowserChannel*, apps/server/src/preview/ServerBrowser.test.ts, apps/server/src/server.ts
The server adds a WebSocket route with secret authorization, frame exchange, and replacement of stale connections. The route is registered with the server, and tests cover authorization, tab attachment, command forwarding, and replacement behavior.
Connect the desktop browser bridge
apps/desktop/src/backend/DesktopBackendConfiguration*, apps/desktop/src/backend/DesktopBackendManager.ts
The WSL primary configuration generates a socket secret. After readiness, the desktop backend manager connects to the browser socket, forwards events and commands, and retries failed connections.
Map WSL download paths
apps/desktop/src/wsl/wslPathParsing.ts, apps/desktop/src/preview/CdpRelay*, apps/desktop/src/preview/DesktopBrowserHost.ts, apps/desktop/src/backend/DesktopBackendManager.ts
The desktop browser relay maps allowed download paths before forwarding them. The WSL path converter produces UNC paths, and the relay tests cover mapped download paths.
Route server tabs by capability
apps/web/src/state/entities.ts, apps/web/src/browser/ElectronBrowserHost*, apps/web/src/browser/previewRuntime.ts
The web app reads the primary environment’s desktop browser capability and includes server-runtime tabs in native rendering only when that capability is present. Tests cover both capability states.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant DesktopBackendManager
3 participant socketRouteLayer
4 participant DesktopBrowserChannel
5 DesktopBackendManager->>socketRouteLayer: WebSocket upgrade with secret header
6 socketRouteLayer->>DesktopBrowserChannel: Authorize and serve socket
7 DesktopBackendManager->>DesktopBrowserChannel: Browser event frames
8 DesktopBrowserChannel->>DesktopBackendManager: Browser command frames

Possibly related PRs

  • pingdotgg/t3code#18217: This PR also changes the rendersServerTabNatively decision, adding serverPageMode handling.

Suggested reviewers: maria-rcks, juliusmarminge

Merge Risk: ⚪ Minimal · up to 462eb

No actionable issue remains from the inspected changes. The PR is mergeable after normal checks.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly identifies the WSL-only desktop preview fix and the return to native browser-tab rendering.
Description checkPassedThe description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the root cause, implementation, linked issue, test results, manual validation, and known v…
Linked Issues checkPassedIssue #17009 reports two related failures: the WSL-only Browser panel cannot navigate, and desktop browser tools report no connection. The change gates native rendering on the desktopBrowser capabil…
Out of Scope Changes checkPassedThe changed contracts, desktop relay, server channel, download path mapping, web capability gate, and tests all support the WSL-only Browser panel fix in #17009. Socket authentication and reconnect ha…
Full details: Description check

Explanation

The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the root cause, implementation, linked issue, test results, manual validation, and known verification gaps. It does not include before-and-after screenshots, but the description is otherwise detailed and complete.

Full details: Linked Issues check

Explanation

Issue #17009 reports two related failures: the WSL-only Browser panel cannot navigate, and desktop browser tools report no connection. The change gates native rendering on the desktopBrowser capability, so a server without a channel streams instead of mounting a dead webview. For WSL-only primaries, the bootstrap secret and /api/desktop-browser WebSocket carry DesktopBrowserChannel events and commands across wsl.exe. The channel supports tab attachment, CDP commands, pointer events, reconnects, and detachment. Tests cover socket authorization, tab attachment and detachment, replacement connections, capability advertisement, and WSL primary configuration. These changes address both reported failures.

Full details: Out of Scope Changes check

Explanation

The changed contracts, desktop relay, server channel, download path mapping, web capability gate, and tests all support the WSL-only Browser panel fix in #17009. Socket authentication and reconnect handling enable the required browser channel transport. Download path mapping supports agent browser operations in WSL. No unrelated feature or refactor is evident.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • 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.

Added 1 commit

  • fix(preview): carry the desktop browser channel to a WSL primary over a WebSocket462eb40
Synced from GitHub

@juliusmarminge could you take a look when you get a chance? This fixes #17009: on the Windows desktop app in WSL-only mode the Browser panel can't be used at all. Several people have reported it on the issue, and it still reproduces on the 0.1.2 nightlies.

It follows the options in your diagnosis there: native rendering now requires the server to actually have the desktop channel, and the channel reaches a WSL primary over a WebSocket, so WSL tabs render natively again with the element picker and agent browser tools on the same tab. I tested it live on Windows + WSL; the description has the details and what I couldn't check. Since it affects every WSL-only user, a review would be much appreciated.

Synced from GitHub

closing at @StiensWout's request under Provide evidence for the changed behavior.

Changes desktop Browser rendering in WSL-only mode from an empty/dead preview or streamed fallback to a native webview with toolbar and element picking. Detailed manual checks and focused tests are provided, but no before/after screenshots or other visual artifacts are attached anywhere in the complete submission.

submission evidence: "With the socket channel, the panel renders a native <webview> (no stream canvas) with the full toolbar."

Attach clear before/after screenshots of the WSL-only Browser panel showing the failed or fallback state and native rendered result; include a short recording for the picking/navigation interaction being claimed.

thanks for trying to help improve t3 code. if you think this closure was a mistake, please open a fresh pr addressing the requirement above, and link back to this one.