diff --git a/.changeset/remove-empty-project-create-action.md b/.changeset/remove-empty-project-create-action.md new file mode 100644 index 00000000..999a7c5b --- /dev/null +++ b/.changeset/remove-empty-project-create-action.md @@ -0,0 +1,7 @@ +--- +"@sapiom/harness": patch +--- + +Remove the inline first-agent creation button from empty projects, including +projects reopened without a live session and clients connected to older servers. +Keep ordinary project session shortcuts, menu actions, and scan boundary notes. diff --git a/packages/harness/README.md b/packages/harness/README.md index 281ecf51..a0f2c6af 100644 --- a/packages/harness/README.md +++ b/packages/harness/README.md @@ -101,6 +101,10 @@ opens that session's ordinary conversation and Canvas/Steps experience. A new project's first ordinary session is initially titled **Plan Agents**; the title does not confer a role and can be renamed like any other session. +Empty projects have no inline first-agent creation button, including after a +session ends. Use the project's session shortcut or its menu to start work; +warnings about separate checkouts that were not searched remain visible. + When a new project gains its first active root binding, Studio durably schedules one evidence-first map bootstrap for that first session. The model reads the current map and uses the same structured tools available to every project diff --git a/packages/harness/web/e2e/create-agent.spec.ts b/packages/harness/web/e2e/create-agent.spec.ts index 5cfbb897..030421d2 100644 --- a/packages/harness/web/e2e/create-agent.spec.ts +++ b/packages/harness/web/e2e/create-agent.spec.ts @@ -199,15 +199,24 @@ test.describe("legacy-server agent creation compatibility", () => { await expect(page.getByTestId("workflow-cancelled")).toHaveCount(0); }); - test("the empty-project row opens the same dialog", async ({ page }) => { - // One create flow, not one per door: the empty project's CTA is the same - // subject and must not be a second mechanism that drifts. + test("an empty project has no inline create action and retains its menu dialog", async ({ + page, + }) => { await page.getByTestId("rail-add-project").click(); await page .getByTestId("folder-field-input") .fill("/Users/demo/blank-slate"); await page.getByTestId("open-project").click(); - await page.getByTestId("project-empty-blank-slate").click(); + const group = page.getByTestId("workspace-group-blank-slate"); + await expect(group).toBeVisible(); + await expect(group.getByTestId("project-empty-blank-slate")).toHaveCount(0); + await expect( + group.getByRole("button", { + name: /^Create (the first |an )agent here$/, + }), + ).toHaveCount(0); + await openProjectMenu(page, "blank-slate"); + await page.getByTestId("project-create-agent-blank-slate").click(); await expect(page.getByTestId("create-agent-dialog")).toBeVisible(); await expect(page.getByTestId("create-agent-project")).toHaveText( diff --git a/packages/harness/web/e2e/open-project.spec.ts b/packages/harness/web/e2e/open-project.spec.ts index dd14596e..709da843 100644 --- a/packages/harness/web/e2e/open-project.spec.ts +++ b/packages/harness/web/e2e/open-project.spec.ts @@ -79,6 +79,7 @@ test.describe("the header + opens a project", () => { await expect(page.getByTestId("agent-map-empty")).toHaveText( "Nothing generated yet", ); + await expect(group.getByTestId("project-empty-blank-slate")).toHaveCount(0); // The server-owned project-open lifecycle contributes one real ordinary // session. Plan Agents is only that tab's initial title—never a pinned row // or a second synthetic navigation element. @@ -126,6 +127,21 @@ test.describe("the header + opens a project", () => { await page.getByTestId("filing-group-by").selectOption("group"); await page.keyboard.press("Escape"); await expect(page.getByTestId("project-row-blank-slate")).toBeVisible(); + + // Ending the automatic conversation must not bring back the retired + // first-agent action beneath the still-empty project. + await page.getByTestId(`session-tab-main-${firstSessionId}`).click(); + await page.getByTestId("session-menu").click(); + await page.getByTestId("session-end-btn").click(); + await page.getByTestId("end-session-confirm-btn").click(); + await expect(page.getByTestId("session-context")).not.toHaveAttribute( + "data-session-id", + firstSessionId!, + ); + await expect(group.getByTestId("project-empty-blank-slate")).toHaveCount(0); + await expect( + group.getByRole("button", { name: /^Create (the first |an )agent here$/ }), + ).toHaveCount(0); }); test("a Studio project keeps ordinary session and agent creation available", async ({ @@ -140,7 +156,10 @@ test.describe("the header + opens a project", () => { await expect(page.getByTestId("agent-map-frame")).toBeVisible(); await expect(page.locator(".harness-terminal .xterm")).toBeVisible(); - await expect(group.getByTestId("project-empty-blank-slate")).toBeVisible(); + await expect(group.getByTestId("project-empty-blank-slate")).toHaveCount(0); + await expect( + group.getByRole("button", { name: /^Create (the first |an )agent here$/ }), + ).toHaveCount(0); await expect( group.getByTestId("project-start-session-blank-slate"), ).toHaveAttribute("aria-label", "Start a session in blank-slate"); @@ -165,16 +184,6 @@ test.describe("the header + opens a project", () => { await expect(page.locator(".rail-empty")).toHaveCount(0); }); - test("the empty row does NOT appear under a merged root-agent project", async ({ - page, - }) => { - // `rfq-agent` is a root that IS an agent — `projectIsEmpty` consults - // `rootAgent` precisely so its row does not get "no agents" printed under - // the agent it is showing. - await expect(page.getByTestId("workflow-rfq")).toBeVisible(); - await expect(page.getByTestId("project-empty-rfq-agent")).toHaveCount(0); - }); - test("opening a folder that IS an agent project registers the agent too", async ({ page, }) => { diff --git a/packages/harness/web/src/components/CreateAgentDialog.tsx b/packages/harness/web/src/components/CreateAgentDialog.tsx index 24819d7b..f084b227 100644 --- a/packages/harness/web/src/components/CreateAgentDialog.tsx +++ b/packages/harness/web/src/components/CreateAgentDialog.tsx @@ -60,9 +60,8 @@ export function CreateAgentDialog({ /** Leaves for the template gallery — the clone journey this dialog does not * own. Omitted, the link is not rendered. */ onBrowseTemplates?: () => void; - /* NO `triggerRef`. Every door into this dialog is a control that unmounts - when it is used — the project row's popover menu closes on click, the - empty-project row is replaced by the agent it creates — so a ref handed in + /* NO `triggerRef`. The project row's popover menu closes on click, + so a ref to the menu item handed in here would point at a detached node and Escape would restore focus to
anyway, only less obviously. Same reason the rail's remove-confirm takes the `⋮` itself rather than the menu item. */ @@ -120,10 +119,8 @@ export function CreateAgentDialog({ per private project name (lib/analytics/before-send.ts, USER_NAMED_OBJECTS). */ tracking={{ dialog: "create_agent", object: "workspace" }} - /* NO `triggerRef`. Every door into this dialog is a control that unmounts - when it is used — the project row's popover menu closes on click, the - empty-project row is replaced by the agent it creates — so a ref handed - in here would point at a detached node. */ + /* NO `triggerRef`. The project row's popover menu closes on click, + so a ref to the menu item would point at a detached node. */ actions={ <>