From e23d148be1a9f1b0153d1dcbe95241151cbff7fe Mon Sep 17 00:00:00 2001 From: Darren Davison Date: Sat, 4 Apr 2026 03:41:54 +0100 Subject: [PATCH] feat: persist collapse/expand state across navigation via localStorage Move collapsedParents from ephemeral useState into IssueViewState, which is already serialised to localStorage under the scoped key. Navigating away and back now restores the exact collapsed/expanded state the user left the list in. Co-Authored-By: Paperclip --- ui/src/components/IssuesList.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 422eb438..3113d41c 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -47,6 +47,7 @@ export type IssueViewState = { groupBy: "status" | "priority" | "assignee" | "none"; viewMode: "list" | "board"; collapsedGroups: string[]; + collapsedParents: string[]; }; const defaultViewState: IssueViewState = { @@ -60,6 +61,7 @@ const defaultViewState: IssueViewState = { groupBy: "none", viewMode: "list", collapsedGroups: [], + collapsedParents: [], }; const quickFilterPresets = [ @@ -219,7 +221,6 @@ export function IssuesList({ return getViewState(scopedKey); }); const [assigneePickerIssueId, setAssigneePickerIssueId] = useState(null); - const [collapsedParents, setCollapsedParents] = useState>(new Set()); const [assigneeSearch, setAssigneeSearch] = useState(""); const [issueSearch, setIssueSearch] = useState(initialSearch ?? ""); const deferredIssueSearch = useDeferredValue(issueSearch); @@ -680,14 +681,14 @@ export function IssuesList({ const renderIssueRow = (issue: Issue, depth: number) => { const children = childMap.get(issue.id) ?? []; const hasChildren = children.length > 0; - const isExpanded = !collapsedParents.has(issue.id); + const isExpanded = !viewState.collapsedParents.includes(issue.id); const toggleCollapse = (e: { preventDefault: () => void; stopPropagation: () => void }) => { e.preventDefault(); e.stopPropagation(); - setCollapsedParents((prev) => { - const next = new Set(prev); - if (next.has(issue.id)) next.delete(issue.id); else next.add(issue.id); - return next; + updateView({ + collapsedParents: isExpanded + ? [...viewState.collapsedParents, issue.id] + : viewState.collapsedParents.filter((id) => id !== issue.id), }); };