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
data/.api-key
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
if (settings.theme) {
setItem(THEME_STORAGE_KEY, settings.theme);
const storedTheme = (currentProject?.theme as string | undefined) || settings.theme;
if (storedTheme) {
setItem(THEME_STORAGE_KEY, storedTheme);
}
useAppStore.setState({

View File

@@ -43,6 +43,12 @@ const NO_STORE_CACHE_MODE: RequestCache = 'no-store';
const AUTO_OPEN_HISTORY_INDEX = 0;
const SINGLE_PROJECT_COUNT = 1;
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)
// This prevents flash of default theme on login/setup pages
@@ -160,6 +166,7 @@ function RootLayoutContent() {
const [streamerPanelOpen, setStreamerPanelOpen] = useState(false);
const authChecked = useAuthStore((s) => s.authChecked);
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
const settingsLoaded = useAuthStore((s) => s.settingsLoaded);
const { openFileBrowser } = useFileBrowser();
// Load project settings when switching projects
@@ -171,6 +178,20 @@ function RootLayoutContent() {
const isDashboardRoute = location.pathname === '/dashboard';
const isBoardRoute = location.pathname === '/board';
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
type SandboxStatus = 'pending' | 'containerized' | 'needs-confirmation' | 'denied' | 'confirmed';
@@ -298,7 +319,6 @@ function RootLayoutContent() {
// Ref to prevent concurrent auth checks from running
const authCheckRunning = useRef(false);
const autoOpenAttemptedRef = useRef(false);
// Global listener for 401/403 responses during normal app usage.
// 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
// 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):
// - If not authenticated: force /logged-out (even /setup is protected)
// - If authenticated but setup incomplete: force /setup
@@ -603,6 +620,9 @@ function RootLayoutContent() {
// Redirect from welcome page based on project state
useEffect(() => {
if (isMounted && isRootRoute) {
if (!settingsLoaded || shouldAutoOpen) {
return;
}
if (currentProject) {
// Project is selected, go to board
navigate({ to: '/board' });
@@ -611,56 +631,58 @@ function RootLayoutContent() {
navigate({ to: '/dashboard' });
}
}
}, [isMounted, currentProject, isRootRoute, navigate]);
}, [isMounted, currentProject, isRootRoute, navigate, shouldAutoOpen, settingsLoaded]);
// Auto-open the most recent project on startup
useEffect(() => {
if (autoOpenAttemptedRef.current) return;
if (!authChecked || !isAuthenticated || !settingsLoaded) return;
if (!setupComplete) return;
if (isLoginRoute || isLoggedOutRoute || isSetupRoute) return;
if (isBoardRoute) return;
if (!canAutoOpen) return;
if (autoOpenStatus !== AUTO_OPEN_STATUS.idle) return;
const projectToOpen = selectAutoOpenProject(currentProject, projects, projectHistory);
if (!projectToOpen) return;
if (!autoOpenCandidate) return;
autoOpenAttemptedRef.current = true;
setAutoOpenStatus(AUTO_OPEN_STATUS.opening);
const openProject = async () => {
const initResult = await initializeProject(projectToOpen.path);
if (!initResult.success) {
logger.warn('Auto-open project failed:', initResult.error);
try {
const initResult = await initializeProject(autoOpenCandidate.path);
if (!initResult.success) {
logger.warn('Auto-open project failed:', initResult.error);
if (isRootRoute) {
navigate({ to: '/dashboard' });
}
return;
}
if (!currentProject || currentProject.id !== autoOpenCandidate.id) {
upsertAndSetCurrentProject(
autoOpenCandidate.path,
autoOpenCandidate.name,
autoOpenCandidate.theme
);
}
if (isRootRoute) {
navigate({ to: '/board' });
}
} catch (error) {
logger.error('Auto-open project crashed:', error);
if (isRootRoute) {
navigate({ to: '/dashboard' });
}
return;
}
if (!currentProject || currentProject.id !== projectToOpen.id) {
upsertAndSetCurrentProject(projectToOpen.path, projectToOpen.name, projectToOpen.theme);
}
if (!isBoardRoute) {
navigate({ to: '/board' });
} finally {
setAutoOpenStatus(AUTO_OPEN_STATUS.done);
}
};
void openProject();
}, [
authChecked,
isAuthenticated,
settingsLoaded,
setupComplete,
isLoginRoute,
isLoggedOutRoute,
isSetupRoute,
isBoardRoute,
isRootRoute,
canAutoOpen,
autoOpenStatus,
autoOpenCandidate,
currentProject,
projects,
projectHistory,
navigate,
upsertAndSetCurrentProject,
isRootRoute,
]);
// 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
if (isSetupRoute) {
return (

View File

@@ -114,6 +114,12 @@ function saveThemeToStorage(theme: ThemeMode): void {
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 interface ApiKeys {
@@ -1241,13 +1247,16 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
};
const isCurrent = get().currentProject?.id === projectId;
const nextCurrentProject = isCurrent ? null : get().currentProject;
set({
projects: remainingProjects,
trashedProjects: [trashedProject, ...existingTrash],
currentProject: isCurrent ? null : get().currentProject,
currentProject: nextCurrentProject,
currentView: isCurrent ? 'welcome' : get().currentView,
});
persistEffectiveThemeForProject(nextCurrentProject, get().theme);
},
restoreTrashedProject: (projectId) => {
@@ -1266,6 +1275,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
currentProject: samePathProject,
currentView: 'board',
});
persistEffectiveThemeForProject(samePathProject, get().theme);
return;
}
@@ -1283,6 +1293,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
currentProject: restoredProject,
currentView: 'board',
});
persistEffectiveThemeForProject(restoredProject, get().theme);
},
deleteTrashedProject: (projectId) => {
@@ -1302,6 +1313,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setCurrentProject: (project) => {
set({ currentProject: project });
persistEffectiveThemeForProject(project, get().theme);
if (project) {
set({ currentView: 'board' });
// Add to project history (MRU order)
@@ -1385,6 +1397,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
projectHistoryIndex: newIndex,
currentView: 'board',
});
persistEffectiveThemeForProject(targetProject, get().theme);
}
},
@@ -1418,6 +1431,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
projectHistoryIndex: newIndex,
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
const currentProject = get().currentProject;
if (currentProject?.id === projectId) {
const updatedTheme = theme === null ? undefined : theme;
set({
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
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined;
const BOARD_ROUTE_PATH = '/board';
const history = isElectron
? createMemoryHistory({ initialEntries: [window.location.pathname || '/'] })
? createMemoryHistory({ initialEntries: [BOARD_ROUTE_PATH] })
: createBrowserHistory();
export const router = createRouter({