From e3b689214f11d549ffe1b3a347476d58f25c5aca Mon Sep 17 00:00:00 2001 From: PaloSP <32291845+PaloSP@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:46:01 +0100 Subject: [PATCH] feat: persist active tab across reloads via localStorage (#414) * feat: persist active tab across reloads via localStorage (closes #387) Remember the last active tab in localStorage instead of always resetting to 'chat'. Also stop force-switching to chat tab on session change, so users stay on their preferred tab (shell, git, etc.). * fix: validate localStorage tab value and add try-catch for restricted contexts Address CodeRabbit feedback: validate stored activeTab against known tab IDs before using it, and wrap localStorage access in try-catch to prevent crashes in restricted environments. --- src/hooks/useProjectsState.ts | 38 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/hooks/useProjectsState.ts b/src/hooks/useProjectsState.ts index 9dc3f22..ff7d35b 100644 --- a/src/hooks/useProjectsState.ts +++ b/src/hooks/useProjectsState.ts @@ -101,6 +101,20 @@ const isUpdateAdditive = ( ); }; +const VALID_TABS: Set = new Set(['chat', 'files', 'shell', 'git', 'tasks', 'preview']); + +const readPersistedTab = (): AppTab => { + try { + const stored = localStorage.getItem('activeTab'); + if (stored && VALID_TABS.has(stored)) { + return stored as AppTab; + } + } catch { + // localStorage unavailable + } + return 'chat'; +}; + export function useProjectsState({ sessionId, navigate, @@ -111,7 +125,16 @@ export function useProjectsState({ const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState(null); const [selectedSession, setSelectedSession] = useState(null); - const [activeTab, setActiveTab] = useState('chat'); + const [activeTab, setActiveTab] = useState(readPersistedTab); + + useEffect(() => { + try { + localStorage.setItem('activeTab', activeTab); + } catch { + // Silently ignore storage errors + } + }, [activeTab]); + const [sidebarOpen, setSidebarOpen] = useState(false); const [isLoadingProjects, setIsLoadingProjects] = useState(true); const [loadingProgress, setLoadingProgress] = useState(null); @@ -265,8 +288,6 @@ export function useProjectsState({ return; } - const shouldSwitchTab = !selectedSession || selectedSession.id !== sessionId; - for (const project of projects) { const claudeSession = project.sessions?.find((session) => session.id === sessionId); if (claudeSession) { @@ -280,9 +301,6 @@ export function useProjectsState({ if (shouldUpdateSession) { setSelectedSession({ ...claudeSession, __provider: 'claude' }); } - if (shouldSwitchTab) { - setActiveTab('chat'); - } return; } @@ -298,9 +316,6 @@ export function useProjectsState({ if (shouldUpdateSession) { setSelectedSession({ ...cursorSession, __provider: 'cursor' }); } - if (shouldSwitchTab) { - setActiveTab('chat'); - } return; } @@ -316,9 +331,6 @@ export function useProjectsState({ if (shouldUpdateSession) { setSelectedSession({ ...codexSession, __provider: 'codex' }); } - if (shouldSwitchTab) { - setActiveTab('chat'); - } return; } } @@ -341,7 +353,7 @@ export function useProjectsState({ (session: ProjectSession) => { setSelectedSession(session); - if (activeTab !== 'git' && activeTab !== 'preview') { + if (activeTab === 'tasks' || activeTab === 'preview') { setActiveTab('chat'); }