From 2d47414515ce3ce0e9ef267e8ae6ec63fb19f003 Mon Sep 17 00:00:00 2001 From: Austin Thesing Date: Wed, 29 Jul 2026 16:04:54 -0500 Subject: [PATCH] fix(desktop): keep Create controls on empty Projects The empty-state early return skipped the Create menu and dialog, so first-time and empty-fetch users could not create a repository from the UI. Co-authored-by: Cursor Co-authored-by: Austin Thesing Signed-off-by: Austin Thesing --- desktop/playwright.config.ts | 1 + .../src/features/projects/ui/ProjectCards.tsx | 22 +++++++- .../src/features/projects/ui/ProjectsView.tsx | 10 ++-- .../tests/e2e/projects-empty-create.spec.ts | 53 +++++++++++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 desktop/tests/e2e/projects-empty-create.spec.ts diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index 459fa75743..1d91c7bebb 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -101,6 +101,7 @@ export default defineConfig({ "**/project-commit-detail.spec.ts", "**/project-inbox.spec.ts", "**/project-pr-review.spec.ts", + "**/projects-empty-create.spec.ts", "**/persona-model-combobox-screenshots.spec.ts", "**/drafts-screenshots.spec.ts", "**/drafts-all-fix-screenshots.spec.ts", diff --git a/desktop/src/features/projects/ui/ProjectCards.tsx b/desktop/src/features/projects/ui/ProjectCards.tsx index 52308f7a05..e10d6e3616 100644 --- a/desktop/src/features/projects/ui/ProjectCards.tsx +++ b/desktop/src/features/projects/ui/ProjectCards.tsx @@ -263,9 +263,16 @@ function StatusPill({ status }: { status: string }) { ); } -export function EmptyState() { +export function EmptyState({ + onCreateRepository, +}: { + onCreateRepository?: () => void; +} = {}) { return ( -
+

No projects yet

@@ -273,6 +280,17 @@ export function EmptyState() { Projects published to this relay will appear here.

+ {onCreateRepository ? ( + + ) : null}
); } diff --git a/desktop/src/features/projects/ui/ProjectsView.tsx b/desktop/src/features/projects/ui/ProjectsView.tsx index 2f22bfe0c1..1097a6209d 100644 --- a/desktop/src/features/projects/ui/ProjectsView.tsx +++ b/desktop/src/features/projects/ui/ProjectsView.tsx @@ -447,10 +447,6 @@ export function ProjectsView() { ); } - if (projects.length === 0) { - return ; - } - const repositoryItems = visibleProjects.length === 0 ? ( @@ -646,7 +642,11 @@ export function ProjectsView() {
- {filter === "all" ? ( + {projects.length === 0 ? ( + setCreateProjectOpen(true)} + /> + ) : filter === "all" ? ( { + window.localStorage.setItem( + "buzz-feature-overrides-v1", + JSON.stringify({ projects: true }), + ); + }); +} + +/** Hide every seeded mock project so Projects renders the true empty state. */ +async function hideAllMockProjects(page: import("@playwright/test").Page) { + const hiddenCards = [ + `30617:${DEFAULT_MOCK_PUBKEY}:buzz`, + `30617:${TEST_IDENTITIES.alice.pubkey}:relay-tools`, + `30617:${TEST_IDENTITIES.bob.pubkey}:design-system`, + ]; + await page.addInitScript((cards) => { + window.localStorage.setItem( + "buzz.projects.hidden-cards.v1", + JSON.stringify(cards), + ); + }, hiddenCards); +} + +test("empty Projects keeps Create repository controls", async ({ page }) => { + await enableProjectsFeature(page); + await hideAllMockProjects(page); + await installMockBridge(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); + await page.getByTestId("open-projects-view").click(); + + await expect( + page.getByRole("heading", { level: 1, name: "Projects" }), + ).toBeVisible(); + await expect(page.getByTestId("projects-empty-state")).toBeVisible(); + await expect(page.getByTestId("projects-create-menu")).toBeVisible(); + + await page.getByTestId("projects-create-menu").hover(); + await page.getByRole("menuitem", { name: "Repository" }).click(); + await expect(page.getByTestId("create-project-dialog")).toBeVisible(); + await page.keyboard.press("Escape"); + + await page.getByTestId("projects-empty-create-repository").click(); + await expect(page.getByTestId("create-project-dialog")).toBeVisible(); + await expect(page.getByTestId("create-project-name")).toBeVisible(); +});