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] <open-swe@users.noreply.github.com>

* 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] <open-swe@users.noreply.github.com>

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Caroline di Vittorio 2026-06-11 17:09:28 -07:00 • committed by GitHub
parent d10cc15e82
commit 3724014d80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -72,6 +72,9 @@ function commonDirPrefix(paths: Array<string>): string {
} }
const PANEL_STORAGE_WIDTH = "open-swe.gitpanel.width" 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_DEFAULT_WIDTH = 420
const PANEL_MIN_WIDTH = 320 const PANEL_MIN_WIDTH = 320
const PANEL_MAX_WIDTH = 720 const PANEL_MAX_WIDTH = 720
@ -84,6 +87,14 @@ function readStoredPanelWidth(): number {
return Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, parsed)) 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({ function PanelResizeHandle({
width, width,
onResize, onResize,
@ -163,10 +174,22 @@ export function treeThemeStyle(): React.CSSProperties {
export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) { export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
const [topTab, setTopTab] = useState<"git" | "desktop" | "terminal">("git") const [topTab, setTopTab] = useState<"git" | "desktop" | "terminal">("git")
const [tab, setTab] = useState<"diff" | "review" | "commits">("diff") const [tab, setTab] = useState<"diff" | "review" | "commits">("diff")
const [collapsed, setCollapsed] = useState(true) const [collapsed, setCollapsedState] = useState(() =>
readStoredPanelCollapsed()
)
const [width, setWidthState] = useState(() => readStoredPanelWidth()) const [width, setWidthState] = useState(() => readStoredPanelWidth())
const [fullScreen, setFullScreen] = useState(false) 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 setWidth = useCallback((next: number) => {
const clamped = Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, next)) const clamped = Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, next))
setWidthState(clamped) setWidthState(clamped)
@ -176,14 +199,13 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({}) const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
const pr = thread.pr const pr = thread.pr
// Always start collapsed; re-collapse when switching threads, and // The open/closed state is persisted to localStorage, so it carries across
// uncollapse when a PR lands mid-session. // threads and reloads. Still uncollapse when a PR lands mid-session.
const [prSeen, setPrSeen] = useState<{ threadId: string; hadPr: boolean }>( const [prSeen, setPrSeen] = useState<{ threadId: string; hadPr: boolean }>(
() => ({ threadId: thread.id, hadPr: Boolean(pr) }) () => ({ threadId: thread.id, hadPr: Boolean(pr) })
) )
if (prSeen.threadId !== thread.id) { if (prSeen.threadId !== thread.id) {
setPrSeen({ threadId: thread.id, hadPr: Boolean(pr) }) setPrSeen({ threadId: thread.id, hadPr: Boolean(pr) })
setCollapsed(true)
} else if (pr && !prSeen.hadPr) { } else if (pr && !prSeen.hadPr) {
setPrSeen({ threadId: thread.id, hadPr: true }) setPrSeen({ threadId: thread.id, hadPr: true })
setCollapsed(false) setCollapsed(false)