fix(preview): embed external URLs in native child webview#277
Open
dcieslak19973 wants to merge 23 commits into
Open
fix(preview): embed external URLs in native child webview#277dcieslak19973 wants to merge 23 commits into
dcieslak19973 wants to merge 23 commits into
Conversation
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
There was a problem hiding this comment.
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 storedknown_fingerprintfrom the profile, not a freshly observed server key fingerprint. With the current conditionif (!profile.knownFingerprint),fpwill 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, havessh_connectreturn the observed fingerprint whenknownFingerprintis missing and require an explicit 'trust' step.
src/modules/statusbar/WorkspaceEnvSelector.tsx:1- The
Refreshitem always callsrefreshDistros(), but the WSL section is now gated behindIS_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 whenIS_WINDOWSis true, or split it into separate refresh actions (e.g., 'Refresh WSL distros' vs 'Refresh SSH profiles').
src/settings/sections/SshSection.tsx:1 - When
authMethodis\"key\", saving without akeyPathwill fail later inssh_connectwith 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.
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
localhostwere 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 tolocalhost.Changes
Rust (
src-tauri/)Cargo.tomltauri = { features = ["unstable"] }(unlocks multi-webview API) and addurl = "2"src/modules/preview.rspreview_open,preview_set_bounds,preview_set_visible,preview_reload,preview_close. Visibility is implemented via size-collapse (0×0) sinceWebview::set_visibleis absent in Tauri 2.10.x.src/modules/mod.rspub mod previewsrc/lib.rsFrontend (
src/)PreviewPane.tsxResizeObserverkeeps bounds in sync, show/hide on tab switch, close on unmount. Localhost URLs keep the existing iframe path (including 30 s suspension).preview:url-changedevents from Rust keep the address bar in sync across redirects.PreviewStack.tsxtabIdso each tab gets a uniquely-labelled webview (preview-{id}).Pre-existing warnings fixed
Arc/PtySizeimports frompty/mod.rspub usere-exports ofssh_profile_delete/ssh_profile_savefromssh/mod.rs(lib.rsalready references them via full path)#[allow(dead_code)]toWorkspaceEnv::is_ssh