fix(desktop): say tunnels are unavailable instead of faking a session #95

Merged
longjacksonle merged 1 commit from fix/VAPP-86-desktop-tunnel-unsupported into main 2026-07-17 04:59:57 +02:00

Stops the desktop app pretending to open remote sessions. It does not make the panes work; native drivers stay open as follow-up. This is the AC floor from VAPP-86: "must not silently pretend to connect".

The bug

The terminal/files/desktop session drivers are built directly on browser APIs (xterm.js and the DOM, canvas + RTCPeerConnection, the File System Access API) and are compiled only for wasm32. Every other target gets no-op stubs. That is correct for the SSR pass, where the pane renders to HTML and is never interactive.

The desktop build inherits it too. dioxus-desktop runs the app natively, so it is not wasm32 and silently selects the same stubs: pane chrome renders, no socket opens, every button is a no-op. files.rs was worst because its stub returns Some(FilesHandle) (files.rs:452), so the pane looked attached and sat on "Connecting to device…" with an empty listing forever.

Telling detail: terminal.rs:113 already resolves the vendored xterm asset URLs under cfg(feature = "desktop") (from VAPP-53). The asset plumbing was made desktop-aware; the driver that would use it never was. This reads as an oversight, not deliberate scoping.

The change

Each pane checks cfg!(feature = "desktop") before any hook and renders a TunnelUnsupported card naming the tunnel and pointing at the web console. Checking before the hooks also means the desktop build stops minting relay cookies and asking an agent to dial a relay for a session it cannot drive.

The subtle part, and why there is a guard

The gate keys on the feature, never on target_arch. target_arch is the more natural-looking cfg (it is what the stubs themselves use) and it is a trap: the SSR pass is also not wasm32, so gating on it would stamp "not available in the desktop app" into every web page load and hydrate it away. That is the VAPP-85 flash class again, on the tunnel panes.

tunnel_gate_guard.rs pins both halves, because both regressions compile cleanly and only surface in front of a user:

  1. gate dropped from a pane -> desktop goes back to faking sessions;
  2. gate keyed on target_arch -> the web console starts flashing a desktop-only message.

I verified the guard fails on each regression rather than only passing as written:

REGRESSION 1: src/components/pages/files.rs no longer gates on `cfg!(feature = "desktop")` ... FAILED
REGRESSION 2: src/components/pages/files.rs gates a tunnel pane on `target_arch`; the SSR pass is
              not wasm32 either ... FAILED

Verification

The whole fix hangs on dx actually enabling the feature the gate reads, so I checked instead of assuming:

$ dx build --release --platform desktop --verbose
DEBUG Found feature desktop for renderer webview
      • features: ["desktop"]

Corroborated by the cfg(feature = "desktop") code already load-bearing on the shipped app: the embedded-asset handler (VAPP-53) and the server-URL editor (VAPP-54/58) both work there, which they could not if the feature were off.

fmt, 102 tests, and the server, wasm and desktop-feature checks all pass (all three matter here, since each selects a different path through the gate). The six clippy::unnecessary_sort_by errors in server_fns/mod.rs are pre-existing and reproduce on a clean main.

Not covered

Not run in a desktop window (no webview in the sandbox), so the rendered card is unconfirmed. The cfg selection is not in doubt; the wording and layout deserve one look on a real build.

Stops the desktop app pretending to open remote sessions. It does **not** make the panes work; native drivers stay open as follow-up. This is the AC floor from VAPP-86: "must not silently pretend to connect". ## The bug The terminal/files/desktop session drivers are built directly on browser APIs (xterm.js and the DOM, canvas + `RTCPeerConnection`, the File System Access API) and are compiled only for `wasm32`. Every other target gets no-op stubs. That is correct for the SSR pass, where the pane renders to HTML and is never interactive. The desktop build inherits it too. `dioxus-desktop` runs the app natively, so it is not `wasm32` and silently selects the same stubs: pane chrome renders, no socket opens, every button is a no-op. `files.rs` was worst because its stub returns `Some(FilesHandle)` (`files.rs:452`), so the pane looked attached and sat on "Connecting to device…" with an empty listing forever. Telling detail: `terminal.rs:113` already resolves the vendored xterm asset URLs under `cfg(feature = "desktop")` (from VAPP-53). The asset plumbing was made desktop-aware; the driver that would use it never was. This reads as an oversight, not deliberate scoping. ## The change Each pane checks `cfg!(feature = "desktop")` before any hook and renders a `TunnelUnsupported` card naming the tunnel and pointing at the web console. Checking before the hooks also means the desktop build stops minting relay cookies and asking an agent to dial a relay for a session it cannot drive. ## The subtle part, and why there is a guard The gate keys on the **feature**, never on `target_arch`. `target_arch` is the more natural-looking cfg (it is what the stubs themselves use) and it is a trap: the SSR pass is *also* not `wasm32`, so gating on it would stamp "not available in the desktop app" into every web page load and hydrate it away. That is the VAPP-85 flash class again, on the tunnel panes. `tunnel_gate_guard.rs` pins both halves, because both regressions compile cleanly and only surface in front of a user: 1. gate dropped from a pane -> desktop goes back to faking sessions; 2. gate keyed on `target_arch` -> the web console starts flashing a desktop-only message. I verified the guard **fails** on each regression rather than only passing as written: ``` REGRESSION 1: src/components/pages/files.rs no longer gates on `cfg!(feature = "desktop")` ... FAILED REGRESSION 2: src/components/pages/files.rs gates a tunnel pane on `target_arch`; the SSR pass is not wasm32 either ... FAILED ``` ## Verification The whole fix hangs on `dx` actually enabling the feature the gate reads, so I checked instead of assuming: ``` $ dx build --release --platform desktop --verbose DEBUG Found feature desktop for renderer webview • features: ["desktop"] ``` Corroborated by the `cfg(feature = "desktop")` code already load-bearing on the shipped app: the embedded-asset handler (VAPP-53) and the server-URL editor (VAPP-54/58) both work there, which they could not if the feature were off. fmt, 102 tests, and the **server**, **wasm** and **desktop-feature** checks all pass (all three matter here, since each selects a different path through the gate). The six `clippy::unnecessary_sort_by` errors in `server_fns/mod.rs` are pre-existing and reproduce on a clean `main`. ## Not covered Not run in a desktop window (no webview in the sandbox), so the rendered card is unconfirmed. The cfg selection is not in doubt; the wording and layout deserve one look on a real build.
fix(desktop): say tunnels are unavailable instead of faking a session
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 2m37s
Create release / Create release from merged PR (pull_request) Has been skipped
3da7f03d63
The terminal, files and remote-desktop session drivers are built directly on browser APIs (xterm.js and the DOM, canvas plus RTCPeerConnection, the File System Access API) and are compiled only for `wasm32`; every other target gets no-op stubs. That is right for the SSR pass, where the pane is rendered to HTML and never interactive, but the desktop build inherits it too: `dioxus-desktop` runs the app natively, so it is not `wasm32` and silently selects the same stubs. The result is a pane that renders working-looking chrome, opens no socket, and whose buttons do nothing. `files.rs` was the worst of the three because its stub returns `Some(FilesHandle)`, so the pane looked attached and sat on "Connecting to device…" with an empty listing forever rather than reporting anything.

Each pane now checks `cfg!(feature = "desktop")` before any hook and renders a `TunnelUnsupported` card naming the tunnel and pointing at the web console. Checking up front also means the desktop build no longer mints relay cookies and asks an agent to dial a relay for a session it cannot drive. This does not make the panes work; it stops them lying about it. The native drivers stay open as follow-up (VAPP-86 covers the options).

The gate is keyed on the feature and never on `target_arch`, which matters more than it looks: `target_arch` is the cfg the stubs themselves use, but the SSR pass is also not `wasm32`, so gating on it would stamp "not available in the desktop app" into every web page load and then hydrate it away, which is the VAPP-85 flash class again on the tunnel panes. `tunnel_gate_guard.rs` pins both halves (gate present in all three panes, and not keyed on the target), since both regressions compile cleanly and only show up in front of a user. The guard was verified to fail on each regression, not just to pass as written.

Verified that `dx` really does enable the feature the gate depends on, rather than assuming it: `dx build --platform desktop --verbose` reports `Found feature desktop for renderer webview / features: ["desktop"]`. It is also corroborated by the existing `cfg(feature = "desktop")` code that is already load-bearing on the shipped app (the embedded-asset handler behind VAPP-53, the server-URL editor behind VAPP-54/58). Not yet run in a desktop window (no webview here), so the rendered card is unconfirmed; the cfg selection itself is not in doubt.

fmt, tests (102), and the server, wasm and desktop-feature checks all pass. The six `clippy::unnecessary_sort_by` errors in `server_fns/mod.rs` predate this branch and reproduce on a clean `main` with the newer local toolchain.

#VAPP-86

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYXyUTntEcVJaTwn5pody
Signed-off-by: longjacksonle <longjacksonle@gmail.com>
longjacksonle deleted branch fix/VAPP-86-desktop-tunnel-unsupported 2026-07-17 04:59:58 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-apps!95
No description provided.