Keyboard shortcuts

Press ← or → to navigate between chapters

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>>:

AppState has 113 fields (state.rs:1328). The excerpt below is the PTY core only — it is NOT the whole struct. Read state.rs for the rest; the remainder covers the config cache, git/GitHub TTL caches, repo watchers, session states, MCP/agent registries, worktree bookkeeping and the event bus.

#![allow(unused)]
fn main() {
pub struct AppState {
    pub sessions: DashMap<String, Mutex<PtySession>>,       // Active PTY sessions
    pub(crate) data_dir: PathBuf,                            // Config/data directory
    pub(crate) worktrees_dir: PathBuf,                       // Worktree storage path
    pub(crate) metrics: SessionMetrics,                      // Atomic counters
    pub output_buffers: DashMap<String, Mutex<OutputRingBuffer>>, // MCP output access
    pub mcp_sessions: DashMap<String, McpSessionMeta>,       // MCP Streamable HTTP sessions
    pub ws_clients: DashMap<String, Vec<WsClientTx>>,        // WebSocket clients per session
    // ... 106 more fields
}
}

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 type SharedPtyWriter = Arc<Mutex<Box<dyn Write + Send>>>;

pub struct PtySession {
    pub writer: SharedPtyWriter,                        // Write to PTY (outside the session mutex)
    pub master: Box<dyn MasterPty + Send>,              // PTY master handle
    pub(crate) _child: Box<dyn Child + Send + Sync>,    // Child process
    pub(crate) paused: Arc<AtomicBool>,                 // Pause flag
    pub worktree: Option<WorktreeInfo>,                 // Associated worktree
    pub cwd: Option<String>,                            // Working directory
    pub display_name: Option<String>,                   // UI/agent/intent title
    pub display_name_is_custom: bool,                   // Explicit user rename wins over OSC/intent
    pub display_name_from_spawn: bool,                  // Named by agent spawn; OSC title must not replace it
    pub is_remote: bool,                                // Created through HTTP/MCP, not the desktop UI
    pub shell: String,                                  // Resolved spawn command, kept for shell-family classification
}
}

writer is deliberately outside the session Mutex so a terminal-generated reply can wait on an in-flight user write without blocking the reader thread.

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 access2 MB

Constants

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

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

A subset — src/stores/ holds 60 store modules. The table covers the ones a contributor meets first; it is not the full inventory.

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
errorLogStoreerrorLog.tsError-log overlay open/closedNot persisted
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
ideasStoreideas.tsIdeas 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)

ideasStore
    │
    ├── 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