diff --git a/server/src/__tests__/heartbeat-worktree-suppression.test.ts b/server/src/__tests__/heartbeat-worktree-suppression.test.ts index 71d9255eff..bb1e23c944 100644 --- a/server/src/__tests__/heartbeat-worktree-suppression.test.ts +++ b/server/src/__tests__/heartbeat-worktree-suppression.test.ts @@ -36,19 +36,36 @@ describeEmbeddedPostgres("heartbeat worktree suppression", () => { let db!: ReturnType; let tempDb: Awaited> | null = null; + function isHeartbeatRunEventFkError(error: unknown) { + const message = error instanceof Error ? `${error.message} ${String(error.cause ?? "")}` : String(error); + return message.includes("heartbeat_run_events_run_id_heartbeat_runs_id_fk"); + } + + async function deleteHeartbeatRunsWithEvents() { + for (let attempt = 0; attempt < 5; attempt += 1) { + await db.delete(heartbeatRunEvents); + try { + await db.delete(heartbeatRuns); + return; + } catch (error) { + if (!isHeartbeatRunEventFkError(error) || attempt === 4) throw error; + await new Promise((resolve) => setTimeout(resolve, 25)); + } + } + } + beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("heartbeat-worktree-suppression-"); db = createDb(tempDb.connectionString); }, 20_000); afterEach(async () => { - await db.delete(heartbeatRunEvents); await db.delete(issueComments); await db.delete(issueDocuments); await db.delete(documentRevisions); await db.delete(documents); await db.delete(activityLog); - await db.delete(heartbeatRuns); + await deleteHeartbeatRunsWithEvents(); await db.delete(agentWakeupRequests); await db.delete(issues); await db.delete(agentRuntimeState); diff --git a/ui/src/components/SidebarStarredProjects.test.tsx b/ui/src/components/SidebarStarredProjects.test.tsx index 501da6a2d3..e8088f4f46 100644 --- a/ui/src/components/SidebarStarredProjects.test.tsx +++ b/ui/src/components/SidebarStarredProjects.test.tsx @@ -112,6 +112,10 @@ function projectLinkLabels(container: HTMLElement) { .filter(Boolean); } +function projectLink(container: HTMLElement, projectRef: string) { + return container.querySelector(`a[href="/projects/${projectRef}/issues"]`) as HTMLAnchorElement | null; +} + describe("SidebarStarredProjects", () => { let container: HTMLDivElement; let root: ReturnType | null; @@ -176,6 +180,30 @@ describe("SidebarStarredProjects", () => { expect(document.body.querySelector('button[aria-label="Unstar Bravo"]')).not.toBeNull(); }); + it("keeps starred projects indented only outside the collapsed rail", async () => { + mockProjectsApi.list.mockResolvedValue([ + makeProject({ id: "project-a", name: "Alpha", urlKey: "alpha" }), + ]); + memberships = { ...memberships, starredProjectIds: ["project-a"] }; + + await render(); + + expect(projectLink(container, "alpha")?.className).toContain("pl-8"); + + await act(async () => root?.unmount()); + root = null; + container.innerHTML = ""; + queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } }); + mockSidebarState.collapsed = true; + + await render(); + + const railProjectLink = projectLink(container, "alpha"); + expect(railProjectLink?.className).not.toContain("pl-8"); + const nameSpan = Array.from(container.querySelectorAll("span")).find((el) => el.textContent === "Alpha"); + expect(nameSpan?.className).toContain("w-0"); + }); + it("renders nothing when no projects are starred", async () => { mockProjectsApi.list.mockResolvedValue([makeProject({ id: "project-a", name: "Alpha" })]); diff --git a/ui/src/components/SidebarStarredProjects.tsx b/ui/src/components/SidebarStarredProjects.tsx index 1b0925666e..f6e8711b17 100644 --- a/ui/src/components/SidebarStarredProjects.tsx +++ b/ui/src/components/SidebarStarredProjects.tsx @@ -121,7 +121,8 @@ export function SidebarStarredProjects() { if (isMobile) setSidebarOpen(false); }} className={cn( - "flex min-w-0 flex-1 items-center gap-2.5 px-3 py-1.5 pl-8 pointer-coarse:py-1 pr-8 text-(length:--text-compact) font-medium transition-colors", + "flex min-w-0 flex-1 items-center gap-2.5 px-3 py-1.5 pointer-coarse:py-1 pr-8 text-(length:--text-compact) font-medium transition-colors", + !rail && "pl-8", isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground",