State Single Source of Truth + Immutable Updates See in Code Tour

Immutable State Management

Preventing UI flicker and bugs in a terminal app through deep immutability and append-only message logs.

Immutable State Management โ€” Architecture Diagram
graph LR
    A[User Action] --> B[Dispatch]
    B --> C[AppStateStore]
    C --> D[New Immutable Snapshot]
    D --> E[Memoized Selectors]
    E --> F[React Component A]
    E --> G[React Component B]
    E --> H[React Component C]

Mermaid diagram definition

Deep Dive

In a terminal UI, re-renders are visible as flicker. The state model is designed to minimize re-renders: DeepImmutable types catch accidental mutations at compile time, and memoized selectors ensure components only re-render when their specific slice changes.

๐Ÿ”‘Key Insight

Messages are append-only. Deleting a message appends a `TombstoneMessage` with the deleted message's ID. This makes the history trivially replayable and avoids the class of bugs caused by array index mutation.

โ„น๏ธInfo

The `speculation` slice of state enables pipelined inline suggestions โ€” the app can prepare the next suggestion while the user is still reading the current response, without blocking the main message loop.

KEY TAKEAWAYS
  • โ†’DeepImmutable types catch mutations at compile time, not runtime
  • โ†’Append-only logs are bug-resistant and trivially replayable
  • โ†’Memoized selectors are essential for UI performance in terminal apps
  • โ†’Speculation state enables pipelining without coupling to the main loop

Source Code

AppStateStore showing DeepImmutable types, the dispatch pattern, and message append-only semantics.

import type { Notification } from 'src/context/notifications.js'
import type { TodoList } from 'src/utils/todo/types.js'
import type { BridgePermissionCallbacks } from '../bridge/bridgePermissionCallbacks.js'
import type { Command } from '../commands.js'
import type { ChannelPermissionCallbacks } from '../services/mcp/channelPermissions.js'
import type { ElicitationRequestEvent } from '../services/mcp/elicitationHandler.js'
import type {
  MCPServerConnection,
  ServerResource,
} from '../services/mcp/types.js'
import { shouldEnablePromptSuggestion } from '../services/PromptSuggestion/promptSuggestion.js'
import {
  getEmptyToolPermissionContext,
  type Tool,
  type ToolPermissionContext,
} from '../Tool.js'
import type { TaskState } from '../tasks/types.js'
import type { AgentColorName } from '../tools/AgentTool/agentColorManager.js'
import type { AgentDefinitionsResult } from '../tools/AgentTool/loadAgentsDir.js'
import type { AllowedPrompt } from '../tools/ExitPlanModeTool/ExitPlanModeV2Tool.js'
import type { AgentId } from '../types/ids.js'
import type { Message, UserMessage } from '../types/message.js'
import type { LoadedPlugin, PluginError } from '../types/plugin.js'
import type { DeepImmutable } from '../types/utils.js'
import {
  type AttributionState,
  createEmptyAttributionState,
} from '../utils/commitAttribution.js'
import type { EffortValue } from '../utils/effort.js'
import type { FileHistoryState } from '../utils/fileHistory.js'
import type { REPLHookContext } from '../utils/hooks/postSamplingHooks.js'
import type { SessionHooksState } from '../utils/hooks/sessionHooks.js'
import type { ModelSetting } from '../utils/model/model.js'
import type { DenialTrackingState } from '../utils/permissions/denialTracking.js'
import type { PermissionMode } from '../utils/permissions/PermissionMode.js'
import { getInitialSettings } from '../utils/settings/settings.js'
import type { SettingsJson } from '../utils/settings/types.js'
import { shouldEnableThinkingByDefault } from '../utils/thinking.js'
import type { Store } from './store.js'

export type CompletionBoundary =
  | { type: 'complete'; completedAt: number; outputTokens: number }
  | { type: 'bash'; command: string; completedAt: number }
  | { type: 'edit'; toolName: string; filePath: string; completedAt: number }
  | {
      type: 'denied_tool'
      toolName: string
      detail: string
      completedAt: number
    }

export type SpeculationResult = {
  messages: Message[]
  boundary: CompletionBoundary | null
  timeSavedMs: number
}

export type SpeculationState =
  | { status: 'idle' }
  | {
AI Assistant

Ask anything about Immutable State Management

Powered by Groq ยท Enter to send, Shift+Enter for newline