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 <agentop718@gmail.com>

* test(app): verify pinned project workspace context

---------

Co-authored-by: Agent Op <agentop718@gmail.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Yuri Gui
2026-08-23 10:44:23 +02:00
committed by GitHub
co-authored by Agent Op Mohamed Boudra
parent dc9fb9aa1b
commit c3594b2079
3 changed files with 25 additions and 8 deletions
@@ -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-" });
@@ -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", () => {
@@ -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