mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
persist sidebar collapsed groups across navigation and restart (#722)
* fix: persist sidebar collapsed groups across navigation and restart The sidebar's collapsed-group state was stored as local React state in WorktreeList, so the Sidebar unmount/remount triggered by navigating into Settings discarded it — returning to the main view re-expanded every previously collapsed group. Lift the state into the UI slice alongside groupBy/sortBy/filterRepoIds and persist it via the existing ui:set IPC so collapse state also survives app restarts. * fix: clear collapsed groups when switching groupBy mode Prevents stale keys from accumulating across mode switches and avoids cross-mode key collisions that could leave groups unexpectedly collapsed. --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
This commit is contained in:
co-authored by
Jinwoo-H
parent
5d985a6563
commit
ab81f04fae
@@ -190,6 +190,7 @@ function App(): React.JSX.Element {
|
||||
sortBy: 'name',
|
||||
showActiveOnly: false,
|
||||
filterRepoIds: [],
|
||||
collapsedGroups: [],
|
||||
uiZoomLevel: 0,
|
||||
editorFontZoomLevel: 0,
|
||||
worktreeCardProperties: [...DEFAULT_WORKTREE_CARD_PROPERTIES],
|
||||
|
||||
@@ -597,20 +597,8 @@ const WorktreeList = React.memo(function WorktreeList() {
|
||||
// the active navigation surface, so any modal should clear and disable them.
|
||||
const { showHints } = useModifierHint(activeModal === 'none')
|
||||
|
||||
// Collapsed group state
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set())
|
||||
|
||||
const toggleGroup = useCallback((key: string) => {
|
||||
setCollapsedGroups((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(key)) {
|
||||
next.delete(key)
|
||||
} else {
|
||||
next.add(key)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
const collapsedGroups = useAppStore((s) => s.collapsedGroups)
|
||||
const toggleGroup = useAppStore((s) => s.toggleCollapsedGroup)
|
||||
|
||||
// Build flat row list for rendering
|
||||
const rows: Row[] = useMemo(
|
||||
|
||||
@@ -122,6 +122,8 @@ export type UISlice = {
|
||||
setShowActiveOnly: (v: boolean) => void
|
||||
filterRepoIds: string[]
|
||||
setFilterRepoIds: (ids: string[]) => void
|
||||
collapsedGroups: Set<string>
|
||||
toggleCollapsedGroup: (key: string) => void
|
||||
worktreeCardProperties: WorktreeCardProperty[]
|
||||
toggleWorktreeCardProperty: (prop: WorktreeCardProperty) => void
|
||||
statusBarItems: StatusBarItem[]
|
||||
@@ -225,7 +227,13 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
|
||||
setSearchQuery: (q) => set({ searchQuery: q }),
|
||||
|
||||
groupBy: 'none',
|
||||
setGroupBy: (g) => set({ groupBy: g }),
|
||||
// Why: group keys are mode-specific (e.g. repo id vs PR status), so
|
||||
// collapsed state from one mode is meaningless in another. Clearing
|
||||
// also prevents unbounded accumulation of stale keys across mode switches.
|
||||
setGroupBy: (g) => {
|
||||
window.api.ui.set({ collapsedGroups: [] }).catch(console.error)
|
||||
set({ groupBy: g, collapsedGroups: new Set<string>() })
|
||||
},
|
||||
|
||||
sortBy: 'name',
|
||||
setSortBy: (s) => set({ sortBy: s }),
|
||||
@@ -236,6 +244,19 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
|
||||
filterRepoIds: [],
|
||||
setFilterRepoIds: (ids) => set({ filterRepoIds: ids }),
|
||||
|
||||
collapsedGroups: new Set<string>(),
|
||||
toggleCollapsedGroup: (key) =>
|
||||
set((s) => {
|
||||
const next = new Set(s.collapsedGroups)
|
||||
if (next.has(key)) {
|
||||
next.delete(key)
|
||||
} else {
|
||||
next.add(key)
|
||||
}
|
||||
window.api.ui.set({ collapsedGroups: [...next] }).catch(console.error)
|
||||
return { collapsedGroups: next }
|
||||
}),
|
||||
|
||||
worktreeCardProperties: [...DEFAULT_WORKTREE_CARD_PROPERTIES],
|
||||
toggleWorktreeCardProperty: (prop) =>
|
||||
set((s) => {
|
||||
@@ -307,6 +328,7 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
|
||||
// worktree list stable across restarts instead of silently widening it.
|
||||
showActiveOnly: ui.showActiveOnly,
|
||||
filterRepoIds: (ui.filterRepoIds ?? []).filter((repoId) => validRepoIds.has(repoId)),
|
||||
collapsedGroups: new Set(ui.collapsedGroups ?? []),
|
||||
uiZoomLevel: ui.uiZoomLevel ?? 0,
|
||||
editorFontZoomLevel: ui.editorFontZoomLevel ?? 0,
|
||||
worktreeCardProperties: ui.worktreeCardProperties ?? [...DEFAULT_WORKTREE_CARD_PROPERTIES],
|
||||
|
||||
@@ -169,6 +169,7 @@ export function getDefaultUIState(): PersistedUIState {
|
||||
sortBy: 'name',
|
||||
showActiveOnly: false,
|
||||
filterRepoIds: [],
|
||||
collapsedGroups: [],
|
||||
uiZoomLevel: 0,
|
||||
editorFontZoomLevel: 0,
|
||||
worktreeCardProperties: [...DEFAULT_WORKTREE_CARD_PROPERTIES],
|
||||
|
||||
@@ -692,6 +692,7 @@ export type PersistedUIState = {
|
||||
sortBy: 'name' | 'smart' | 'recent' | 'repo'
|
||||
showActiveOnly: boolean
|
||||
filterRepoIds: string[]
|
||||
collapsedGroups: string[]
|
||||
uiZoomLevel: number
|
||||
editorFontZoomLevel: number
|
||||
worktreeCardProperties: WorktreeCardProperty[]
|
||||
|
||||
Reference in New Issue
Block a user