Architecture
Terminality is built on a pure-Rust MVVM core (libszt) with replaceable UI clients. The desktop uses Tauri 2 + React; mobile uses React Native with UniFFI-generated Swift/Kotlin bindings.
Process Model
┌─ One Desktop Process ───────────────────────────────┐
│ libszt Context (shared) │
│ ├─ WorkspaceModel │
│ ├─ HostCatalogModel │
│ ├─ SessionRegistry │
│ ├─ Vault (EncryptedVaultRepository) │
│ ├─ SettingsModel │
│ ├─ QuickCommandModel │
│ └─ AsyncExecutor (Tokio) │
│ │
│ ┌─ Window A ───────┐ ┌─ Window B ───────┐ │
│ │ WebviewWindow │ │ WebviewWindow │ │
│ │ Tab 1, Tab 2... │ │ Tab 1, Tab 2... │ │
│ │ Split trees │ │ Split trees │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌─ Dialog Windows ────────────────────────────┐ │
│ │ Settings | Connection Library | New Host │ │
│ │ tmux Sessions | Open Source Licenses │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘One process, one libszt Context. Many native windows. Dialog Windows share state but own no main-window tabs or panes.
Layered Architecture
┌─────────────────────────────────┐
│ React / React Native │ Presentation
│ xterm.js, canvas, touch │
├─────────────────────────────────┤
│ @szt/libszt │ TypeScript facade
│ ViewModel hooks, adapters │ Handwritten bindings
├─────────────────────────────────┤
│ Tauri Commands / UniFFI │ IPC boundary
│ Binary Channels (terminal) │
├─────────────────────────────────┤
│ libszt (Pure Rust) │ Behavioral core
│ ┌─────────────────────────┐ │
│ │ ViewModels │ │ UI state, commands, events
│ │ Models │ │ Domain state, validation
│ │ Services │ │ I/O, PTY, SSH, networking
│ │ Binding Interface │ │ Typed foreign-language contract
│ │ Vault / Crypto │ │ Encryption, persistence
│ └─────────────────────────┘ │
├─────────────────────────────────┤
│ Platform Adapters │
│ PTY, Keychain, trash, fonts │
└─────────────────────────────────┘MVVM Component Model
All application behavior lives in typed components registered in a shared Context:
| Layer | Role | Example |
|---|---|---|
| Model | Domain state, validation, serializable command args | HostCatalogModel, WorkspaceModel |
| Service | I/O, orchestration, dependency registration | Tmux Module, FileService, OminiService |
| ViewModel | UI-facing state, commands, events. Owns keyed children | WorkspaceViewModel, SettingsViewModel |
Component lookup is checked by TypeId at Rust level. Foreign clients use stable versioned contracts. ViewModel facades are handwritten — no reflection scan or syntax parser.
State, Command, Event Primitives
State<T> // Observable, ordered delivery, panic-isolated
ReadonlyState<T> // Non-mutating subscription
Command<T> // Cancellable intent, typed argument
Task<T> // Async execution with awaitable cancellation
Event<T> // Bidirectional or readonly, explicit subscription ownershipAsync Runtime
Production async Commands run on a Context-owned Tokio reactor through a generic AsyncExecutor boundary:
- Awaitable cancellation: tasks cooperatively yield on cancel.
- Linearized commit:
CancellationToken::try_commit— cancellation wins before the side effect, or returnsfalse. A Task cannot publish Cancelled and then mutate state in the background. - Safe shutdown: even when the final owner is released by a worker task.
Data Flow: Terminal Bytes
PTY reader
→ bounded queue (64 × 8 KiB)
→ 16 ms or 32 KiB batch
→ raw Tauri Channel (binary)
→ xterm TerminalSurface (WebGL)Terminal bytes use a specialized raw channel — not JSON, not the binding protocol. Control state and intent use opaque handles plus versioned JSON slots.
Data Flow: Binding Protocol
React Component
→ useViewModel("workspace") // TypeScript hook
→ WorkspaceViewModel (handwritten) // Typed facade
→ Tauri invoke / UniFFI call // IPC
→ BindingRuntime // Rust dispatch
→ ViewModel.commands.handle(...) // Typed command execution
→ State update → UI re-render // Observable notificationuseViewModel("workspace") infers WorkspaceViewModel through a handwritten ViewModelMap. The complete native contract is validated before the facade is exposed.
Repository Map
apps/desktop/ Tauri 2 + React + xterm.js desktop application
apps/mobile/ React Native bare iOS/Android + TurboModule spec
crates/szt-core/ Pure-Rust libszt: base, models, viewmodels, services, i18n, binding
crates/szt-mobile-ffi/ UniFFI boundary for generated Swift/Kotlin bindings
packages/contracts/ Generated TypeScript DTOs (mechanical export, never edit by hand)
packages/libszt/ Handwritten TypeScript facade, React hooks, adapter protocol
assets/fonts/ Audited, pinned MesloLGS NF font assetsRuntime vs Durable State
| Runtime (ephemeral) | Durable (encrypted in SQLite) |
|---|---|
| Windows, tabs, splits, panes | Host profiles |
| Session registry, PTY children | Settings |
| Scrollback | Quick Commands |
| Discovered machine snapshots | QCS recent history |
| Command Composer draft | Credential records (future) |
Closing the app drops the Rust session registry (kills PTY children). The next launch creates a fresh runtime layout and empty discovery snapshot. Only the Vault survives.
Supported Targets
macOS 13+ (Intel & Apple Silicon) · Windows 11 x64 · Ubuntu 22.04/24.04 x64 (X11 & Wayland) · iOS 16+ · Android 10+ ARM64
CI compilation alone does not certify this matrix; release gates require physical/virtual device acceptance runs.