Two-process model and IPC¶
The split between React and Rust is a security boundary, not only an implementation detail.
Frontend responsibilities¶
React owns:
- rendering and user interaction;
- tab, pane and space state;
- CodeMirror and xterm instances;
- provider configuration UI and AI session presentation;
- diff review and approval cards;
- shortcut routing and command-palette presentation.
The frontend can request a native operation through a typed invoke() bridge or subscribe to a Tauri event/channel. It cannot open a file, spawn a process or call a provider directly.
Rust responsibilities¶
Rust owns:
- PTY and shell lifecycle;
- filesystem reads, writes, search and watches;
- Git command execution and repository resolution;
- workspace authorization;
- AI HTTP proxying and SSRF protection;
- OS keychain access;
- LSP process hosts and JSON-RPC framing;
- serial, SSH and WSL native integrations.
Command flow¶
user action
-> React module validates display state
-> typed invoke() bridge
-> Rust command validates path, cwd, ids and authorization
-> native operation
-> serialized result or Channel/event stream
-> React store updates the view
Frontend validation improves UX but never replaces native validation. Rust must treat every webview argument as untrusted.
Registration checklist¶
When adding a command:
- Add a focused implementation in the owning Rust module.
- Register it in
src-tauri/src/lib.rs. - Add plugin and capability configuration when required.
- Add a typed bridge under the matching frontend module.
- Check workspace and secret-path authorization.
- Add tests for success, malformed input and denied input.
Streaming¶
Long-lived PTY output uses a Tauri Channel<PtyEvent> rather than repeated JSON polling. AI streams use the native network proxy and are surfaced through the AI SDK transport. Background process logs use bounded ring buffers so a process cannot grow memory without limit.
Capability allowlist¶
Tauri plugin APIs are allowlisted in src-tauri/capabilities/default.json. A plugin addition normally needs a Cargo dependency, a .plugin(...) registration and a capability entry. Do not grant a broad capability to make a narrow feature work.