Files
kandev/apps/web/lib/state/default-state.ts
T
2026-09-17 10:58:47 +02:00

604 lines
28 KiB
TypeScript

import { mapSidebarWorkspaces } from "./slices/ui/sidebar-workspace-state";
import {
defaultKanbanState,
defaultWorkspaceState,
defaultSettingsState,
defaultSessionState,
defaultSessionRuntimeState,
defaultUIState,
defaultGitHubState,
defaultGitLabState,
defaultAzureDevOpsState,
defaultJiraState,
defaultLinearState,
defaultOfficeState,
defaultFeaturesState,
defaultAuthState,
defaultAutomationsState,
defaultSystemState,
defaultReviewState,
defaultNeedsYouInboxState,
defaultFailedInboxState,
} from "./slices";
import { mergeHydratedQuickChatSessions } from "@/lib/state/slices/ui/quick-chat-sync";
import type { AgentRuntimeAvailability } from "@/lib/types/agent-runtime";
import type { HydrationState } from "./store";
import { seedSettledSessionBoundaries } from "@/lib/state/slices/session/turn-actions";
import { migrateSidebarViewDraft, migrateView } from "./slices/ui/ui-slice";
import { mergeAgentProfileRecentUseState } from "@/lib/agent-profile-recent-use";
import { normalizeThreadViews } from "./slices/ui/thread-view-builtins";
export const defaultState = {
kanban: defaultKanbanState.kanban,
kanbanMulti: defaultKanbanState.kanbanMulti,
sidebarArchivedTasks: defaultKanbanState.sidebarArchivedTasks,
workflows: defaultKanbanState.workflows,
workspaceContextGeneration: defaultKanbanState.workspaceContextGeneration,
workspaceContextRead: defaultKanbanState.workspaceContextRead,
tasks: defaultKanbanState.tasks,
taskRemoval: defaultKanbanState.taskRemoval,
workspaces: defaultWorkspaceState.workspaces,
repositories: defaultWorkspaceState.repositories,
repositorySets: defaultWorkspaceState.repositorySets,
repositoryBranchPolicies: defaultWorkspaceState.repositoryBranchPolicies,
repositoryBranches: defaultWorkspaceState.repositoryBranches,
repositoryScripts: defaultWorkspaceState.repositoryScripts,
executors: defaultSettingsState.executors,
settingsAgents: defaultSettingsState.settingsAgents,
agentDiscovery: defaultSettingsState.agentDiscovery,
availableAgents: defaultSettingsState.availableAgents,
agentProfiles: defaultSettingsState.agentProfiles,
editors: defaultSettingsState.editors,
prompts: defaultSettingsState.prompts,
secrets: defaultSettingsState.secrets,
notificationProviders: defaultSettingsState.notificationProviders,
settingsData: defaultSettingsState.settingsData,
sleepInhibition: defaultSettingsState.sleepInhibition,
userSettings: defaultSettingsState.userSettings,
agentProfileRecentUse: defaultSettingsState.agentProfileRecentUse,
messages: defaultSessionState.messages,
turns: defaultSessionState.turns,
taskSessions: defaultSessionState.taskSessions,
taskSessionsByTask: defaultSessionState.taskSessionsByTask,
pendingActionProjectionsBySessionId: defaultSessionState.pendingActionProjectionsBySessionId,
sessionAgentctl: defaultSessionState.sessionAgentctl,
worktrees: defaultSessionState.worktrees,
sessionWorktreesBySessionId: defaultSessionState.sessionWorktreesBySessionId,
pendingModel: defaultSessionState.pendingModel,
activeModel: defaultSessionState.activeModel,
messagePrompts: defaultSessionState.messagePrompts,
taskPlans: defaultSessionState.taskPlans,
walkthroughs: defaultSessionState.walkthroughs,
taskReview: defaultReviewState.taskReview,
needsYouInbox: defaultNeedsYouInboxState.needsYouInbox,
failedInbox: defaultFailedInboxState.failedInbox,
queue: defaultSessionState.queue,
terminal: defaultSessionRuntimeState.terminal,
shell: defaultSessionRuntimeState.shell,
processes: defaultSessionRuntimeState.processes,
gitStatus: defaultSessionRuntimeState.gitStatus,
environmentIdBySessionId: defaultSessionRuntimeState.environmentIdBySessionId,
sessionCommits: defaultSessionRuntimeState.sessionCommits,
gitCheckoutGeneration: defaultSessionRuntimeState.gitCheckoutGeneration,
contextWindow: defaultSessionRuntimeState.contextWindow,
agents: defaultSessionRuntimeState.agents,
availableCommands: defaultSessionRuntimeState.availableCommands,
sessionMode: defaultSessionRuntimeState.sessionMode,
userShells: defaultSessionRuntimeState.userShells,
prepareProgress: defaultSessionRuntimeState.prepareProgress,
sessionTodos: defaultSessionRuntimeState.sessionTodos,
agentCapabilities: defaultSessionRuntimeState.agentCapabilities,
sessionModels: defaultSessionRuntimeState.sessionModels,
sessionMcpStatus: defaultSessionRuntimeState.sessionMcpStatus,
promptUsage: defaultSessionRuntimeState.promptUsage,
sessionPollMode: defaultSessionRuntimeState.sessionPollMode,
embeddedVscodeSupport: defaultSessionRuntimeState.embeddedVscodeSupport,
workspaceRestoration: defaultSessionRuntimeState.workspaceRestoration,
githubStatus: defaultGitHubState.githubStatus,
githubAppRegistrations: defaultGitHubState.githubAppRegistrations,
taskPRs: defaultGitHubState.taskPRs,
taskIssues: defaultGitHubState.taskIssues,
pendingPrUrlByTaskId: defaultGitHubState.pendingPrUrlByTaskId,
prWatches: defaultGitHubState.prWatches,
reviewWatches: defaultGitHubState.reviewWatches,
issueWatches: defaultGitHubState.issueWatches,
actionPresets: defaultGitHubState.actionPresets,
prFeedbackCache: defaultGitHubState.prFeedbackCache,
taskCIAutomation: defaultGitHubState.taskCIAutomation,
taskMRs: defaultGitLabState.taskMRs,
azureDevOpsTaskPullRequests: defaultAzureDevOpsState.azureDevOpsTaskPullRequests,
azureDevOpsTaskWorkItems: defaultAzureDevOpsState.azureDevOpsTaskWorkItems,
gitlabReviewWatches: defaultGitLabState.gitlabReviewWatches,
gitlabIssueWatches: defaultGitLabState.gitlabIssueWatches,
gitlabMRWatches: defaultGitLabState.gitlabMRWatches,
gitlabActionPresets: defaultGitLabState.gitlabActionPresets,
gitlabStats: defaultGitLabState.gitlabStats,
gitlabStatus: defaultGitLabState.gitlabStatus,
taskMRAutomation: defaultGitLabState.taskMRAutomation,
jiraIssueWatches: defaultJiraState.jiraIssueWatches,
linearIssueWatches: defaultLinearState.linearIssueWatches,
office: defaultOfficeState.office,
features: defaultFeaturesState.features,
auth: defaultAuthState.auth,
sessionHostnames: defaultAuthState.sessionHostnames,
sessionHostnamesEpoch: defaultAuthState.sessionHostnamesEpoch,
automations: defaultAutomationsState.automations,
automationRuns: defaultAutomationsState.automationRuns,
system: defaultSystemState.system,
agentRuntime: null as AgentRuntimeAvailability | null,
previewPanel: defaultUIState.previewPanel,
rightPanel: defaultUIState.rightPanel,
diffs: defaultUIState.diffs,
connection: defaultUIState.connection,
mobileKanban: defaultUIState.mobileKanban,
mobileSession: defaultUIState.mobileSession,
chatInput: defaultUIState.chatInput,
reviewPRSelection: defaultUIState.reviewPRSelection,
documentPanel: defaultUIState.documentPanel,
systemHealth: defaultUIState.systemHealth,
quickChat: defaultUIState.quickChat,
sessionFailureNotification: defaultUIState.sessionFailureNotification,
bottomTerminal: defaultUIState.bottomTerminal,
sidebarViews: defaultUIState.sidebarViews,
sidebarViewsByWorkspace: defaultUIState.sidebarViewsByWorkspace,
threadViews: defaultUIState.threadViews,
collapsedSubtaskParents: defaultUIState.collapsedSubtaskParents,
kanbanPreviewedTaskId: defaultUIState.kanbanPreviewedTaskId,
sidebarTaskPrefs: defaultUIState.sidebarTaskPrefs,
};
export type DefaultState = typeof defaultState;
/** Merge the code-host slice fields (MRs, watches, presets, stats, status, Azure DevOps PRs/work items) from hydration state over defaults. */
function mergeCodeHostFields(
d: DefaultState,
s: HydrationState,
): Pick<
DefaultState,
| "taskMRs"
| "gitlabReviewWatches"
| "gitlabIssueWatches"
| "gitlabMRWatches"
| "gitlabActionPresets"
| "gitlabStats"
| "gitlabStatus"
| "azureDevOpsTaskPullRequests"
| "azureDevOpsTaskWorkItems"
> {
return {
taskMRs: { ...d.taskMRs, ...s.taskMRs },
gitlabReviewWatches: { ...d.gitlabReviewWatches, ...s.gitlabReviewWatches },
gitlabIssueWatches: { ...d.gitlabIssueWatches, ...s.gitlabIssueWatches },
gitlabMRWatches: { ...d.gitlabMRWatches, ...s.gitlabMRWatches },
gitlabActionPresets: { ...d.gitlabActionPresets, ...s.gitlabActionPresets },
gitlabStats: { ...d.gitlabStats, ...s.gitlabStats },
gitlabStatus: { ...d.gitlabStatus, ...s.gitlabStatus },
azureDevOpsTaskPullRequests: {
...d.azureDevOpsTaskPullRequests,
...s.azureDevOpsTaskPullRequests,
},
azureDevOpsTaskWorkItems: {
...d.azureDevOpsTaskWorkItems,
...s.azureDevOpsTaskWorkItems,
},
};
}
/** Merge quick-chat state from hydration over defaults, applying locally stored chat names to the SSR-provided sessions. */
function mergeQuickChatState(initialState: HydrationState): DefaultState["quickChat"] {
const { sessions, ...hydratedQuickChat } = initialState.quickChat ?? {};
const quickChat: DefaultState["quickChat"] = {
...defaultState.quickChat,
...hydratedQuickChat,
unseenIdleByWorkspace: {},
lastSettledAtBySession: {},
sessionOwnership: {},
syncRevisionByWorkspace: {},
tombstonedSessions: {},
rememberedSelectionByWorkspace: {},
rememberedSelectionOrder: [],
selectionStorageIdentity: null,
selectionReadyByWorkspace: {},
selectionRevisionByWorkspace: {},
pendingOpen: null,
};
const merged = sessions ? mergeHydratedQuickChatSessions(quickChat, sessions) : quickChat;
for (const session of sessions ?? []) {
merged.selectionReadyByWorkspace[session.workspaceId] = true;
}
if (sessions?.length === 0 && initialState.workspaces?.activeId) {
merged.selectionReadyByWorkspace[initialState.workspaces.activeId] = true;
}
return merged;
}
/** Merge sidebar view state, preferring the server-provided views, active view, and draft from user settings when present. */
function mergeSidebarViewState(initialState: HydrationState): DefaultState["sidebarViews"] {
const sidebarViews = { ...defaultState.sidebarViews, ...initialState.sidebarViews };
const userSettings = initialState.userSettings;
const serverViews = userSettings?.sidebarViews?.map(migrateView) ?? [];
if (serverViews.length > 0) sidebarViews.views = serverViews;
const activeViewId = userSettings?.sidebarActiveViewId;
if (activeViewId && sidebarViews.views.some((view) => view.id === activeViewId)) {
sidebarViews.activeViewId = activeViewId;
} else if (!sidebarViews.views.some((view) => view.id === sidebarViews.activeViewId)) {
sidebarViews.activeViewId = sidebarViews.views[0].id;
}
if (userSettings?.sidebarDraft !== undefined) {
sidebarViews.draft = userSettings.sidebarDraft
? migrateSidebarViewDraft(userSettings.sidebarDraft)
: null;
}
return sidebarViews;
}
/** Merge Threads saved views from user settings over the UI defaults. */
function mergeThreadViewState(initialState: HydrationState): DefaultState["threadViews"] {
const threadViews = { ...defaultState.threadViews, ...initialState.threadViews };
const serverViews = initialState.userSettings?.threadViews;
if (serverViews) threadViews.views = normalizeThreadViews(serverViews);
const activeViewId = initialState.userSettings?.threadActiveViewId;
if (activeViewId && threadViews.views.some((view) => view.id === activeViewId)) {
threadViews.activeViewId = activeViewId;
} else if (!threadViews.views.some((view) => view.id === threadViews.activeViewId)) {
threadViews.activeViewId = threadViews.views[0].id;
}
if (initialState.userSettings?.threadViewDraft !== undefined) {
threadViews.draft = initialState.userSettings.threadViewDraft;
}
return threadViews;
}
/** Merge sidebar task prefs, copying the server-provided pinned, ordered, and subtask-order lists from user settings over defaults. */
function mergeSidebarTaskPrefsState(
initialState: HydrationState,
): DefaultState["sidebarTaskPrefs"] {
const sidebarTaskPrefs = {
...defaultState.sidebarTaskPrefs,
...initialState.sidebarTaskPrefs,
};
const serverPrefs = initialState.userSettings?.sidebarTaskPrefs;
if (!serverPrefs) return sidebarTaskPrefs;
return {
...sidebarTaskPrefs,
pinnedTaskIds: [...serverPrefs.pinnedTaskIds],
orderedTaskIds: [...serverPrefs.orderedTaskIds],
subtaskOrderByParentId: Object.fromEntries(
Object.entries(serverPrefs.subtaskOrderByParentId).map(([parentId, taskIds]) => [
parentId,
[...taskIds],
]),
),
};
}
/** Merge review-PR selection state, deep-merging the per-task selected keys from hydration over defaults. */
function mergeReviewPRSelectionState(
initialState: HydrationState,
): DefaultState["reviewPRSelection"] {
return {
...defaultState.reviewPRSelection,
...initialState.reviewPRSelection,
selectedKeyByTaskId: {
...defaultState.reviewPRSelection.selectedKeyByTaskId,
...initialState.reviewPRSelection?.selectedKeyByTaskId,
},
};
}
/** Return the hydrated session-failure notification, or the default when none is provided. */
function mergeSessionFailureNotification(initialState: HydrationState) {
return initialState.sessionFailureNotification ?? defaultState.sessionFailureNotification;
}
/**
* Merges the agent-authored review artifacts (walkthroughs and code-review
* findings). Extracted so mergeInitialState stays under the function line cap.
*/
function mergeAgentReviewArtifacts(initialState: HydrationState) {
return {
walkthroughs: { ...defaultState.walkthroughs, ...initialState.walkthroughs },
taskReview: { ...defaultState.taskReview, ...initialState.taskReview },
};
}
/** Merges the independently hydrated Prompt History projection. */
function mergePromptHistoryState(initialState: HydrationState) {
return {
...defaultState.messagePrompts,
...initialState.messagePrompts,
generationBySession: {
...defaultState.messagePrompts.generationBySession,
...initialState.messagePrompts?.generationBySession,
},
refreshGenerationBySession: {
...defaultState.messagePrompts.refreshGenerationBySession,
...initialState.messagePrompts?.refreshGenerationBySession,
},
};
}
/** Merges the GitHub slices for initial (SSR/boot) hydration. */
/** Merges the GitHub slices for initial (SSR/boot) hydration. */
function mergeGitHubState(initialState: HydrationState) {
return {
githubStatus: { ...defaultState.githubStatus, ...initialState.githubStatus },
githubAppRegistrations: {
...defaultState.githubAppRegistrations,
...initialState.githubAppRegistrations,
},
taskPRs: mergeTaskPRState(initialState),
};
}
function mergeTaskPRState(initialState: HydrationState) {
const incoming = initialState.taskPRs;
return {
...defaultState.taskPRs,
...incoming,
byTaskId: { ...defaultState.taskPRs.byTaskId, ...incoming?.byTaskId },
deletedAssociationIdsByTaskId: {
...defaultState.taskPRs.deletedAssociationIdsByTaskId,
...incoming?.deletedAssociationIdsByTaskId,
},
workspaceId: incoming?.workspaceId ?? initialState.workspaces?.activeId ?? null,
workspaceContextGeneration:
incoming?.workspaceContextGeneration ?? initialState.workspaceContextGeneration ?? 0,
};
}
/**
* Merges the turns slice for initial (SSR/boot) hydration. The server-side
* turn lists are complete per-session snapshots, so every session the payload
* installs is marked `loadedBySession`; without the marker, turn-derived UI
* would mistake WS-seeded live turns for the full history (the debug
* metadata dialog's `turn_metadata` regression). Settled sessions also seed
* their settled boundary from `updated_at` (monotonically) so an
* old/unknown delayed WS start arriving before any session-list refresh is
* rejected.
*/
function mergeTurnsState(
current: DefaultState["turns"],
incoming: HydrationState["turns"],
sessions: HydrationState["taskSessions"],
): DefaultState["turns"] {
const merged = { ...current, ...incoming };
const settledBoundaryBySession = { ...merged.settledBoundaryBySession };
// One shared seeding invariant (turn-actions) with the production
// StateHydrator path, so the SSR and hydration boundary rules cannot drift.
seedSettledSessionBoundaries(settledBoundaryBySession, Object.values(sessions?.items ?? {}));
if (!incoming?.bySession) return { ...merged, settledBoundaryBySession };
const loadedBySession: Record<string, boolean> = {};
for (const sessionId of Object.keys(incoming.bySession)) {
loadedBySession[sessionId] = true;
}
return { ...merged, loadedBySession, settledBoundaryBySession };
}
function mergeTaskSessionState(initialState: HydrationState) {
return {
taskSessions: { ...defaultState.taskSessions, ...initialState.taskSessions },
taskSessionsByTask: {
...defaultState.taskSessionsByTask,
...initialState.taskSessionsByTask,
itemsByTaskId: {
...defaultState.taskSessionsByTask.itemsByTaskId,
...initialState.taskSessionsByTask?.itemsByTaskId,
},
loadingByTaskId: {
...defaultState.taskSessionsByTask.loadingByTaskId,
...initialState.taskSessionsByTask?.loadingByTaskId,
},
loadedByTaskId: {
...defaultState.taskSessionsByTask.loadedByTaskId,
...initialState.taskSessionsByTask?.loadedByTaskId,
},
errorByTaskId: {
...defaultState.taskSessionsByTask.errorByTaskId,
...initialState.taskSessionsByTask?.errorByTaskId,
},
},
pendingActionProjectionsBySessionId: {
...defaultState.pendingActionProjectionsBySessionId,
...initialState.pendingActionProjectionsBySessionId,
},
};
}
/**
* Builds the full default state from the SSR/boot hydration payload, merging
* per-slice (kanban, turns, settings, ...) so partial payloads never clobber
* the client's live defaults.
*/
// eslint-disable-next-line max-lines-per-function -- merges every hydrated state slice in one place.
export function mergeInitialState(initialState?: HydrationState): DefaultState {
if (!initialState) return defaultState;
const hydration = { ...initialState };
delete hydration.taskRemoval;
return {
...defaultState,
...hydration,
kanban: { ...defaultState.kanban, ...initialState.kanban },
kanbanMulti: { ...defaultState.kanbanMulti, ...initialState.kanbanMulti },
workflows: { ...defaultState.workflows, ...initialState.workflows },
workspaceContextRead: {
...defaultState.workspaceContextRead,
...initialState.workspaceContextRead,
pending: {
...defaultState.workspaceContextRead.pending,
...initialState.workspaceContextRead?.pending,
},
errors: {
...defaultState.workspaceContextRead.errors,
...initialState.workspaceContextRead?.errors,
},
retryAfterMs: {
...defaultState.workspaceContextRead.retryAfterMs,
...initialState.workspaceContextRead?.retryAfterMs,
},
requestIds: {
...defaultState.workspaceContextRead.requestIds,
...initialState.workspaceContextRead?.requestIds,
},
},
workspaceContextGeneration: mergeWorkspaceContextGeneration(initialState),
tasks: { ...defaultState.tasks, ...initialState.tasks },
workspaces: { ...defaultState.workspaces, ...initialState.workspaces },
repositories: { ...defaultState.repositories, ...initialState.repositories },
repositorySets: { ...defaultState.repositorySets, ...initialState.repositorySets },
repositoryBranchPolicies: {
...defaultState.repositoryBranchPolicies,
...initialState.repositoryBranchPolicies,
},
repositoryBranches: { ...defaultState.repositoryBranches, ...initialState.repositoryBranches },
repositoryScripts: { ...defaultState.repositoryScripts, ...initialState.repositoryScripts },
executors: { ...defaultState.executors, ...initialState.executors },
settingsAgents: { ...defaultState.settingsAgents, ...initialState.settingsAgents },
agentDiscovery: { ...defaultState.agentDiscovery, ...initialState.agentDiscovery },
availableAgents: { ...defaultState.availableAgents, ...initialState.availableAgents },
agentProfiles: { ...defaultState.agentProfiles, ...initialState.agentProfiles },
editors: { ...defaultState.editors, ...initialState.editors },
prompts: { ...defaultState.prompts, ...initialState.prompts },
secrets: { ...defaultState.secrets, ...initialState.secrets },
notificationProviders: {
...defaultState.notificationProviders,
...initialState.notificationProviders,
},
settingsData: { ...defaultState.settingsData, ...initialState.settingsData },
sleepInhibition: { ...defaultState.sleepInhibition, ...initialState.sleepInhibition },
userSettings: { ...defaultState.userSettings, ...initialState.userSettings },
agentProfileRecentUse: mergeAgentProfileRecentUseState(
defaultState.agentProfileRecentUse,
initialState.agentProfileRecentUse ?? {},
),
messages: { ...defaultState.messages, ...initialState.messages },
messagePrompts: mergePromptHistoryState(initialState),
turns: mergeTurnsState(defaultState.turns, initialState.turns, initialState.taskSessions),
...mergeTaskSessionState(initialState),
sessionAgentctl: { ...defaultState.sessionAgentctl, ...initialState.sessionAgentctl },
...mergeWorktreeState(initialState),
pendingModel: { ...defaultState.pendingModel, ...initialState.pendingModel },
activeModel: { ...defaultState.activeModel, ...initialState.activeModel },
taskPlans: { ...defaultState.taskPlans, ...initialState.taskPlans },
...mergeAgentReviewArtifacts(initialState),
queue: { ...defaultState.queue, ...initialState.queue },
terminal: { ...defaultState.terminal, ...initialState.terminal },
shell: { ...defaultState.shell, ...initialState.shell },
processes: { ...defaultState.processes, ...initialState.processes },
gitStatus: { ...defaultState.gitStatus, ...initialState.gitStatus },
sessionCommits: { ...defaultState.sessionCommits, ...initialState.sessionCommits },
gitCheckoutGeneration: {
...defaultState.gitCheckoutGeneration,
...initialState.gitCheckoutGeneration,
},
contextWindow: { ...defaultState.contextWindow, ...initialState.contextWindow },
agents: { ...defaultState.agents, ...initialState.agents },
sessionMode: { ...defaultState.sessionMode, ...initialState.sessionMode },
userShells: { ...defaultState.userShells, ...initialState.userShells },
prepareProgress: { ...defaultState.prepareProgress, ...initialState.prepareProgress },
sessionTodos: { ...defaultState.sessionTodos, ...initialState.sessionTodos },
agentCapabilities: { ...defaultState.agentCapabilities, ...initialState.agentCapabilities },
sessionModels: { ...defaultState.sessionModels, ...initialState.sessionModels },
sessionMcpStatus: { ...defaultState.sessionMcpStatus, ...initialState.sessionMcpStatus },
promptUsage: { ...defaultState.promptUsage, ...initialState.promptUsage },
sessionPollMode: { ...defaultState.sessionPollMode, ...initialState.sessionPollMode },
embeddedVscodeSupport: {
...defaultState.embeddedVscodeSupport,
...initialState.embeddedVscodeSupport,
},
...mergeGitHubState(initialState),
taskIssues: { ...defaultState.taskIssues, ...initialState.taskIssues },
pendingPrUrlByTaskId: {
...defaultState.pendingPrUrlByTaskId,
...initialState.pendingPrUrlByTaskId,
},
prWatches: { ...defaultState.prWatches, ...initialState.prWatches },
reviewWatches: { ...defaultState.reviewWatches, ...initialState.reviewWatches },
issueWatches: { ...defaultState.issueWatches, ...initialState.issueWatches },
actionPresets: { ...defaultState.actionPresets, ...initialState.actionPresets },
prFeedbackCache: { ...defaultState.prFeedbackCache, ...initialState.prFeedbackCache },
taskCIAutomation: { ...defaultState.taskCIAutomation, ...initialState.taskCIAutomation },
taskMRAutomation: { ...defaultState.taskMRAutomation, ...initialState.taskMRAutomation },
...mergeCodeHostFields(defaultState, initialState),
jiraIssueWatches: { ...defaultState.jiraIssueWatches, ...initialState.jiraIssueWatches },
linearIssueWatches: {
...defaultState.linearIssueWatches,
...initialState.linearIssueWatches,
},
office: { ...defaultState.office, ...initialState.office },
needsYouInbox: { ...defaultState.needsYouInbox, ...initialState.needsYouInbox },
failedInbox: {
...defaultState.failedInbox,
...initialState.failedInbox,
readRevisionByWorkspaceId: {
...defaultState.failedInbox.readRevisionByWorkspaceId,
...initialState.failedInbox?.readRevisionByWorkspaceId,
},
},
features: { ...defaultState.features, ...initialState.features },
auth: { ...defaultState.auth, ...initialState.auth },
...mergeSessionHostnamesState(initialState),
automations: { ...defaultState.automations, ...initialState.automations },
automationRuns: { ...defaultState.automationRuns, ...initialState.automationRuns },
system: { ...defaultState.system, ...initialState.system },
...mergeUIPanelState(initialState),
};
}
/** Merge the worktree state fields (worktrees, session worktrees) from hydration state over defaults. */
function mergeWorktreeState(initialState: HydrationState) {
return {
worktrees: { ...defaultState.worktrees, ...initialState.worktrees },
sessionWorktreesBySessionId: {
...defaultState.sessionWorktreesBySessionId,
...initialState.sessionWorktreesBySessionId,
},
};
}
/** Merge the session-hostname resolution fields from hydration state over defaults. */
function mergeSessionHostnamesState(initialState: HydrationState) {
return {
sessionHostnames: { ...defaultState.sessionHostnames, ...initialState.sessionHostnames },
sessionHostnamesEpoch: initialState.sessionHostnamesEpoch ?? defaultState.sessionHostnamesEpoch,
};
}
function mergeWorkspaceContextGeneration(initialState: HydrationState) {
return initialState.workspaceContextGeneration ?? defaultState.workspaceContextGeneration;
}
// Split out of mergeInitialState to stay under the per-function line limit —
// these fields are the UI/panel slice of the merge and have no cross-field
// dependencies on the rest of DefaultState.
/** Merge the UI/panel slice fields (panels, quick chat, sidebar views/task prefs, review selection, notification) from hydration state over defaults. */
function mergeUIPanelState(initialState: HydrationState) {
return {
previewPanel: { ...defaultState.previewPanel, ...initialState.previewPanel },
rightPanel: { ...defaultState.rightPanel, ...initialState.rightPanel },
diffs: { ...defaultState.diffs, ...initialState.diffs },
connection: { ...defaultState.connection, ...initialState.connection },
mobileKanban: { ...defaultState.mobileKanban, ...initialState.mobileKanban },
mobileSession: { ...defaultState.mobileSession, ...initialState.mobileSession },
chatInput: { ...defaultState.chatInput, ...initialState.chatInput },
reviewPRSelection: mergeReviewPRSelectionState(initialState),
documentPanel: { ...defaultState.documentPanel, ...initialState.documentPanel },
systemHealth: { ...defaultState.systemHealth, ...initialState.systemHealth },
quickChat: mergeQuickChatState(initialState),
sessionFailureNotification: mergeSessionFailureNotification(initialState),
bottomTerminal: { ...defaultState.bottomTerminal, ...initialState.bottomTerminal },
sidebarViews: mergeSidebarViewState(initialState),
sidebarViewsByWorkspace: mapSidebarWorkspaces(
initialState.userSettings?.sidebarViewsByWorkspace,
initialState.sidebarViewsByWorkspace,
initialState.userSettings?.revision,
),
threadViews: mergeThreadViewState(initialState),
sidebarTaskPrefs: mergeSidebarTaskPrefsState(initialState),
collapsedSubtaskParents:
initialState.collapsedSubtaskParents ?? defaultState.collapsedSubtaskParents,
};
}