mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
feat: enhance terminal functionality and settings
- Added new endpoints for terminal settings: GET and PUT /settings to retrieve and update terminal configurations. - Implemented session limit checks during session creation, returning a 429 status when the limit is reached. - Introduced a new TerminalSection in settings view for customizing terminal appearance and behavior, including font family, default font size, line height, and screen reader mode. - Added support for new terminal features such as search functionality and improved error handling with a TerminalErrorBoundary component. - Updated terminal layout persistence to include session IDs for reconnection and enhanced terminal state management. - Introduced new keyboard shortcuts for terminal actions, including creating new terminal tabs. - Enhanced UI with scrollbar theming for terminal components.
This commit is contained in:
@@ -176,6 +176,7 @@ export interface KeyboardShortcuts {
|
||||
splitTerminalRight: string;
|
||||
splitTerminalDown: string;
|
||||
closeTerminal: string;
|
||||
newTerminalTab: string;
|
||||
}
|
||||
|
||||
// Default keyboard shortcuts
|
||||
@@ -210,6 +211,7 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
|
||||
splitTerminalRight: "Alt+D",
|
||||
splitTerminalDown: "Alt+S",
|
||||
closeTerminal: "Alt+W",
|
||||
newTerminalTab: "Alt+T",
|
||||
};
|
||||
|
||||
export interface ImageAttachment {
|
||||
@@ -356,6 +358,7 @@ export type TerminalPanelContent =
|
||||
| { type: "terminal"; sessionId: string; size?: number; fontSize?: number }
|
||||
| {
|
||||
type: "split";
|
||||
id: string; // Stable ID for React key stability
|
||||
direction: "horizontal" | "vertical";
|
||||
panels: TerminalPanelContent[];
|
||||
size?: number;
|
||||
@@ -374,7 +377,57 @@ export interface TerminalState {
|
||||
tabs: TerminalTab[];
|
||||
activeTabId: string | null;
|
||||
activeSessionId: string | null;
|
||||
maximizedSessionId: string | null; // Session ID of the maximized terminal pane (null if none)
|
||||
defaultFontSize: number; // Default font size for new terminals
|
||||
defaultRunScript: string; // Script to run when a new terminal is created (e.g., "claude" to start Claude Code)
|
||||
screenReaderMode: boolean; // Enable screen reader accessibility mode
|
||||
fontFamily: string; // Font family for terminal text
|
||||
scrollbackLines: number; // Number of lines to keep in scrollback buffer
|
||||
lineHeight: number; // Line height multiplier for terminal text
|
||||
maxSessions: number; // Maximum concurrent terminal sessions (server setting)
|
||||
}
|
||||
|
||||
// Persisted terminal layout - now includes sessionIds for reconnection
|
||||
// Used to restore terminal layout structure when switching projects
|
||||
export type PersistedTerminalPanel =
|
||||
| { type: "terminal"; size?: number; fontSize?: number; sessionId?: string }
|
||||
| {
|
||||
type: "split";
|
||||
id?: string; // Optional for backwards compatibility with older persisted layouts
|
||||
direction: "horizontal" | "vertical";
|
||||
panels: PersistedTerminalPanel[];
|
||||
size?: number;
|
||||
};
|
||||
|
||||
// Helper to generate unique split IDs
|
||||
const generateSplitId = () => `split-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
export interface PersistedTerminalTab {
|
||||
id: string;
|
||||
name: string;
|
||||
layout: PersistedTerminalPanel | null;
|
||||
}
|
||||
|
||||
export interface PersistedTerminalState {
|
||||
tabs: PersistedTerminalTab[];
|
||||
activeTabIndex: number; // Use index instead of ID since IDs are regenerated
|
||||
defaultFontSize: number;
|
||||
defaultRunScript?: string; // Optional to support existing persisted data
|
||||
screenReaderMode?: boolean; // Optional to support existing persisted data
|
||||
fontFamily?: string; // Optional to support existing persisted data
|
||||
scrollbackLines?: number; // Optional to support existing persisted data
|
||||
lineHeight?: number; // Optional to support existing persisted data
|
||||
}
|
||||
|
||||
// Persisted terminal settings - stored globally (not per-project)
|
||||
export interface PersistedTerminalSettings {
|
||||
defaultFontSize: number;
|
||||
defaultRunScript: string;
|
||||
screenReaderMode: boolean;
|
||||
fontFamily: string;
|
||||
scrollbackLines: number;
|
||||
lineHeight: number;
|
||||
maxSessions: number;
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
@@ -491,6 +544,10 @@ export interface AppState {
|
||||
// Terminal state
|
||||
terminalState: TerminalState;
|
||||
|
||||
// Terminal layout persistence (per-project, keyed by project path)
|
||||
// Stores the tab/split structure so it can be restored when switching projects
|
||||
terminalLayoutByProject: Record<string, PersistedTerminalState>;
|
||||
|
||||
// Spec Creation State (per-project, keyed by project path)
|
||||
// Tracks which project is currently having its spec generated
|
||||
specCreatingForProject: string | null;
|
||||
@@ -720,6 +777,7 @@ export interface AppActions {
|
||||
// Terminal actions
|
||||
setTerminalUnlocked: (unlocked: boolean, token?: string) => void;
|
||||
setActiveTerminalSession: (sessionId: string | null) => void;
|
||||
toggleTerminalMaximized: (sessionId: string) => void;
|
||||
addTerminalToLayout: (
|
||||
sessionId: string,
|
||||
direction?: "horizontal" | "vertical",
|
||||
@@ -729,16 +787,37 @@ export interface AppActions {
|
||||
swapTerminals: (sessionId1: string, sessionId2: string) => void;
|
||||
clearTerminalState: () => void;
|
||||
setTerminalPanelFontSize: (sessionId: string, fontSize: number) => void;
|
||||
setTerminalDefaultFontSize: (fontSize: number) => void;
|
||||
setTerminalDefaultRunScript: (script: string) => void;
|
||||
setTerminalScreenReaderMode: (enabled: boolean) => void;
|
||||
setTerminalFontFamily: (fontFamily: string) => void;
|
||||
setTerminalScrollbackLines: (lines: number) => void;
|
||||
setTerminalLineHeight: (lineHeight: number) => void;
|
||||
setTerminalMaxSessions: (maxSessions: number) => void;
|
||||
addTerminalTab: (name?: string) => string;
|
||||
removeTerminalTab: (tabId: string) => void;
|
||||
setActiveTerminalTab: (tabId: string) => void;
|
||||
renameTerminalTab: (tabId: string, name: string) => void;
|
||||
reorderTerminalTabs: (fromTabId: string, toTabId: string) => void;
|
||||
moveTerminalToTab: (sessionId: string, targetTabId: string | "new") => void;
|
||||
addTerminalToTab: (
|
||||
sessionId: string,
|
||||
tabId: string,
|
||||
direction?: "horizontal" | "vertical"
|
||||
) => void;
|
||||
setTerminalTabLayout: (
|
||||
tabId: string,
|
||||
layout: TerminalPanelContent,
|
||||
activeSessionId?: string
|
||||
) => void;
|
||||
updateTerminalPanelSizes: (
|
||||
tabId: string,
|
||||
panelKeys: string[],
|
||||
sizes: number[]
|
||||
) => void;
|
||||
saveTerminalLayout: (projectPath: string) => void;
|
||||
getPersistedTerminalLayout: (projectPath: string) => PersistedTerminalState | null;
|
||||
clearPersistedTerminalLayout: (projectPath: string) => void;
|
||||
|
||||
// Spec Creation actions
|
||||
setSpecCreatingForProject: (projectPath: string | null) => void;
|
||||
@@ -841,8 +920,16 @@ const initialState: AppState = {
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
activeSessionId: null,
|
||||
maximizedSessionId: null,
|
||||
defaultFontSize: 14,
|
||||
defaultRunScript: "",
|
||||
screenReaderMode: false,
|
||||
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
|
||||
scrollbackLines: 5000,
|
||||
lineHeight: 1.0,
|
||||
maxSessions: 100,
|
||||
},
|
||||
terminalLayoutByProject: {},
|
||||
specCreatingForProject: null,
|
||||
defaultPlanningMode: 'skip' as PlanningMode,
|
||||
defaultRequirePlanApproval: false,
|
||||
@@ -1732,6 +1819,19 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
toggleTerminalMaximized: (sessionId) => {
|
||||
const current = get().terminalState;
|
||||
const newMaximized = current.maximizedSessionId === sessionId ? null : sessionId;
|
||||
set({
|
||||
terminalState: {
|
||||
...current,
|
||||
maximizedSessionId: newMaximized,
|
||||
// Also set as active when maximizing
|
||||
activeSessionId: newMaximized ?? current.activeSessionId,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
addTerminalToLayout: (
|
||||
sessionId,
|
||||
direction = "horizontal",
|
||||
@@ -1781,6 +1881,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
// Found the target - split it
|
||||
return {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction: targetDirection,
|
||||
panels: [{ ...node, size: 50 }, newTerminal],
|
||||
};
|
||||
@@ -1805,6 +1906,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
if (node.type === "terminal") {
|
||||
return {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction: targetDirection,
|
||||
panels: [{ ...node, size: 50 }, newTerminal],
|
||||
};
|
||||
@@ -1823,6 +1925,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
// Different direction, wrap in new split
|
||||
return {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction: targetDirection,
|
||||
panels: [{ ...node, size: 50 }, newTerminal],
|
||||
};
|
||||
@@ -1884,7 +1987,12 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
}
|
||||
if (newPanels.length === 0) return null;
|
||||
if (newPanels.length === 1) return newPanels[0];
|
||||
return { ...node, panels: newPanels };
|
||||
// Normalize sizes to sum to 100%
|
||||
const totalSize = newPanels.reduce((sum, p) => sum + (p.size || 0), 0);
|
||||
const normalizedPanels = totalSize > 0
|
||||
? newPanels.map((p) => ({ ...p, size: ((p.size || 0) / totalSize) * 100 }))
|
||||
: newPanels.map((p) => ({ ...p, size: 100 / newPanels.length }));
|
||||
return { ...node, panels: normalizedPanels };
|
||||
};
|
||||
|
||||
let newTabs = current.tabs.map((tab) => {
|
||||
@@ -1948,14 +2056,25 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
},
|
||||
|
||||
clearTerminalState: () => {
|
||||
const current = get().terminalState;
|
||||
set({
|
||||
terminalState: {
|
||||
isUnlocked: false,
|
||||
authToken: null,
|
||||
// Preserve auth state - user shouldn't need to re-authenticate
|
||||
isUnlocked: current.isUnlocked,
|
||||
authToken: current.authToken,
|
||||
// Clear session-specific state only
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
activeSessionId: null,
|
||||
defaultFontSize: 14,
|
||||
maximizedSessionId: null,
|
||||
// Preserve user preferences - these should persist across projects
|
||||
defaultFontSize: current.defaultFontSize,
|
||||
defaultRunScript: current.defaultRunScript,
|
||||
screenReaderMode: current.screenReaderMode,
|
||||
fontFamily: current.fontFamily,
|
||||
scrollbackLines: current.scrollbackLines,
|
||||
lineHeight: current.lineHeight,
|
||||
maxSessions: current.maxSessions,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -1986,6 +2105,62 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalDefaultFontSize: (fontSize) => {
|
||||
const current = get().terminalState;
|
||||
const clampedSize = Math.max(8, Math.min(32, fontSize));
|
||||
set({
|
||||
terminalState: { ...current, defaultFontSize: clampedSize },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalDefaultRunScript: (script) => {
|
||||
const current = get().terminalState;
|
||||
set({
|
||||
terminalState: { ...current, defaultRunScript: script },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalScreenReaderMode: (enabled) => {
|
||||
const current = get().terminalState;
|
||||
set({
|
||||
terminalState: { ...current, screenReaderMode: enabled },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalFontFamily: (fontFamily) => {
|
||||
const current = get().terminalState;
|
||||
set({
|
||||
terminalState: { ...current, fontFamily },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalScrollbackLines: (lines) => {
|
||||
const current = get().terminalState;
|
||||
// Clamp to reasonable range: 1000 - 100000 lines
|
||||
const clampedLines = Math.max(1000, Math.min(100000, lines));
|
||||
set({
|
||||
terminalState: { ...current, scrollbackLines: clampedLines },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalLineHeight: (lineHeight) => {
|
||||
const current = get().terminalState;
|
||||
// Clamp to reasonable range: 1.0 - 2.0
|
||||
const clampedHeight = Math.max(1.0, Math.min(2.0, lineHeight));
|
||||
set({
|
||||
terminalState: { ...current, lineHeight: clampedHeight },
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalMaxSessions: (maxSessions) => {
|
||||
const current = get().terminalState;
|
||||
// Clamp to reasonable range: 1 - 500
|
||||
const clampedMax = Math.max(1, Math.min(500, maxSessions));
|
||||
set({
|
||||
terminalState: { ...current, maxSessions: clampedMax },
|
||||
});
|
||||
},
|
||||
|
||||
addTerminalTab: (name) => {
|
||||
const current = get().terminalState;
|
||||
const newTabId = `tab-${Date.now()}`;
|
||||
@@ -2078,6 +2253,25 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
reorderTerminalTabs: (fromTabId, toTabId) => {
|
||||
const current = get().terminalState;
|
||||
const fromIndex = current.tabs.findIndex((t) => t.id === fromTabId);
|
||||
const toIndex = current.tabs.findIndex((t) => t.id === toTabId);
|
||||
|
||||
if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reorder tabs by moving fromIndex to toIndex
|
||||
const newTabs = [...current.tabs];
|
||||
const [movedTab] = newTabs.splice(fromIndex, 1);
|
||||
newTabs.splice(toIndex, 0, movedTab);
|
||||
|
||||
set({
|
||||
terminalState: { ...current, tabs: newTabs },
|
||||
});
|
||||
},
|
||||
|
||||
moveTerminalToTab: (sessionId, targetTabId) => {
|
||||
const current = get().terminalState;
|
||||
|
||||
@@ -2128,7 +2322,12 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
}
|
||||
if (newPanels.length === 0) return null;
|
||||
if (newPanels.length === 1) return newPanels[0];
|
||||
return { ...node, panels: newPanels };
|
||||
// Normalize sizes to sum to 100%
|
||||
const totalSize = newPanels.reduce((sum, p) => sum + (p.size || 0), 0);
|
||||
const normalizedPanels = totalSize > 0
|
||||
? newPanels.map((p) => ({ ...p, size: ((p.size || 0) / totalSize) * 100 }))
|
||||
: newPanels.map((p) => ({ ...p, size: 100 / newPanels.length }));
|
||||
return { ...node, panels: normalizedPanels };
|
||||
};
|
||||
|
||||
const newSourceLayout = removeAndCollapse(sourceTab.layout);
|
||||
@@ -2178,6 +2377,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
} else if (targetTab.layout.type === "terminal") {
|
||||
newTargetLayout = {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction: "horizontal",
|
||||
panels: [{ ...targetTab.layout, size: 50 }, terminalNode],
|
||||
};
|
||||
@@ -2228,6 +2428,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
} else if (tab.layout.type === "terminal") {
|
||||
newLayout = {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction,
|
||||
panels: [{ ...tab.layout, size: 50 }, terminalNode],
|
||||
};
|
||||
@@ -2244,6 +2445,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
} else {
|
||||
newLayout = {
|
||||
type: "split",
|
||||
id: generateSplitId(),
|
||||
direction,
|
||||
panels: [{ ...tab.layout, size: 50 }, terminalNode],
|
||||
};
|
||||
@@ -2264,6 +2466,154 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
setTerminalTabLayout: (tabId, layout, activeSessionId) => {
|
||||
const current = get().terminalState;
|
||||
const tab = current.tabs.find((t) => t.id === tabId);
|
||||
if (!tab) return;
|
||||
|
||||
const newTabs = current.tabs.map((t) =>
|
||||
t.id === tabId ? { ...t, layout } : t
|
||||
);
|
||||
|
||||
// Find first terminal in layout if no activeSessionId provided
|
||||
const findFirst = (node: TerminalPanelContent): string | null => {
|
||||
if (node.type === "terminal") return node.sessionId;
|
||||
for (const p of node.panels) {
|
||||
const found = findFirst(p);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const newActiveSessionId = activeSessionId || findFirst(layout);
|
||||
|
||||
set({
|
||||
terminalState: {
|
||||
...current,
|
||||
tabs: newTabs,
|
||||
activeTabId: tabId,
|
||||
activeSessionId: newActiveSessionId,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
updateTerminalPanelSizes: (tabId, panelKeys, sizes) => {
|
||||
const current = get().terminalState;
|
||||
const tab = current.tabs.find((t) => t.id === tabId);
|
||||
if (!tab || !tab.layout) return;
|
||||
|
||||
// Create a map of panel key to new size
|
||||
const sizeMap = new Map<string, number>();
|
||||
panelKeys.forEach((key, index) => {
|
||||
sizeMap.set(key, sizes[index]);
|
||||
});
|
||||
|
||||
// Helper to generate panel key (matches getPanelKey in terminal-view.tsx)
|
||||
const getPanelKey = (panel: TerminalPanelContent): string => {
|
||||
if (panel.type === "terminal") return panel.sessionId;
|
||||
const childKeys = panel.panels.map(getPanelKey).join("-");
|
||||
return `split-${panel.direction}-${childKeys}`;
|
||||
};
|
||||
|
||||
// Recursively update sizes in the layout
|
||||
const updateSizes = (panel: TerminalPanelContent): TerminalPanelContent => {
|
||||
const key = getPanelKey(panel);
|
||||
const newSize = sizeMap.get(key);
|
||||
|
||||
if (panel.type === "terminal") {
|
||||
return newSize !== undefined ? { ...panel, size: newSize } : panel;
|
||||
}
|
||||
|
||||
return {
|
||||
...panel,
|
||||
size: newSize !== undefined ? newSize : panel.size,
|
||||
panels: panel.panels.map(updateSizes),
|
||||
};
|
||||
};
|
||||
|
||||
const updatedLayout = updateSizes(tab.layout);
|
||||
|
||||
const newTabs = current.tabs.map((t) =>
|
||||
t.id === tabId ? { ...t, layout: updatedLayout } : t
|
||||
);
|
||||
|
||||
set({
|
||||
terminalState: { ...current, tabs: newTabs },
|
||||
});
|
||||
},
|
||||
|
||||
// Convert runtime layout to persisted format (preserves sessionIds for reconnection)
|
||||
saveTerminalLayout: (projectPath) => {
|
||||
const current = get().terminalState;
|
||||
if (current.tabs.length === 0) {
|
||||
// Nothing to save, clear any existing layout
|
||||
const { [projectPath]: _, ...rest } = get().terminalLayoutByProject;
|
||||
set({ terminalLayoutByProject: rest });
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert TerminalPanelContent to PersistedTerminalPanel
|
||||
// Now preserves sessionId so we can reconnect when switching back
|
||||
const persistPanel = (
|
||||
panel: TerminalPanelContent
|
||||
): PersistedTerminalPanel => {
|
||||
if (panel.type === "terminal") {
|
||||
return {
|
||||
type: "terminal",
|
||||
size: panel.size,
|
||||
fontSize: panel.fontSize,
|
||||
sessionId: panel.sessionId, // Preserve for reconnection
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "split",
|
||||
id: panel.id, // Preserve stable ID
|
||||
direction: panel.direction,
|
||||
panels: panel.panels.map(persistPanel),
|
||||
size: panel.size,
|
||||
};
|
||||
};
|
||||
|
||||
const persistedTabs: PersistedTerminalTab[] = current.tabs.map(
|
||||
(tab) => ({
|
||||
id: tab.id,
|
||||
name: tab.name,
|
||||
layout: tab.layout ? persistPanel(tab.layout) : null,
|
||||
})
|
||||
);
|
||||
|
||||
const activeTabIndex = current.tabs.findIndex(
|
||||
(t) => t.id === current.activeTabId
|
||||
);
|
||||
|
||||
const persisted: PersistedTerminalState = {
|
||||
tabs: persistedTabs,
|
||||
activeTabIndex: activeTabIndex >= 0 ? activeTabIndex : 0,
|
||||
defaultFontSize: current.defaultFontSize,
|
||||
defaultRunScript: current.defaultRunScript,
|
||||
screenReaderMode: current.screenReaderMode,
|
||||
fontFamily: current.fontFamily,
|
||||
scrollbackLines: current.scrollbackLines,
|
||||
lineHeight: current.lineHeight,
|
||||
};
|
||||
|
||||
set({
|
||||
terminalLayoutByProject: {
|
||||
...get().terminalLayoutByProject,
|
||||
[projectPath]: persisted,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
getPersistedTerminalLayout: (projectPath) => {
|
||||
return get().terminalLayoutByProject[projectPath] || null;
|
||||
},
|
||||
|
||||
clearPersistedTerminalLayout: (projectPath) => {
|
||||
const { [projectPath]: _, ...rest } = get().terminalLayoutByProject;
|
||||
set({ terminalLayoutByProject: rest });
|
||||
},
|
||||
|
||||
// Spec Creation actions
|
||||
setSpecCreatingForProject: (projectPath) => {
|
||||
set({ specCreatingForProject: projectPath });
|
||||
@@ -2312,6 +2662,30 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
}
|
||||
}
|
||||
|
||||
// Rehydrate terminal settings from persisted state
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const persistedSettings = (state as any).terminalSettings as PersistedTerminalSettings | undefined;
|
||||
if (persistedSettings) {
|
||||
state.terminalState = {
|
||||
...state.terminalState,
|
||||
// Preserve session state (tabs, activeTabId, etc.) but restore settings
|
||||
isUnlocked: state.terminalState?.isUnlocked ?? false,
|
||||
authToken: state.terminalState?.authToken ?? null,
|
||||
tabs: state.terminalState?.tabs ?? [],
|
||||
activeTabId: state.terminalState?.activeTabId ?? null,
|
||||
activeSessionId: state.terminalState?.activeSessionId ?? null,
|
||||
maximizedSessionId: state.terminalState?.maximizedSessionId ?? null,
|
||||
// Restore persisted settings
|
||||
defaultFontSize: persistedSettings.defaultFontSize ?? 14,
|
||||
defaultRunScript: persistedSettings.defaultRunScript ?? "",
|
||||
screenReaderMode: persistedSettings.screenReaderMode ?? false,
|
||||
fontFamily: persistedSettings.fontFamily ?? "Menlo, Monaco, 'Courier New', monospace",
|
||||
scrollbackLines: persistedSettings.scrollbackLines ?? 5000,
|
||||
lineHeight: persistedSettings.lineHeight ?? 1.0,
|
||||
maxSessions: persistedSettings.maxSessions ?? 100,
|
||||
};
|
||||
}
|
||||
|
||||
return state as AppState;
|
||||
},
|
||||
partialize: (state) => ({
|
||||
@@ -2349,10 +2723,23 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
|
||||
// Board background settings
|
||||
boardBackgroundByProject: state.boardBackgroundByProject,
|
||||
// Terminal layout persistence (per-project)
|
||||
terminalLayoutByProject: state.terminalLayoutByProject,
|
||||
// Terminal settings persistence (global)
|
||||
terminalSettings: {
|
||||
defaultFontSize: state.terminalState.defaultFontSize,
|
||||
defaultRunScript: state.terminalState.defaultRunScript,
|
||||
screenReaderMode: state.terminalState.screenReaderMode,
|
||||
fontFamily: state.terminalState.fontFamily,
|
||||
scrollbackLines: state.terminalState.scrollbackLines,
|
||||
lineHeight: state.terminalState.lineHeight,
|
||||
maxSessions: state.terminalState.maxSessions,
|
||||
} as PersistedTerminalSettings,
|
||||
defaultPlanningMode: state.defaultPlanningMode,
|
||||
defaultRequirePlanApproval: state.defaultRequirePlanApproval,
|
||||
defaultAIProfileId: state.defaultAIProfileId,
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}) as any,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user