From 2d47414515ce3ce0e9ef267e8ae6ec63fb19f003 Mon Sep 17 00:00:00 2001
From: Austin Thesing <austin@designxdevelop.com>
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 <cursoragent@cursor.com>
Co-authored-by: Austin Thesing <austin@designxdevelop.com>
Signed-off-by: Austin Thesing <austin@designxdevelop.com>
---
 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 (
-    <div className="flex flex-1 flex-col items-center justify-center gap-3 px-4 py-16 text-center">
+    <div
+      className="flex flex-1 flex-col items-center justify-center gap-3 px-4 py-16 text-center"
+      data-testid="projects-empty-state"
+    >
       <FolderGit2 className="h-10 w-10 text-muted-foreground/40" />
       <div className="space-y-1">
         <p className="text-sm font-medium text-foreground">No projects yet</p>
@@ -273,6 +280,17 @@ export function EmptyState() {
           Projects published to this relay will appear here.
         </p>
       </div>
+      {onCreateRepository ? (
+        <Button
+          className="mt-2"
+          data-testid="projects-empty-create-repository"
+          onClick={onCreateRepository}
+          size="sm"
+          type="button"
+        >
+          Create repository
+        </Button>
+      ) : null}
     </div>
   );
 }
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 <EmptyState />;
-  }
-
   const repositoryItems =
     visibleProjects.length === 0 ? (
       <EmptyFilteredState />
@@ -646,7 +642,11 @@ export function ProjectsView() {
           </div>
           <div className="mx-auto w-full max-w-6xl">
             <div className="w-full min-w-0 pb-4 pt-4">
-              {filter === "all" ? (
+              {projects.length === 0 ? (
+                <EmptyState
+                  onCreateRepository={() => setCreateProjectOpen(true)}
+                />
+              ) : filter === "all" ? (
                 <ProjectsOverviewPanel
                   localRepositoryCount={localProjectCount}
                   metadata={
diff --git a/desktop/tests/e2e/projects-empty-create.spec.ts b/desktop/tests/e2e/projects-empty-create.spec.ts
new file mode 100644
index 0000000000..fdd6d70f3d
--- /dev/null
+++ b/desktop/tests/e2e/projects-empty-create.spec.ts
@@ -0,0 +1,53 @@
+import { expect, test } from "@playwright/test";
+
+import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
+
+const DEFAULT_MOCK_PUBKEY = "deadbeef".repeat(8);
+
+// The projects surface is a preview feature — opt in before the app mounts.
+async function enableProjectsFeature(page: import("@playwright/test").Page) {
+  await page.addInitScript(() => {
+    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();
+});
