Skip to main content

Architecture Overview

Tempest’s desktop app is built with Tauri 2, a lightweight framework that wraps a Rust backend with a React frontend. The invoke handler at the end of lib.rs routes all IPC (Inter-Process Communication) commands from the frontend to their corresponding Rust implementations.

Key characteristics:

  • Stateless Rust backend: No persistent in-memory state; all app data flows through React
  • Async/blocking operations: Heavy I/O (git, PTY, file operations) runs on dedicated threads to avoid blocking the IPC worker pool
  • Cross-platform: Commands handle Windows/Unix differences (paths, shell, process termination)
  • Error handling: All commands return Result<T, String> with descriptive error messages

Window Configuration

The main window is created frameless with these properties:
Configuration details:
  • Frameless: decorations: false removes OS chrome, allowing custom titlebar drawn in React
  • No drag-drop: dragDropEnabled: false prevents default behavior; file drops handled by React handlers instead
  • Asset protocol: CSP allows asset:// protocol with ** scope for full resource access
  • Updater plugin: Configured with GitHub releases endpoint for auto-updates signed with minisign public key

Tauri Plugins

Three plugins provide native OS functionality:

State Management

PtyState (Concurrent PTY Registry)

Global state holding all active PTY sessions. Passed to commands that need PTY access. DashMap: Lock-free concurrent HashMap allowing parallel reads/writes without global locks.

ZenState (Secondary Window Config)

Maps window label (e.g., "zen-1234567890") to (project_path, project_name).

Runtime State and Persistence

read_runtime_state

Reads the persisted runtime state from disk.
Parameters: Returns: Result<String, String>
  • Ok: JSON string of RuntimeState (may be {} if file doesn’t exist on first launch)
  • Err: Filesystem error message
Implementation:
  • Reads from <app-data-dir>/runtime-state.json
  • Returns empty object {} if file missing (first launch case)
  • Parsed by frontend, falls back to localStorage migration
TypeScript usage:
Error cases:
  • Invalid JSON in file: Frontend falls back to localStorage
  • Permission denied on app data dir: Returns error string

write_runtime_state

Writes runtime state to disk atomically.
Parameters: Returns: Result<(), String>
  • Ok: File written successfully
  • Err: Filesystem error
Implementation:
  1. Creates app data directory if missing
  2. Writes to temp file runtime-state.json.tmp
  3. Atomically renames temp to runtime-state.json (prevents corruption on crash)
  4. No validation of JSON content (frontend is responsible)
TypeScript usage:
Error cases:
  • Insufficient disk space
  • Permission denied
  • Temp file creation fails

Workspace and File Operations

create_workspace

Creates a new workspace directory.
Parameters: Returns: Result<String, String>
  • Ok: Full path to created directory (e.g., /home/user/projects/my-app)
  • Err: Filesystem error (path invalid, permission denied, etc)
Implementation:
  • Calls fs::create_dir_all (creates parent dirs if needed)
  • Returns path as lossy UTF-8 string
TypeScript usage:
Error cases:
  • Invalid location path (doesn’t exist, not a directory)
  • Permission denied on parent
  • Disk full

list_directory

Lists files and directories in a folder.
Parameters: Return type:
Returns: Result<Vec<DirEntry>, String>
  • Ok: Sorted vector of directory entries
  • Err: Path doesn’t exist, permission denied, etc
Sorting: Directories first, then files, alphabetically within each group. Platform details:
  • Windows: Converts forward slashes to backslashes for consistency
  • Unix: Uses paths as-is
TypeScript usage:
Error cases:
  • Path doesn’t exist
  • Permission denied
  • Path is not a directory

read_file

Reads entire file contents.
Parameters: Returns: Result<String, String>
  • Ok: File contents as UTF-8 string
  • Err: File doesn’t exist, permission denied, invalid UTF-8, etc
