2025-12-07 19:32:53 +02:00
|
|
|
import { create } from 'zustand';
|
|
|
|
|
import { devtools, persist, createJSONStorage } from 'zustand/middleware';
|
|
|
|
|
import type { SidebarSection } from '@/constants/sidebar';
|
|
|
|
|
import { getSafeStorage } from './utils/safeStorage';
|
2025-12-16 13:14:28 +02:00
|
|
|
import { SEMANTIC_TYPOGRAPHY, getTypographyVariable, type SemanticTypographyKey } from '@/lib/typography';
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-01-25 15:52:02 +02:00
|
|
|
export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files';
|
2026-02-16 14:15:19 +02:00
|
|
|
export type RightSidebarTab = 'git' | 'files';
|
2026-02-18 19:52:18 -03:00
|
|
|
export type ContextPanelMode = 'diff' | 'file' | 'context' | 'plan';
|
2026-02-16 14:15:19 +02:00
|
|
|
|
|
|
|
|
type ContextPanelDirectoryState = {
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
expanded: boolean;
|
|
|
|
|
mode: ContextPanelMode | null;
|
|
|
|
|
targetPath: string | null;
|
|
|
|
|
width: number;
|
|
|
|
|
touchedAt: number;
|
|
|
|
|
};
|
2026-01-18 17:29:22 +02:00
|
|
|
|
|
|
|
|
export type MainTabGuard = (nextTab: MainTab) => boolean;
|
2025-12-07 19:32:53 +02:00
|
|
|
export type EventStreamStatus =
|
|
|
|
|
| 'idle'
|
|
|
|
|
| 'connecting'
|
|
|
|
|
| 'connected'
|
|
|
|
|
| 'reconnecting'
|
|
|
|
|
| 'paused'
|
|
|
|
|
| 'offline'
|
|
|
|
|
| 'error';
|
|
|
|
|
|
2026-02-08 17:58:29 -08:00
|
|
|
const LEGACY_DEFAULT_NOTIFICATION_TEMPLATES = {
|
|
|
|
|
completion: { title: '{agent_name} is ready', message: '{last_message}' },
|
|
|
|
|
error: { title: 'Tool error', message: '{last_message}' },
|
|
|
|
|
question: { title: '{agent_name} needs input', message: '{last_message}' },
|
|
|
|
|
subtask: { title: 'Subtask complete', message: '{last_message}' },
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const EMPTY_NOTIFICATION_TEMPLATES = {
|
|
|
|
|
completion: { title: '', message: '' },
|
|
|
|
|
error: { title: '', message: '' },
|
|
|
|
|
question: { title: '', message: '' },
|
|
|
|
|
subtask: { title: '', message: '' },
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const isSameTemplateValue = (
|
|
|
|
|
a: { title: string; message: string } | undefined,
|
|
|
|
|
b: { title: string; message: string }
|
|
|
|
|
) => {
|
|
|
|
|
if (!a) return false;
|
|
|
|
|
return a.title === b.title && a.message === b.message;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isLegacyDefaultTemplates = (value: unknown): boolean => {
|
|
|
|
|
if (!value || typeof value !== 'object') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const candidate = value as Record<string, { title: string; message: string } | undefined>;
|
|
|
|
|
return (
|
|
|
|
|
isSameTemplateValue(candidate.completion, LEGACY_DEFAULT_NOTIFICATION_TEMPLATES.completion)
|
|
|
|
|
&& isSameTemplateValue(candidate.error, LEGACY_DEFAULT_NOTIFICATION_TEMPLATES.error)
|
|
|
|
|
&& isSameTemplateValue(candidate.question, LEGACY_DEFAULT_NOTIFICATION_TEMPLATES.question)
|
|
|
|
|
&& isSameTemplateValue(candidate.subtask, LEGACY_DEFAULT_NOTIFICATION_TEMPLATES.subtask)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
const CONTEXT_PANEL_DEFAULT_WIDTH = 600;
|
|
|
|
|
const CONTEXT_PANEL_MIN_WIDTH = 360;
|
|
|
|
|
const CONTEXT_PANEL_MAX_WIDTH = 1400;
|
|
|
|
|
const LEFT_SIDEBAR_MIN_WIDTH = 300;
|
|
|
|
|
const RIGHT_SIDEBAR_MIN_WIDTH = 400;
|
|
|
|
|
|
|
|
|
|
const normalizeDirectoryPath = (value: string): string => {
|
|
|
|
|
if (!value) return '';
|
|
|
|
|
|
|
|
|
|
const raw = value.replace(/\\/g, '/');
|
|
|
|
|
const hadUncPrefix = raw.startsWith('//');
|
|
|
|
|
let normalized = raw.replace(/\/+$/g, '');
|
|
|
|
|
normalized = normalized.replace(/\/+/g, '/');
|
|
|
|
|
|
|
|
|
|
if (hadUncPrefix && !normalized.startsWith('//')) {
|
|
|
|
|
normalized = `/${normalized}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (normalized === '') {
|
|
|
|
|
return raw.startsWith('/') ? '/' : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return normalized;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const clampContextPanelWidth = (width: number): number => {
|
|
|
|
|
if (!Number.isFinite(width)) {
|
|
|
|
|
return CONTEXT_PANEL_DEFAULT_WIDTH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Math.min(CONTEXT_PANEL_MAX_WIDTH, Math.max(CONTEXT_PANEL_MIN_WIDTH, Math.round(width)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const touchContextPanelState = (prev?: ContextPanelDirectoryState): ContextPanelDirectoryState => {
|
|
|
|
|
if (prev) {
|
|
|
|
|
return { ...prev, touchedAt: Date.now() };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
isOpen: false,
|
|
|
|
|
expanded: false,
|
|
|
|
|
mode: null,
|
|
|
|
|
targetPath: null,
|
|
|
|
|
width: CONTEXT_PANEL_DEFAULT_WIDTH,
|
|
|
|
|
touchedAt: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const clampContextPanelRoots = (
|
|
|
|
|
byDirectory: Record<string, ContextPanelDirectoryState>,
|
|
|
|
|
maxRoots: number
|
|
|
|
|
): Record<string, ContextPanelDirectoryState> => {
|
|
|
|
|
const entries = Object.entries(byDirectory);
|
|
|
|
|
if (entries.length <= maxRoots) {
|
|
|
|
|
return byDirectory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entries.sort((a, b) => (b[1]?.touchedAt ?? 0) - (a[1]?.touchedAt ?? 0));
|
|
|
|
|
const next: Record<string, ContextPanelDirectoryState> = {};
|
|
|
|
|
for (const [directory, state] of entries.slice(0, maxRoots)) {
|
|
|
|
|
next[directory] = state;
|
|
|
|
|
}
|
|
|
|
|
return next;
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
interface UIStore {
|
|
|
|
|
|
|
|
|
|
theme: 'light' | 'dark' | 'system';
|
2026-01-01 14:59:51 +02:00
|
|
|
isMultiRunLauncherOpen: boolean;
|
|
|
|
|
multiRunLauncherPrefillPrompt: string;
|
2025-12-07 19:32:53 +02:00
|
|
|
isSidebarOpen: boolean;
|
|
|
|
|
sidebarWidth: number;
|
|
|
|
|
hasManuallyResizedLeftSidebar: boolean;
|
2026-02-09 03:13:34 +02:00
|
|
|
isRightSidebarOpen: boolean;
|
|
|
|
|
rightSidebarWidth: number;
|
|
|
|
|
hasManuallyResizedRightSidebar: boolean;
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarTab: RightSidebarTab;
|
|
|
|
|
contextPanelByDirectory: Record<string, ContextPanelDirectoryState>;
|
2026-02-09 03:13:34 +02:00
|
|
|
isBottomTerminalOpen: boolean;
|
2026-02-16 14:15:19 +02:00
|
|
|
isBottomTerminalExpanded: boolean;
|
2026-02-09 03:13:34 +02:00
|
|
|
bottomTerminalHeight: number;
|
|
|
|
|
hasManuallyResizedBottomTerminal: boolean;
|
2025-12-07 19:32:53 +02:00
|
|
|
isSessionSwitcherOpen: boolean;
|
|
|
|
|
activeMainTab: MainTab;
|
2026-01-18 17:29:22 +02:00
|
|
|
mainTabGuard: MainTabGuard | null;
|
2026-02-01 18:29:34 +02:00
|
|
|
sidebarOpenBeforeFullscreenTab: boolean | null;
|
2025-12-07 19:32:53 +02:00
|
|
|
pendingDiffFile: string | null;
|
|
|
|
|
isMobile: boolean;
|
2026-01-01 01:01:46 -08:00
|
|
|
isKeyboardOpen: boolean;
|
2025-12-07 19:32:53 +02:00
|
|
|
isCommandPaletteOpen: boolean;
|
|
|
|
|
isHelpDialogOpen: boolean;
|
2025-12-19 18:59:08 +02:00
|
|
|
isAboutDialogOpen: boolean;
|
2026-02-05 01:59:49 +02:00
|
|
|
isOpenCodeStatusDialogOpen: boolean;
|
|
|
|
|
openCodeStatusText: string;
|
2025-12-07 19:32:53 +02:00
|
|
|
isSessionCreateDialogOpen: boolean;
|
|
|
|
|
isSettingsDialogOpen: boolean;
|
2026-01-02 13:08:14 +02:00
|
|
|
isModelSelectorOpen: boolean;
|
2025-12-07 19:32:53 +02:00
|
|
|
sidebarSection: SidebarSection;
|
|
|
|
|
eventStreamStatus: EventStreamStatus;
|
|
|
|
|
eventStreamHint: string | null;
|
|
|
|
|
showReasoningTraces: boolean;
|
2026-01-26 01:09:31 +08:00
|
|
|
showTextJustificationActivity: boolean;
|
2025-12-20 02:06:00 +02:00
|
|
|
autoDeleteEnabled: boolean;
|
|
|
|
|
autoDeleteAfterDays: number;
|
|
|
|
|
autoDeleteLastRunAt: number | null;
|
2026-02-12 20:36:02 +02:00
|
|
|
messageLimit: number;
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
|
|
|
|
fontSize: number;
|
2026-02-04 16:32:25 +02:00
|
|
|
terminalFontSize: number;
|
2025-12-15 18:11:38 +02:00
|
|
|
padding: number;
|
2026-01-18 18:28:49 +06:00
|
|
|
cornerRadius: number;
|
2026-01-01 01:01:46 -08:00
|
|
|
inputBarOffset: number;
|
2025-12-15 18:11:38 +02:00
|
|
|
|
|
|
|
|
favoriteModels: Array<{ providerID: string; modelID: string }>;
|
|
|
|
|
recentModels: Array<{ providerID: string; modelID: string }>;
|
2026-01-30 06:13:37 -03:00
|
|
|
recentAgents: string[];
|
|
|
|
|
recentEfforts: Record<string, string[]>;
|
2025-12-15 18:11:38 +02:00
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
diffLayoutPreference: 'dynamic' | 'inline' | 'side-by-side';
|
|
|
|
|
diffFileLayout: Record<string, 'inline' | 'side-by-side'>;
|
2025-12-14 02:29:46 +02:00
|
|
|
diffWrapLines: boolean;
|
2026-01-14 10:15:16 -03:00
|
|
|
diffViewMode: 'single' | 'stacked';
|
2026-01-02 18:33:35 -05:00
|
|
|
isTimelineDialogOpen: boolean;
|
2026-01-30 02:46:43 +02:00
|
|
|
isImagePreviewOpen: boolean;
|
2026-01-11 21:21:13 +08:00
|
|
|
nativeNotificationsEnabled: boolean;
|
2026-01-14 01:03:26 +02:00
|
|
|
notificationMode: 'always' | 'hidden-only';
|
2026-01-30 20:15:00 +08:00
|
|
|
notifyOnSubtasks: boolean;
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-02-08 17:58:29 -08:00
|
|
|
// Event toggles (which events trigger notifications)
|
|
|
|
|
notifyOnCompletion: boolean;
|
|
|
|
|
notifyOnError: boolean;
|
|
|
|
|
notifyOnQuestion: boolean;
|
|
|
|
|
|
|
|
|
|
// Per-event notification templates
|
|
|
|
|
notificationTemplates: {
|
|
|
|
|
completion: { title: string; message: string };
|
|
|
|
|
error: { title: string; message: string };
|
|
|
|
|
question: { title: string; message: string };
|
|
|
|
|
subtask: { title: string; message: string };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Summarization settings
|
|
|
|
|
summarizeLastMessage: boolean;
|
|
|
|
|
summaryThreshold: number; // chars — messages longer than this get summarized
|
|
|
|
|
summaryLength: number; // chars — target length for summary
|
|
|
|
|
maxLastMessageLength: number; // chars — truncate {last_message} when summarization is off
|
|
|
|
|
|
2026-01-30 14:48:53 +02:00
|
|
|
showTerminalQuickKeysOnDesktop: boolean;
|
2026-02-06 01:14:09 -08:00
|
|
|
persistChatDraft: boolean;
|
2026-02-07 09:11:55 +08:00
|
|
|
isMobileSessionStatusBarCollapsed: boolean;
|
2026-01-30 14:48:53 +02:00
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setTheme: (theme: 'light' | 'dark' | 'system') => void;
|
|
|
|
|
toggleSidebar: () => void;
|
|
|
|
|
setSidebarOpen: (open: boolean) => void;
|
|
|
|
|
setSidebarWidth: (width: number) => void;
|
2026-02-09 03:13:34 +02:00
|
|
|
toggleRightSidebar: () => void;
|
|
|
|
|
setRightSidebarOpen: (open: boolean) => void;
|
|
|
|
|
setRightSidebarWidth: (width: number) => void;
|
2026-02-16 14:15:19 +02:00
|
|
|
setRightSidebarTab: (tab: RightSidebarTab) => void;
|
|
|
|
|
openContextDiff: (directory: string, filePath: string) => void;
|
|
|
|
|
openContextFile: (directory: string, filePath: string) => void;
|
2026-02-17 18:25:03 +02:00
|
|
|
openContextOverview: (directory: string) => void;
|
2026-02-18 19:52:18 -03:00
|
|
|
openContextPlan: (directory: string) => void;
|
2026-02-16 14:15:19 +02:00
|
|
|
closeContextPanel: (directory: string) => void;
|
|
|
|
|
toggleContextPanelExpanded: (directory: string) => void;
|
|
|
|
|
setContextPanelWidth: (directory: string, width: number) => void;
|
2026-02-09 03:13:34 +02:00
|
|
|
toggleBottomTerminal: () => void;
|
|
|
|
|
setBottomTerminalOpen: (open: boolean) => void;
|
2026-02-16 14:15:19 +02:00
|
|
|
setBottomTerminalExpanded: (expanded: boolean) => void;
|
2026-02-09 03:13:34 +02:00
|
|
|
setBottomTerminalHeight: (height: number) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
setSessionSwitcherOpen: (open: boolean) => void;
|
|
|
|
|
setActiveMainTab: (tab: MainTab) => void;
|
2026-01-18 17:29:22 +02:00
|
|
|
setMainTabGuard: (guard: MainTabGuard | null) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
setPendingDiffFile: (filePath: string | null) => void;
|
|
|
|
|
navigateToDiff: (filePath: string) => void;
|
|
|
|
|
consumePendingDiffFile: () => string | null;
|
|
|
|
|
setIsMobile: (isMobile: boolean) => void;
|
|
|
|
|
toggleCommandPalette: () => void;
|
|
|
|
|
setCommandPaletteOpen: (open: boolean) => void;
|
|
|
|
|
toggleHelpDialog: () => void;
|
|
|
|
|
setHelpDialogOpen: (open: boolean) => void;
|
2025-12-19 18:59:08 +02:00
|
|
|
setAboutDialogOpen: (open: boolean) => void;
|
2026-02-05 01:59:49 +02:00
|
|
|
setOpenCodeStatusDialogOpen: (open: boolean) => void;
|
|
|
|
|
setOpenCodeStatusText: (text: string) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
setSessionCreateDialogOpen: (open: boolean) => void;
|
|
|
|
|
setSettingsDialogOpen: (open: boolean) => void;
|
2026-01-02 13:08:14 +02:00
|
|
|
setModelSelectorOpen: (open: boolean) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
applyTheme: () => void;
|
|
|
|
|
setSidebarSection: (section: SidebarSection) => void;
|
|
|
|
|
setEventStreamStatus: (status: EventStreamStatus, hint?: string | null) => void;
|
|
|
|
|
setShowReasoningTraces: (value: boolean) => void;
|
2026-01-26 01:09:31 +08:00
|
|
|
setShowTextJustificationActivity: (value: boolean) => void;
|
2025-12-20 02:06:00 +02:00
|
|
|
setAutoDeleteEnabled: (value: boolean) => void;
|
|
|
|
|
setAutoDeleteAfterDays: (days: number) => void;
|
|
|
|
|
setAutoDeleteLastRunAt: (timestamp: number | null) => void;
|
2026-02-12 20:36:02 +02:00
|
|
|
setMessageLimit: (value: number) => void;
|
2025-12-15 18:11:38 +02:00
|
|
|
setToolCallExpansion: (value: 'collapsed' | 'activity' | 'detailed') => void;
|
|
|
|
|
setFontSize: (size: number) => void;
|
2026-02-04 16:32:25 +02:00
|
|
|
setTerminalFontSize: (size: number) => void;
|
2025-12-15 18:11:38 +02:00
|
|
|
setPadding: (size: number) => void;
|
2026-01-18 18:28:49 +06:00
|
|
|
setCornerRadius: (radius: number) => void;
|
2026-01-01 01:01:46 -08:00
|
|
|
setInputBarOffset: (offset: number) => void;
|
|
|
|
|
setKeyboardOpen: (open: boolean) => void;
|
2025-12-15 18:11:38 +02:00
|
|
|
applyTypography: () => void;
|
|
|
|
|
applyPadding: () => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
updateProportionalSidebarWidths: () => void;
|
2025-12-15 18:11:38 +02:00
|
|
|
toggleFavoriteModel: (providerID: string, modelID: string) => void;
|
|
|
|
|
isFavoriteModel: (providerID: string, modelID: string) => boolean;
|
|
|
|
|
addRecentModel: (providerID: string, modelID: string) => void;
|
2026-01-30 06:13:37 -03:00
|
|
|
addRecentAgent: (agentName: string) => void;
|
|
|
|
|
addRecentEffort: (providerID: string, modelID: string, variant: string | undefined) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
setDiffLayoutPreference: (mode: 'dynamic' | 'inline' | 'side-by-side') => void;
|
|
|
|
|
setDiffFileLayout: (filePath: string, mode: 'inline' | 'side-by-side') => void;
|
2025-12-14 02:29:46 +02:00
|
|
|
setDiffWrapLines: (wrap: boolean) => void;
|
2026-01-14 10:15:16 -03:00
|
|
|
setDiffViewMode: (mode: 'single' | 'stacked') => void;
|
2026-01-01 14:59:51 +02:00
|
|
|
setMultiRunLauncherOpen: (open: boolean) => void;
|
2026-01-02 18:33:35 -05:00
|
|
|
setTimelineDialogOpen: (open: boolean) => void;
|
2026-01-30 02:46:43 +02:00
|
|
|
setImagePreviewOpen: (open: boolean) => void;
|
2026-01-11 21:21:13 +08:00
|
|
|
setNativeNotificationsEnabled: (value: boolean) => void;
|
2026-01-14 01:03:26 +02:00
|
|
|
setNotificationMode: (mode: 'always' | 'hidden-only') => void;
|
2026-01-30 14:48:53 +02:00
|
|
|
setShowTerminalQuickKeysOnDesktop: (value: boolean) => void;
|
2026-01-30 20:15:00 +08:00
|
|
|
setNotifyOnSubtasks: (value: boolean) => void;
|
2026-02-08 17:58:29 -08:00
|
|
|
setNotifyOnCompletion: (value: boolean) => void;
|
|
|
|
|
setNotifyOnError: (value: boolean) => void;
|
|
|
|
|
setNotifyOnQuestion: (value: boolean) => void;
|
|
|
|
|
setNotificationTemplates: (templates: UIStore['notificationTemplates']) => void;
|
|
|
|
|
setSummarizeLastMessage: (value: boolean) => void;
|
|
|
|
|
setSummaryThreshold: (value: number) => void;
|
|
|
|
|
setSummaryLength: (value: number) => void;
|
|
|
|
|
setMaxLastMessageLength: (value: number) => void;
|
2026-02-06 01:14:09 -08:00
|
|
|
setPersistChatDraft: (value: boolean) => void;
|
2026-02-07 09:11:55 +08:00
|
|
|
setIsMobileSessionStatusBarCollapsed: (value: boolean) => void;
|
2026-01-01 14:59:51 +02:00
|
|
|
openMultiRunLauncher: () => void;
|
|
|
|
|
openMultiRunLauncherWithPrompt: (prompt: string) => void;
|
2025-12-07 19:32:53 +02:00
|
|
|
}
|
|
|
|
|
|
2026-02-05 01:59:49 +02:00
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
export const useUIStore = create<UIStore>()(
|
|
|
|
|
devtools(
|
|
|
|
|
persist(
|
|
|
|
|
(set, get) => ({
|
|
|
|
|
|
|
|
|
|
theme: 'system',
|
2026-01-01 14:59:51 +02:00
|
|
|
isMultiRunLauncherOpen: false,
|
|
|
|
|
multiRunLauncherPrefillPrompt: '',
|
2025-12-07 19:32:53 +02:00
|
|
|
isSidebarOpen: true,
|
2026-02-16 14:15:19 +02:00
|
|
|
sidebarWidth: LEFT_SIDEBAR_MIN_WIDTH,
|
2025-12-07 19:32:53 +02:00
|
|
|
hasManuallyResizedLeftSidebar: false,
|
2026-02-09 03:13:34 +02:00
|
|
|
isRightSidebarOpen: false,
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarWidth: RIGHT_SIDEBAR_MIN_WIDTH,
|
2026-02-09 03:13:34 +02:00
|
|
|
hasManuallyResizedRightSidebar: false,
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarTab: 'git',
|
|
|
|
|
contextPanelByDirectory: {},
|
2026-02-09 03:13:34 +02:00
|
|
|
isBottomTerminalOpen: false,
|
2026-02-16 14:15:19 +02:00
|
|
|
isBottomTerminalExpanded: false,
|
2026-02-09 03:13:34 +02:00
|
|
|
bottomTerminalHeight: 300,
|
|
|
|
|
hasManuallyResizedBottomTerminal: false,
|
2025-12-07 19:32:53 +02:00
|
|
|
isSessionSwitcherOpen: false,
|
|
|
|
|
activeMainTab: 'chat',
|
2026-01-18 17:29:22 +02:00
|
|
|
mainTabGuard: null,
|
2026-02-01 18:29:34 +02:00
|
|
|
sidebarOpenBeforeFullscreenTab: null,
|
2025-12-07 19:32:53 +02:00
|
|
|
pendingDiffFile: null,
|
|
|
|
|
isMobile: false,
|
2026-01-01 01:01:46 -08:00
|
|
|
isKeyboardOpen: false,
|
2025-12-07 19:32:53 +02:00
|
|
|
isCommandPaletteOpen: false,
|
|
|
|
|
isHelpDialogOpen: false,
|
2025-12-19 18:59:08 +02:00
|
|
|
isAboutDialogOpen: false,
|
2026-02-05 01:59:49 +02:00
|
|
|
isOpenCodeStatusDialogOpen: false,
|
|
|
|
|
openCodeStatusText: '',
|
2025-12-07 19:32:53 +02:00
|
|
|
isSessionCreateDialogOpen: false,
|
|
|
|
|
isSettingsDialogOpen: false,
|
2026-01-02 13:08:14 +02:00
|
|
|
isModelSelectorOpen: false,
|
2025-12-07 19:32:53 +02:00
|
|
|
sidebarSection: 'sessions',
|
|
|
|
|
eventStreamStatus: 'idle',
|
|
|
|
|
eventStreamHint: null,
|
2026-01-26 00:24:40 +02:00
|
|
|
showReasoningTraces: true,
|
2026-01-26 01:09:31 +08:00
|
|
|
showTextJustificationActivity: false,
|
2025-12-20 02:06:00 +02:00
|
|
|
autoDeleteEnabled: false,
|
|
|
|
|
autoDeleteAfterDays: 30,
|
|
|
|
|
autoDeleteLastRunAt: null,
|
2026-02-12 20:36:02 +02:00
|
|
|
messageLimit: 200,
|
2025-12-15 18:11:38 +02:00
|
|
|
toolCallExpansion: 'collapsed',
|
|
|
|
|
fontSize: 100,
|
2026-02-04 16:32:25 +02:00
|
|
|
terminalFontSize: 13,
|
2025-12-15 18:11:38 +02:00
|
|
|
padding: 100,
|
2026-01-18 18:28:49 +06:00
|
|
|
cornerRadius: 12,
|
2026-01-01 01:01:46 -08:00
|
|
|
inputBarOffset: 0,
|
2025-12-15 18:11:38 +02:00
|
|
|
favoriteModels: [],
|
|
|
|
|
recentModels: [],
|
2026-01-30 06:13:37 -03:00
|
|
|
recentAgents: [],
|
|
|
|
|
recentEfforts: {},
|
2026-01-26 00:24:40 +02:00
|
|
|
diffLayoutPreference: 'inline',
|
2025-12-07 19:32:53 +02:00
|
|
|
diffFileLayout: {},
|
2025-12-14 02:29:46 +02:00
|
|
|
diffWrapLines: false,
|
2026-01-26 00:24:40 +02:00
|
|
|
diffViewMode: 'stacked',
|
2026-01-02 18:33:35 -05:00
|
|
|
isTimelineDialogOpen: false,
|
2026-01-30 02:46:43 +02:00
|
|
|
isImagePreviewOpen: false,
|
2026-01-11 21:21:13 +08:00
|
|
|
nativeNotificationsEnabled: false,
|
2026-01-14 01:03:26 +02:00
|
|
|
notificationMode: 'hidden-only',
|
2026-01-30 20:15:00 +08:00
|
|
|
notifyOnSubtasks: true,
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-02-08 17:58:29 -08:00
|
|
|
// Event toggles (which events trigger notifications)
|
|
|
|
|
notifyOnCompletion: true,
|
|
|
|
|
notifyOnError: true,
|
|
|
|
|
notifyOnQuestion: true,
|
|
|
|
|
notificationTemplates: {
|
|
|
|
|
completion: { ...EMPTY_NOTIFICATION_TEMPLATES.completion },
|
|
|
|
|
error: { ...EMPTY_NOTIFICATION_TEMPLATES.error },
|
|
|
|
|
question: { ...EMPTY_NOTIFICATION_TEMPLATES.question },
|
|
|
|
|
subtask: { ...EMPTY_NOTIFICATION_TEMPLATES.subtask },
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Summarization settings
|
|
|
|
|
summarizeLastMessage: false,
|
|
|
|
|
summaryThreshold: 200,
|
|
|
|
|
summaryLength: 100,
|
|
|
|
|
maxLastMessageLength: 250,
|
|
|
|
|
|
2026-01-30 14:48:53 +02:00
|
|
|
showTerminalQuickKeysOnDesktop: false,
|
2026-02-06 01:14:09 -08:00
|
|
|
persistChatDraft: true,
|
2026-02-07 09:11:55 +08:00
|
|
|
isMobileSessionStatusBarCollapsed: false,
|
2026-01-30 14:48:53 +02:00
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setTheme: (theme) => {
|
|
|
|
|
set({ theme });
|
|
|
|
|
get().applyTheme();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleSidebar: () => {
|
|
|
|
|
set((state) => {
|
|
|
|
|
const newOpen = !state.isSidebarOpen;
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
if (newOpen && !state.hasManuallyResizedLeftSidebar) {
|
2025-12-07 19:32:53 +02:00
|
|
|
return {
|
|
|
|
|
isSidebarOpen: newOpen,
|
2026-02-16 14:15:19 +02:00
|
|
|
sidebarWidth: LEFT_SIDEBAR_MIN_WIDTH,
|
2025-12-07 19:32:53 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isSidebarOpen: newOpen };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setSidebarOpen: (open) => {
|
2026-02-16 14:15:19 +02:00
|
|
|
set((state) => {
|
2026-02-17 18:25:03 +02:00
|
|
|
if (state.isSidebarOpen === open) {
|
|
|
|
|
if (!open) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
if (!state.hasManuallyResizedLeftSidebar && state.sidebarWidth !== LEFT_SIDEBAR_MIN_WIDTH) {
|
|
|
|
|
return {
|
|
|
|
|
isSidebarOpen: open,
|
|
|
|
|
sidebarWidth: LEFT_SIDEBAR_MIN_WIDTH,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return state;
|
|
|
|
|
}
|
2026-02-16 14:15:19 +02:00
|
|
|
if (open && !state.hasManuallyResizedLeftSidebar) {
|
2025-12-07 19:32:53 +02:00
|
|
|
return {
|
|
|
|
|
isSidebarOpen: open,
|
2026-02-16 14:15:19 +02:00
|
|
|
sidebarWidth: LEFT_SIDEBAR_MIN_WIDTH,
|
2025-12-07 19:32:53 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isSidebarOpen: open };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setSidebarWidth: (width) => {
|
|
|
|
|
set({ sidebarWidth: width, hasManuallyResizedLeftSidebar: true });
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-09 03:13:34 +02:00
|
|
|
toggleRightSidebar: () => {
|
|
|
|
|
set((state) => {
|
|
|
|
|
const newOpen = !state.isRightSidebarOpen;
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
if (newOpen && !state.hasManuallyResizedRightSidebar) {
|
2026-02-09 03:13:34 +02:00
|
|
|
return {
|
|
|
|
|
isRightSidebarOpen: newOpen,
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarWidth: RIGHT_SIDEBAR_MIN_WIDTH,
|
2026-02-09 03:13:34 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isRightSidebarOpen: newOpen };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setRightSidebarOpen: (open) => {
|
2026-02-16 14:15:19 +02:00
|
|
|
set((state) => {
|
2026-02-17 18:25:03 +02:00
|
|
|
if (state.isRightSidebarOpen === open) {
|
|
|
|
|
if (!open) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
if (!state.hasManuallyResizedRightSidebar && state.rightSidebarWidth !== RIGHT_SIDEBAR_MIN_WIDTH) {
|
|
|
|
|
return {
|
|
|
|
|
isRightSidebarOpen: open,
|
|
|
|
|
rightSidebarWidth: RIGHT_SIDEBAR_MIN_WIDTH,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return state;
|
|
|
|
|
}
|
2026-02-16 14:15:19 +02:00
|
|
|
if (open && !state.hasManuallyResizedRightSidebar) {
|
2026-02-09 03:13:34 +02:00
|
|
|
return {
|
|
|
|
|
isRightSidebarOpen: open,
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarWidth: RIGHT_SIDEBAR_MIN_WIDTH,
|
2026-02-09 03:13:34 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isRightSidebarOpen: open };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setRightSidebarWidth: (width) => {
|
|
|
|
|
set({ rightSidebarWidth: width, hasManuallyResizedRightSidebar: true });
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
setRightSidebarTab: (tab) => {
|
|
|
|
|
set({ rightSidebarTab: tab });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
openContextDiff: (directory, filePath) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
const normalizedFilePath = (filePath || '').trim();
|
|
|
|
|
if (!normalizedDirectory || !normalizedFilePath) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
isOpen: true,
|
|
|
|
|
mode: 'diff' as const,
|
|
|
|
|
targetPath: normalizedFilePath,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
get().setPendingDiffFile(normalizedFilePath);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
openContextFile: (directory, filePath) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
const normalizedFilePath = (filePath || '').trim();
|
|
|
|
|
if (!normalizedDirectory || !normalizedFilePath) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
isOpen: true,
|
|
|
|
|
mode: 'file' as const,
|
|
|
|
|
targetPath: normalizedFilePath,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-17 18:25:03 +02:00
|
|
|
openContextOverview: (directory) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
if (!normalizedDirectory) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
isOpen: true,
|
|
|
|
|
mode: 'context' as const,
|
|
|
|
|
targetPath: null,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-18 19:52:18 -03:00
|
|
|
openContextPlan: (directory) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
if (!normalizedDirectory) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
isOpen: true,
|
|
|
|
|
mode: 'plan' as const,
|
|
|
|
|
targetPath: null,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
closeContextPanel: (directory) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
if (!normalizedDirectory) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
if (!prev || !prev.isOpen) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...touchContextPanelState(prev),
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleContextPanelExpanded: (directory) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
if (!normalizedDirectory) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
expanded: !current.expanded,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setContextPanelWidth: (directory, width) => {
|
|
|
|
|
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
|
|
|
|
|
if (!normalizedDirectory) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const prev = state.contextPanelByDirectory[normalizedDirectory];
|
|
|
|
|
const current = touchContextPanelState(prev);
|
|
|
|
|
const byDirectory = {
|
|
|
|
|
...state.contextPanelByDirectory,
|
|
|
|
|
[normalizedDirectory]: {
|
|
|
|
|
...current,
|
|
|
|
|
width: clampContextPanelWidth(width),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-09 03:13:34 +02:00
|
|
|
toggleBottomTerminal: () => {
|
|
|
|
|
set((state) => {
|
|
|
|
|
const newOpen = !state.isBottomTerminalOpen;
|
|
|
|
|
|
|
|
|
|
if (newOpen && typeof window !== 'undefined') {
|
|
|
|
|
const proportionalHeight = Math.floor(window.innerHeight * 0.32);
|
|
|
|
|
return {
|
|
|
|
|
isBottomTerminalOpen: newOpen,
|
|
|
|
|
bottomTerminalHeight: proportionalHeight,
|
|
|
|
|
hasManuallyResizedBottomTerminal: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { isBottomTerminalOpen: newOpen };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setBottomTerminalOpen: (open) => {
|
2026-02-17 18:25:03 +02:00
|
|
|
set((state) => {
|
|
|
|
|
if (state.isBottomTerminalOpen === open) {
|
|
|
|
|
if (!open) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
if (!state.hasManuallyResizedBottomTerminal && typeof window !== 'undefined') {
|
|
|
|
|
const proportionalHeight = Math.floor(window.innerHeight * 0.32);
|
|
|
|
|
if (state.bottomTerminalHeight === proportionalHeight && state.hasManuallyResizedBottomTerminal === false) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
isBottomTerminalOpen: open,
|
|
|
|
|
bottomTerminalHeight: proportionalHeight,
|
|
|
|
|
hasManuallyResizedBottomTerminal: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 03:13:34 +02:00
|
|
|
if (open && typeof window !== 'undefined') {
|
|
|
|
|
const proportionalHeight = Math.floor(window.innerHeight * 0.32);
|
|
|
|
|
return {
|
|
|
|
|
isBottomTerminalOpen: open,
|
|
|
|
|
bottomTerminalHeight: proportionalHeight,
|
|
|
|
|
hasManuallyResizedBottomTerminal: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { isBottomTerminalOpen: open };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
setBottomTerminalExpanded: (expanded) => {
|
|
|
|
|
set({ isBottomTerminalExpanded: expanded });
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-09 03:13:34 +02:00
|
|
|
setBottomTerminalHeight: (height) => {
|
|
|
|
|
set({ bottomTerminalHeight: height, hasManuallyResizedBottomTerminal: true });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setSessionSwitcherOpen: (open) => {
|
|
|
|
|
set({ isSessionSwitcherOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-18 17:29:22 +02:00
|
|
|
setMainTabGuard: (guard) => {
|
2026-02-01 21:43:49 +02:00
|
|
|
if (get().mainTabGuard === guard) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-18 17:29:22 +02:00
|
|
|
set({ mainTabGuard: guard });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setActiveMainTab: (tab) => {
|
2026-01-18 17:29:22 +02:00
|
|
|
const guard = get().mainTabGuard;
|
|
|
|
|
if (guard && !guard(tab)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-16 14:15:19 +02:00
|
|
|
set({ activeMainTab: tab });
|
2025-12-07 19:32:53 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setPendingDiffFile: (filePath) => {
|
|
|
|
|
set({ pendingDiffFile: filePath });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
navigateToDiff: (filePath) => {
|
2026-01-18 17:29:22 +02:00
|
|
|
const guard = get().mainTabGuard;
|
|
|
|
|
if (guard && !guard('diff')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-16 14:15:19 +02:00
|
|
|
set({ pendingDiffFile: filePath, activeMainTab: 'diff' });
|
2025-12-07 19:32:53 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
consumePendingDiffFile: () => {
|
|
|
|
|
const { pendingDiffFile } = get();
|
|
|
|
|
if (pendingDiffFile) {
|
|
|
|
|
set({ pendingDiffFile: null });
|
|
|
|
|
}
|
|
|
|
|
return pendingDiffFile;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setIsMobile: (isMobile) => {
|
|
|
|
|
set({ isMobile });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleCommandPalette: () => {
|
|
|
|
|
set((state) => ({ isCommandPaletteOpen: !state.isCommandPaletteOpen }));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setCommandPaletteOpen: (open) => {
|
|
|
|
|
set({ isCommandPaletteOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleHelpDialog: () => {
|
|
|
|
|
set((state) => ({ isHelpDialogOpen: !state.isHelpDialogOpen }));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setHelpDialogOpen: (open) => {
|
|
|
|
|
set({ isHelpDialogOpen: open });
|
2025-12-19 18:59:08 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setAboutDialogOpen: (open) => {
|
|
|
|
|
set({ isAboutDialogOpen: open });
|
2025-12-07 19:32:53 +02:00
|
|
|
},
|
|
|
|
|
|
2026-02-05 01:59:49 +02:00
|
|
|
setOpenCodeStatusDialogOpen: (open) => {
|
|
|
|
|
set({ isOpenCodeStatusDialogOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setOpenCodeStatusText: (text) => {
|
|
|
|
|
set({ openCodeStatusText: text });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setSessionCreateDialogOpen: (open) => {
|
|
|
|
|
set({ isSessionCreateDialogOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setSettingsDialogOpen: (open) => {
|
|
|
|
|
set({ isSettingsDialogOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-02 13:08:14 +02:00
|
|
|
setModelSelectorOpen: (open) => {
|
|
|
|
|
set({ isModelSelectorOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setSidebarSection: (section) => {
|
|
|
|
|
set({ sidebarSection: section });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setEventStreamStatus: (status, hint) => {
|
|
|
|
|
set({
|
|
|
|
|
eventStreamStatus: status,
|
|
|
|
|
eventStreamHint: hint ?? null,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setShowReasoningTraces: (value) => {
|
|
|
|
|
set({ showReasoningTraces: value });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-26 01:09:31 +08:00
|
|
|
setShowTextJustificationActivity: (value) => {
|
|
|
|
|
set({ showTextJustificationActivity: value });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-20 02:06:00 +02:00
|
|
|
setAutoDeleteEnabled: (value) => {
|
|
|
|
|
set({ autoDeleteEnabled: value });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setAutoDeleteAfterDays: (days) => {
|
|
|
|
|
const clampedDays = Math.max(1, Math.min(365, days));
|
|
|
|
|
set({ autoDeleteAfterDays: clampedDays });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setAutoDeleteLastRunAt: (timestamp) => {
|
|
|
|
|
set({ autoDeleteLastRunAt: timestamp });
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-12 20:36:02 +02:00
|
|
|
setMessageLimit: (value) => {
|
2026-01-19 14:56:27 +02:00
|
|
|
const clamped = Math.max(10, Math.min(500, Math.round(value)));
|
2026-02-12 20:36:02 +02:00
|
|
|
set({ messageLimit: clamped });
|
2026-01-19 14:56:27 +02:00
|
|
|
},
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
setToolCallExpansion: (value) => {
|
|
|
|
|
set({ toolCallExpansion: value });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setFontSize: (size) => {
|
|
|
|
|
// Clamp between 50% and 200%
|
|
|
|
|
const clampedSize = Math.max(50, Math.min(200, size));
|
|
|
|
|
set({ fontSize: clampedSize });
|
|
|
|
|
get().applyTypography();
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-04 16:32:25 +02:00
|
|
|
setTerminalFontSize: (size) => {
|
|
|
|
|
const rounded = Math.round(size);
|
|
|
|
|
const clamped = Math.max(9, Math.min(52, rounded));
|
|
|
|
|
set({ terminalFontSize: clamped });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
setPadding: (size) => {
|
|
|
|
|
// Clamp between 50% and 200%
|
|
|
|
|
const clampedSize = Math.max(50, Math.min(200, size));
|
|
|
|
|
set({ padding: clampedSize });
|
|
|
|
|
get().applyPadding();
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-18 18:28:49 +06:00
|
|
|
setCornerRadius: (radius) => {
|
|
|
|
|
set({ cornerRadius: radius });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
applyTypography: () => {
|
|
|
|
|
const { fontSize } = get();
|
|
|
|
|
const root = document.documentElement;
|
2025-12-16 13:14:28 +02:00
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
// 100 = default (1.0x), 50 = half size (0.5x), 200 = double (2.0x)
|
|
|
|
|
const scale = fontSize / 100;
|
2025-12-16 13:14:28 +02:00
|
|
|
|
|
|
|
|
const entries = Object.entries(SEMANTIC_TYPOGRAPHY) as Array<[SemanticTypographyKey, string]>;
|
|
|
|
|
|
|
|
|
|
// Default must be SEMANTIC_TYPOGRAPHY (from CSS). Remove overrides.
|
|
|
|
|
if (scale === 1) {
|
|
|
|
|
for (const [key] of entries) {
|
|
|
|
|
root.style.removeProperty(getTypographyVariable(key));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [key, baseValue] of entries) {
|
2025-12-15 18:11:38 +02:00
|
|
|
const numericValue = parseFloat(baseValue);
|
2025-12-16 13:14:28 +02:00
|
|
|
if (!Number.isFinite(numericValue)) {
|
|
|
|
|
continue;
|
2025-12-15 18:11:38 +02:00
|
|
|
}
|
2025-12-16 13:14:28 +02:00
|
|
|
root.style.setProperty(getTypographyVariable(key), `${numericValue * scale}rem`);
|
|
|
|
|
}
|
2025-12-15 18:11:38 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
applyPadding: () => {
|
|
|
|
|
const { padding } = get();
|
|
|
|
|
const root = document.documentElement;
|
2025-12-16 13:14:28 +02:00
|
|
|
|
|
|
|
|
const scale = padding / 100;
|
|
|
|
|
|
|
|
|
|
if (scale === 1) {
|
|
|
|
|
root.style.removeProperty('--padding-scale');
|
|
|
|
|
root.style.removeProperty('--line-height-tight');
|
|
|
|
|
root.style.removeProperty('--line-height-normal');
|
|
|
|
|
root.style.removeProperty('--line-height-relaxed');
|
|
|
|
|
root.style.removeProperty('--line-height-loose');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
// Apply padding as a percentage scale with non-linear scaling
|
|
|
|
|
// Use square root for more natural scaling at extremes
|
|
|
|
|
const adjustedScale = Math.sqrt(scale);
|
2025-12-16 13:14:28 +02:00
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
// Set the CSS custom property that all spacing tokens reference
|
|
|
|
|
root.style.setProperty('--padding-scale', adjustedScale.toString());
|
2025-12-16 13:14:28 +02:00
|
|
|
|
|
|
|
|
// Dampened line-height scaling at extremes
|
|
|
|
|
const lineHeightScale = 1 + (scale - 1) * 0.15;
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
root.style.setProperty('--line-height-tight', (1.25 * lineHeightScale).toFixed(3));
|
|
|
|
|
root.style.setProperty('--line-height-normal', (1.5 * lineHeightScale).toFixed(3));
|
|
|
|
|
root.style.setProperty('--line-height-relaxed', (1.625 * lineHeightScale).toFixed(3));
|
|
|
|
|
root.style.setProperty('--line-height-loose', (2 * lineHeightScale).toFixed(3));
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
setDiffLayoutPreference: (mode) => {
|
|
|
|
|
set({ diffLayoutPreference: mode });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setDiffFileLayout: (filePath, mode) => {
|
|
|
|
|
set((state) => ({
|
|
|
|
|
diffFileLayout: {
|
|
|
|
|
...state.diffFileLayout,
|
|
|
|
|
[filePath]: mode,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-14 02:29:46 +02:00
|
|
|
setDiffWrapLines: (wrap) => {
|
|
|
|
|
set({ diffWrapLines: wrap });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-14 10:15:16 -03:00
|
|
|
setDiffViewMode: (mode) => {
|
|
|
|
|
set({ diffViewMode: mode });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-01 01:01:46 -08:00
|
|
|
setInputBarOffset: (offset) => {
|
|
|
|
|
set({ inputBarOffset: offset });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setKeyboardOpen: (open) => {
|
|
|
|
|
set({ isKeyboardOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-15 18:11:38 +02:00
|
|
|
toggleFavoriteModel: (providerID, modelID) => {
|
|
|
|
|
set((state) => {
|
|
|
|
|
const exists = state.favoriteModels.some(
|
|
|
|
|
(fav) => fav.providerID === providerID && fav.modelID === modelID
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (exists) {
|
|
|
|
|
// Remove from favorites
|
|
|
|
|
return {
|
|
|
|
|
favoriteModels: state.favoriteModels.filter(
|
|
|
|
|
(fav) => !(fav.providerID === providerID && fav.modelID === modelID)
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// Add to favorites (newest first)
|
|
|
|
|
return {
|
|
|
|
|
favoriteModels: [{ providerID, modelID }, ...state.favoriteModels],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isFavoriteModel: (providerID, modelID) => {
|
|
|
|
|
const { favoriteModels } = get();
|
|
|
|
|
return favoriteModels.some(
|
|
|
|
|
(fav) => fav.providerID === providerID && fav.modelID === modelID
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
addRecentModel: (providerID, modelID) => {
|
|
|
|
|
set((state) => {
|
|
|
|
|
// Remove existing instance if any
|
|
|
|
|
const filtered = state.recentModels.filter(
|
|
|
|
|
(m) => !(m.providerID === providerID && m.modelID === modelID)
|
|
|
|
|
);
|
|
|
|
|
// Add to front, limit to 5
|
|
|
|
|
return {
|
|
|
|
|
recentModels: [{ providerID, modelID }, ...filtered].slice(0, 5),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-30 06:13:37 -03:00
|
|
|
addRecentAgent: (agentName) => {
|
|
|
|
|
const normalized = typeof agentName === 'string' ? agentName.trim() : '';
|
|
|
|
|
if (!normalized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
set((state) => {
|
|
|
|
|
if (state.recentAgents.includes(normalized)) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
const filtered = state.recentAgents;
|
|
|
|
|
return {
|
|
|
|
|
recentAgents: [normalized, ...filtered].slice(0, 5),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
addRecentEffort: (providerID, modelID, variant) => {
|
|
|
|
|
const provider = typeof providerID === 'string' ? providerID.trim() : '';
|
|
|
|
|
const model = typeof modelID === 'string' ? modelID.trim() : '';
|
|
|
|
|
if (!provider || !model) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const key = `${provider}/${model}`;
|
|
|
|
|
const normalizedVariant = typeof variant === 'string' && variant.trim().length > 0 ? variant.trim() : 'default';
|
|
|
|
|
set((state) => {
|
|
|
|
|
const current = state.recentEfforts[key] ?? [];
|
|
|
|
|
if (current.includes(normalizedVariant)) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
const filtered = current;
|
|
|
|
|
return {
|
|
|
|
|
recentEfforts: {
|
|
|
|
|
...state.recentEfforts,
|
|
|
|
|
[key]: [normalizedVariant, ...filtered].slice(0, 5),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
updateProportionalSidebarWidths: () => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
const updates: Partial<UIStore> = {};
|
|
|
|
|
|
2026-02-09 03:13:34 +02:00
|
|
|
if (state.isBottomTerminalOpen && !state.hasManuallyResizedBottomTerminal) {
|
|
|
|
|
updates.bottomTerminalHeight = Math.floor(window.innerHeight * 0.32);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
return updates;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
applyTheme: () => {
|
|
|
|
|
const { theme } = get();
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
|
|
|
|
|
root.classList.remove('light', 'dark');
|
|
|
|
|
|
|
|
|
|
if (theme === 'system') {
|
|
|
|
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
|
|
|
root.classList.add(systemTheme);
|
|
|
|
|
} else {
|
|
|
|
|
root.classList.add(theme);
|
|
|
|
|
}
|
2026-01-01 14:59:51 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setMultiRunLauncherOpen: (open) => {
|
|
|
|
|
set((state) => ({
|
|
|
|
|
isMultiRunLauncherOpen: open,
|
|
|
|
|
multiRunLauncherPrefillPrompt: open ? state.multiRunLauncherPrefillPrompt : '',
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
openMultiRunLauncher: () => {
|
|
|
|
|
set({
|
|
|
|
|
isMultiRunLauncherOpen: true,
|
|
|
|
|
multiRunLauncherPrefillPrompt: '',
|
|
|
|
|
isSessionSwitcherOpen: false,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
openMultiRunLauncherWithPrompt: (prompt) => {
|
|
|
|
|
set({
|
|
|
|
|
isMultiRunLauncherOpen: true,
|
|
|
|
|
multiRunLauncherPrefillPrompt: prompt,
|
|
|
|
|
isSessionSwitcherOpen: false,
|
|
|
|
|
});
|
|
|
|
|
},
|
2026-01-02 18:33:35 -05:00
|
|
|
|
|
|
|
|
setTimelineDialogOpen: (open) => {
|
|
|
|
|
set({ isTimelineDialogOpen: open });
|
|
|
|
|
},
|
2026-01-11 21:21:13 +08:00
|
|
|
|
2026-01-30 02:46:43 +02:00
|
|
|
setImagePreviewOpen: (open) => {
|
|
|
|
|
set({ isImagePreviewOpen: open });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-11 21:21:13 +08:00
|
|
|
setNativeNotificationsEnabled: (value) => {
|
|
|
|
|
set({ nativeNotificationsEnabled: value });
|
|
|
|
|
},
|
2026-01-14 01:03:26 +02:00
|
|
|
|
|
|
|
|
setNotificationMode: (mode) => {
|
|
|
|
|
set({ notificationMode: mode });
|
|
|
|
|
},
|
2026-01-30 20:15:00 +08:00
|
|
|
|
2026-01-30 14:48:53 +02:00
|
|
|
setShowTerminalQuickKeysOnDesktop: (value) => {
|
|
|
|
|
set({ showTerminalQuickKeysOnDesktop: value });
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-30 20:15:00 +08:00
|
|
|
setNotifyOnSubtasks: (value) => {
|
|
|
|
|
set({ notifyOnSubtasks: value });
|
|
|
|
|
},
|
2026-02-06 01:14:09 -08:00
|
|
|
|
2026-02-08 17:58:29 -08:00
|
|
|
setNotifyOnCompletion: (value) => { set({ notifyOnCompletion: value }); },
|
|
|
|
|
setNotifyOnError: (value) => { set({ notifyOnError: value }); },
|
|
|
|
|
setNotifyOnQuestion: (value) => { set({ notifyOnQuestion: value }); },
|
|
|
|
|
setNotificationTemplates: (templates) => { set({ notificationTemplates: templates }); },
|
|
|
|
|
setSummarizeLastMessage: (value) => { set({ summarizeLastMessage: value }); },
|
|
|
|
|
setSummaryThreshold: (value) => { set({ summaryThreshold: value }); },
|
|
|
|
|
setSummaryLength: (value) => { set({ summaryLength: value }); },
|
|
|
|
|
setMaxLastMessageLength: (value) => { set({ maxLastMessageLength: value }); },
|
2026-02-06 01:14:09 -08:00
|
|
|
setPersistChatDraft: (value) => {
|
|
|
|
|
set({ persistChatDraft: value });
|
|
|
|
|
},
|
2026-02-07 09:11:55 +08:00
|
|
|
setIsMobileSessionStatusBarCollapsed: (value) => {
|
|
|
|
|
set({ isMobileSessionStatusBarCollapsed: value });
|
|
|
|
|
},
|
2025-12-07 19:32:53 +02:00
|
|
|
}),
|
|
|
|
|
{
|
|
|
|
|
name: 'ui-store',
|
|
|
|
|
storage: createJSONStorage(() => getSafeStorage()),
|
2026-02-16 14:15:19 +02:00
|
|
|
version: 4,
|
2026-02-08 17:58:29 -08:00
|
|
|
migrate: (persistedState, version) => {
|
2026-02-12 20:36:02 +02:00
|
|
|
if (!persistedState || typeof persistedState !== 'object') {
|
2026-02-08 17:58:29 -08:00
|
|
|
return persistedState;
|
|
|
|
|
}
|
|
|
|
|
const state = persistedState as Record<string, unknown>;
|
2026-02-12 20:36:02 +02:00
|
|
|
|
|
|
|
|
// v0 -> v1: reset legacy notification templates
|
|
|
|
|
if (version < 1) {
|
|
|
|
|
if (isLegacyDefaultTemplates(state.notificationTemplates)) {
|
|
|
|
|
state.notificationTemplates = {
|
|
|
|
|
completion: { ...EMPTY_NOTIFICATION_TEMPLATES.completion },
|
|
|
|
|
error: { ...EMPTY_NOTIFICATION_TEMPLATES.error },
|
|
|
|
|
question: { ...EMPTY_NOTIFICATION_TEMPLATES.question },
|
|
|
|
|
subtask: { ...EMPTY_NOTIFICATION_TEMPLATES.subtask },
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-02-08 17:58:29 -08:00
|
|
|
}
|
2026-02-12 20:36:02 +02:00
|
|
|
|
|
|
|
|
// v2 -> v3: collapse 3 memory-limit fields into single messageLimit.
|
|
|
|
|
// Pick the best user-customised value (prefer historical, fall back to active).
|
|
|
|
|
// Discard old defaults (90/120/180) — they become the new single default (200).
|
|
|
|
|
if (version < 3) {
|
|
|
|
|
const OLD_DEFAULTS = new Set([90, 120, 180, 220]);
|
|
|
|
|
const hist = state.memoryLimitHistorical as number | undefined;
|
|
|
|
|
const active = state.memoryLimitActiveSession as number | undefined;
|
|
|
|
|
|
|
|
|
|
// If user had a non-default custom value, keep it as the new messageLimit.
|
|
|
|
|
if (typeof hist === 'number' && !OLD_DEFAULTS.has(hist)) {
|
|
|
|
|
state.messageLimit = hist;
|
|
|
|
|
} else if (typeof active === 'number' && !OLD_DEFAULTS.has(active)) {
|
|
|
|
|
state.messageLimit = active;
|
|
|
|
|
}
|
|
|
|
|
// Otherwise leave undefined → Zustand uses the initial default (200).
|
|
|
|
|
|
|
|
|
|
delete state.memoryLimitHistorical;
|
|
|
|
|
delete state.memoryLimitViewport;
|
|
|
|
|
delete state.memoryLimitActiveSession;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 14:15:19 +02:00
|
|
|
if (typeof state.rightSidebarTab !== 'string' || (state.rightSidebarTab !== 'git' && state.rightSidebarTab !== 'files')) {
|
|
|
|
|
state.rightSidebarTab = 'git';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!state.contextPanelByDirectory || typeof state.contextPanelByDirectory !== 'object') {
|
|
|
|
|
state.contextPanelByDirectory = {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 20:36:02 +02:00
|
|
|
return state;
|
2026-02-08 17:58:29 -08:00
|
|
|
},
|
2025-12-07 19:32:53 +02:00
|
|
|
partialize: (state) => ({
|
|
|
|
|
theme: state.theme,
|
|
|
|
|
isSidebarOpen: state.isSidebarOpen,
|
|
|
|
|
sidebarWidth: state.sidebarWidth,
|
2026-02-09 03:13:34 +02:00
|
|
|
isRightSidebarOpen: state.isRightSidebarOpen,
|
|
|
|
|
rightSidebarWidth: state.rightSidebarWidth,
|
2026-02-16 14:15:19 +02:00
|
|
|
rightSidebarTab: state.rightSidebarTab,
|
|
|
|
|
contextPanelByDirectory: state.contextPanelByDirectory,
|
2026-02-09 03:13:34 +02:00
|
|
|
isBottomTerminalOpen: state.isBottomTerminalOpen,
|
2026-02-16 14:15:19 +02:00
|
|
|
isBottomTerminalExpanded: state.isBottomTerminalExpanded,
|
2026-02-09 03:13:34 +02:00
|
|
|
bottomTerminalHeight: state.bottomTerminalHeight,
|
2025-12-07 19:32:53 +02:00
|
|
|
isSessionSwitcherOpen: state.isSessionSwitcherOpen,
|
|
|
|
|
activeMainTab: state.activeMainTab,
|
|
|
|
|
sidebarSection: state.sidebarSection,
|
|
|
|
|
isSessionCreateDialogOpen: state.isSessionCreateDialogOpen,
|
2026-02-02 00:18:12 +02:00
|
|
|
// Note: isSettingsDialogOpen intentionally NOT persisted
|
2025-12-07 19:32:53 +02:00
|
|
|
showReasoningTraces: state.showReasoningTraces,
|
2026-01-26 01:09:31 +08:00
|
|
|
showTextJustificationActivity: state.showTextJustificationActivity,
|
2025-12-20 02:06:00 +02:00
|
|
|
autoDeleteEnabled: state.autoDeleteEnabled,
|
|
|
|
|
autoDeleteAfterDays: state.autoDeleteAfterDays,
|
|
|
|
|
autoDeleteLastRunAt: state.autoDeleteLastRunAt,
|
2026-02-12 20:36:02 +02:00
|
|
|
messageLimit: state.messageLimit,
|
2025-12-15 18:11:38 +02:00
|
|
|
toolCallExpansion: state.toolCallExpansion,
|
|
|
|
|
fontSize: state.fontSize,
|
2026-02-04 16:32:25 +02:00
|
|
|
terminalFontSize: state.terminalFontSize,
|
2025-12-15 18:11:38 +02:00
|
|
|
padding: state.padding,
|
2026-01-18 18:28:49 +06:00
|
|
|
cornerRadius: state.cornerRadius,
|
2025-12-15 18:11:38 +02:00
|
|
|
favoriteModels: state.favoriteModels,
|
|
|
|
|
recentModels: state.recentModels,
|
2026-01-30 06:13:37 -03:00
|
|
|
recentAgents: state.recentAgents,
|
|
|
|
|
recentEfforts: state.recentEfforts,
|
2025-12-07 19:32:53 +02:00
|
|
|
diffLayoutPreference: state.diffLayoutPreference,
|
2025-12-14 02:29:46 +02:00
|
|
|
diffWrapLines: state.diffWrapLines,
|
2026-01-14 10:15:16 -03:00
|
|
|
diffViewMode: state.diffViewMode,
|
2026-01-11 21:21:13 +08:00
|
|
|
nativeNotificationsEnabled: state.nativeNotificationsEnabled,
|
2026-01-14 01:03:26 +02:00
|
|
|
notificationMode: state.notificationMode,
|
2026-01-30 14:48:53 +02:00
|
|
|
showTerminalQuickKeysOnDesktop: state.showTerminalQuickKeysOnDesktop,
|
2026-01-30 20:15:00 +08:00
|
|
|
notifyOnSubtasks: state.notifyOnSubtasks,
|
2026-02-08 17:58:29 -08:00
|
|
|
notifyOnCompletion: state.notifyOnCompletion,
|
|
|
|
|
notifyOnError: state.notifyOnError,
|
|
|
|
|
notifyOnQuestion: state.notifyOnQuestion,
|
|
|
|
|
notificationTemplates: state.notificationTemplates,
|
|
|
|
|
summarizeLastMessage: state.summarizeLastMessage,
|
|
|
|
|
summaryThreshold: state.summaryThreshold,
|
|
|
|
|
summaryLength: state.summaryLength,
|
|
|
|
|
maxLastMessageLength: state.maxLastMessageLength,
|
2026-02-06 01:14:09 -08:00
|
|
|
persistChatDraft: state.persistChatDraft,
|
2026-02-07 09:11:55 +08:00
|
|
|
isMobileSessionStatusBarCollapsed: state.isMobileSessionStatusBarCollapsed,
|
2025-12-07 19:32:53 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
{
|
|
|
|
|
name: 'ui-store'
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|