Three of the highest-power v2 tools, plus a defense-in-depth pass
on SSH-specific risk.
write_pane sends keystrokes (or any bytes) to a pane's PTY. The
policy engine matches against the text content directly so rules
like write_pane(npm test*) match by what would run, and the
compiled-in hard-deny catches rm -rf /, fork bombs, etc. regardless
of policy. Per-pane token-bucket rate limiter (30 calls / 10s,
3/sec refill) prevents a runaway loop from spamming the user with
confirm modals or burning audit-log capacity. The frontend handler
truncates the text in modal/audit summaries to ~60 chars + escapes
control characters so secrets pasted into write_pane don't echo
verbatim into the UI.
spawn_pane mirrors the existing SpawnSpec enum (WSL distro,
PowerShell, SSH) as the tool schema. New splitLeafWith helper
inserts a caller-built LeafNode (with a pre-generated id) so the
handler can await waitForPaneRegistration on that exact leaf before
replying with the resulting {leafId, paneId}. 15s spawn timeout
covers cold-start WSL distros; 30s for connect_host covers SSH
handshake + auth. Outer dispatch timeout bumped 30s → 60s. SSH
spawns without a saved hostId are refused — LeafNode only persists
sshHostId, no inline params, so use connect_host.
connect_host is a thin wrapper that looks up a saved SSH host by
id and routes through the same spawn machinery.
McpConfirm.tsx gains an optional ssh context — when the call
targets or spawns an SSH pane, a red warning banner renders
explaining that pattern matching is best-effort on the bytes we
send (remote shell expands aliases/subshells before executing).
buildConfirmSummary became buildConfirmInfo and returns the SSH
context alongside the summary string.
PR-3.5 — SSH safeguards. Two new switches in the Policy tab,
both off by default, both gated by mcp_policy::SshSafeguards:
allowOpenSsh: when off, connect_host and spawn_pane(kind=ssh)
refuse server-side with a clear "ssh-disabled" message pointing
at the Policy tab. User must open SSH manually via the titlebar
🔑 picker and toggle 🤖 on to grant Claude access.
autoAllowSpawnedSsh: when off, an SSH pane Claude spawns starts
with mcpAllow=false. User must explicitly toggle 🤖 before
Claude can read scrollback or send keystrokes. The second switch
is disabled in the UI when the first is off.
The safe-by-default design means a fresh install gives Claude no
ability to autonomously touch SSH — full safety with one click per
level to enable when consciously wanted. Both switches read fresh
per call so policy edits take effect without a server restart.
ErrorBoundary.tsx — last-resort guard against React render
exceptions. Wraps the App root + each MCP panel tab independently
so a bug in one tab doesn't blank the entire app. Shows a small
red error card with the exception message and a "Try again"
button. Caught a serde rename_all bug during PR-3.5 testing where
PolicyTab read policy.sshSafeguards but Rust serialized
ssh_safeguards (snake_case); without the boundary the whole window
went black.
newId() now exported from tree.ts for the splitLeafWith path.
McpPolicy struct gained #[serde(rename_all = "camelCase")] so
sshSafeguards survives the IPC round-trip cleanly; older policy
files without the field still load (serde defaults to safe).
After hours of fighting Svelte 5's prop-reactivity through the
recursive Pane → SplitNode → LeafPane chain (props captured at
mount, never updated; context+getter pattern crashed; DOM-direct
workarounds created zombie-split click-intercept bugs), we
checkpointed the Svelte version (branch svelte-archive at e9015b2,
tarball at D:\archives\tiletopia-svelte-2026-05-22.tar.gz) and
rewrote the frontend in React.
Kept verbatim:
- All of src-tauri/ (Rust backend, Tauri config, icons)
- scripts/ (make-icon.py, release.sh)
- README.md, CLAUDE.md, memory.md
- src/lib/layout/tree.ts (pure TS — 43 tests still pass)
- src/ipc.ts (Tauri command wrappers)
Rewrote in React:
- src/App.tsx (top-level state via useState, OrchestrationProvider
for descendants via React.Context)
- src/lib/layout/orchestration.tsx (React Context API for shared
state — known-reliable reactivity, no Svelte 5 wall)
- src/lib/layout/Pane.tsx (recursive dispatcher)
- src/lib/layout/SplitNode.tsx (draggable gutter, local ratio state)
- src/lib/layout/LeafPane.tsx (toolbar + XtermPane)
- src/components/XtermPane.tsx (xterm.js wrapper, refs for callbacks)
- src/components/Notifications.tsx, Palette.tsx
Build: Vite + @vitejs/plugin-react. TypeScript strict. Same Tauri 2
config. Verified: pnpm check (clean), pnpm test (43/43 pass).
Not yet verified: pnpm tauri dev — that requires the Windows host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>