fix(ui): restore startup project context

This commit is contained in:
DhanushSantosh
2026-01-11 21:58:36 +05:30
parent 785a4d2c3b
commit d724e782dd
5 changed files with 98 additions and 41 deletions

1
.gitignore vendored
View File

@@ -95,3 +95,4 @@ check-sync.sh
# API key files # API key files
data/.api-key data/.api-key
data/credentials.json data/credentials.json
data/

View File

@@ -517,8 +517,9 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
} }
// Save theme to localStorage for fallback when server settings aren't available // Save theme to localStorage for fallback when server settings aren't available
if (settings.theme) { const storedTheme = (currentProject?.theme as string | undefined) || settings.theme;
setItem(THEME_STORAGE_KEY, settings.theme); if (storedTheme) {
setItem(THEME_STORAGE_KEY, storedTheme);
} }
useAppStore.setState({ useAppStore.setState({

View File

@@ -43,6 +43,12 @@ const NO_STORE_CACHE_MODE: RequestCache = 'no-store';
const AUTO_OPEN_HISTORY_INDEX = 0; const AUTO_OPEN_HISTORY_INDEX = 0;
const SINGLE_PROJECT_COUNT = 1; const SINGLE_PROJECT_COUNT = 1;
const DEFAULT_LAST_OPENED_TIME_MS = 0; const DEFAULT_LAST_OPENED_TIME_MS = 0;
const AUTO_OPEN_STATUS = {
idle: 'idle',
opening: 'opening',
done: 'done',
} as const;
type AutoOpenStatus = (typeof AUTO_OPEN_STATUS)[keyof typeof AUTO_OPEN_STATUS];
// Apply stored theme immediately on page load (before React hydration) // Apply stored theme immediately on page load (before React hydration)
// This prevents flash of default theme on login/setup pages // This prevents flash of default theme on login/setup pages
@@ -160,6 +166,7 @@ function RootLayoutContent() {
const [streamerPanelOpen, setStreamerPanelOpen] = useState(false); const [streamerPanelOpen, setStreamerPanelOpen] = useState(false);
const authChecked = useAuthStore((s) => s.authChecked); const authChecked = useAuthStore((s) => s.authChecked);
const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
const settingsLoaded = useAuthStore((s) => s.settingsLoaded);
const { openFileBrowser } = useFileBrowser(); const { openFileBrowser } = useFileBrowser();
// Load project settings when switching projects // Load project settings when switching projects
@@ -171,6 +178,20 @@ function RootLayoutContent() {
const isDashboardRoute = location.pathname === '/dashboard'; const isDashboardRoute = location.pathname === '/dashboard';
const isBoardRoute = location.pathname === '/board'; const isBoardRoute = location.pathname === '/board';
const isRootRoute = location.pathname === '/'; const isRootRoute = location.pathname === '/';
const [autoOpenStatus, setAutoOpenStatus] = useState<AutoOpenStatus>(AUTO_OPEN_STATUS.idle);
const autoOpenCandidate = selectAutoOpenProject(currentProject, projects, projectHistory);
const canAutoOpen =
authChecked &&
isAuthenticated &&
settingsLoaded &&
setupComplete &&
!isLoginRoute &&
!isLoggedOutRoute &&
!isSetupRoute &&
!!autoOpenCandidate;
const shouldAutoOpen = canAutoOpen && autoOpenStatus !== AUTO_OPEN_STATUS.done;
const shouldBlockForSettings =
authChecked && isAuthenticated && !settingsLoaded && !isLoginRoute && !isLoggedOutRoute;
// Sandbox environment check state // Sandbox environment check state
type SandboxStatus = 'pending' | 'containerized' | 'needs-confirmation' | 'denied' | 'confirmed'; type SandboxStatus = 'pending' | 'containerized' | 'needs-confirmation' | 'denied' | 'confirmed';
@@ -298,7 +319,6 @@ function RootLayoutContent() {
// Ref to prevent concurrent auth checks from running // Ref to prevent concurrent auth checks from running
const authCheckRunning = useRef(false); const authCheckRunning = useRef(false);
const autoOpenAttemptedRef = useRef(false);
// Global listener for 401/403 responses during normal app usage. // Global listener for 401/403 responses during normal app usage.
// This is triggered by the HTTP client whenever an authenticated request returns 401/403. // This is triggered by the HTTP client whenever an authenticated request returns 401/403.
@@ -479,9 +499,6 @@ function RootLayoutContent() {
// Note: Settings are now loaded in __root.tsx after successful session verification // Note: Settings are now loaded in __root.tsx after successful session verification
// This ensures a unified flow across all modes (Electron, web, external server) // This ensures a unified flow across all modes (Electron, web, external server)
// Get settingsLoaded from auth store for routing decisions
const settingsLoaded = useAuthStore((s) => s.settingsLoaded);
// Routing rules (ALL modes - unified flow): // Routing rules (ALL modes - unified flow):
// - If not authenticated: force /logged-out (even /setup is protected) // - If not authenticated: force /logged-out (even /setup is protected)
// - If authenticated but setup incomplete: force /setup // - If authenticated but setup incomplete: force /setup
@@ -603,6 +620,9 @@ function RootLayoutContent() {
// Redirect from welcome page based on project state // Redirect from welcome page based on project state
useEffect(() => { useEffect(() => {
if (isMounted && isRootRoute) { if (isMounted && isRootRoute) {
if (!settingsLoaded || shouldAutoOpen) {
return;
}
if (currentProject) { if (currentProject) {
// Project is selected, go to board // Project is selected, go to board
navigate({ to: '/board' }); navigate({ to: '/board' });
@@ -611,23 +631,20 @@ function RootLayoutContent() {
navigate({ to: '/dashboard' }); navigate({ to: '/dashboard' });
} }
} }
}, [isMounted, currentProject, isRootRoute, navigate]); }, [isMounted, currentProject, isRootRoute, navigate, shouldAutoOpen, settingsLoaded]);
// Auto-open the most recent project on startup // Auto-open the most recent project on startup
useEffect(() => { useEffect(() => {
if (autoOpenAttemptedRef.current) return; if (!canAutoOpen) return;
if (!authChecked || !isAuthenticated || !settingsLoaded) return; if (autoOpenStatus !== AUTO_OPEN_STATUS.idle) return;
if (!setupComplete) return;
if (isLoginRoute || isLoggedOutRoute || isSetupRoute) return;
if (isBoardRoute) return;
const projectToOpen = selectAutoOpenProject(currentProject, projects, projectHistory); if (!autoOpenCandidate) return;
if (!projectToOpen) return;
autoOpenAttemptedRef.current = true; setAutoOpenStatus(AUTO_OPEN_STATUS.opening);
const openProject = async () => { const openProject = async () => {
const initResult = await initializeProject(projectToOpen.path); try {
const initResult = await initializeProject(autoOpenCandidate.path);
if (!initResult.success) { if (!initResult.success) {
logger.warn('Auto-open project failed:', initResult.error); logger.warn('Auto-open project failed:', initResult.error);
if (isRootRoute) { if (isRootRoute) {
@@ -636,31 +653,36 @@ function RootLayoutContent() {
return; return;
} }
if (!currentProject || currentProject.id !== projectToOpen.id) { if (!currentProject || currentProject.id !== autoOpenCandidate.id) {
upsertAndSetCurrentProject(projectToOpen.path, projectToOpen.name, projectToOpen.theme); upsertAndSetCurrentProject(
autoOpenCandidate.path,
autoOpenCandidate.name,
autoOpenCandidate.theme
);
} }
if (!isBoardRoute) { if (isRootRoute) {
navigate({ to: '/board' }); navigate({ to: '/board' });
} }
} catch (error) {
logger.error('Auto-open project crashed:', error);
if (isRootRoute) {
navigate({ to: '/dashboard' });
}
} finally {
setAutoOpenStatus(AUTO_OPEN_STATUS.done);
}
}; };
void openProject(); void openProject();
}, [ }, [
authChecked, canAutoOpen,
isAuthenticated, autoOpenStatus,
settingsLoaded, autoOpenCandidate,
setupComplete,
isLoginRoute,
isLoggedOutRoute,
isSetupRoute,
isBoardRoute,
isRootRoute,
currentProject, currentProject,
projects,
projectHistory,
navigate, navigate,
upsertAndSetCurrentProject, upsertAndSetCurrentProject,
isRootRoute,
]); ]);
// Bootstrap Codex models on app startup (after auth completes) // Bootstrap Codex models on app startup (after auth completes)
@@ -736,6 +758,22 @@ function RootLayoutContent() {
); );
} }
if (shouldBlockForSettings) {
return (
<main className="flex h-screen items-center justify-center" data-testid="app-container">
<LoadingState message="Loading settings..." />
</main>
);
}
if (shouldAutoOpen) {
return (
<main className="flex h-screen items-center justify-center" data-testid="app-container">
<LoadingState message="Opening project..." />
</main>
);
}
// Show setup page (full screen, no sidebar) - authenticated only // Show setup page (full screen, no sidebar) - authenticated only
if (isSetupRoute) { if (isSetupRoute) {
return ( return (

View File

@@ -114,6 +114,12 @@ function saveThemeToStorage(theme: ThemeMode): void {
setItem(THEME_STORAGE_KEY, theme); setItem(THEME_STORAGE_KEY, theme);
} }
function persistEffectiveThemeForProject(project: Project | null, fallbackTheme: ThemeMode): void {
const projectTheme = project?.theme as ThemeMode | undefined;
const themeToStore = projectTheme ?? fallbackTheme;
saveThemeToStorage(themeToStore);
}
export type BoardViewMode = 'kanban' | 'graph'; export type BoardViewMode = 'kanban' | 'graph';
export interface ApiKeys { export interface ApiKeys {
@@ -1241,13 +1247,16 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
}; };
const isCurrent = get().currentProject?.id === projectId; const isCurrent = get().currentProject?.id === projectId;
const nextCurrentProject = isCurrent ? null : get().currentProject;
set({ set({
projects: remainingProjects, projects: remainingProjects,
trashedProjects: [trashedProject, ...existingTrash], trashedProjects: [trashedProject, ...existingTrash],
currentProject: isCurrent ? null : get().currentProject, currentProject: nextCurrentProject,
currentView: isCurrent ? 'welcome' : get().currentView, currentView: isCurrent ? 'welcome' : get().currentView,
}); });
persistEffectiveThemeForProject(nextCurrentProject, get().theme);
}, },
restoreTrashedProject: (projectId) => { restoreTrashedProject: (projectId) => {
@@ -1266,6 +1275,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
currentProject: samePathProject, currentProject: samePathProject,
currentView: 'board', currentView: 'board',
}); });
persistEffectiveThemeForProject(samePathProject, get().theme);
return; return;
} }
@@ -1283,6 +1293,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
currentProject: restoredProject, currentProject: restoredProject,
currentView: 'board', currentView: 'board',
}); });
persistEffectiveThemeForProject(restoredProject, get().theme);
}, },
deleteTrashedProject: (projectId) => { deleteTrashedProject: (projectId) => {
@@ -1302,6 +1313,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setCurrentProject: (project) => { setCurrentProject: (project) => {
set({ currentProject: project }); set({ currentProject: project });
persistEffectiveThemeForProject(project, get().theme);
if (project) { if (project) {
set({ currentView: 'board' }); set({ currentView: 'board' });
// Add to project history (MRU order) // Add to project history (MRU order)
@@ -1385,6 +1397,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
projectHistoryIndex: newIndex, projectHistoryIndex: newIndex,
currentView: 'board', currentView: 'board',
}); });
persistEffectiveThemeForProject(targetProject, get().theme);
} }
}, },
@@ -1418,6 +1431,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
projectHistoryIndex: newIndex, projectHistoryIndex: newIndex,
currentView: 'board', currentView: 'board',
}); });
persistEffectiveThemeForProject(targetProject, get().theme);
} }
}, },
@@ -1477,12 +1491,14 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
// Also update currentProject if it's the same project // Also update currentProject if it's the same project
const currentProject = get().currentProject; const currentProject = get().currentProject;
if (currentProject?.id === projectId) { if (currentProject?.id === projectId) {
const updatedTheme = theme === null ? undefined : theme;
set({ set({
currentProject: { currentProject: {
...currentProject, ...currentProject,
theme: theme === null ? undefined : theme, theme: updatedTheme,
}, },
}); });
persistEffectiveThemeForProject({ ...currentProject, theme: updatedTheme }, get().theme);
} }
}, },

View File

@@ -3,9 +3,10 @@ import { routeTree } from '../routeTree.gen';
// Use browser history in web mode (for e2e tests and dev), memory history in Electron // Use browser history in web mode (for e2e tests and dev), memory history in Electron
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined; const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined;
const BOARD_ROUTE_PATH = '/board';
const history = isElectron const history = isElectron
? createMemoryHistory({ initialEntries: [window.location.pathname || '/'] }) ? createMemoryHistory({ initialEntries: [BOARD_ROUTE_PATH] })
: createBrowserHistory(); : createBrowserHistory();
export const router = createRouter({ export const router = createRouter({