Skip to content

fix(preview): embed external URLs in native child webview#277

Open
dcieslak19973 wants to merge 23 commits into
crynta:mainfrom
dcieslak19973:fix/external-url-preview
Open

fix(preview): embed external URLs in native child webview#277
dcieslak19973 wants to merge 23 commits into
crynta:mainfrom
dcieslak19973:fix/external-url-preview

Conversation

@dcieslak19973
Copy link
Copy Markdown

Problem

When navigating to a non-localhost URL in the Preview tab, users saw either a blank page (X-Frame-Options blocking the iframe) or the previous amber warning banner with a link to open externally. OAuth workflows that redirect back to localhost were impossible to complete inside the app.

Solution

Replace the iframe (and the external-browser fallback) with a native Tauri child webview for all non-localhost URLs.

A child webview is an OS-level WebView component embedded directly in the main window. Because it performs a top-level navigation rather than an embedded <iframe> load, the browser's X-Frame-Options / CSP frame-restriction rules simply do not apply. OAuth flows work naturally — the webview follows every redirect including back to localhost.

Changes

Rust (src-tauri/)

File Change
Cargo.toml Enable tauri = { features = ["unstable"] } (unlocks multi-webview API) and add url = "2"
src/modules/preview.rs New. Five commands: preview_open, preview_set_bounds, preview_set_visible, preview_reload, preview_close. Visibility is implemented via size-collapse (0×0) since Webview::set_visible is absent in Tauri 2.10.x.
src/modules/mod.rs Register pub mod preview
src/lib.rs Import and wire up the five new commands

Frontend (src/)

File Change
PreviewPane.tsx Manages the native child webview lifecycle: open/navigate on URL change, ResizeObserver keeps bounds in sync, show/hide on tab switch, close on unmount. Localhost URLs keep the existing iframe path (including 30 s suspension). preview:url-changed events from Rust keep the address bar in sync across redirects.
PreviewStack.tsx Pass tabId so each tab gets a uniquely-labelled webview (preview-{id}).

Pre-existing warnings fixed

  • Remove unused Arc / PtySize imports from pty/mod.rs
  • Remove dead pub use re-exports of ssh_profile_delete/ssh_profile_save from ssh/mod.rs (lib.rs already references them via full path)
  • Add #[allow(dead_code)] to WorkspaceEnv::is_ssh

dcieslak19973 and others added 22 commits May 15, 2026 17:24
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…RUD, search, grep

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…de tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… mod, profile cmd paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix russh API paths for Windows (keys::key::PublicKey, keys::load_secret_key)
- Fix profile_id serde rename to camelCase for frontend compat
- Fix pty flush: use interval timer so output isn't stuck in pending buffer
- Add ssh_home command to fetch remote home dir via exec
- Auto-reconnect last SSH profile on app startup, persisted via lastSshProfileId pref
- Connect SSH before fetching home dir in switchWorkspace

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the iframe + X-Frame-Options warning with a native Tauri child
webview for non-localhost URLs.  Since the child webview is a top-level
OS WebView component (not an iframe), X-Frame-Options and CSP frame
restrictions have no effect, and OAuth flows with localhost redirects
work naturally.

Changes:
- src-tauri: add 'unstable' feature to tauri dep (enables multi-webview
  API: WebviewBuilder, Window::add_child, Manager::get_webview)
- src-tauri: add url = '2' crate for URL parsing
- src-tauri/src/modules/preview.rs: new module with five commands —
  preview_open, preview_set_bounds, preview_set_visible, preview_reload,
  preview_close.  Visibility is implemented via size-collapse (0x0) since
  Webview::set_visible does not exist in Tauri 2.10.x.
- PreviewPane.tsx: manage the native child webview lifecycle (open,
  navigate, resize via ResizeObserver, show/hide on tab switch, close on
  unmount).  Localhost URLs continue to use the existing iframe path
  including the 30s suspension logic.  Navigation events emitted by the
  Rust on_navigation hook keep the address bar in sync across redirects.
- PreviewStack.tsx: pass tabId prop so each tab gets a uniquely-labelled
  webview (preview-{id}).

Also fix four pre-existing Rust warnings:
- Remove unused Arc import from pty/mod.rs
- Remove unused PtySize import from pty/mod.rs
- Remove dead pub-use re-exports of ssh_profile_delete/save from
  ssh/mod.rs (lib.rs references them via full path)
- Add #[allow(dead_code)] to WorkspaceEnv::is_ssh
@dcieslak19973 dcieslak19973 requested a review from crynta as a code owner May 16, 2026 01:10
Copilot AI review requested due to automatic review settings May 16, 2026 01:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds first-class SSH workspace support (profiles, connection management, remote PTY + SFTP) and upgrades the preview experience by using native child webviews for external URLs.