Encoding: Always UTF-8; fails if file contains invalid UTF-8 sequences. TypeScript usage:
Error cases:
  • File doesn’t exist
  • Permission denied
  • File is invalid UTF-8
  • Is a directory (not a file)

write_file

Writes string content to a file.
Parameters: Returns: Result<(), String>
  • Ok: File written successfully
  • Err: Permission denied, disk full, etc
Behavior:
  • Creates file if it doesn’t exist
  • Overwrites entire file (not append)
  • Creates parent directories: NO (must exist)
TypeScript usage:
Error cases:
  • Permission denied
  • Disk full
  • Parent directory doesn’t exist
  • Path is a directory

Git Repository Setup

git_init

Initializes a new git repository.
Parameters: Returns: Result<(), String>
  • Ok: Repository initialized
  • Err: git command failed (not in PATH, permission denied, etc)
Implementation:
  1. Runs git init
  2. Sets default branch to main via git symbolic-ref HEAD refs/heads/main
  3. Writes .tempest and .tempest-pid to .gitignore
  4. Stages all files with git add -A (respects existing .gitignore)
  5. Creates initial commit with Tempest author
TypeScript usage:
Error cases:
  • git not in PATH
  • Directory doesn’t exist
  • Already a git repository (should be idempotent in future)
  • Permission denied

check_git_initialized

Checks if a directory is a git repository.
Parameters: Returns: bool
  • true if directory is a git repository (has .git/)
  • false otherwise
Implementation:
  • Runs git rev-parse --git-dir and checks exit code
  • If true, ensures .gitignore contains .tempest entries
TypeScript usage:
Error cases:
  • git not in PATH: Returns false
  • Path doesn’t exist: Returns false

git_add_remote

Adds a new git remote.
Parameters: Returns: Result<(), String>
  • Ok: Remote added successfully
  • Err: Repository error, remote already exists, invalid URL, etc
Implementation:
  • Runs git remote add origin <url>
  • Fails if remote named “origin” already exists
TypeScript usage:
Error cases:
  • Repository is not a git repo
  • Remote “origin” already exists
  • URL is invalid

Git Worktrees (Multi-workspace)

create_terminal_worktree

Creates a new git worktree for isolated sessions.
Parameters: Returns: Result<String, String>
  • Ok: Full path to worktree (e.g., /project/.tempest/my-workspace)
  • Err: Git error, worktree already exists, etc
Execution: Runs on a dedicated blocking thread to avoid starving the IPC worker pool. Implementation steps:
  1. Ensures at least one commit exists in repository (auto-commits empty repo if needed)
  2. Prunes stale worktree metadata from failed previous runs
  3. Creates worktree via git worktree add .tempest/<name> -b <name>
  4. Handles pre-existing path (removes orphan dir or errors if registered)
  5. Copies gitignored files (.env, .env.local, .env.development, .env.production) from project root
  6. Creates junctions (Windows) or symlinks (Unix) for large dependency dirs (node_modules, .venv)
  7. Adds .tempest-pid to .git/worktrees/<name>/info/exclude (local-only, not committed)
  8. Validates worktree is not empty
  9. Writes to .gitignore in project root
TypeScript usage:
Error cases:
  • Repository has no commits and auto-commit fails
  • Worktree name already exists
  • Permission denied in project directory
  • Disk full
  • Worktree created but ends up empty (all files .gitignored)
  • Link creation fails (continue without link, log warning)

git_worktree_remove

Removes a git worktree directory.
Parameters: Returns: Result<(), String>
  • Ok: Worktree removed
  • Err: Directory still in use (process holding handle), permission denied, etc
Implementation:
  1. Removes directory junctions/symlinks (Windows) or symlinks (Unix) first to avoid following them into the real directories
  2. Attempts git worktree remove --force <path>
  3. If git fails, falls back to direct fs::remove_dir_all with retry loop (6 attempts, 500ms intervals)
  4. On Windows, last resort uses cmd /c rmdir /s /q if Rust’s remove fails
  5. Prunes dangling .git/worktrees/<name> metadata
