Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

State Management

Overview

State is split between the Rust backend (source of truth for persistence) and SolidJS frontend stores (reactive UI state).

Backend State (src-tauri/src/state.rs)

AppState

The central backend state, shared across all Tauri commands via State<'_, Arc<AppState>>:

#![allow(unused)]
fn main() {
pub struct AppState {
    pub sessions: DashMap<String, Mutex<PtySession>>,       // Active PTY sessions
    pub worktrees_dir: PathBuf,                              // Worktree storage path
    pub metrics: SessionMetrics,                             // Atomic counters
    pub output_buffers: DashMap<String, Mutex<OutputRingBuffer>>, // MCP output access
    pub mcp_sse_sessions: DashMap<String, UnboundedSender<String>>, // SSE clients
    pub ws_clients: DashMap<String, Vec<UnboundedSender<String>>>,  // WebSocket clients
}
}

Concurrency model:

  • DashMap for lock-free concurrent read/write of session maps
  • Mutex for interior mutability of individual PTY writers and buffers
  • Arc<AtomicBool> for pause/resume signaling per session
  • AtomicUsize for zero-overhead metrics counters

PtySession

#![allow(unused)]
fn main() {
pub struct PtySession {
    pub writer: Box<dyn Write + Send>,          // Write to PTY
    pub master: Box<dyn MasterPty + Send>,      // PTY master handle
    pub(crate) _child: Box<dyn Child + Send>,   // Child process
    pub(crate) paused: Arc<AtomicBool>,         // Pause flag
    pub worktree: Option<WorktreeInfo>,         // Associated worktree
    pub cwd: Option<String>,                    // Working directory
}
}

SessionMetrics

Zero-overhead atomic counters:

#![allow(unused)]
fn main() {
pub(crate) struct SessionMetrics {
    pub(crate) total_spawned: AtomicUsize,
    pub(crate) failed_spawns: AtomicUsize,
    pub(crate) active_sessions: AtomicUsize,
    pub(crate) bytes_emitted: AtomicUsize,
    pub(crate) pauses_triggered: AtomicUsize,
}
}

Buffer Types

BufferPurposeCapacity
Utf8ReadBufferAccumulates bytes until valid UTF-8 boundaryVariable
EscapeAwareBufferHolds incomplete ANSI escape sequencesVariable
OutputRingBufferCircular buffer for MCP output access64 KB

Constants

#![allow(unused)]
fn main() {
pub(crate) const MAX_CONCURRENT_SESSIONS: usize = 50;
pub(crate) const OUTPUT_RING_BUFFER_CAPACITY: usize = 64 * 1024;
}

Frontend Stores

Store Pattern

All stores follow a consistent pattern:

// Internal reactive state
const [state, setState] = createStore<Type>(defaults);

// Exported as a module object
export const myStore = {
  get state() { return state; },  // Read-only access
  hydrate() { ... },              // Load from Rust
  action() { ... },               // Mutate + persist
};

Store Registry

StoreFilePurposePersisted
terminalsStoreterminals.tsTerminal instances, active tab, split layoutPartial (IDs in repos)
repositoriesStorerepositories.tsSaved repos, branches, terminal associations, repo groupsrepositories.json
settingsStoresettings.tsApp settings (font, shell, IDE, theme, update channel)config.json
repoSettingsStorerepoSettings.tsPer-repository settings (scripts, worktree)repo-settings.json
repoDefaultsStorerepoDefaults.tsDefault settings for new repositoriesrepo-defaults.json
uiStoreui.tsPanel visibility, sidebar widthui-prefs.json
githubStoregithub.tsPR/CI data per branch, remote tracking (ahead/behind), PR state transitionsNot persisted
promptLibraryStorepromptLibrary.tsPrompt templatesprompt-library.json
notificationsStorenotifications.tsNotification preferencesnotification-config.json
dictationStoredictation.tsDictation config and statedictation-config.json
errorHandlingStoreerrorHandling.tsError retry configui-prefs.json
rateLimitStoreratelimit.tsActive rate limitsNot persisted
tasksStoretasks.tsAgent task queueNot persisted
promptStoreprompt.tsActive prompt overlay stateNot persisted
diffTabsStorediffTabs.tsOpen diff tabsNot persisted
mdTabsStoremdTabs.tsOpen markdown tabs and plugin panelsNot persisted
notesStorenotes.tsIdeas/notes with repo tagging and used-at trackingnotes.json
statusBarTickerstatusBarTicker.tsPriority-based rotating status bar messagesNot persisted
userActivityStoreuserActivity.tsTracks last user click/keydown for activity-based timeoutsNot persisted
updaterStoreupdater.tsApp update state (check, download, install)Not persisted
keybindingsStorekeybindings.tsCustom keyboard shortcut bindingskeybindings.json
agentConfigsStoreagentConfigs.tsPer-agent run configs and togglesagents.json