Changes:

  • Add SSH profiles UI (Settings tab) + statusbar environment selector integration
  • Implement SSH backend (russh) with cached connections, SSH PTY, and SFTP-backed FS operations
  • Add native preview child webviews to bypass X-Frame-Options/CSP restrictions for external URLs

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/settings/sections/SshSection.tsx New SSH profiles settings UI (CRUD + validation)
src/settings/SettingsApp.tsx Adds “SSH” settings tab + icon
src/modules/workspace/env.ts Adds WorkspaceEnv SSH variant + persists last profile selection
src/modules/statusbar/WorkspaceEnvSelector.tsx Adds SSH profile selection + TOFU dialog flow in env picker
src/modules/ssh/types.ts Defines frontend SSH profile/auth types
src/modules/ssh/store.ts Zustand store for SSH profiles + connect state
src/modules/ssh/index.ts Re-exports SSH module public surface
src/modules/ssh/commands.ts Tauri invoke wrappers for SSH commands
src/modules/ssh/FingerprintDialog.tsx UI dialog for TOFU fingerprint confirmation
src/modules/settings/store.ts Adds preference lastSshProfileId + persistence helpers
src/modules/settings/openSettingsWindow.ts Extends Settings tabs union with ssh
src/modules/preview/PreviewStack.tsx Passes tabId to PreviewPane for native webview management
src/modules/preview/PreviewPane.tsx Adds native webview path for external URLs + URL sync + bounds management
src/app/App.tsx Auto-reconnect to last SSH profile and use SSH home directory
src-tauri/src/modules/workspace.rs Adds Rust WorkspaceEnv::Ssh + guards in resolve_path
src-tauri/src/modules/ssh/sftp.rs Implements SFTP filesystem operations + remote find/grep
src-tauri/src/modules/ssh/pty.rs Implements SSH PTY channel plumbing via russh
src-tauri/src/modules/ssh/profiles.rs SSH profile CRUD in plugin store + serde tests
src-tauri/src/modules/ssh/mod.rs Core SSH connect/disconnect/home/fingerprint commands
src-tauri/src/modules/ssh/handler.rs Host key verification handler (TOFU/mismatch)
src-tauri/src/modules/ssh/connection.rs Shared SSH connection state management
src-tauri/src/modules/pty/session.rs Adds PtyHandle enum + SSH PTY command channel types
src-tauri/src/modules/pty/mod.rs Refactors PTY commands to support SSH sessions + async pty_open
src-tauri/src/modules/preview.rs Implements native preview child webview commands
src-tauri/src/modules/mod.rs Registers new preview and ssh modules
src-tauri/src/modules/fs/tree.rs Adds SSH branching + moves local IO to spawn_blocking
src-tauri/src/modules/fs/search.rs Adds SSH search via remote exec + spawn_blocking for local
src-tauri/src/modules/fs/mutate.rs Adds SSH mutate operations + spawn_blocking for local
src-tauri/src/modules/fs/grep.rs Adds SSH grep via remote exec + spawn_blocking for local/glob
src-tauri/src/modules/fs/file.rs Adds SSH read/write/stat via SFTP + spawn_blocking for local
src-tauri/src/lib.rs Registers SSH + Preview commands and manages SSH state
src-tauri/Cargo.toml Adds russh/tokio/url deps and enables tauri unstable
docs/superpowers/specs/2026-05-15-ssh-support-design.md SSH design spec document
docs/superpowers/plans/2026-05-15-ssh-support.md SSH implementation plan document
Comments suppressed due to low confidence (3)

src/modules/statusbar/WorkspaceEnvSelector.tsx:1

  • sshFingerprintGet(profileId) (per the Rust implementation) returns the stored known_fingerprint from the profile, not a freshly observed server key fingerprint. With the current condition if (!profile.knownFingerprint), fp will typically be null and the TOFU dialog will never show. Consider adding a dedicated backend command that performs a handshake and returns the observed fingerprint without persisting it, then base the dialog on that; alternatively, have ssh_connect return the observed fingerprint when knownFingerprint is missing and require an explicit 'trust' step.
    src/modules/statusbar/WorkspaceEnvSelector.tsx:1
  • The Refresh item always calls refreshDistros(), but the WSL section is now gated behind IS_WINDOWS. On non-Windows platforms this button is confusing (and may trigger avoidable errors/log noise if WSL commands aren’t meaningful). Consider conditionally rendering this refresh item only when IS_WINDOWS is true, or split it into separate refresh actions (e.g., 'Refresh WSL distros' vs 'Refresh SSH profiles').
    src/settings/sections/SshSection.tsx:1
  • When authMethod is \"key\", saving without a keyPath will fail later in ssh_connect with a backend error (\"key auth requires keyPath\"). This is preventable in the UI with a targeted validation and a user-facing message (e.g., 'Key path is required for key file auth') so the failure is immediate and actionable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src-tauri/src/modules/ssh/mod.rs Outdated
Comment thread src-tauri/src/modules/ssh/sftp.rs
Comment thread src-tauri/src/modules/workspace.rs
Comment thread src-tauri/src/modules/pty/session.rs
Comment thread src-tauri/src/modules/pty/session.rs
- ssh/mod.rs: remove silent TOFU auto-persist; return TOFU_REQUIRED:<fp>
  error so the frontend shows a confirmation dialog before persisting.
  Add ssh_fingerprint_save command for explicit trust after acceptance.
- ssh/sftp.rs: fix shell_escape double-quoting in sftp_search; build the
  full *query* glob pattern first, then escape once.
- workspace.rs: replace panic! in resolve_path SSH arm with log::error +
  PathBuf::from(path) fallback to avoid crashing the backend on
  unexpected callsites.
- pty/session.rs + ssh/pty.rs: switch SshPtySession from bounded mpsc
  channel (256) to unbounded_channel so that pty_write/pty_resize never
  silently drop keystrokes or resize events under load.
- commands.ts: add sshFingerprintSave invoke wrapper.
- WorkspaceEnvSelector.tsx: fix TOFU flow — attempt connect directly,
  detect TOFU_REQUIRED error, show dialog with the observed fingerprint,
  save via sshFingerprintSave on acceptance, then retry connect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants