fix: restore auth and auto-open last project

This commit is contained in:
DhanushSantosh
2026-01-11 20:43:55 +05:30
parent 6e4b611662
commit 785a4d2c3b
8 changed files with 378 additions and 30 deletions

View File

@@ -89,6 +89,7 @@ export function useSettingsSync(): SettingsSyncState {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
const authChecked = useAuthStore((s) => s.authChecked);
const settingsLoaded = useAuthStore((s) => s.settingsLoaded);
const syncTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastSyncedRef = useRef<string>('');
@@ -117,9 +118,17 @@ export function useSettingsSync(): SettingsSyncState {
// Debounced sync function
const syncToServer = useCallback(async () => {
try {
// Never sync when not authenticated (prevents overwriting server settings during logout/login transitions)
// Never sync when not authenticated or settings not loaded
// The settingsLoaded flag ensures we don't sync default empty state before hydration
const auth = useAuthStore.getState();
if (!auth.authChecked || !auth.isAuthenticated) {
logger.debug('syncToServer check:', {
authChecked: auth.authChecked,
isAuthenticated: auth.isAuthenticated,
settingsLoaded: auth.settingsLoaded,
projectsCount: useAppStore.getState().projects?.length ?? 0,
});
if (!auth.authChecked || !auth.isAuthenticated || !auth.settingsLoaded) {
logger.debug('Sync skipped: not authenticated or settings not loaded');
return;
}
@@ -127,6 +136,8 @@ export function useSettingsSync(): SettingsSyncState {
const api = getHttpApiClient();
const appState = useAppStore.getState();
logger.debug('Syncing to server:', { projectsCount: appState.projects?.length ?? 0 });
// Build updates object from current state
const updates: Record<string, unknown> = {};
for (const field of SETTINGS_FIELDS_TO_SYNC) {
@@ -147,10 +158,13 @@ export function useSettingsSync(): SettingsSyncState {
// Create a hash of the updates to avoid redundant syncs
const updateHash = JSON.stringify(updates);
if (updateHash === lastSyncedRef.current) {
logger.debug('Sync skipped: no changes');
setState((s) => ({ ...s, syncing: false }));
return;
}
logger.info('Sending settings update:', { projects: updates.projects });
const result = await api.settings.updateGlobal(updates);
if (result.success) {
lastSyncedRef.current = updateHash;
@@ -184,11 +198,20 @@ export function useSettingsSync(): SettingsSyncState {
void syncToServer();
}, [syncToServer]);
// Initialize sync - WAIT for migration to complete first
// Initialize sync - WAIT for settings to be loaded and migration to complete
useEffect(() => {
// Don't initialize syncing until we know auth status and are authenticated.
// Prevents accidental overwrites when the app boots before settings are hydrated.
if (!authChecked || !isAuthenticated) return;
// Don't initialize syncing until:
// 1. Auth has been checked
// 2. User is authenticated
// 3. Settings have been loaded from server (settingsLoaded flag)
// This prevents syncing empty/default state before hydration completes.
logger.debug('useSettingsSync initialization check:', {
authChecked,
isAuthenticated,
settingsLoaded,
stateLoaded: state.loaded,
});
if (!authChecked || !isAuthenticated || !settingsLoaded) return;
if (isInitializedRef.current) return;
isInitializedRef.current = true;
@@ -198,14 +221,26 @@ export function useSettingsSync(): SettingsSyncState {
await waitForApiKeyInit();
// CRITICAL: Wait for migration/hydration to complete before we start syncing
// This prevents overwriting server data with empty/default state
// This is a backup to the settingsLoaded flag for extra safety
logger.info('Waiting for migration to complete before starting sync...');
await waitForMigrationComplete();
// Wait for React to finish rendering after store hydration.
// Zustand's subscribe() fires during setState(), which happens BEFORE React's
// render completes. Use a small delay to ensure all pending state updates
// have propagated through the React tree before we read state.
await new Promise((resolve) => setTimeout(resolve, 50));
logger.info('Migration complete, initializing sync');
// Read state - at this point React has processed the store update
const appState = useAppStore.getState();
const setupState = useSetupStore.getState();
logger.info('Initial state read:', { projectsCount: appState.projects?.length ?? 0 });
// Store the initial state hash to avoid immediate re-sync
// (migration has already hydrated the store from server/localStorage)
const appState = useAppStore.getState();
const updates: Record<string, unknown> = {};
for (const field of SETTINGS_FIELDS_TO_SYNC) {
if (field === 'currentProjectId') {
@@ -214,7 +249,6 @@ export function useSettingsSync(): SettingsSyncState {
updates[field] = appState[field as keyof typeof appState];
}
}
const setupState = useSetupStore.getState();
for (const field of SETUP_FIELDS_TO_SYNC) {
updates[field] = setupState[field as keyof typeof setupState];
}
@@ -233,16 +267,33 @@ export function useSettingsSync(): SettingsSyncState {
}
initializeSync();
}, [authChecked, isAuthenticated]);
}, [authChecked, isAuthenticated, settingsLoaded]);
// Subscribe to store changes and sync to server
useEffect(() => {
if (!state.loaded || !authChecked || !isAuthenticated) return;
if (!state.loaded || !authChecked || !isAuthenticated || !settingsLoaded) return;
// Subscribe to app store changes
const unsubscribeApp = useAppStore.subscribe((newState, prevState) => {
const auth = useAuthStore.getState();
logger.debug('Store subscription fired:', {
prevProjects: prevState.projects?.length ?? 0,
newProjects: newState.projects?.length ?? 0,
authChecked: auth.authChecked,
isAuthenticated: auth.isAuthenticated,
settingsLoaded: auth.settingsLoaded,
loaded: state.loaded,
});
// Don't sync if settings not loaded yet
if (!auth.settingsLoaded) {
logger.debug('Store changed but settings not loaded, skipping sync');
return;
}
// If the current project changed, sync immediately so we can restore on next launch
if (newState.currentProject?.id !== prevState.currentProject?.id) {
logger.debug('Current project changed, syncing immediately');
syncNow();
return;
}
@@ -266,6 +317,7 @@ export function useSettingsSync(): SettingsSyncState {
}
if (changed) {
logger.debug('Store changed, scheduling sync');
scheduleSyncToServer();
}
});
@@ -294,11 +346,11 @@ export function useSettingsSync(): SettingsSyncState {
clearTimeout(syncTimeoutRef.current);
}
};
}, [state.loaded, authChecked, isAuthenticated, scheduleSyncToServer, syncNow]);
}, [state.loaded, authChecked, isAuthenticated, settingsLoaded, scheduleSyncToServer, syncNow]);
// Best-effort flush on tab close / backgrounding
useEffect(() => {
if (!state.loaded || !authChecked || !isAuthenticated) return;
if (!state.loaded || !authChecked || !isAuthenticated || !settingsLoaded) return;
const handleBeforeUnload = () => {
// Fire-and-forget; may not complete in all browsers, but helps in Electron/webview
@@ -318,7 +370,7 @@ export function useSettingsSync(): SettingsSyncState {
window.removeEventListener('beforeunload', handleBeforeUnload);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [state.loaded, authChecked, isAuthenticated, syncNow]);
}, [state.loaded, authChecked, isAuthenticated, settingsLoaded, syncNow]);
return state;
}