From 3724014d80486428cf9d7163937ef7c718b1a786 Mon Sep 17 00:00:00 2001 From: Caroline di Vittorio <43390382+carolinedivittorio@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:09:28 -0700 Subject: [PATCH] feat: persist git panel open/closed state to localStorage (#1506) * feat: persist git panel open/closed state to localStorage Make the agent thread git panel remember whether the user opened or closed it. The collapsed state now reads from and writes to localStorage, so it stays open or closed across thread navigation and page reloads instead of always re-collapsing per thread. Co-authored-by: open-swe[bot] * refactor: use plain function and named constants for git panel collapse Drop the unnecessary useCallback around setCollapsed and replace the "1"/"0" string literals with COLLAPSED_STATE_TRUE / COLLAPSED_STATE_FALSE constants. Co-authored-by: open-swe[bot] --------- Co-authored-by: open-swe[bot] --- ui/src/components/agents/AgentGitPanel.tsx | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/ui/src/components/agents/AgentGitPanel.tsx b/ui/src/components/agents/AgentGitPanel.tsx index e2871505..b85c97e5 100644 --- a/ui/src/components/agents/AgentGitPanel.tsx +++ b/ui/src/components/agents/AgentGitPanel.tsx @@ -72,6 +72,9 @@ function commonDirPrefix(paths: Array): string { } const PANEL_STORAGE_WIDTH = "open-swe.gitpanel.width" +const PANEL_STORAGE_COLLAPSED = "open-swe.gitpanel.collapsed" +const COLLAPSED_STATE_TRUE = "1" +const COLLAPSED_STATE_FALSE = "0" const PANEL_DEFAULT_WIDTH = 420 const PANEL_MIN_WIDTH = 320 const PANEL_MAX_WIDTH = 720 @@ -84,6 +87,14 @@ function readStoredPanelWidth(): number { return Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, parsed)) } +function readStoredPanelCollapsed(): boolean { + if (typeof window === "undefined") return true + // Default to collapsed until the user opens it once. + return ( + window.localStorage.getItem(PANEL_STORAGE_COLLAPSED) !== COLLAPSED_STATE_FALSE + ) +} + function PanelResizeHandle({ width, onResize, @@ -163,10 +174,22 @@ export function treeThemeStyle(): React.CSSProperties { export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) { const [topTab, setTopTab] = useState<"git" | "desktop" | "terminal">("git") const [tab, setTab] = useState<"diff" | "review" | "commits">("diff") - const [collapsed, setCollapsed] = useState(true) + const [collapsed, setCollapsedState] = useState(() => + readStoredPanelCollapsed() + ) const [width, setWidthState] = useState(() => readStoredPanelWidth()) const [fullScreen, setFullScreen] = useState(false) + const setCollapsed = (next: boolean) => { + setCollapsedState(next) + if (typeof window !== "undefined") { + window.localStorage.setItem( + PANEL_STORAGE_COLLAPSED, + next ? COLLAPSED_STATE_TRUE : COLLAPSED_STATE_FALSE + ) + } + } + const setWidth = useCallback((next: number) => { const clamped = Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, next)) setWidthState(clamped) @@ -176,14 +199,13 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) { const sectionRefs = useRef>({}) const pr = thread.pr - // Always start collapsed; re-collapse when switching threads, and - // uncollapse when a PR lands mid-session. + // The open/closed state is persisted to localStorage, so it carries across + // threads and reloads. Still uncollapse when a PR lands mid-session. const [prSeen, setPrSeen] = useState<{ threadId: string; hadPr: boolean }>( () => ({ threadId: thread.id, hadPr: Boolean(pr) }) ) if (prSeen.threadId !== thread.id) { setPrSeen({ threadId: thread.id, hadPr: Boolean(pr) }) - setCollapsed(true) } else if (pr && !prSeen.hadPr) { setPrSeen({ threadId: thread.id, hadPr: true }) setCollapsed(false)