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 <noreply@paperclip.ing>
This commit is contained in:
Darren Davison 2026-04-04 03:41:54 +01:00
parent 58a1a20f5b
commit e23d148be1

View file

@ -47,6 +47,7 @@ export type IssueViewState = {
groupBy: "status" | "priority" | "assignee" | "none"; groupBy: "status" | "priority" | "assignee" | "none";
viewMode: "list" | "board"; viewMode: "list" | "board";
collapsedGroups: string[]; collapsedGroups: string[];
collapsedParents: string[];
}; };
const defaultViewState: IssueViewState = { const defaultViewState: IssueViewState = {
@ -60,6 +61,7 @@ const defaultViewState: IssueViewState = {
groupBy: "none", groupBy: "none",
viewMode: "list", viewMode: "list",
collapsedGroups: [], collapsedGroups: [],
collapsedParents: [],
}; };
const quickFilterPresets = [ const quickFilterPresets = [
@ -219,7 +221,6 @@ export function IssuesList({
return getViewState(scopedKey); return getViewState(scopedKey);
}); });
const [assigneePickerIssueId, setAssigneePickerIssueId] = useState<string | null>(null); const [assigneePickerIssueId, setAssigneePickerIssueId] = useState<string | null>(null);
const [collapsedParents, setCollapsedParents] = useState<Set<string>>(new Set());
const [assigneeSearch, setAssigneeSearch] = useState(""); const [assigneeSearch, setAssigneeSearch] = useState("");
const [issueSearch, setIssueSearch] = useState(initialSearch ?? ""); const [issueSearch, setIssueSearch] = useState(initialSearch ?? "");
const deferredIssueSearch = useDeferredValue(issueSearch); const deferredIssueSearch = useDeferredValue(issueSearch);
@ -680,14 +681,14 @@ export function IssuesList({
const renderIssueRow = (issue: Issue, depth: number) => { const renderIssueRow = (issue: Issue, depth: number) => {
const children = childMap.get(issue.id) ?? []; const children = childMap.get(issue.id) ?? [];
const hasChildren = children.length > 0; 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 }) => { const toggleCollapse = (e: { preventDefault: () => void; stopPropagation: () => void }) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setCollapsedParents((prev) => { updateView({
const next = new Set(prev); collapsedParents: isExpanded
if (next.has(issue.id)) next.delete(issue.id); else next.add(issue.id); ? [...viewState.collapsedParents, issue.id]
return next; : viewState.collapsedParents.filter((id) => id !== issue.id),
}); });
}; };