TypeScript usage:
Windows-specific: Converts forward slashes to backslashes automatically. Error cases:
  • PTY process still alive (CWD handle held on Windows): Fails after retries
  • Permission denied
  • Worktree path doesn’t exist: Succeeds (idempotent)

close_and_remove_worktree

Atomically kills a PTY and removes its worktree.
Parameters: Returns: Result<(), String> Implementation:
  1. Removes session from PtyState DashMap (if present)
  2. Kills child process tree (via portable-pty job object on Windows, SIGTERM then SIGKILL on Unix)
  3. Waits up to 3 seconds for process to exit (polling every 50ms)
  4. Reads and force-kills any PID from .tempest-pid sidecar (handles app restart case)
  5. Waits 500ms for OS to release directory handle
  6. Removes directory junctions/symlinks
  7. Attempts git worktree remove --force
  8. Falls back to direct directory removal with retry loop (6 attempts, 500ms)
  9. On Windows, uses cmd /c rmdir /s /q as last resort
  10. Prunes .git/worktrees/<name> metadata
Critical: Collapses two separate operations (close PTY + remove dir) into one Rust round-trip to avoid race conditions where frontend state updates drop the close callback before Rust finishes. TypeScript usage:
Error cases:
  • PTY not found in state: Continues with directory removal
  • Directory still in use after all retries: Returns error
  • .tempest-pid corrupted: Ignores and continues

Git Branches

get_git_branch

Returns the current branch name.
Parameters: Returns: Result<String, String>
  • Ok: Branch name (e.g., “main”, “feature/x”)
  • Err: Not a git repo, detached HEAD, etc
Implementation:
  • Uses git symbolic-ref --short HEAD
  • Works even on repos with zero commits (no HEAD yet)
TypeScript usage:
Error cases:
  • Not a git repository
  • HEAD doesn’t exist (no commits yet)
  • Detached HEAD (error message includes current commit)

git_list_branches

Lists all branches in a repository.
Parameters: Return type:
Returns: Result<Vec<BranchInfo>, String>
  • Ok: List of all branches with current status
  • Err: Not a git repo, git command failed, etc
Implementation:
  • Runs git branch and parses output
  • Current branch marked with * prefix
  • Detached HEAD marker + filtered out
TypeScript usage:
Error cases:
  • Not a git repository
  • No branches exist (repo with zero commits)

git_switch_branch

Switches to a branch, with auto-stash of uncommitted changes.
Parameters: Returns: Result<(), String>
  • Ok: Switched successfully
  • Err: Branch doesn’t exist, checkout failed, etc
Implementation (smart stash):
  1. Gets current branch name
  2. Checks if working tree is dirty (git status --porcelain)
  3. If dirty, stashes changes with label tempest-autostash-from-<branch>
  4. Switches branch via git checkout <branch>
  5. If checkout fails, restores stash so changes aren’t lost
  6. If checkout succeeds, checks stash list for a stash from a previous visit to the new branch
  7. If found, auto-applies stash (restores changes from previous session on this branch)
TypeScript usage:
Error cases:
  • Branch doesn’t exist
  • Stash operation fails
  • Working tree has merge conflicts

git_delete_branch

Deletes a branch locally and optionally remotely.
Parameters: Returns: Result<(), String> Implementation:
  1. Deletes locally with git branch -d <branch> (or -D if force)
  2. If delete_remote, attempts git push origin --delete <branch> (best-effort, doesn’t fail if remote branch doesn’t exist)
TypeScript usage:
Error cases:
  • Branch doesn’t exist
  • Branch not fully merged (unless force=true)
  • Permission denied on remote

git_branch_delete

Force deletes a branch (internal use).
Parameters: Returns: Result<(), String> Implementation:
  • Equivalent to git branch -D <branch> (always force)
  • Used internally for worktree branch cleanup
