From e1b0ef70a4f83f02b06a8c16c26e84be4f80e41b Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Thu, 15 Jan 2026 12:29:17 +0000 Subject: [PATCH] feat: Remember panel state per workspace (Vibe Kanban) (#2064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * I've successfully implemented workspace-specific panel state persistence. Here's a summary of the changes: ## Summary ### Changes Made 1. **`frontend/src/stores/useUiPreferencesStore.ts`** - Added `WorkspacePanelState` type to track `rightMainPanelMode` and `isLeftMainPanelVisible` per workspace - Added `workspacePanelStates: Record` to store state keyed by workspace ID - Updated `toggleLeftMainPanel`, `toggleRightMainPanelMode`, `setRightMainPanelMode`, and `setLeftMainPanelVisible` to accept an optional `workspaceId` parameter - Added `getWorkspacePanelState` and `setWorkspacePanelState` actions - Added `useWorkspacePanelState(workspaceId)` hook for easy consumption - Updated `partialize` to persist `workspacePanelStates` to localStorage 2. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** - Changed from using `useUiPreferencesStore` directly to using `useWorkspacePanelState(workspaceId)` hook - Panel state is now workspace-specific 3. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`** - Updated to use `useWorkspacePanelState` for visibility context 4. **`frontend/src/components/ui-new/actions/index.ts`** - Updated `ToggleLeftMainPanel`, `ToggleChangesMode`, `ToggleLogsMode`, `TogglePreviewMode`, and `ToggleDevServer` actions to pass `currentWorkspaceId` when toggling panel modes ### Scope **Workspace-specific:** - `rightMainPanelMode` - Which panel (Preview/Changes/Logs) is open - `isLeftMainPanelVisible` - Whether the chat panel is visible **Stays global:** - `isLeftSidebarVisible` - Workspace list sidebar (navigation element) - `isRightSidebarVisible` - Git/Settings sidebar (global actions) - Panel sizes (already work correctly) ### Testing Verified the feature works: 1. Opened Preview panel in Workspace A 2. Navigated to Workspace B → Preview panel is closed (default state) 3. Navigated back to Workspace A → Preview panel is still open ✓ * Cleanup script changes for workspace c8b73e59-ac41-4c0f-be78-7b6499d514f9 * The refactoring is complete. Let me provide a summary of the changes: ## Summary Refactored `useUiPreferencesStore.ts` to remove duplicate code paths for workspace-specific panel state: ### Changes Made 1. **`frontend/src/stores/useUiPreferencesStore.ts`** - Removed legacy global state (`isLeftMainPanelVisible`, `rightMainPanelMode`) from State type and initialization - Simplified 4 panel state actions by removing `else` branches for legacy behavior: - `toggleLeftMainPanel` - now early-returns if no workspaceId - `toggleRightMainPanelMode` - simplified to single set() call - `setRightMainPanelMode` - simplified to single set() call - `setLeftMainPanelVisible` - simplified - Removed `resetForCreateMode` action (was only setting legacy state) - Removed unused `useIsRightMainPanelVisible` hook 2. **`frontend/src/contexts/WorkspaceContext.tsx`** - Removed the `useEffect` that called `resetForCreateMode()` on create mode - Removed unused `useUiPreferencesStore` import 3. **`frontend/src/contexts/LogsPanelContext.tsx`** - Updated to use `useWorkspacePanelState` with workspace ID from context 4. **`frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx`** - Updated to use `useWorkspacePanelState` with workspace ID ### Impact - **~50 lines removed** from the codebase - **Cleaner code** - no more `if (workspaceId) ... else ...` branching in every action - **Same behavior** - all callers were already passing workspaceId * Cleanup script changes for workspace c8b73e59-ac41-4c0f-be78-7b6499d514f9 --- .../src/components/ui-new/actions/index.ts | 69 +++-- .../ui-new/actions/useActionVisibility.ts | 26 +- .../containers/SessionChatBoxContainer.tsx | 5 +- .../ui-new/containers/WorkspacesLayout.tsx | 6 +- frontend/src/contexts/LogsPanelContext.tsx | 8 +- frontend/src/contexts/WorkspaceContext.tsx | 8 - frontend/src/stores/useUiPreferencesStore.ts | 252 ++++++++++++++---- 7 files changed, 273 insertions(+), 101 deletions(-) diff --git a/frontend/src/components/ui-new/actions/index.ts b/frontend/src/components/ui-new/actions/index.ts index 35d4baa7..e7c4bdcc 100644 --- a/frontend/src/components/ui-new/actions/index.ts +++ b/frontend/src/components/ui-new/actions/index.ts @@ -478,17 +478,18 @@ export const Actions = { ToggleLeftMainPanel: { id: 'toggle-left-main-panel', - label: () => - useUiPreferencesStore.getState().isLeftMainPanelVisible - ? 'Hide Chat Panel' - : 'Show Chat Panel', + label: 'Toggle Chat Panel', icon: ChatsTeardropIcon, requiresTarget: false, isActive: (ctx) => ctx.isLeftMainPanelVisible, isEnabled: (ctx) => !(ctx.isLeftMainPanelVisible && ctx.rightMainPanelMode === null), - execute: () => { - useUiPreferencesStore.getState().toggleLeftMainPanel(); + getLabel: (ctx) => + ctx.isLeftMainPanelVisible ? 'Hide Chat Panel' : 'Show Chat Panel', + execute: (ctx) => { + useUiPreferencesStore + .getState() + .toggleLeftMainPanel(ctx.currentWorkspaceId ?? undefined); }, }, @@ -508,60 +509,69 @@ export const Actions = { ToggleChangesMode: { id: 'toggle-changes-mode', - label: () => - useUiPreferencesStore.getState().rightMainPanelMode === - RIGHT_MAIN_PANEL_MODES.CHANGES - ? 'Hide Changes Panel' - : 'Show Changes Panel', + label: 'Toggle Changes Panel', icon: GitDiffIcon, requiresTarget: false, isVisible: (ctx) => !ctx.isCreateMode, isActive: (ctx) => ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.CHANGES, isEnabled: (ctx) => !ctx.isCreateMode, - execute: () => { + getLabel: (ctx) => + ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.CHANGES + ? 'Hide Changes Panel' + : 'Show Changes Panel', + execute: (ctx) => { useUiPreferencesStore .getState() - .toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.CHANGES); + .toggleRightMainPanelMode( + RIGHT_MAIN_PANEL_MODES.CHANGES, + ctx.currentWorkspaceId ?? undefined + ); }, }, ToggleLogsMode: { id: 'toggle-logs-mode', - label: () => - useUiPreferencesStore.getState().rightMainPanelMode === - RIGHT_MAIN_PANEL_MODES.LOGS - ? 'Hide Logs Panel' - : 'Show Logs Panel', + label: 'Toggle Logs Panel', icon: TerminalIcon, requiresTarget: false, isVisible: (ctx) => !ctx.isCreateMode, isActive: (ctx) => ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.LOGS, isEnabled: (ctx) => !ctx.isCreateMode, - execute: () => { + getLabel: (ctx) => + ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.LOGS + ? 'Hide Logs Panel' + : 'Show Logs Panel', + execute: (ctx) => { useUiPreferencesStore .getState() - .toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.LOGS); + .toggleRightMainPanelMode( + RIGHT_MAIN_PANEL_MODES.LOGS, + ctx.currentWorkspaceId ?? undefined + ); }, }, TogglePreviewMode: { id: 'toggle-preview-mode', - label: () => - useUiPreferencesStore.getState().rightMainPanelMode === - RIGHT_MAIN_PANEL_MODES.PREVIEW - ? 'Hide Preview Panel' - : 'Show Preview Panel', + label: 'Toggle Preview Panel', icon: DesktopIcon, requiresTarget: false, isVisible: (ctx) => !ctx.isCreateMode, isActive: (ctx) => ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.PREVIEW, isEnabled: (ctx) => !ctx.isCreateMode, - execute: () => { + getLabel: (ctx) => + ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.PREVIEW + ? 'Hide Preview Panel' + : 'Show Preview Panel', + execute: (ctx) => { useUiPreferencesStore .getState() - .toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.PREVIEW); + .toggleRightMainPanelMode( + RIGHT_MAIN_PANEL_MODES.PREVIEW, + ctx.currentWorkspaceId ?? undefined + ); }, }, @@ -707,7 +717,10 @@ export const Actions = { // Auto-open preview mode when starting dev server useUiPreferencesStore .getState() - .setRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.PREVIEW); + .setRightMainPanelMode( + RIGHT_MAIN_PANEL_MODES.PREVIEW, + ctx.currentWorkspaceId ?? undefined + ); } }, }, diff --git a/frontend/src/components/ui-new/actions/useActionVisibility.ts b/frontend/src/components/ui-new/actions/useActionVisibility.ts index 63935b49..6badad5e 100644 --- a/frontend/src/components/ui-new/actions/useActionVisibility.ts +++ b/frontend/src/components/ui-new/actions/useActionVisibility.ts @@ -1,5 +1,8 @@ import { useMemo } from 'react'; -import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore'; +import { + useUiPreferencesStore, + useWorkspacePanelState, +} from '@/stores/useUiPreferencesStore'; import { useDiffViewStore, useDiffViewMode } from '@/stores/useDiffViewStore'; import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; import { useUserSystem } from '@/components/ConfigProvider'; @@ -22,8 +25,11 @@ import type { CommandBarPage } from './pages'; * action visibility and state conditions. */ export function useActionVisibilityContext(): ActionVisibilityContext { - const layout = useUiPreferencesStore(); const { workspace, workspaceId, isCreateMode, repos } = useWorkspaceContext(); + // Use workspace-specific panel state (pass undefined when in create mode) + const panelState = useWorkspacePanelState( + isCreateMode ? undefined : workspaceId + ); const diffPaths = useDiffViewStore((s) => s.diffPaths); const diffViewMode = useDiffViewMode(); const expanded = useUiPreferencesStore((s) => s.expanded); @@ -61,10 +67,10 @@ export function useActionVisibilityContext(): ActionVisibilityContext { false; return { - rightMainPanelMode: layout.rightMainPanelMode, - isLeftSidebarVisible: layout.isLeftSidebarVisible, - isLeftMainPanelVisible: layout.isLeftMainPanelVisible, - isRightSidebarVisible: layout.isRightSidebarVisible, + rightMainPanelMode: panelState.rightMainPanelMode, + isLeftSidebarVisible: panelState.isLeftSidebarVisible, + isLeftMainPanelVisible: panelState.isLeftMainPanelVisible, + isRightSidebarVisible: panelState.isRightSidebarVisible, isCreateMode, hasWorkspace: !!workspace, workspaceArchived: workspace?.archived ?? false, @@ -81,10 +87,10 @@ export function useActionVisibilityContext(): ActionVisibilityContext { isAttemptRunning: isAttemptRunningVisible, }; }, [ - layout.rightMainPanelMode, - layout.isLeftSidebarVisible, - layout.isLeftMainPanelVisible, - layout.isRightSidebarVisible, + panelState.rightMainPanelMode, + panelState.isLeftSidebarVisible, + panelState.isLeftMainPanelVisible, + panelState.isRightSidebarVisible, isCreateMode, workspace, repos, diff --git a/frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx b/frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx index f05f59f0..e5ba8ccd 100644 --- a/frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx +++ b/frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx @@ -29,7 +29,7 @@ import { type ExecutionStatus, } from '../primitives/SessionChatBox'; import { - useUiPreferencesStore, + useWorkspacePanelState, RIGHT_MAIN_PANEL_MODES, } from '@/stores/useUiPreferencesStore'; import { Actions, type ActionDefinition } from '../actions'; @@ -102,7 +102,8 @@ export function SessionChatBoxContainer({ const { executeAction } = useActions(); const actionCtx = useActionVisibilityContext(); - const { rightMainPanelMode, setRightMainPanelMode } = useUiPreferencesStore(); + const { rightMainPanelMode, setRightMainPanelMode } = + useWorkspacePanelState(workspaceId); const handleViewCode = useCallback(() => { setRightMainPanelMode( diff --git a/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx b/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx index f1655769..9bb175aa 100644 --- a/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx +++ b/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx @@ -20,7 +20,7 @@ import { useUserSystem } from '@/components/ConfigProvider'; import { PERSIST_KEYS, usePaneSize, - useUiPreferencesStore, + useWorkspacePanelState, RIGHT_MAIN_PANEL_MODES, } from '@/stores/useUiPreferencesStore'; @@ -60,6 +60,7 @@ function ModeProvider({ export function WorkspacesLayout() { const { + workspaceId, workspace: selectedWorkspace, isLoading, isCreateMode, @@ -72,6 +73,7 @@ export function WorkspacesLayout() { startNewSession, } = useWorkspaceContext(); + // Use workspace-specific panel state (pass undefined when in create mode) const { isLeftSidebarVisible, isLeftMainPanelVisible, @@ -79,7 +81,7 @@ export function WorkspacesLayout() { rightMainPanelMode, setLeftSidebarVisible, setLeftMainPanelVisible, - } = useUiPreferencesStore(); + } = useWorkspacePanelState(isCreateMode ? undefined : workspaceId); const { config, diff --git a/frontend/src/contexts/LogsPanelContext.tsx b/frontend/src/contexts/LogsPanelContext.tsx index 6af70dc7..9ed6f2cf 100644 --- a/frontend/src/contexts/LogsPanelContext.tsx +++ b/frontend/src/contexts/LogsPanelContext.tsx @@ -9,9 +9,10 @@ import { } from 'react'; import type { LogsPanelContent } from '@/components/ui-new/containers/LogsContentContainer'; import { - useUiPreferencesStore, + useWorkspacePanelState, RIGHT_MAIN_PANEL_MODES, } from '@/stores/useUiPreferencesStore'; +import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; interface LogsPanelContextValue { logsPanelContent: LogsPanelContent | null; @@ -50,7 +51,10 @@ interface LogsPanelProviderProps { } export function LogsPanelProvider({ children }: LogsPanelProviderProps) { - const { rightMainPanelMode, setRightMainPanelMode } = useUiPreferencesStore(); + const { workspaceId, isCreateMode } = useWorkspaceContext(); + const { rightMainPanelMode, setRightMainPanelMode } = useWorkspacePanelState( + isCreateMode ? undefined : workspaceId + ); const [logsPanelContent, setLogsPanelContent] = useState(null); diff --git a/frontend/src/contexts/WorkspaceContext.tsx b/frontend/src/contexts/WorkspaceContext.tsx index 40a12fb2..ec7a869d 100644 --- a/frontend/src/contexts/WorkspaceContext.tsx +++ b/frontend/src/contexts/WorkspaceContext.tsx @@ -22,7 +22,6 @@ import { } from '@/hooks/useGitHubComments'; import { useDiffStream } from '@/hooks/useDiffStream'; import { attemptsApi } from '@/lib/api'; -import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore'; import { useDiffViewStore } from '@/stores/useDiffViewStore'; import type { Workspace as ApiWorkspace, @@ -91,13 +90,6 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { // Derive isCreateMode from URL path instead of prop to allow provider to persist across route changes const isCreateMode = location.pathname === '/workspaces/create'; - // Reset UI state when entering create mode - useEffect(() => { - if (isCreateMode) { - useUiPreferencesStore.getState().resetForCreateMode(); - } - }, [isCreateMode]); - // Fetch workspaces for sidebar display const { workspaces: activeWorkspaces, diff --git a/frontend/src/stores/useUiPreferencesStore.ts b/frontend/src/stores/useUiPreferencesStore.ts index d78e0ad7..5a53b117 100644 --- a/frontend/src/stores/useUiPreferencesStore.ts +++ b/frontend/src/stores/useUiPreferencesStore.ts @@ -20,6 +20,17 @@ export type ContextBarPosition = | 'bottom-left' | 'bottom-right'; +// Workspace-specific panel state +export type WorkspacePanelState = { + rightMainPanelMode: RightMainPanelMode | null; + isLeftMainPanelVisible: boolean; +}; + +const DEFAULT_WORKSPACE_PANEL_STATE: WorkspacePanelState = { + rightMainPanelMode: null, + isLeftMainPanelVisible: true, +}; + // Centralized persist keys for type safety export const PERSIST_KEYS = { // Sidebar sections @@ -76,13 +87,14 @@ type State = { paneSizes: Record; collapsedPaths: Record; - // Layout state + // Global layout state (applies across all workspaces) isLeftSidebarVisible: boolean; - isLeftMainPanelVisible: boolean; isRightSidebarVisible: boolean; - rightMainPanelMode: RightMainPanelMode | null; previewRefreshKey: number; + // Workspace-specific panel state + workspacePanelStates: Record; + // UI preferences actions setRepoAction: (repoId: string, action: RepoAction) => void; setExpanded: (key: string, value: boolean) => void; @@ -94,14 +106,26 @@ type State = { // Layout actions toggleLeftSidebar: () => void; - toggleLeftMainPanel: () => void; + toggleLeftMainPanel: (workspaceId?: string) => void; toggleRightSidebar: () => void; - toggleRightMainPanelMode: (mode: RightMainPanelMode) => void; - setRightMainPanelMode: (mode: RightMainPanelMode | null) => void; + toggleRightMainPanelMode: ( + mode: RightMainPanelMode, + workspaceId?: string + ) => void; + setRightMainPanelMode: ( + mode: RightMainPanelMode | null, + workspaceId?: string + ) => void; setLeftSidebarVisible: (value: boolean) => void; - setLeftMainPanelVisible: (value: boolean) => void; + setLeftMainPanelVisible: (value: boolean, workspaceId?: string) => void; triggerPreviewRefresh: () => void; - resetForCreateMode: () => void; + + // Workspace-specific panel state actions + getWorkspacePanelState: (workspaceId: string) => WorkspacePanelState; + setWorkspacePanelState: ( + workspaceId: string, + state: Partial + ) => void; }; export const useUiPreferencesStore = create()( @@ -114,13 +138,14 @@ export const useUiPreferencesStore = create()( paneSizes: {}, collapsedPaths: {}, - // Layout state + // Global layout state isLeftSidebarVisible: true, - isLeftMainPanelVisible: true, isRightSidebarVisible: true, - rightMainPanelMode: null, previewRefreshKey: 0, + // Workspace-specific panel state + workspacePanelStates: {}, + // UI preferences actions setRepoAction: (repoId, action) => set((s) => ({ repoActions: { ...s.repoActions, [repoId]: action } })), @@ -151,59 +176,123 @@ export const useUiPreferencesStore = create()( toggleLeftSidebar: () => set((s) => ({ isLeftSidebarVisible: !s.isLeftSidebarVisible })), - toggleLeftMainPanel: () => { - const { isLeftMainPanelVisible, rightMainPanelMode } = get(); - if (isLeftMainPanelVisible && rightMainPanelMode === null) return; - set({ isLeftMainPanelVisible: !isLeftMainPanelVisible }); + toggleLeftMainPanel: (workspaceId) => { + if (!workspaceId) return; + const state = get(); + const wsState = + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE; + if ( + wsState.isLeftMainPanelVisible && + wsState.rightMainPanelMode === null + ) + return; + set({ + workspacePanelStates: { + ...state.workspacePanelStates, + [workspaceId]: { + ...wsState, + isLeftMainPanelVisible: !wsState.isLeftMainPanelVisible, + }, + }, + }); }, toggleRightSidebar: () => set((s) => ({ isRightSidebarVisible: !s.isRightSidebarVisible })), - toggleRightMainPanelMode: (mode) => { - const { rightMainPanelMode } = get(); - const isCurrentlyActive = rightMainPanelMode === mode; + toggleRightMainPanelMode: (mode, workspaceId) => { + if (!workspaceId) return; + const state = get(); + const wsState = + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE; + const isCurrentlyActive = wsState.rightMainPanelMode === mode; - if (isCurrentlyActive) { - set({ - rightMainPanelMode: null, - isLeftSidebarVisible: true, - }); - } else { - set({ - rightMainPanelMode: mode, - isLeftSidebarVisible: isWideScreen() - ? get().isLeftSidebarVisible + set({ + workspacePanelStates: { + ...state.workspacePanelStates, + [workspaceId]: { + ...wsState, + rightMainPanelMode: isCurrentlyActive ? null : mode, + }, + }, + isLeftSidebarVisible: isCurrentlyActive + ? true + : isWideScreen() + ? state.isLeftSidebarVisible : false, - }); - } + }); }, - setRightMainPanelMode: (mode) => { - if (mode !== null) { - set({ - rightMainPanelMode: mode, + setRightMainPanelMode: (mode, workspaceId) => { + if (!workspaceId) return; + const state = get(); + const wsState = + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE; + set({ + workspacePanelStates: { + ...state.workspacePanelStates, + [workspaceId]: { + ...wsState, + rightMainPanelMode: mode, + }, + }, + ...(mode !== null && { isLeftSidebarVisible: isWideScreen() - ? get().isLeftSidebarVisible + ? state.isLeftSidebarVisible : false, - }); - } else { - set({ rightMainPanelMode: null }); - } + }), + }); }, setLeftSidebarVisible: (value) => set({ isLeftSidebarVisible: value }), - setLeftMainPanelVisible: (value) => - set({ isLeftMainPanelVisible: value }), + setLeftMainPanelVisible: (value, workspaceId) => { + if (!workspaceId) return; + const state = get(); + const wsState = + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE; + set({ + workspacePanelStates: { + ...state.workspacePanelStates, + [workspaceId]: { + ...wsState, + isLeftMainPanelVisible: value, + }, + }, + }); + }, triggerPreviewRefresh: () => set((s) => ({ previewRefreshKey: s.previewRefreshKey + 1 })), - resetForCreateMode: () => + // Workspace-specific panel state actions + getWorkspacePanelState: (workspaceId) => { + const state = get(); + return ( + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE + ); + }, + + setWorkspacePanelState: (workspaceId, panelState) => { + const state = get(); + const currentWsState = + state.workspacePanelStates[workspaceId] ?? + DEFAULT_WORKSPACE_PANEL_STATE; set({ - rightMainPanelMode: null, - }), + workspacePanelStates: { + ...state.workspacePanelStates, + [workspaceId]: { + ...currentWsState, + ...panelState, + }, + }, + }); + }, }), { name: 'ui-preferences', @@ -214,10 +303,11 @@ export const useUiPreferencesStore = create()( contextBarPosition: state.contextBarPosition, paneSizes: state.paneSizes, collapsedPaths: state.collapsedPaths, - // Layout (only persist panel visibility, not mode states) + // Global layout (persist sidebar visibility) isLeftSidebarVisible: state.isLeftSidebarVisible, - isLeftMainPanelVisible: state.isLeftMainPanelVisible, isRightSidebarVisible: state.isRightSidebarVisible, + // Workspace-specific panel state (persisted) + workspacePanelStates: state.workspacePanelStates, }), } ) @@ -301,6 +391,70 @@ export function usePersistedCollapsedPaths( return [pathSet, setPathSet]; } -// Layout convenience hooks -export const useIsRightMainPanelVisible = () => - useUiPreferencesStore((s) => s.rightMainPanelMode !== null); +// Hook for workspace-specific panel state +export function useWorkspacePanelState(workspaceId: string | undefined) { + // Get workspace-specific state (falls back to defaults when no workspaceId) + const workspacePanelStates = useUiPreferencesStore( + (s) => s.workspacePanelStates + ); + const wsState = workspaceId + ? (workspacePanelStates[workspaceId] ?? DEFAULT_WORKSPACE_PANEL_STATE) + : DEFAULT_WORKSPACE_PANEL_STATE; + + // Global state (sidebars are global) + const isLeftSidebarVisible = useUiPreferencesStore( + (s) => s.isLeftSidebarVisible + ); + const isRightSidebarVisible = useUiPreferencesStore( + (s) => s.isRightSidebarVisible + ); + + // Actions from store + const toggleRightMainPanelMode = useUiPreferencesStore( + (s) => s.toggleRightMainPanelMode + ); + const setRightMainPanelMode = useUiPreferencesStore( + (s) => s.setRightMainPanelMode + ); + const setLeftMainPanelVisible = useUiPreferencesStore( + (s) => s.setLeftMainPanelVisible + ); + const setLeftSidebarVisible = useUiPreferencesStore( + (s) => s.setLeftSidebarVisible + ); + + // Memoized callbacks that include workspaceId + const toggleRightMainPanelModeForWorkspace = useCallback( + (mode: RightMainPanelMode) => toggleRightMainPanelMode(mode, workspaceId), + [toggleRightMainPanelMode, workspaceId] + ); + + const setRightMainPanelModeForWorkspace = useCallback( + (mode: RightMainPanelMode | null) => + setRightMainPanelMode(mode, workspaceId), + [setRightMainPanelMode, workspaceId] + ); + + const setLeftMainPanelVisibleForWorkspace = useCallback( + (value: boolean) => setLeftMainPanelVisible(value, workspaceId), + [setLeftMainPanelVisible, workspaceId] + ); + + return { + // Workspace-specific state + rightMainPanelMode: wsState.rightMainPanelMode, + isLeftMainPanelVisible: wsState.isLeftMainPanelVisible, + + // Global state (sidebars) + isLeftSidebarVisible, + isRightSidebarVisible, + + // Workspace-specific actions + toggleRightMainPanelMode: toggleRightMainPanelModeForWorkspace, + setRightMainPanelMode: setRightMainPanelModeForWorkspace, + setLeftMainPanelVisible: setLeftMainPanelVisibleForWorkspace, + + // Global actions + setLeftSidebarVisible, + }; +}