mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-05 16:13:08 +08:00
fix: wait for zustand hydration before workspace initialization
Fix blank page on first load via `ccw view` by waiting for zustand persist hydration to complete before initializing workspace. - Add _hasHydrated state tracking in workflowStore - Add setHasHydrated action to mark hydration complete - Update AppShell to wait for hydration before calling switchWorkspace - Ensures projectPath is properly restored from localStorage before queries execute
This commit is contained in:
@@ -40,15 +40,23 @@ export function AppShell({
|
|||||||
// Workspace initialization from URL query parameter
|
// Workspace initialization from URL query parameter
|
||||||
const switchWorkspace = useWorkflowStore((state) => state.switchWorkspace);
|
const switchWorkspace = useWorkflowStore((state) => state.switchWorkspace);
|
||||||
const projectPath = useWorkflowStore((state) => state.projectPath);
|
const projectPath = useWorkflowStore((state) => state.projectPath);
|
||||||
|
const hasHydrated = useWorkflowStore((state) => state._hasHydrated);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Immersive mode (fullscreen) - hide chrome
|
// Immersive mode (fullscreen) - hide chrome
|
||||||
const isImmersiveMode = useAppStore(selectIsImmersiveMode);
|
const isImmersiveMode = useAppStore(selectIsImmersiveMode);
|
||||||
|
|
||||||
// Workspace initialization logic (URL > localStorage)
|
// Workspace initialization logic (URL > localStorage)
|
||||||
|
// Wait for zustand persist hydration to complete before initializing
|
||||||
const [isWorkspaceInitialized, setWorkspaceInitialized] = useState(false);
|
const [isWorkspaceInitialized, setWorkspaceInitialized] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
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.
|
// This effect should only run once to decide the initial workspace.
|
||||||
if (isWorkspaceInitialized) {
|
if (isWorkspaceInitialized) {
|
||||||
return;
|
return;
|
||||||
@@ -76,7 +84,7 @@ export function AppShell({
|
|||||||
|
|
||||||
// Mark as initialized regardless of whether a path was found.
|
// Mark as initialized regardless of whether a path was found.
|
||||||
setWorkspaceInitialized(true);
|
setWorkspaceInitialized(true);
|
||||||
}, [isWorkspaceInitialized, projectPath, location.search, switchWorkspace]);
|
}, [hasHydrated, isWorkspaceInitialized, projectPath, location.search, switchWorkspace]);
|
||||||
|
|
||||||
// Sidebar collapse state – default to collapsed (hidden)
|
// Sidebar collapse state – default to collapsed (hidden)
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ const initialState: WorkflowState = {
|
|||||||
// Filters and sorting
|
// Filters and sorting
|
||||||
filters: defaultFilters,
|
filters: defaultFilters,
|
||||||
sorting: defaultSorting,
|
sorting: defaultSorting,
|
||||||
|
|
||||||
|
// Hydration state (internal)
|
||||||
|
_hasHydrated: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWorkflowStore = create<WorkflowStore>()(
|
export const useWorkflowStore = create<WorkflowStore>()(
|
||||||
@@ -414,6 +417,10 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
set({ _invalidateQueriesCallback: callback }, false, 'registerQueryInvalidator');
|
set({ _invalidateQueriesCallback: callback }, false, 'registerQueryInvalidator');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setHasHydrated: (state: boolean) => {
|
||||||
|
set({ _hasHydrated: state }, false, 'setHasHydrated');
|
||||||
|
},
|
||||||
|
|
||||||
// ========== Filters and Sorting ==========
|
// ========== Filters and Sorting ==========
|
||||||
|
|
||||||
setFilters: (filters: Partial<WorkflowFilters>) => {
|
setFilters: (filters: Partial<WorkflowFilters>) => {
|
||||||
@@ -538,6 +545,8 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
console.error('[WorkflowStore] Rehydration error:', error);
|
console.error('[WorkflowStore] Rehydration error:', error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Mark hydration as complete
|
||||||
|
useWorkflowStore.getState().setHasHydrated(true);
|
||||||
if (state?.projectPath) {
|
if (state?.projectPath) {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|||||||
@@ -335,6 +335,9 @@ export interface WorkflowState {
|
|||||||
|
|
||||||
// Query invalidation callback (internal)
|
// Query invalidation callback (internal)
|
||||||
_invalidateQueriesCallback?: () => void;
|
_invalidateQueriesCallback?: () => void;
|
||||||
|
|
||||||
|
// Hydration state (internal)
|
||||||
|
_hasHydrated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowActions {
|
export interface WorkflowActions {
|
||||||
@@ -369,6 +372,7 @@ export interface WorkflowActions {
|
|||||||
removeRecentPath: (path: string) => Promise<void>;
|
removeRecentPath: (path: string) => Promise<void>;
|
||||||
refreshRecentPaths: () => Promise<void>;
|
refreshRecentPaths: () => Promise<void>;
|
||||||
registerQueryInvalidator: (callback: () => void) => void;
|
registerQueryInvalidator: (callback: () => void) => void;
|
||||||
|
setHasHydrated: (state: boolean) => void;
|
||||||
|
|
||||||
// Filters and sorting
|
// Filters and sorting
|
||||||
setFilters: (filters: Partial<WorkflowFilters>) => void;
|
setFilters: (filters: Partial<WorkflowFilters>) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user