From fa309d06278567ca25c926d7af6790ffcd443325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Salavisa?= Date: Tue, 28 Jul 2026 22:42:59 +0100 Subject: [PATCH] chore(web): delete dead tracked store.ts.backup (#2012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `apps/web/lib/state/store.ts.backup` was a 1202-line snapshot of the pre-slice-refactor monolithic Zustand store, accidentally committed in b090a0157 (#1049). It never compiled and never ran — `tsconfig.json` includes `**/*.ts`, which does not match a `.backup` suffix — so its only effect was polluting grep/search results for anyone working on the real store at `apps/web/lib/state/store.ts`. No runtime code referenced it: `grep -rn "store.ts.backup" apps/` returns nothing, and no build, lint, or tsconfig glob picks up `*.backup`. Verified no logic was dropped. Of the 177 state keys and actions in the backup, 172 exist in the live slice tree under `lib/state/slices/`. The five that don't belong to two concepts that were deliberately renamed after the snapshot was taken: - `boardId` / `setBoards` / `setActiveBoard` — boards became workflows in 658bd349b (#228); the live kanban slice uses `workflowId` / `setWorkflows` / `setActiveWorkflow`. - `environmentsLoaded` / `setEnvironments` — environments became executors; the live settings slice has `ExecutorsState` / `setExecutors`. Also add `*.backup`, `*.bak`, and `*.orig` to `.gitignore` so editor and merge scratch copies cannot be committed again. Co-authored-by: Kandev Agent Co-authored-by: Claude Opus 5 (1M context) --- .gitignore | 6 + apps/web/lib/state/store.ts.backup | 1202 ---------------------------- 2 files changed, 6 insertions(+), 1202 deletions(-) delete mode 100644 apps/web/lib/state/store.ts.backup diff --git a/.gitignore b/.gitignore index dfe3ed5fc0..c8e4a99796 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,12 @@ __pycache__/ *.swp *.swo *~ + +# Editor/merge scratch copies. These never compile and only pollute grep +# results for whoever works on the real file next. +*.backup +*.bak +*.orig .DS_Store Thumbs.db diff --git a/apps/web/lib/state/store.ts.backup b/apps/web/lib/state/store.ts.backup deleted file mode 100644 index cf13c1ec20..0000000000 --- a/apps/web/lib/state/store.ts.backup +++ /dev/null @@ -1,1202 +0,0 @@ -import type { ReactNode } from 'react'; -import type { - Agent, - AvailableAgent, - AgentDiscovery, - Branch, - CustomPrompt, - Environment, - EditorOption, - Executor, - Message, - NotificationProvider, - Repository, - TaskState as TaskStatus, - TaskSession, - Turn, -} from '@/lib/types/http'; -import { createStore } from 'zustand/vanilla'; -import { immer } from 'zustand/middleware/immer'; -import { setLocalStorage } from '@/lib/local-storage'; - -const maxProcessOutputBytes = 2 * 1024 * 1024; - -function trimProcessOutput(value: string) { - if (value.length <= maxProcessOutputBytes) { - return value; - } - return value.slice(value.length - maxProcessOutputBytes); -} - -export type KanbanState = { - boardId: string | null; - columns: Array<{ id: string; title: string; color: string; position: number }>; - tasks: Array<{ - id: string; - columnId: string; - title: string; - description?: string; - position: number; - state?: TaskStatus; - repositoryId?: string; - }>; -}; - -export type WorkspaceState = { - items: Array<{ - id: string; - name: string; - description?: string | null; - owner_id: string; - default_executor_id?: string | null; - default_environment_id?: string | null; - default_agent_profile_id?: string | null; - created_at: string; - updated_at: string; - }>; - activeId: string | null; -}; - -export type AgentProfileOption = { - id: string; - label: string; - agent_id: string; -}; - -export type ExecutorsState = { - items: Executor[]; -}; - -export type EnvironmentsState = { - items: Environment[]; -}; - -export type SettingsAgentsState = { - items: Agent[]; -}; - -export type AgentDiscoveryState = { - items: AgentDiscovery[]; -}; - -export type EditorsState = { - items: EditorOption[]; - loaded: boolean; - loading: boolean; -}; - -export type PromptsState = { - items: CustomPrompt[]; - loaded: boolean; - loading: boolean; -}; - -export type NotificationProvidersState = { - items: NotificationProvider[]; - events: string[]; - appriseAvailable: boolean; - loaded: boolean; - loading: boolean; -}; - -export type AvailableAgentsState = { - items: AvailableAgent[]; - loading: boolean; - loaded: boolean; -}; - -export type BoardState = { - items: Array<{ id: string; workspaceId: string; name: string }>; - activeId: string | null; -}; - -export type TaskState = { - activeTaskId: string | null; - activeSessionId: string | null; -}; - -export type AgentState = { - agents: Array<{ id: string; status: 'idle' | 'running' | 'error' }>; -}; - -export type AgentProfilesState = { - items: AgentProfileOption[]; - version: number; -}; - -export type RepositoriesState = { - itemsByWorkspaceId: Record; - loadingByWorkspaceId: Record; - loadedByWorkspaceId: Record; -}; - -export type RepositoryBranchesState = { - itemsByRepositoryId: Record; - loadingByRepositoryId: Record; - loadedByRepositoryId: Record; -}; - -export type SettingsDataState = { - executorsLoaded: boolean; - environmentsLoaded: boolean; - agentsLoaded: boolean; -}; - -export type UserSettingsState = { - workspaceId: string | null; - boardId: string | null; - repositoryIds: string[]; - preferredShell: string | null; - shellOptions: Array<{ value: string; label: string }>; - defaultEditorId: string | null; - enablePreviewOnClick: boolean; - loaded: boolean; -}; - -export type TerminalState = { - terminals: Array<{ id: string; output: string[] }>; -}; - -export type ShellState = { - // Map of sessionId to shell output buffer (raw bytes as string) - outputs: Record; - // Map of sessionId to shell status - statuses: Record< - string, - { - available: boolean; - running?: boolean; - shell?: string; - cwd?: string; - } - >; -}; - -export type ProcessStatusEntry = { - processId: string; - sessionId: string; - kind: string; - scriptName?: string; - status: string; - command?: string; - workingDir?: string; - exitCode?: number | null; - startedAt?: string; - updatedAt?: string; -}; - -export type ProcessState = { - outputsByProcessId: Record; - processesById: Record; - processIdsBySessionId: Record; - activeProcessBySessionId: Record; - devProcessBySessionId: Record; -}; - -export type PreviewStage = 'closed' | 'logs' | 'preview'; -export type PreviewViewMode = 'preview' | 'output'; -export type PreviewDevicePreset = 'desktop' | 'tablet' | 'mobile'; - -export type PreviewPanelState = { - openBySessionId: Record; - viewBySessionId: Record; - deviceBySessionId: Record; - stageBySessionId: Record; - urlBySessionId: Record; - urlDraftBySessionId: Record; -}; - -export type RightPanelState = { - activeTabBySessionId: Record; -}; - - -export type FileInfo = { - path: string; - status: 'modified' | 'added' | 'deleted' | 'untracked' | 'renamed'; - staged: boolean; - additions?: number; - deletions?: number; - old_path?: string; - diff?: string; -}; - -export type GitStatusEntry = { - branch: string | null; - remote_branch: string | null; - modified: string[]; - added: string[]; - deleted: string[]; - untracked: string[]; - renamed: string[]; - ahead: number; - behind: number; - files: Record; - timestamp: string | null; -}; - -export type GitStatusState = { - bySessionId: Record; -}; - -export type ContextWindowEntry = { - size: number; - used: number; - remaining: number; - efficiency: number; - timestamp?: string; -}; - -export type ContextWindowState = { - bySessionId: Record; -}; - -export type Worktree = { - id: string; - sessionId: string; - repositoryId?: string; - path?: string; - branch?: string; -}; - -export type WorktreesState = { - items: Record; -}; - -export type SessionWorktreesState = { - itemsBySessionId: Record; -}; - -export type DiffState = { - files: Array<{ path: string; status: 'A' | 'M' | 'D'; plus: number; minus: number }>; -}; - -export type ConnectionState = { - status: 'disconnected' | 'connecting' | 'connected' | 'error' | 'reconnecting'; - error: string | null; -}; - -export type MessagesState = { - bySession: Record; - metaBySession: Record< - string, - { - isLoading: boolean; - hasMore: boolean; - oldestCursor: string | null; - } - >; -}; - -export type TurnsState = { - bySession: Record; - activeBySession: Record; // sessionId -> active turnId -}; - -export type TaskSessionsState = { - items: Record; -}; - -export type TaskSessionsByTaskState = { - itemsByTaskId: Record; - loadingByTaskId: Record; - loadedByTaskId: Record; -}; - -export type SessionAgentctlStatus = { - status: 'starting' | 'ready' | 'error'; - errorMessage?: string; - agentExecutionId?: string; - updatedAt?: string; -}; - -export type SessionAgentctlState = { - itemsBySessionId: Record; -}; - -export type PendingModelState = { - bySessionId: Record; -}; - -export type ActiveModelState = { - bySessionId: Record; -}; - -export type AppState = { - kanban: KanbanState; - workspaces: WorkspaceState; - boards: BoardState; - executors: ExecutorsState; - environments: EnvironmentsState; - settingsAgents: SettingsAgentsState; - agentDiscovery: AgentDiscoveryState; - availableAgents: AvailableAgentsState; - repositories: RepositoriesState; - repositoryBranches: RepositoryBranchesState; - settingsData: SettingsDataState; - editors: EditorsState; - prompts: PromptsState; - notificationProviders: NotificationProvidersState; - tasks: TaskState; - agents: AgentState; - agentProfiles: AgentProfilesState; - userSettings: UserSettingsState; - terminal: TerminalState; - shell: ShellState; - processes: ProcessState; - previewPanel: PreviewPanelState; - rightPanel: RightPanelState; - diffs: DiffState; - gitStatus: GitStatusState; - contextWindow: ContextWindowState; - connection: ConnectionState; - messages: MessagesState; - turns: TurnsState; - taskSessions: TaskSessionsState; - taskSessionsByTask: TaskSessionsByTaskState; - sessionAgentctl: SessionAgentctlState; - worktrees: WorktreesState; - sessionWorktreesBySessionId: SessionWorktreesState; - pendingModel: PendingModelState; - activeModel: ActiveModelState; - hydrate: (state: Partial) => void; - setActiveWorkspace: (workspaceId: string | null) => void; - setWorkspaces: (workspaces: WorkspaceState['items']) => void; - setActiveBoard: (boardId: string | null) => void; - setBoards: (boards: BoardState['items']) => void; - setExecutors: (executors: ExecutorsState['items']) => void; - setEnvironments: (environments: EnvironmentsState['items']) => void; - setSettingsAgents: (agents: SettingsAgentsState['items']) => void; - setAgentDiscovery: (agents: AgentDiscoveryState['items']) => void; - setAvailableAgents: (agents: AvailableAgentsState['items']) => void; - setAvailableAgentsLoading: (loading: boolean) => void; - setAgentProfiles: (profiles: AgentProfilesState['items']) => void; - setRepositories: (workspaceId: string, repositories: Repository[]) => void; - setRepositoriesLoading: (workspaceId: string, loading: boolean) => void; - setRepositoryBranches: (repositoryId: string, branches: Branch[]) => void; - setRepositoryBranchesLoading: (repositoryId: string, loading: boolean) => void; - setSettingsData: (next: Partial) => void; - setEditors: (editors: EditorsState['items']) => void; - setEditorsLoading: (loading: boolean) => void; - setPrompts: (prompts: PromptsState['items']) => void; - setPromptsLoading: (loading: boolean) => void; - setNotificationProviders: (state: NotificationProvidersState) => void; - setNotificationProvidersLoading: (loading: boolean) => void; - setUserSettings: (settings: UserSettingsState) => void; - setTerminalOutput: (terminalId: string, data: string) => void; - appendShellOutput: (sessionId: string, data: string) => void; - setShellStatus: ( - sessionId: string, - status: { available: boolean; running?: boolean; shell?: string; cwd?: string } - ) => void; - clearShellOutput: (sessionId: string) => void; - appendProcessOutput: (processId: string, data: string) => void; - upsertProcessStatus: (status: ProcessStatusEntry) => void; - clearProcessOutput: (processId: string) => void; - setActiveProcess: (sessionId: string, processId: string) => void; - setPreviewOpen: (sessionId: string, open: boolean) => void; - togglePreviewOpen: (sessionId: string) => void; - setPreviewView: (sessionId: string, view: PreviewViewMode) => void; - setPreviewDevice: (sessionId: string, device: PreviewDevicePreset) => void; - setPreviewStage: (sessionId: string, stage: PreviewStage) => void; - setPreviewUrl: (sessionId: string, url: string) => void; - setPreviewUrlDraft: (sessionId: string, url: string) => void; - setRightPanelActiveTab: (sessionId: string, tab: string) => void; - setConnectionStatus: (status: ConnectionState['status'], error?: string | null) => void; - setMessages: ( - sessionId: string, - messages: Message[], - meta?: { hasMore?: boolean; oldestCursor?: string | null } - ) => void; - addMessage: (message: Message) => void; - addTurn: (turn: Turn) => void; - completeTurn: (sessionId: string, turnId: string, completedAt: string) => void; - setActiveTurn: (sessionId: string, turnId: string | null) => void; - updateMessage: (message: Message) => void; - prependMessages: ( - sessionId: string, - messages: Message[], - meta?: { hasMore?: boolean; oldestCursor?: string | null } - ) => void; - setMessagesMetadata: ( - sessionId: string, - meta: { hasMore?: boolean; isLoading?: boolean; oldestCursor?: string | null } - ) => void; - setMessagesLoading: (sessionId: string, loading: boolean) => void; - setActiveSession: (taskId: string, sessionId: string) => void; - setActiveTask: (taskId: string) => void; - clearActiveSession: () => void; - setTaskSession: (session: TaskSession) => void; - setTaskSessionsForTask: (taskId: string, sessions: TaskSession[]) => void; - setTaskSessionsLoading: (taskId: string, loading: boolean) => void; - setSessionAgentctlStatus: (sessionId: string, status: SessionAgentctlStatus) => void; - setWorktree: (worktree: Worktree) => void; - setSessionWorktrees: (sessionId: string, worktreeIds: string[]) => void; - setGitStatus: (sessionId: string, gitStatus: GitStatusEntry) => void; - clearGitStatus: (sessionId: string) => void; - setContextWindow: (sessionId: string, contextWindow: ContextWindowEntry) => void; - bumpAgentProfilesVersion: () => void; - setPendingModel: (sessionId: string, modelId: string) => void; - clearPendingModel: (sessionId: string) => void; - setActiveModel: (sessionId: string, modelId: string) => void; -}; - -export type AppStore = ReturnType; - -const defaultState: AppState = { - kanban: { boardId: null, columns: [], tasks: [] }, - workspaces: { items: [], activeId: null }, - boards: { items: [], activeId: null }, - executors: { items: [] }, - environments: { items: [] }, - settingsAgents: { items: [] }, - agentDiscovery: { items: [] }, - availableAgents: { items: [], loading: false, loaded: false }, - repositories: { itemsByWorkspaceId: {}, loadingByWorkspaceId: {}, loadedByWorkspaceId: {} }, - repositoryBranches: { itemsByRepositoryId: {}, loadingByRepositoryId: {}, loadedByRepositoryId: {} }, - settingsData: { executorsLoaded: false, environmentsLoaded: false, agentsLoaded: false }, - editors: { items: [], loaded: false, loading: false }, - prompts: { items: [], loaded: false, loading: false }, - notificationProviders: { - items: [], - events: [], - appriseAvailable: false, - loaded: false, - loading: false, - }, - tasks: { activeTaskId: null, activeSessionId: null }, - agents: { agents: [] }, - agentProfiles: { items: [], version: 0 }, - userSettings: { - workspaceId: null, - boardId: null, - repositoryIds: [], - preferredShell: null, - shellOptions: [], - defaultEditorId: null, - enablePreviewOnClick: false, - loaded: false, - }, - terminal: { terminals: [] }, - shell: { outputs: {}, statuses: {} }, - processes: { - outputsByProcessId: {}, - processesById: {}, - processIdsBySessionId: {}, - activeProcessBySessionId: {}, - devProcessBySessionId: {}, - }, - previewPanel: { - openBySessionId: {}, - viewBySessionId: {}, - deviceBySessionId: {}, - stageBySessionId: {}, - urlBySessionId: {}, - urlDraftBySessionId: {}, - }, - rightPanel: { activeTabBySessionId: {} }, - diffs: { files: [] }, - gitStatus: { bySessionId: {} }, - contextWindow: { bySessionId: {} }, - connection: { status: 'disconnected', error: null }, - messages: { bySession: {}, metaBySession: {} }, - turns: { - bySession: {}, - activeBySession: {}, - }, - taskSessions: { items: {} }, - taskSessionsByTask: { itemsByTaskId: {}, loadingByTaskId: {}, loadedByTaskId: {} }, - sessionAgentctl: { itemsBySessionId: {} }, - worktrees: { items: {} }, - sessionWorktreesBySessionId: { itemsBySessionId: {} }, - pendingModel: { bySessionId: {} }, - activeModel: { bySessionId: {} }, - hydrate: () => undefined, - setActiveWorkspace: () => undefined, - setWorkspaces: () => undefined, - setActiveBoard: () => undefined, - setBoards: () => undefined, - setExecutors: () => undefined, - setEnvironments: () => undefined, - setSettingsAgents: () => undefined, - setAgentDiscovery: () => undefined, - setAvailableAgents: () => undefined, - setAvailableAgentsLoading: () => undefined, - setAgentProfiles: () => undefined, - setRepositories: () => undefined, - setRepositoriesLoading: () => undefined, - setRepositoryBranches: () => undefined, - setRepositoryBranchesLoading: () => undefined, - setSettingsData: () => undefined, - setEditors: () => undefined, - setEditorsLoading: () => undefined, - setPrompts: () => undefined, - setPromptsLoading: () => undefined, - setNotificationProviders: () => undefined, - setNotificationProvidersLoading: () => undefined, - setUserSettings: () => undefined, - setTerminalOutput: () => undefined, - appendShellOutput: () => undefined, - setShellStatus: () => undefined, - clearShellOutput: () => undefined, - appendProcessOutput: () => undefined, - upsertProcessStatus: () => undefined, - clearProcessOutput: () => undefined, - setActiveProcess: () => undefined, - setPreviewOpen: () => undefined, - togglePreviewOpen: () => undefined, - setPreviewView: () => undefined, - setPreviewDevice: () => undefined, - setPreviewStage: () => undefined, - setPreviewUrl: () => undefined, - setPreviewUrlDraft: () => undefined, - setRightPanelActiveTab: () => undefined, - setConnectionStatus: () => undefined, - setMessages: () => undefined, - addMessage: () => undefined, - addTurn: () => undefined, - completeTurn: () => undefined, - setActiveTurn: () => undefined, - updateMessage: () => undefined, - prependMessages: () => undefined, - setMessagesMetadata: () => undefined, - setMessagesLoading: () => undefined, - setActiveSession: () => undefined, - setActiveTask: () => undefined, - clearActiveSession: () => undefined, - setTaskSession: () => undefined, - setTaskSessionsForTask: () => undefined, - setTaskSessionsLoading: () => undefined, - setSessionAgentctlStatus: () => undefined, - setWorktree: () => undefined, - setSessionWorktrees: () => undefined, - setGitStatus: () => undefined, - clearGitStatus: () => undefined, - setContextWindow: () => undefined, - bumpAgentProfilesVersion: () => undefined, - setPendingModel: () => undefined, - clearPendingModel: () => undefined, - setActiveModel: () => undefined, -}; - -function mergeInitialState( - initialState?: Partial -): Omit< - AppState, - | 'hydrate' - | 'setActiveWorkspace' - | 'setWorkspaces' - | 'setActiveBoard' - | 'setBoards' - | 'setExecutors' - | 'setEnvironments' - | 'setSettingsAgents' - | 'setAgentDiscovery' - | 'setAvailableAgents' - | 'setAvailableAgentsLoading' - | 'setAgentProfiles' - | 'setRepositories' - | 'setRepositoriesLoading' - | 'setRepositoryBranches' - | 'setRepositoryBranchesLoading' - | 'setSettingsData' - | 'setEditors' - | 'setEditorsLoading' - | 'setPrompts' - | 'setPromptsLoading' - | 'setNotificationProviders' - | 'setNotificationProvidersLoading' - | 'setUserSettings' - | 'setTerminalOutput' - | 'appendShellOutput' - | 'setShellStatus' - | 'clearShellOutput' - | 'appendProcessOutput' - | 'upsertProcessStatus' - | 'clearProcessOutput' - | 'setActiveProcess' - | 'setPreviewOpen' - | 'togglePreviewOpen' - | 'setPreviewView' - | 'setPreviewDevice' - | 'setPreviewStage' - | 'setPreviewUrl' - | 'setPreviewUrlDraft' - | 'setRightPanelActiveTab' - | 'setConnectionStatus' - | 'setMessages' - | 'addMessage' - | 'addTurn' - | 'completeTurn' - | 'setActiveTurn' - | 'updateMessage' - | 'prependMessages' - | 'setMessagesMetadata' - | 'setMessagesLoading' - | 'setActiveSession' - | 'setActiveTask' - | 'clearActiveSession' - | 'setTaskSession' - | 'setTaskSessionsForTask' - | 'setTaskSessionsLoading' - | 'setSessionAgentctlStatus' - | 'setWorktree' - | 'setSessionWorktrees' - | 'setGitStatus' - | 'clearGitStatus' - | 'setContextWindow' - | 'bumpAgentProfilesVersion' - | 'setPendingModel' - | 'clearPendingModel' - | 'setActiveModel' -> { - if (!initialState) return defaultState; - return { - workspaces: { ...defaultState.workspaces, ...initialState.workspaces }, - boards: { ...defaultState.boards, ...initialState.boards }, - executors: { ...defaultState.executors, ...initialState.executors }, - environments: { ...defaultState.environments, ...initialState.environments }, - settingsAgents: { ...defaultState.settingsAgents, ...initialState.settingsAgents }, - agentDiscovery: { ...defaultState.agentDiscovery, ...initialState.agentDiscovery }, - availableAgents: { ...defaultState.availableAgents, ...initialState.availableAgents }, - repositories: { ...defaultState.repositories, ...initialState.repositories }, - repositoryBranches: { ...defaultState.repositoryBranches, ...initialState.repositoryBranches }, - settingsData: { ...defaultState.settingsData, ...initialState.settingsData }, - editors: { ...defaultState.editors, ...initialState.editors }, - prompts: { ...defaultState.prompts, ...initialState.prompts }, - notificationProviders: { - ...defaultState.notificationProviders, - ...initialState.notificationProviders, - }, - kanban: { ...defaultState.kanban, ...initialState.kanban }, - tasks: { ...defaultState.tasks, ...initialState.tasks }, - agents: { ...defaultState.agents, ...initialState.agents }, - agentProfiles: { ...defaultState.agentProfiles, ...initialState.agentProfiles }, - userSettings: { ...defaultState.userSettings, ...initialState.userSettings }, - terminal: { ...defaultState.terminal, ...initialState.terminal }, - shell: { ...defaultState.shell, ...initialState.shell }, - processes: { ...defaultState.processes, ...initialState.processes }, - previewPanel: { ...defaultState.previewPanel, ...initialState.previewPanel }, - rightPanel: { ...defaultState.rightPanel, ...initialState.rightPanel }, - diffs: { ...defaultState.diffs, ...initialState.diffs }, - gitStatus: { ...defaultState.gitStatus, ...initialState.gitStatus }, - contextWindow: { ...defaultState.contextWindow, ...initialState.contextWindow }, - connection: { ...defaultState.connection, ...initialState.connection }, - messages: { ...defaultState.messages, ...initialState.messages }, - turns: { ...defaultState.turns, ...initialState.turns }, - taskSessions: { ...defaultState.taskSessions, ...initialState.taskSessions }, - taskSessionsByTask: { ...defaultState.taskSessionsByTask, ...initialState.taskSessionsByTask }, - sessionAgentctl: { ...defaultState.sessionAgentctl, ...initialState.sessionAgentctl }, - worktrees: { ...defaultState.worktrees, ...initialState.worktrees }, - sessionWorktreesBySessionId: { - ...defaultState.sessionWorktreesBySessionId, - ...initialState.sessionWorktreesBySessionId, - }, - pendingModel: { ...defaultState.pendingModel, ...initialState.pendingModel }, - activeModel: { ...defaultState.activeModel, ...initialState.activeModel }, - }; -} - -export function createAppStore(initialState?: Partial) { - const merged = mergeInitialState(initialState); - return createStore()( - immer((set, get) => ({ - ...merged, - hydrate: (state) => - set((draft) => { - // Deep merge to avoid overwriting nested objects with undefined - if (state.workspaces) Object.assign(draft.workspaces, state.workspaces); - if (state.boards) Object.assign(draft.boards, state.boards); - if (state.executors) Object.assign(draft.executors, state.executors); - if (state.environments) Object.assign(draft.environments, state.environments); - if (state.settingsAgents) Object.assign(draft.settingsAgents, state.settingsAgents); - if (state.agentDiscovery) Object.assign(draft.agentDiscovery, state.agentDiscovery); - if (state.availableAgents) Object.assign(draft.availableAgents, state.availableAgents); - if (state.repositories) Object.assign(draft.repositories, state.repositories); - if (state.repositoryBranches) Object.assign(draft.repositoryBranches, state.repositoryBranches); - if (state.settingsData) Object.assign(draft.settingsData, state.settingsData); - if (state.editors) Object.assign(draft.editors, state.editors); - if (state.prompts) Object.assign(draft.prompts, state.prompts); - if (state.notificationProviders) { - Object.assign(draft.notificationProviders, state.notificationProviders); - } - if (state.kanban) Object.assign(draft.kanban, state.kanban); - if (state.tasks) Object.assign(draft.tasks, state.tasks); - if (state.agents) Object.assign(draft.agents, state.agents); - if (state.agentProfiles) Object.assign(draft.agentProfiles, state.agentProfiles); - if (state.userSettings) Object.assign(draft.userSettings, state.userSettings); - if (state.terminal) Object.assign(draft.terminal, state.terminal); - if (state.shell) Object.assign(draft.shell, state.shell); - if (state.processes) Object.assign(draft.processes, state.processes); - if (state.previewPanel) Object.assign(draft.previewPanel, state.previewPanel); - if (state.rightPanel) Object.assign(draft.rightPanel, state.rightPanel); - if (state.diffs) Object.assign(draft.diffs, state.diffs); - if (state.gitStatus) Object.assign(draft.gitStatus, state.gitStatus); - if (state.connection) Object.assign(draft.connection, state.connection); - if (state.messages) Object.assign(draft.messages, state.messages); - if (state.turns) Object.assign(draft.turns, state.turns); - if (state.taskSessions) Object.assign(draft.taskSessions, state.taskSessions); - if (state.taskSessionsByTask) { - Object.assign(draft.taskSessionsByTask, state.taskSessionsByTask); - } - if (state.sessionAgentctl) { - Object.assign(draft.sessionAgentctl, state.sessionAgentctl); - } - if (state.worktrees) Object.assign(draft.worktrees, state.worktrees); - if (state.sessionWorktreesBySessionId) { - Object.assign(draft.sessionWorktreesBySessionId, state.sessionWorktreesBySessionId); - } - }), - setActiveWorkspace: (workspaceId) => { - if (get().workspaces.activeId === workspaceId) { - return; - } - set((draft) => { - draft.workspaces.activeId = workspaceId; - }); - }, - setWorkspaces: (workspaces) => - set((draft) => { - draft.workspaces.items = workspaces; - if (!draft.workspaces.activeId && workspaces.length) { - draft.workspaces.activeId = workspaces[0].id; - } - }), - setBoards: (boards) => - set((draft) => { - draft.boards.items = boards; - if (!draft.boards.activeId && boards.length) { - draft.boards.activeId = boards[0].id; - } - }), - setActiveBoard: (boardId) => { - if (get().boards.activeId === boardId) { - return; - } - set((draft) => { - draft.boards.activeId = boardId; - }); - }, - setExecutors: (executors) => - set((draft) => { - draft.executors.items = executors; - }), - setEnvironments: (environments) => - set((draft) => { - draft.environments.items = environments; - }), - setSettingsAgents: (agents) => - set((draft) => { - draft.settingsAgents.items = agents; - }), - setAgentDiscovery: (agents) => - set((draft) => { - draft.agentDiscovery.items = agents; - }), - setAvailableAgents: (agents) => - set((draft) => { - draft.availableAgents.items = agents; - draft.availableAgents.loading = false; - draft.availableAgents.loaded = true; - }), - setAvailableAgentsLoading: (loading) => - set((draft) => { - draft.availableAgents.loading = loading; - }), - setAgentProfiles: (profiles) => - set((draft) => { - draft.agentProfiles.items = profiles; - }), - setRepositories: (workspaceId, repositories) => - set((draft) => { - draft.repositories.itemsByWorkspaceId[workspaceId] = repositories; - draft.repositories.loadingByWorkspaceId[workspaceId] = false; - draft.repositories.loadedByWorkspaceId[workspaceId] = true; - }), - setRepositoriesLoading: (workspaceId, loading) => - set((draft) => { - draft.repositories.loadingByWorkspaceId[workspaceId] = loading; - }), - setRepositoryBranches: (repositoryId, branches) => - set((draft) => { - draft.repositoryBranches.itemsByRepositoryId[repositoryId] = branches; - draft.repositoryBranches.loadingByRepositoryId[repositoryId] = false; - draft.repositoryBranches.loadedByRepositoryId[repositoryId] = true; - }), - setRepositoryBranchesLoading: (repositoryId, loading) => - set((draft) => { - draft.repositoryBranches.loadingByRepositoryId[repositoryId] = loading; - }), - setSettingsData: (next) => - set((draft) => { - draft.settingsData = { ...draft.settingsData, ...next }; - }), - setEditors: (editors) => - set((draft) => { - draft.editors.items = editors; - draft.editors.loaded = true; - }), - setEditorsLoading: (loading) => - set((draft) => { - draft.editors.loading = loading; - }), - setPrompts: (prompts) => - set((draft) => { - draft.prompts.items = prompts; - draft.prompts.loaded = true; - }), - setPromptsLoading: (loading) => - set((draft) => { - draft.prompts.loading = loading; - }), - setNotificationProviders: (state) => - set((draft) => { - draft.notificationProviders.items = state.items; - draft.notificationProviders.events = state.events; - draft.notificationProviders.appriseAvailable = state.appriseAvailable; - draft.notificationProviders.loaded = state.loaded; - draft.notificationProviders.loading = state.loading; - }), - setNotificationProvidersLoading: (loading) => - set((draft) => { - draft.notificationProviders.loading = loading; - }), - setUserSettings: (settings) => - set((draft) => { - draft.userSettings = settings; - }), - setTerminalOutput: (terminalId, data) => - set((draft) => { - const existing = draft.terminal.terminals.find((terminal) => terminal.id === terminalId); - if (existing) { - existing.output.push(data); - } else { - draft.terminal.terminals.push({ id: terminalId, output: [data] }); - } - }), - appendShellOutput: (sessionId, data) => - set((draft) => { - draft.shell.outputs[sessionId] = (draft.shell.outputs[sessionId] || '') + data; - }), - setShellStatus: (sessionId, status) => - set((draft) => { - draft.shell.statuses[sessionId] = status; - }), - clearShellOutput: (sessionId) => - set((draft) => { - draft.shell.outputs[sessionId] = ''; - }), - appendProcessOutput: (processId, data) => - set((draft) => { - const next = - (draft.processes.outputsByProcessId[processId] || '') + data; - draft.processes.outputsByProcessId[processId] = trimProcessOutput(next); - }), - upsertProcessStatus: (status) => - set((draft) => { - draft.processes.processesById[status.processId] = status; - const list = draft.processes.processIdsBySessionId[status.sessionId] || []; - if (!list.includes(status.processId)) { - list.push(status.processId); - draft.processes.processIdsBySessionId[status.sessionId] = list; - } - if (!draft.processes.activeProcessBySessionId[status.sessionId]) { - draft.processes.activeProcessBySessionId[status.sessionId] = status.processId; - } - if (status.kind === 'dev') { - draft.processes.devProcessBySessionId[status.sessionId] = status.processId; - } - }), - clearProcessOutput: (processId) => - set((draft) => { - draft.processes.outputsByProcessId[processId] = ''; - }), - setActiveProcess: (sessionId, processId) => - set((draft) => { - draft.processes.activeProcessBySessionId[sessionId] = processId; - }), - setPreviewOpen: (sessionId, open) => - set((draft) => { - draft.previewPanel.openBySessionId[sessionId] = open; - if (!draft.previewPanel.viewBySessionId[sessionId]) { - draft.previewPanel.viewBySessionId[sessionId] = 'preview'; - } - if (!draft.previewPanel.deviceBySessionId[sessionId]) { - draft.previewPanel.deviceBySessionId[sessionId] = 'desktop'; - } - }), - togglePreviewOpen: (sessionId) => - set((draft) => { - const current = draft.previewPanel.openBySessionId[sessionId] ?? false; - draft.previewPanel.openBySessionId[sessionId] = !current; - if (!draft.previewPanel.viewBySessionId[sessionId]) { - draft.previewPanel.viewBySessionId[sessionId] = 'preview'; - } - if (!draft.previewPanel.deviceBySessionId[sessionId]) { - draft.previewPanel.deviceBySessionId[sessionId] = 'desktop'; - } - }), - setPreviewView: (sessionId, view) => - set((draft) => { - draft.previewPanel.viewBySessionId[sessionId] = view; - // Persist to localStorage - const key = `preview-view:${sessionId}`; - setLocalStorage(key, view); - }), - setPreviewDevice: (sessionId, device) => - set((draft) => { - draft.previewPanel.deviceBySessionId[sessionId] = device; - }), - setPreviewStage: (sessionId, stage) => - set((draft) => { - draft.previewPanel.stageBySessionId[sessionId] = stage; - // layout changes handled by layout-store - }), - setPreviewUrl: (sessionId, url) => - set((draft) => { - draft.previewPanel.urlBySessionId[sessionId] = url; - }), - setPreviewUrlDraft: (sessionId, url) => - set((draft) => { - draft.previewPanel.urlDraftBySessionId[sessionId] = url; - }), - setRightPanelActiveTab: (sessionId, tab) => - set((draft) => { - draft.rightPanel.activeTabBySessionId[sessionId] = tab; - }), - setConnectionStatus: (status, error = null) => - set((draft) => { - draft.connection.status = status; - draft.connection.error = error; - }), - setMessages: (sessionId, messages, meta) => - set((draft) => { - draft.messages.bySession[sessionId] = messages; - const existingMeta = draft.messages.metaBySession[sessionId]; - draft.messages.metaBySession[sessionId] = { - isLoading: false, - hasMore: meta?.hasMore ?? existingMeta?.hasMore ?? false, - oldestCursor: - meta?.oldestCursor ?? (messages.length ? messages[0].id : existingMeta?.oldestCursor ?? null), - }; - }), - addMessage: (message) => - set((draft) => { - if (!message.session_id) { - return; - } - const sessionId = message.session_id; - const list = draft.messages.bySession[sessionId] ?? []; - const exists = list.some((item) => item.id === message.id); - if (exists) { - return; - } - draft.messages.bySession[sessionId] = [...list, message]; - const meta = draft.messages.metaBySession[sessionId] ?? { - isLoading: false, - hasMore: false, - oldestCursor: null, - }; - if (!meta.oldestCursor) { - meta.oldestCursor = message.id; - } - draft.messages.metaBySession[sessionId] = meta; - }), - addTurn: (turn) => - set((state) => { - const sessionId = turn.session_id; - if (!state.turns.bySession[sessionId]) { - state.turns.bySession[sessionId] = []; - } - // Add turn if not already present - const existing = state.turns.bySession[sessionId].find((t) => t.id === turn.id); - if (!existing) { - state.turns.bySession[sessionId].push(turn); - } - // Set as active turn if not completed - if (!turn.completed_at) { - state.turns.activeBySession[sessionId] = turn.id; - } - }), - completeTurn: (sessionId, turnId, completedAt) => - set((state) => { - const turns = state.turns.bySession[sessionId]; - if (turns) { - const turn = turns.find((t) => t.id === turnId); - if (turn) { - turn.completed_at = completedAt; - turn.updated_at = completedAt; - } - } - // Clear active turn if it matches - if (state.turns.activeBySession[sessionId] === turnId) { - state.turns.activeBySession[sessionId] = null; - } - }), - setActiveTurn: (sessionId, turnId) => - set((state) => { - state.turns.activeBySession[sessionId] = turnId; - }), - updateMessage: (message) => - set((draft) => { - if (!message.session_id) { - return; - } - const sessionId = message.session_id; - const list = draft.messages.bySession[sessionId]; - if (!list) return; - const index = list.findIndex((item) => item.id === message.id); - if (index !== -1) { - list[index] = message; - } - }), - prependMessages: (sessionId, messages, meta) => - set((draft) => { - const existing = draft.messages.bySession[sessionId] ?? []; - const existingIds = new Set(existing.map((item) => item.id)); - const incoming = messages.filter((item) => !existingIds.has(item.id)); - const next = incoming.length ? [...incoming, ...existing] : existing; - draft.messages.bySession[sessionId] = next; - const currentMeta = draft.messages.metaBySession[sessionId] ?? { - isLoading: false, - hasMore: false, - oldestCursor: null, - }; - draft.messages.metaBySession[sessionId] = { - isLoading: false, - hasMore: meta?.hasMore ?? currentMeta.hasMore, - oldestCursor: - meta?.oldestCursor ?? (next.length ? next[0].id : currentMeta.oldestCursor ?? null), - }; - }), - setMessagesMetadata: (sessionId, meta) => - set((draft) => { - const currentMeta = draft.messages.metaBySession[sessionId] ?? { - isLoading: false, - hasMore: false, - oldestCursor: null, - }; - draft.messages.metaBySession[sessionId] = { - isLoading: meta.isLoading ?? currentMeta.isLoading, - hasMore: meta.hasMore ?? currentMeta.hasMore, - oldestCursor: meta.oldestCursor ?? currentMeta.oldestCursor, - }; - }), - setMessagesLoading: (sessionId, loading) => - set((draft) => { - const currentMeta = draft.messages.metaBySession[sessionId] ?? { - isLoading: false, - hasMore: false, - oldestCursor: null, - }; - currentMeta.isLoading = loading; - draft.messages.metaBySession[sessionId] = currentMeta; - }), - setActiveSession: (taskId, sessionId) => - set((draft) => { - draft.tasks.activeTaskId = taskId; - draft.tasks.activeSessionId = sessionId; - }), - setActiveTask: (taskId) => - set((draft) => { - draft.tasks.activeTaskId = taskId; - draft.tasks.activeSessionId = null; - }), - clearActiveSession: () => - set((draft) => { - draft.tasks.activeTaskId = null; - draft.tasks.activeSessionId = null; - }), - setTaskSession: (session) => - set((draft) => { - // Merge with existing session data to preserve fields like agent_profile_id - const existingSession = draft.taskSessions.items[session.id]; - draft.taskSessions.items[session.id] = existingSession - ? { ...existingSession, ...session } - : session; - if (session.worktree_id) { - draft.worktrees.items[session.worktree_id] = { - id: session.worktree_id, - sessionId: session.id, - repositoryId: session.repository_id ?? undefined, - path: session.worktree_path ?? undefined, - branch: session.worktree_branch ?? undefined, - }; - draft.sessionWorktreesBySessionId.itemsBySessionId[session.id] = [session.worktree_id]; - } - }), - setTaskSessionsForTask: (taskId, sessions) => - set((draft) => { - sessions.forEach((session) => { - draft.taskSessions.items[session.id] = session; - if (session.worktree_id) { - draft.worktrees.items[session.worktree_id] = { - id: session.worktree_id, - sessionId: session.id, - repositoryId: session.repository_id ?? undefined, - path: session.worktree_path ?? undefined, - branch: session.worktree_branch ?? undefined, - }; - draft.sessionWorktreesBySessionId.itemsBySessionId[session.id] = [session.worktree_id]; - } - }); - draft.taskSessionsByTask.itemsByTaskId[taskId] = sessions; - draft.taskSessionsByTask.loadedByTaskId[taskId] = true; - draft.taskSessionsByTask.loadingByTaskId[taskId] = false; - }), - setTaskSessionsLoading: (taskId, loading) => - set((draft) => { - draft.taskSessionsByTask.loadingByTaskId[taskId] = loading; - }), - setSessionAgentctlStatus: (sessionId, status) => - set((draft) => { - draft.sessionAgentctl.itemsBySessionId[sessionId] = status; - }), - setWorktree: (worktree) => - set((draft) => { - draft.worktrees.items[worktree.id] = worktree; - const existing = draft.sessionWorktreesBySessionId.itemsBySessionId[worktree.sessionId] ?? []; - if (!existing.includes(worktree.id)) { - draft.sessionWorktreesBySessionId.itemsBySessionId[worktree.sessionId] = [...existing, worktree.id]; - } - }), - setSessionWorktrees: (sessionId, worktreeIds) => - set((draft) => { - draft.sessionWorktreesBySessionId.itemsBySessionId[sessionId] = worktreeIds; - }), - setGitStatus: (sessionId, gitStatus) => - set((draft) => { - draft.gitStatus.bySessionId[sessionId] = gitStatus; - }), - clearGitStatus: (sessionId) => - set((draft) => { - delete draft.gitStatus.bySessionId[sessionId]; - }), - setContextWindow: (sessionId, contextWindow) => - set((draft) => { - draft.contextWindow.bySessionId[sessionId] = contextWindow; - }), - bumpAgentProfilesVersion: () => - set((draft) => { - draft.agentProfiles.version += 1; - }), - setPendingModel: (sessionId, modelId) => - set((draft) => { - draft.pendingModel.bySessionId[sessionId] = modelId; - }), - clearPendingModel: (sessionId) => - set((draft) => { - delete draft.pendingModel.bySessionId[sessionId]; - }), - setActiveModel: (sessionId, modelId) => - set((draft) => { - draft.activeModel.bySessionId[sessionId] = modelId; - }), - })) - ); -} - -export type StoreProviderProps = { - children: ReactNode; - initialState?: Partial; -};