From 19f11e5a6f53d32991a671ff504ba15aade0573d Mon Sep 17 00:00:00 2001 From: pseudo Date: Tue, 8 Sep 2026 21:46:20 -0600 Subject: [PATCH] fix(mobile): restore projects in the iPad sidebar The iPad sidebar showed connected environments and nothing else: no projects, no threads. Upstream's v2 thread list does not read scopedThreads or scopedPendingTasks, so #10413 short-circuits both to an empty array whenever the v2 list is on. The fork's project sections do read them. Every section was built with no rows, and the section item builder drops a section whose thread count is zero, header included. Seven projects in, nothing out. Reproduced against a copy of a real database: 8 projects and 78 threads produced 7 sections and 0 list items with the short-circuit, and 91 items without it. Only the sidebar had the extra consumer. HomeScreen's copies feed buildHomeThreadGroups, which short-circuits on v2 itself, so they keep the optimization. scopedProjects keeps it for the same reason. Claude Opus 5 (1M context) via T3 Code. Co-Authored-By: Claude Opus 5 (1M context) --- .../threads/ThreadNavigationSidebar.tsx | 33 +++++++++---------- .../src/features/threads/threadListV2.test.ts | 14 ++++++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx index 55d773b9f8fe..2e786224778e 100644 --- a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx +++ b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx @@ -297,29 +297,28 @@ function ThreadNavigationSidebarPane( ), [threadListV2Enabled, projects, selectedProjectRefs], ); + // Not short-circuited on v2 the way `scopedProjects` is: the project + // sections below partition these same lists, so emptying them here leaves + // every section with no rows and the sidebar blank. const scopedThreads = useMemo( () => - threadListV2Enabled - ? [] - : selectedProjectRefs === null - ? threads - : threads.filter((thread) => - selectedProjectRefs.has(scopedProjectKey(thread.environmentId, thread.projectId)), - ), - [threadListV2Enabled, selectedProjectRefs, threads], + selectedProjectRefs === null + ? threads + : threads.filter((thread) => + selectedProjectRefs.has(scopedProjectKey(thread.environmentId, thread.projectId)), + ), + [selectedProjectRefs, threads], ); const scopedPendingTasks = useMemo( () => - threadListV2Enabled - ? [] - : selectedProjectRefs === null - ? pendingTasks - : pendingTasks.filter((pendingTask) => - selectedProjectRefs.has( - scopedProjectKey(pendingTask.environmentId, pendingTask.projectId), - ), + selectedProjectRefs === null + ? pendingTasks + : pendingTasks.filter((pendingTask) => + selectedProjectRefs.has( + scopedProjectKey(pendingTask.environmentId, pendingTask.projectId), ), - [threadListV2Enabled, pendingTasks, selectedProjectRefs], + ), + [pendingTasks, selectedProjectRefs], ); const groups = useMemo( () => diff --git a/apps/mobile/src/features/threads/threadListV2.test.ts b/apps/mobile/src/features/threads/threadListV2.test.ts index d1389a0a909c..4c750e58435f 100644 --- a/apps/mobile/src/features/threads/threadListV2.test.ts +++ b/apps/mobile/src/features/threads/threadListV2.test.ts @@ -1069,6 +1069,20 @@ describe("buildThreadListV2ProjectSectionItems", () => { settledShelfExpanded: true, }); + // A regression guard: the sidebar once fed these sections an empty thread + // list, which drops every header too and leaves the whole sidebar blank. + it("drops a project entirely when it is handed no threads", () => { + const populated = buildThreadListV2ProjectSectionItems({ + sections: [section("alpha", ["a1"]), section("beta", ["b1"])], + }); + expect(populated.filter((item) => item.type === "v2-project-header")).toHaveLength(2); + + const starved = buildThreadListV2ProjectSectionItems({ + sections: [section("alpha", []), section("beta", [])], + }); + expect(starved).toEqual([]); + }); + it("puts every project's rows under its own header, in section order", () => { const items = buildThreadListV2ProjectSectionItems({ sections: [section("alpha", ["a1"]), section("beta", ["b1", "b2"])],