TypeScript usage:

check_branch_merged

Checks if a branch has been merged into a base branch.
Parameters: Returns: Result<bool, String>
  • true if merged (or remote branch deleted)
  • false if not merged
  • Err if git command fails
Implementation:
  1. Refreshes remote state with git fetch --prune origin (offline is OK, falls back to local refs)
  2. Checks if remote branch exists with git ls-remote --heads origin <branch>
  3. If remote doesn’t exist, returns true (deleted after merge)
  4. Checks if branch tip is ancestor of base branches: git merge-base --is-ancestor <branch> <base>
  5. Tries base branches in order: origin/main, origin/master, origin/develop
  6. Returns true if ancestor of any base branch
TypeScript usage:
Error cases:
  • Not a git repository
  • No origin remote configured
  • Network error during fetch (falls back to local refs)

Git Staging and Commits

git_stage

Stages a file for commit.
Parameters: Returns: Result<(), String> Implementation:
  • Runs git add -- <file_path>
  • -- prevents file_path from being interpreted as a flag
TypeScript usage:

git_unstage

Unstages a file.
Parameters: Returns: Result<(), String> Implementation:
  • Runs git restore --staged -- <file_path>
TypeScript usage:

git_discard

Discards changes to a file or deletes untracked file.
Parameters: Returns: Result<(), String> Implementation:
  • If untracked: true: Attempts fs::remove_file, falls back to fs::remove_dir_all for directories
  • If untracked: false: Runs git restore -- <file_path>
TypeScript usage:
Error cases:
  • File doesn’t exist
  • Permission denied
  • File is directory (for untracked deletion)

git_commit_staged

Commits staged changes.
Parameters: Returns: Result<(), String> Validation: Fails if message is empty after trimming. Implementation:
  • Runs git commit -m <message>
  • No author configuration (uses git config defaults)
TypeScript usage:
Error cases:
  • Empty message
  • Nothing staged for commit
  • Not a git repository
  • User identity not configured in git

Git Status and Diff

git_status

Returns working tree status.
Parameters: Return type:
Returns: Result<Vec<GitStatusEntry>, String> Implementation:
  • Runs git status --short
  • Parses unified two-character status codes
  • Filters empty lines
Status codes (git format):
  • First char: index status (staging area)
  • Second char: worktree status
TypeScript usage:

git_diff

Returns all uncommitted changes.
Parameters: Return type:
Returns: Result<Vec<FileDiff>, String> Implementation:
  • Runs git diff HEAD (shows uncommitted changes vs HEAD)
  • Parses unified diff format
  • Extracts hunk headers, line numbers, and change type
  • Includes both staged and unstaged changes
TypeScript usage:

git_diff_file

Returns diff for a single file.
Parameters: Returns: Result<Vec<DiffLine>, String> Implementation:
  • If untracked: true: Reads file and renders each line as “added” without running git
  • If staged: true: Runs git diff --cached -- <file>
  • If staged: false: Runs git diff -- <file>
  • Parses unified diff output
TypeScript usage:

Git Push

git_push_branch

Commits pending changes and pushes branch.
Parameters: Returns: Result<String, String> (JSON string) Response:
Implementation:
  1. Gets current branch name
  2. Checks if working tree has changes (git status --porcelain)
  3. If changes exist:
    • Checks if anything is staged (git diff --cached --quiet)
    • If nothing staged, auto-stages all changes (git add -A)
    • Commits with provided message or default
  4. Pushes with git push -u origin <branch>
  5. Queries remote URL with git remote get-url origin
  6. Returns JSON with remoteUrl and branch
TypeScript usage:
Error cases:
  • Branch doesn’t exist
  • No remote configured
  • Network error during push
  • Commit fails (e.g., user identity not configured)

git_push_current_branch

