diff --git a/web/src/components/SessionFiles/DirectoryTree.tsx b/web/src/components/SessionFiles/DirectoryTree.tsx index 80b1e532a2..4f282cb33d 100644 --- a/web/src/components/SessionFiles/DirectoryTree.tsx +++ b/web/src/components/SessionFiles/DirectoryTree.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import type { ApiClient } from '@/api/client' import { FileIcon } from '@/components/FileIcon' import { useSessionDirectory } from '@/hooks/queries/useSessionDirectory' @@ -171,13 +171,40 @@ function DirectoryNode(props: { ) } +const STORAGE_KEY_PREFIX = 'hapi-dir-expanded-' + +function readExpanded(sessionId: string): Set { + try { + const raw = sessionStorage.getItem(STORAGE_KEY_PREFIX + sessionId) + if (raw) { + const parsed = JSON.parse(raw) + if (Array.isArray(parsed)) return new Set(parsed as string[]) + } + } catch { + // ignore + } + return new Set(['']) +} + +function writeExpanded(sessionId: string, expanded: Set) { + try { + sessionStorage.setItem(STORAGE_KEY_PREFIX + sessionId, JSON.stringify([...expanded])) + } catch { + // ignore + } +} + export function DirectoryTree(props: { api: ApiClient | null sessionId: string rootLabel: string onOpenFile: (path: string) => void }) { - const [expanded, setExpanded] = useState>(() => new Set([''])) + const [expanded, setExpanded] = useState>(() => readExpanded(props.sessionId)) + + useEffect(() => { + writeExpanded(props.sessionId, expanded) + }, [props.sessionId, expanded]) const handleToggle = useCallback((path: string) => { setExpanded((prev) => { diff --git a/web/src/routes/sessions/files.tsx b/web/src/routes/sessions/files.tsx index f8c698d76d..a77e05b7a3 100644 --- a/web/src/routes/sessions/files.tsx +++ b/web/src/routes/sessions/files.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useNavigate, useParams, useSearch } from '@tanstack/react-router' import type { FileSearchItem, GitFileStatus } from '@/types/api' import { FileIcon } from '@/components/FileIcon' @@ -236,6 +236,8 @@ function FileListSkeleton(props: { label: string; rows?: number }) { ) } +const SCROLL_KEY_PREFIX = 'hapi-dir-scroll-' + export default function FilesPage() { const { api } = useAppContext() const { t } = useTranslation() @@ -246,10 +248,31 @@ export default function FilesPage() { const search = useSearch({ from: '/sessions/$sessionId/files' }) const { session } = useSession(api, sessionId) const [searchQuery, setSearchQuery] = useState('') + const scrollRef = useRef(null) const initialTab = search.tab === 'directories' ? 'directories' : 'changes' const [activeTab, setActiveTab] = useState<'changes' | 'directories'>(initialTab) + useEffect(() => { + const el = scrollRef.current + if (!el) return + const key = SCROLL_KEY_PREFIX + sessionId + try { + const saved = sessionStorage.getItem(key) + if (saved !== null) el.scrollTop = Number(saved) + } catch { + // ignore + } + return () => { + try { + sessionStorage.setItem(key, String(el.scrollTop)) + } catch { + // ignore + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sessionId]) + const { status: gitStatus, error: gitError, @@ -411,7 +434,7 @@ export default function FilesPage() { ) : null} -
+
{showGitErrorBanner && activeTab === 'changes' ? (
@@ -441,6 +464,7 @@ export default function FilesPage() { ) ) : activeTab === 'directories' ? (