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.
This commit is contained in:
PaloSP
2026-02-26 12:46:01 +01:00
committed by GitHub
parent 1f903baf2c
commit e3b689214f

View File

@@ -101,6 +101,20 @@ const isUpdateAdditive = (
);
};
const VALID_TABS: Set<string> = 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<Project[]>([]);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const [selectedSession, setSelectedSession] = useState<ProjectSession | null>(null);
const [activeTab, setActiveTab] = useState<AppTab>('chat');
const [activeTab, setActiveTab] = useState<AppTab>(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<LoadingProgress | null>(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');
}