diff --git a/ccw/frontend/src/components/layout/AppShell.tsx b/ccw/frontend/src/components/layout/AppShell.tsx index b579a0b0..d3ad6246 100644 --- a/ccw/frontend/src/components/layout/AppShell.tsx +++ b/ccw/frontend/src/components/layout/AppShell.tsx @@ -40,15 +40,23 @@ export function AppShell({ // Workspace initialization from URL query parameter const switchWorkspace = useWorkflowStore((state) => state.switchWorkspace); const projectPath = useWorkflowStore((state) => state.projectPath); + const hasHydrated = useWorkflowStore((state) => state._hasHydrated); const location = useLocation(); // Immersive mode (fullscreen) - hide chrome const isImmersiveMode = useAppStore(selectIsImmersiveMode); // Workspace initialization logic (URL > localStorage) + // Wait for zustand persist hydration to complete before initializing const [isWorkspaceInitialized, setWorkspaceInitialized] = useState(false); useEffect(() => { + // Wait for hydration to complete before initializing workspace + // This ensures projectPath is properly restored from localStorage + if (!hasHydrated) { + return; + } + // This effect should only run once to decide the initial workspace. if (isWorkspaceInitialized) { return; @@ -76,7 +84,7 @@ export function AppShell({ // Mark as initialized regardless of whether a path was found. setWorkspaceInitialized(true); - }, [isWorkspaceInitialized, projectPath, location.search, switchWorkspace]); + }, [hasHydrated, isWorkspaceInitialized, projectPath, location.search, switchWorkspace]); // Sidebar collapse state – default to collapsed (hidden) const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { diff --git a/ccw/frontend/src/stores/workflowStore.ts b/ccw/frontend/src/stores/workflowStore.ts index 2a136cbc..797f610f 100644 --- a/ccw/frontend/src/stores/workflowStore.ts +++ b/ccw/frontend/src/stores/workflowStore.ts @@ -56,6 +56,9 @@ const initialState: WorkflowState = { // Filters and sorting filters: defaultFilters, sorting: defaultSorting, + + // Hydration state (internal) + _hasHydrated: false, }; export const useWorkflowStore = create()( @@ -414,6 +417,10 @@ export const useWorkflowStore = create()( set({ _invalidateQueriesCallback: callback }, false, 'registerQueryInvalidator'); }, + setHasHydrated: (state: boolean) => { + set({ _hasHydrated: state }, false, 'setHasHydrated'); + }, + // ========== Filters and Sorting ========== setFilters: (filters: Partial) => { @@ -538,6 +545,8 @@ export const useWorkflowStore = create()( console.error('[WorkflowStore] Rehydration error:', error); return; } + // Mark hydration as complete + useWorkflowStore.getState().setHasHydrated(true); if (state?.projectPath) { if (process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console diff --git a/ccw/frontend/src/types/store.ts b/ccw/frontend/src/types/store.ts index 170e5522..5729a9fa 100644 --- a/ccw/frontend/src/types/store.ts +++ b/ccw/frontend/src/types/store.ts @@ -335,6 +335,9 @@ export interface WorkflowState { // Query invalidation callback (internal) _invalidateQueriesCallback?: () => void; + + // Hydration state (internal) + _hasHydrated: boolean; } export interface WorkflowActions { @@ -369,6 +372,7 @@ export interface WorkflowActions { removeRecentPath: (path: string) => Promise; refreshRecentPaths: () => Promise; registerQueryInvalidator: (callback: () => void) => void; + setHasHydrated: (state: boolean) => void; // Filters and sorting setFilters: (filters: Partial) => void;