diff --git a/packages/app/src/components/left-sidebar.tsx b/packages/app/src/components/left-sidebar.tsx
index 759f0f991..55675e096 100644
--- a/packages/app/src/components/left-sidebar.tsx
+++ b/packages/app/src/components/left-sidebar.tsx
@@ -213,7 +213,7 @@ export const LeftSidebar = memo(function LeftSidebar({
enabled: isCompactLayout || isOpen,
});
const { collapsedProjectKeys, shortcutIndexByWorkspaceKey, toggleProjectCollapsed } =
- useSidebarShortcutModel({ projects, isInitialLoad });
+ useSidebarShortcutModel({ projects });
const groupMode = useSidebarViewStore((state) =>
activeServerId ? state.getGroupMode(activeServerId) : "project",
diff --git a/packages/app/src/hooks/use-sidebar-shortcut-model.test.tsx b/packages/app/src/hooks/use-sidebar-shortcut-model.test.tsx
new file mode 100644
index 000000000..42af68b15
--- /dev/null
+++ b/packages/app/src/hooks/use-sidebar-shortcut-model.test.tsx
@@ -0,0 +1,102 @@
+/**
+ * @vitest-environment jsdom
+ */
+import React from "react";
+import { act } from "@testing-library/react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import type {
+ SidebarProjectEntry,
+ SidebarWorkspaceEntry,
+} from "@/hooks/use-sidebar-workspaces-list";
+import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
+import { useSidebarShortcutModel } from "./use-sidebar-shortcut-model";
+
+function workspace(projectKey: string, workspaceId: string): SidebarWorkspaceEntry {
+ return {
+ workspaceKey: `srv:${workspaceId}`,
+ serverId: "srv",
+ workspaceId,
+ projectKey,
+ projectRootPath: `/repo/${projectKey}`,
+ workspaceDirectory: `/repo/${projectKey}/${workspaceId}`,
+ projectKind: "git",
+ workspaceKind: "worktree",
+ name: workspaceId,
+ title: null,
+ currentBranch: null,
+ statusBucket: "done",
+ statusEnteredAt: null,
+ archivingAt: null,
+ diffStat: null,
+ prHint: null,
+ archiveHasUncommittedChanges: null,
+ archiveUnpushedCommitCount: null,
+ scripts: [],
+ hasRunningScripts: false,
+ };
+}
+
+function project(projectKey: string): SidebarProjectEntry {
+ return {
+ projectKey,
+ projectName: projectKey,
+ projectKind: "git",
+ iconWorkingDir: `/repo/${projectKey}`,
+ canCreateWorktree: true,
+ workspaces: [workspace(projectKey, `${projectKey}-main`)],
+ };
+}
+
+const PROJECTS_BOTH = [project("p1"), project("p2")];
+const PROJECTS_ONLY_SECOND = [project("p2")];
+
+function Probe({ projectSet }: { projectSet: "both" | "onlySecond" }) {
+ const projects = projectSet === "both" ? PROJECTS_BOTH : PROJECTS_ONLY_SECOND;
+ useSidebarShortcutModel({ projects });
+ return null;
+}
+
+describe("useSidebarShortcutModel", () => {
+ let root: Root | null = null;
+ let container: HTMLElement | null = null;
+
+ beforeEach(() => {
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ root = createRoot(container);
+ useSidebarCollapsedSectionsStore.setState({
+ collapsedProjectKeys: new Set(),
+ collapsedStatusGroupKeys: new Set(),
+ });
+ });
+
+ afterEach(() => {
+ if (root) {
+ act(() => {
+ root?.unmount();
+ });
+ }
+ root = null;
+ container?.remove();
+ container = null;
+ });
+
+ it("keeps a collapsed project collapsed when the project list temporarily omits it", async () => {
+ useSidebarCollapsedSectionsStore.setState({
+ collapsedProjectKeys: new Set(["p1"]),
+ });
+
+ await act(async () => {
+ root?.render();
+ });
+ await act(async () => {
+ root?.render();
+ });
+ await act(async () => {
+ root?.render();
+ });
+
+ expect(useSidebarCollapsedSectionsStore.getState().collapsedProjectKeys.has("p1")).toBe(true);
+ });
+});
diff --git a/packages/app/src/hooks/use-sidebar-shortcut-model.ts b/packages/app/src/hooks/use-sidebar-shortcut-model.ts
index 2d2bc617d..565b71f08 100644
--- a/packages/app/src/hooks/use-sidebar-shortcut-model.ts
+++ b/packages/app/src/hooks/use-sidebar-shortcut-model.ts
@@ -1,13 +1,10 @@
-import { useEffect, useMemo } from "react";
+import { useMemo } from "react";
import type { SidebarProjectEntry } from "@/hooks/use-sidebar-workspaces-list";
import { buildSidebarShortcutModel } from "@/utils/sidebar-shortcuts";
import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
-export function useSidebarShortcutModel(input: {
- projects: SidebarProjectEntry[];
- isInitialLoad: boolean;
-}) {
- const { projects, isInitialLoad } = input;
+export function useSidebarShortcutModel(input: { projects: SidebarProjectEntry[] }) {
+ const { projects } = input;
const collapsedProjectKeys = useSidebarCollapsedSectionsStore(
(state) => state.collapsedProjectKeys,
);
@@ -27,19 +24,6 @@ export function useSidebarShortcutModel(input: {
[collapsedProjectKeys, projects],
);
- useEffect(() => {
- if (isInitialLoad || projects.length === 0) {
- return;
- }
-
- const projectKeys = new Set(projects.map((project) => project.projectKey));
- for (const key of collapsedProjectKeys) {
- if (!projectKeys.has(key)) {
- setProjectCollapsed(key, false);
- }
- }
- }, [collapsedProjectKeys, isInitialLoad, projects, setProjectCollapsed]);
-
return {
collapsedProjectKeys,
shortcutIndexByWorkspaceKey: shortcutModel.shortcutIndexByWorkspaceKey,