Pushes current branch without committing.
Parameters: Returns: Result<String, String> (JSON string) Response format: Same as git_push_branch Implementation:
  • Runs git push -u origin <current-branch>
  • Fails if working tree is dirty (no auto-commit like git_push_branch)
TypeScript usage:

git_create_push_branch

Creates a new branch and pushes to remote.
Parameters: Returns: Result<String, String> (JSON string) Response format: Same as git_push_branch Implementation:
  1. Creates branch locally with git checkout -b <branch>
  2. Pushes with git push -u origin <branch>
  3. On push failure, switches back to original branch so repo isn’t left detached
  4. Returns remote URL and branch name
TypeScript usage:
Error cases:
  • Branch already exists
  • Push fails (no network, auth error)

Terminal and PTY Sessions

create_pty_session

Creates an interactive pseudo-terminal session.
Parameters: Returns: Result<(), String> Payload type:
Execution: Blocking operation runs on dedicated thread. Implementation:
  1. Shell resolution: Probes platform for preferred shell (cached in static SHELL):
    • Windows: Checks if PowerShell Core (pwsh) available, falls back to powershell.exe
    • Unix: Honors $SHELL env var, falls back to /bin/bash
  2. PTY creation: Opens PTY pair with given terminal size
  3. Command construction:
    • If command is None: Bare shell session
    • If command is Some: Agent session
      • Shell-quotes args (handles spaces, single quotes)
      • On Windows: <shell> -NoLogo -NoExit -Command <agent> <args>
      • On Unix: <shell> -c "<agent> <args>; exec $SHELL -i"
  4. Spawn child: Spawns shell process with full agent command
  5. PID persistence: Writes child’s OS PID to <cwd>/.tempest-pid for recovery after app restart
  6. Output streaming: Starts background thread that reads PTY master in 4KB chunks and sends via on_event channel
  7. Registry: Inserts session into PtyState DashMap
TypeScript usage:
Error cases:
  • Working directory doesn’t exist
  • Shell executable not found
  • PTY creation fails (system resource limit)
  • Child process spawn fails

write_to_pty

Writes data to PTY stdin.
Parameters: Returns: Result<(), String> Implementation:
  • Looks up session in DashMap by session_id
  • Writes bytes to PTY master’s writer (stdin)
  • No buffering; write is synchronous
TypeScript usage:
Error cases:
  • Session not found
  • PTY closed or broken pipe
  • I/O error

resize_pty

Resizes terminal.
Parameters: Returns: Result<(), String> Implementation:
  • Looks up session in DashMap
  • Calls master.resize(PtySize { rows, cols, pixel_width: 0, pixel_height: 0 })
  • Sends SIGWINCH signal to shell process (shell updates internal state)
TypeScript usage:
Error cases:
  • Session not found
  • Resize fails (closed PTY)

close_pty_session

Closes a PTY session.
Parameters: Returns: Result<(), String> Implementation:
  1. Removes session from DashMap (no new data can arrive after this)
  2. Kills child process (entire job object on Windows)
  3. Waits up to 1 second for process to exit (polling every 25ms, 40 attempts)
  4. Removes .tempest-pid sidecar file
  5. Does nothing if session already closed
TypeScript usage:
Note: Closing a PTY does NOT remove the worktree directory. Use close_and_remove_worktree for that.

Git Hooks (Co-author Attribution)

write_coauthor_hook

Installs prepare-commit-msg hook for co-author attribution.
Parameters: Returns: Result<(), String> Idempotency: Multiple calls have no extra effect (wrapped in markers). Implementation:
  1. Creates .git/hooks/ directory if missing
  2. Builds hook script block wrapped in # Tempest-attribution-begin and # Tempest-attribution-end
  3. If hook exists but has no Tempest block: Appends block
  4. If hook exists with block: Overwrites block (idempotent)
  5. If hook doesn’t exist: Creates with shebang
  6. On Unix/macOS: Sets executable bit (0o755)