Key Store Relationships

repositoriesStore
    │
    ├── BranchState.terminals: string[]  ──references──> terminalsStore IDs
    ├── BranchState.worktreePath         ──managed by──> worktree.rs
    └── BranchState.additions/deletions  ──from──> git.rs (get_diff_stats)

terminalsStore
    │
    ├── TerminalData.sessionId           ──maps to──> AppState.sessions key
    ├── TerminalData.agentType           ──read by──> StatusBar (agent badge)
    ├── TerminalData.usageLimit          ──read by──> StatusBar (usage display)
    └── TabLayout.panes                  ──indexes into──> TerminalData[]

githubStore
    │
    ├── Per-branch PR status             ──from──> github.rs (get_repo_pr_statuses)
    ├── Per-repo remote status           ──from──> github.rs (get_github_status)
    ├── CheckSummary                     ──drives──> CiRing component
    └── PR state transitions             ──emits to──> prNotificationsStore

statusBarTicker
    │
    ├── TickerMessage[]                  ──rendered by──> StatusBar
    ├── Claude Usage messages            ──from──> features/claudeUsage.ts (native)
    └── Plugin messages                  ──from──> pluginRegistry (ui:ticker capability)

notesStore
    │
    ├── Note.repoPath                    ──filters by──> active repo
    ├── Note.usedAt                      ──marks when──> sent to terminal
    └── filteredCount()                  ──drives──> StatusBar badge

settingsStore
    │
    ├── font/theme                       ──configures──> Terminal component
    ├── shell                            ──passed to──> create_pty
    ├── ide                              ──used by──> open_in_app
    └── updateChannel                    ──used by──> updaterStore

Debug Registry (MCP Introspection)

Stores self-register snapshot functions via src/stores/debugRegistry.ts. Snapshots are exposed on window.__TUIC__ and accessible through MCP debug(action=invoke_js).

APIReturns
__TUIC__.stores()List of registered store names
__TUIC__.store(name)Snapshot of the named store

Registered: github, globalWorkspace, keybindings, notes, paneLayout, repositories, settings, tasks, ui.

To register a new store, append at the bottom of the store file:

import { registerDebugSnapshot } from "./debugRegistry";
registerDebugSnapshot("name", () => ({ /* safe subset */ }));

Configuration Files

All config files are JSON, stored in the platform config directory:

PlatformPath
macOS~/Library/Application Support/tuicommander/
Linux~/.config/tuicommander/
Windows%APPDATA%/tuicommander/

Legacy path ~/.tuicommander/ is auto-migrated on first launch.

Config File Map

FileContentsRust Type
config.jsonShell, font, theme, MCP, remote access, update channelAppConfig
notification-config.jsonSound preferences, volumeNotificationConfig
ui-prefs.jsonSidebar, error handling settingsUIPrefsConfig
repo-settings.jsonPer-repo scripts, worktree optionsRepoSettingsMap
repo-defaults.jsonDefault settings for new repos (base branch, scripts)RepoDefaultsConfig
repositories.jsonSaved repos, branches, groupsserde_json::Value
prompt-library.jsonPrompt templatesPromptLibraryConfig
dictation-config.jsonDictation on/off, hotkey, language, modelDictationConfig
notes.jsonIdeas/notes with repo tags and used-at timestampsserde_json::Value
keybindings.jsonCustom keyboard shortcut overridesserde_json::Value
agents.jsonPer-agent run configs and togglesAgentsConfig
claude-usage-cache.jsonIncremental JSONL parse offsets for session statsSessionStatsCache