diff --git a/packages/app/e2e/regression/new-session.spec.ts b/packages/app/e2e/regression/new-session.spec.ts index 6e86bdcc090d..b2b01b7dd98d 100644 --- a/packages/app/e2e/regression/new-session.spec.ts +++ b/packages/app/e2e/regression/new-session.spec.ts @@ -589,19 +589,20 @@ test("the location dialog lists every ready server's projects in one list and ta const names = options.locator(".new-session-location-name") const paths = options.locator(".command-palette-description") const chip = dialog.getByRole("button", { name: "Back to projects", exact: true }) + const heading = dialog.getByText("Projects", { exact: true }) // Used projects first, then the rest, each by recent activity across servers; only remote ones carry a tag. await expect(names).toHaveText(["opencodeapollo", "opencode", "console", "sstapollo"]) await expect(paths).toHaveText(["/home/dax/opencode", "C:/work/opencode", "C:/work/console", "/home/dax/sst"]) - // "Add project" is the first row of the scrolling list, under its heading, yet the first project is still the - // opening row, not the draft's current project (the second row). + // "Add project" is the first row of the scrolling list, above the Projects heading, yet the first project is still + // the opening row, not the draft's current project (the second row). await expect(dialog.locator("[data-scrollable]").getByRole("option").first()).toHaveText("Add project") await expect(rows).toHaveCount(4) await expect .poll(async () => { const row = await add.boundingBox() - const heading = await dialog.getByText("Projects", { exact: true }).boundingBox() + const title = await heading.boundingBox() - return row && heading ? row.y >= heading.y + heading.height : false + return row && title ? row.y + row.height <= title.y : false }) .toBe(true) await expect(rows.nth(0)).toHaveAttribute("aria-selected", "true") @@ -634,6 +635,7 @@ test("the location dialog lists every ready server's projects in one list and ta await search.fill("nothing matches") await expect(options).toHaveText(["Add project"]) await expect(add).toHaveAttribute("aria-selected", "true") + await expect(heading).toHaveCount(0) // The worktree step keeps the server's tag beside the project. await search.fill("opencode") diff --git a/packages/app/src/new-session/location/dialog.tsx b/packages/app/src/new-session/location/dialog.tsx index ca6fbd3d3584..969471e359c6 100644 --- a/packages/app/src/new-session/location/dialog.tsx +++ b/packages/app/src/new-session/location/dialog.tsx @@ -123,8 +123,9 @@ function NewSessionLocationDialog(props: { const addEntry: LocationEntry = { id: "add", type: "add" } - // Remote projects also match their server's name, which their tag shows. "Add project" is listed first but navigates - // last, so the first project stays the opening row and the arrow keys' wrap-around still follows the rows on screen. + // Remote projects also match their server's name, which their tag shows. "Add project" is shown first, above the + // heading, but navigates last, so the first project stays the opening row and the arrow keys' wrap-around still + // follows the rows on screen. const projectEntries = createMemo(() => [ ...props.project .projects() @@ -168,7 +169,7 @@ function NewSessionLocationDialog(props: { const drillable = (project: PromptProject) => !props.projectsOnly && !!project.id && project.vcs === "git" - const rows = () => (entries().at(-1) === addEntry ? [addEntry, ...entries().slice(0, -1)] : entries()) + const grouped = () => entries().filter((entry) => entry !== addEntry) const title = () => { if (store.drilled) return language.t("session.new.workspace.runIn") @@ -307,6 +308,72 @@ function NewSessionLocationDialog(props: { typeahead.onKeyDown(event) } + const option = (entry: LocationEntry) => ( + + ) + return ( @@ -356,7 +423,7 @@ function NewSessionLocationDialog(props: {
(resultsRef = el)}>
0} + when={entries().length > 0} fallback={ // The worktree list may still be loading; hold the row's height without flashing a label.
@@ -364,80 +431,13 @@ function NewSessionLocationDialog(props: {
} > -
-
{title()}
- - {(entry) => ( - - )} - -
+ {option(addEntry)} + 0}> +
+
{title()}
+ {option} +
+