From c3594b20797e07286b6aea361e40f6ea710e7de7 Mon Sep 17 00:00:00 2001 From: Yuri Gui Date: Sun, 23 Aug 2026 04:44:23 -0400 Subject: [PATCH] fix(app): keep pinned project actions reachable (#3722) * fix(app): keep pinned project actions reachable Preserve an empty project shell after its final workspace is hoisted into the pinned section so project-scoped workspace creation remains available. Co-authored-by: Agent Op * test(app): verify pinned project workspace context --------- Co-authored-by: Agent Op Co-authored-by: Mohamed Boudra --- .../sidebar-workspace-pin-shortcut.spec.ts | 23 +++++++++++++++++++ .../app/src/hooks/use-sidebar-pins.test.ts | 4 ++-- packages/app/src/hooks/use-sidebar-pins.ts | 6 ----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/app/e2e/browser/sidebar-workspace-pin-shortcut.spec.ts b/packages/app/e2e/browser/sidebar-workspace-pin-shortcut.spec.ts index 4feca297ea..a471980e09 100644 --- a/packages/app/e2e/browser/sidebar-workspace-pin-shortcut.spec.ts +++ b/packages/app/e2e/browser/sidebar-workspace-pin-shortcut.spec.ts @@ -1,6 +1,10 @@ import { test, expect, type Page } from "../support/fixtures"; import { gotoAppShell } from "../support/helpers/app"; import { daemonWsRoutePattern } from "../support/helpers/daemon-port"; +import { + expectNewWorkspaceProjectSelected, + openNewWorkspaceComposer, +} from "../support/helpers/new-workspace"; import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client"; import { getServerId } from "../support/helpers/server-id"; import { selectSidebarStatusGrouping } from "../support/helpers/sidebar"; @@ -142,6 +146,25 @@ async function installPinRpcGate( } test.describe("Pin workspace shortcut", () => { + test("keeps project creation reachable after pinning its only workspace", async ({ page }) => { + const workspace = await seedWorkspace({ + repoPrefix: "pin-project-creation-", + title: "Pinned workspace", + }); + + try { + await gotoAppShell(page); + await openWorkspace(page, workspace.workspaceId); + await page.keyboard.press(PIN_SHORTCUT); + await expect(pinnedSection(page)).toBeVisible({ timeout: 10_000 }); + + await openNewWorkspaceComposer(page, workspace); + await expectNewWorkspaceProjectSelected(page, workspace.projectDisplayName); + } finally { + await workspace.cleanup(); + } + }); + test("pins the active workspace while its project section is collapsed", async ({ page }) => { const workspace = await seedWorkspace({ repoPrefix: "pin-shortcut-collapsed-" }); diff --git a/packages/app/src/hooks/use-sidebar-pins.test.ts b/packages/app/src/hooks/use-sidebar-pins.test.ts index f418027fca..3f56fb667c 100644 --- a/packages/app/src/hooks/use-sidebar-pins.test.ts +++ b/packages/app/src/hooks/use-sidebar-pins.test.ts @@ -30,7 +30,7 @@ function project(projectKey: string, workspaces: SidebarWorkspacePlacement[]): S } describe("splitPinnedSidebarGroups", () => { - it("drops the empty shell when every chat of a project is pinned", () => { + it("keeps the project shell reachable when every chat is pinned", () => { const only = placement("w1"); const projects = [project("p1", [only])]; const result = splitPinnedSidebarGroups({ @@ -42,7 +42,7 @@ describe("splitPinnedSidebarGroups", () => { pinnedWorkspaceOrder: [], }); expect(result.pinnedChats).toHaveLength(1); - expect(result.unpinnedProjects).toHaveLength(0); + expect(result.unpinnedProjects).toEqual([{ ...projects[0], workspaces: [] }]); }); it("keeps a genuinely empty project so its new-workspace row stays reachable", () => { diff --git a/packages/app/src/hooks/use-sidebar-pins.ts b/packages/app/src/hooks/use-sidebar-pins.ts index fade0507d4..2b6e2f53a7 100644 --- a/packages/app/src/hooks/use-sidebar-pins.ts +++ b/packages/app/src/hooks/use-sidebar-pins.ts @@ -121,12 +121,6 @@ export function splitPinnedSidebarGroups(input: { remainingWorkspaces.push(workspace); } } - // Every chat got hoisted into the Pinned section: drop the empty shell instead of - // leaving a duplicate project header below. A genuinely empty project (no chats to - // begin with) is kept so its "new workspace" row stays reachable. - if (remainingWorkspaces.length === 0 && project.workspaces.length > 0) { - continue; - } unpinnedProjects.push( remainingWorkspaces.length === project.workspaces.length ? project