Preventing UI flicker and bugs in a terminal app through deep immutability and append-only message logs.
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
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.
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.
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.
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' }
| {Ask anything about Immutable State Management
Powered by Groq ยท Enter to send, Shift+Enter for newline