Hook behavior:
Appends co-author line to commit message if not already present. TypeScript usage:

remove_coauthor_hook

Removes Tempest co-author hook block.
Parameters: Returns: Result<(), String> Implementation:
  1. Reads .git/hooks/prepare-commit-msg
  2. Strips lines between Tempest markers
  3. If nothing remains (or only shebang): Deletes hook file
  4. Otherwise: Rewrites hook without Tempest block
  5. No-op if file doesn’t exist or has no Tempest block
TypeScript usage:

IDE Panel Integration

embed_ide_panel

Creates an embedded child webview for live preview or IDE panels.
Parameters: Returns: Result<(), String> TypeScript usage:

resize_ide_panel

Resizes and repositions an IDE panel.
Same parameters as embed_ide_panel. No-op if panel doesn’t exist.

destroy_ide_panel

Closes and removes an IDE panel.
TypeScript usage:

get_ide_panel_url

Returns the current URL of an IDE panel.
Returns: Some(url) if panel exists, None otherwise.

Secondary Windows (Zen Mode)

open_zen_window

Opens a secondary frameless window for distraction-free editing.
Parameters: Returns: Result<(), String> Window properties:
  • Label: zen-<timestamp-ms> (unique, based on Unix epoch milliseconds)
  • Title: “Tempest”
  • Decorations: false (frameless)
  • Size: 1280x800 (logical pixels)
  • Centered on screen
  • Drag-drop disabled
Implementation:
  1. Generates unique label based on current timestamp
  2. Stores (path, name) in ZenState under label
  3. Creates new WebviewWindow with index.html entry point
  4. The React component reads label and calls get_zen_config to retrieve project info
TypeScript usage:

get_zen_config

Retrieves path and name for a secondary window.
Parameters: Returns: Option<(String, String)>
  • Some((path, name)) if label exists
  • None if label not found
TypeScript usage (in secondary window):

Code Indexing (Atlas)

start_atlas_index

Spawns Node.js process to index project codebase.
Parameters: Returns: Result<(), String> Execution: Fire-and-forget. Returns immediately; indexing happens in background. Implementation:
  1. Resolves Atlas entry point path:
    • Dev builds: <cargo-manifest-dir>/resources/atlas/dist/mcp/server-entry.js
    • Release builds: <exe-dir>/resources/atlas/dist/mcp/server-entry.js
  2. Validates entry point exists
  3. Spawns node --liftoff-only <entry> --init --path <project> with:
    • Stdin: null
    • Stdout: piped to separate reader thread
    • Stderr: piped to separate reader thread
  4. Each thread emits atlas:log events to frontend with log lines as they arrive
  5. Waits for child process to exit
  6. Writes Atlas MCP server config to:
    • .mcp.json (Claude Code, Cline, Zed, Windsurf)
    • .cursor/mcp.json (Cursor)
    • .gemini/settings.json (Gemini CLI)
    • .kiro/settings/mcp.json (Kiro/AWS)
    • opencode.jsonc (OpenCode)
  7. Updates .gitignore with config file paths (local-only, not committed)
TypeScript usage:
Error cases:
  • Atlas entry point not bundled
  • Node.js not in PATH
  • Permission denied on project directory

check_atlas_db

Checks if Atlas has indexed a project.
Parameters: Returns: bool
  • true if .tempest/atlas/atlas.db exists
  • false otherwise
TypeScript usage:

Dependencies and Technologies

Core Tauri

  • tauri 2 with unstable features and asset protocol support
  • url for URL parsing and validation

Terminal Emulation

  • portable-pty 0.8 for cross-platform PTY support
  • libc 0.2 (Unix) for SIGKILL in process tree cleanup
  • junction 1 (Windows) for directory junction creation

State Management

  • dashmap 6 for lock-free concurrent HashMap storage

Serialization

  • serde 1 with derive macros
  • serde_json 1 for JSON serialization