fix(app): keep Search projects to the project list (#58978)

Co-authored-by: vimtor <36263538+vimtor@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-10-25 12:57:45 +00:00
committed by GitHub
Unverified
parent 32db0d5dba
commit 507fc19e82
4 changed files with 52 additions and 40 deletions
@@ -153,6 +153,12 @@ test("Search projects opens a new session's project picker from a session and fr
await dialog.getByRole("option", { name: /^Search projects/ }).click()
await expect(page).toHaveURL(/\/new-session\?draftId=.+$/)
await expect(picker).toBeFocused()
// Just the projects: no "Add project", no worktrees and no key hints.
const projects = page.getByRole("dialog").getByRole("option")
await expect(projects).toHaveCount(1)
await expect(projects).toHaveText(/command-palette/)
await expect(projects.getByRole("button", { name: "Worktrees", exact: true })).toHaveCount(0)
await expect(page.locator(".new-session-location-hints")).toHaveCount(0)
await picker.press("Escape")
await expect(picker).toHaveCount(0)
await expect(editor).toBeFocused()
@@ -34,7 +34,8 @@ type LocationEntry =
/**
* Opens the new-session location dialog: one project list across every ready server, → on a Git project lists where
* the session can run, and → on "Add project" picks the server when there are several.
* the session can run, and → on "Add project" picks the server when there are several. Search projects passes
* `projectsOnly` for just the project list: no "Add project", no worktrees and no key hints, like the other palettes.
*/
export function useNewSessionLocationDialog(input: {
project: PromptProjectController
@@ -43,13 +44,14 @@ export function useNewSessionLocationDialog(input: {
}) {
const dialog = useDialog()
return (options?: { onWorkspace?: (value: string) => void }) => {
return (options?: { onWorkspace?: (value: string) => void; projectsOnly?: boolean }) => {
void dialog.show(
() => (
<NewSessionLocationDialog
project={input.project}
workspace={input.workspace}
onWorkspace={options?.onWorkspace}
projectsOnly={options?.projectsOnly}
/>
),
input.onDone,
@@ -61,6 +63,7 @@ function NewSessionLocationDialog(props: {
project: PromptProjectController
workspace: NewSessionWorkspaceController
onWorkspace?: (value: string) => void
projectsOnly?: boolean
}) {
const dialog = useDialog()
const language = useLanguage()
@@ -127,7 +130,7 @@ function NewSessionLocationDialog(props: {
.projects()
.filter((project) => matches(displayName(project), project.worktree, tagged(project.server)?.name))
.map((project) => ({ id: projectEntryID(project), type: "project" as const, project })),
addEntry,
...(props.projectsOnly ? [] : [addEntry]),
])
const serverEntries = createMemo<LocationEntry[]>(() =>
@@ -163,7 +166,9 @@ function NewSessionLocationDialog(props: {
return projectEntries()
}
const pinned = () => !store.drilled && !store.picking
const pinned = () => !props.projectsOnly && !store.drilled && !store.picking
const drillable = (project: PromptProject) => !props.projectsOnly && !!project.id && project.vcs === "git"
const listEntries = () => entries().filter((entry) => entry.type !== "add")
@@ -439,28 +444,30 @@ function NewSessionLocationDialog(props: {
</div>
</div>
</div>
<div class="new-session-location-hints">
<span>
<Keybind keys={["↵"]} variant="neutral" />
{language.t("session.new.location.select")}
</span>
<Show
when={store.drilled || store.picking}
fallback={
<span>
<Keybind keys={["→"]} variant="neutral" />
{activeEntry()?.type === "add" && choosesServer()
? language.t("session.new.location.servers")
: language.t("session.new.location.worktrees")}
</span>
}
>
<Show when={!props.projectsOnly}>
<div class="new-session-location-hints">
<span>
<Keybind keys={["←"]} variant="neutral" />
{language.t("session.new.location.back")}
<Keybind keys={["↵"]} variant="neutral" />
{language.t("session.new.location.select")}
</span>
</Show>
</div>
<Show
when={store.drilled || store.picking}
fallback={
<span>
<Keybind keys={["→"]} variant="neutral" />
{activeEntry()?.type === "add" && choosesServer()
? language.t("session.new.location.servers")
: language.t("session.new.location.worktrees")}
</span>
}
>
<span>
<Keybind keys={["←"]} variant="neutral" />
{language.t("session.new.location.back")}
</span>
</Show>
</div>
</Show>
</DialogBody>
</Dialog>
)
@@ -558,10 +565,6 @@ function WorkspaceRow(props: { title: string; value: string; description: string
)
}
function drillable(project: PromptProject) {
return !!project.id && project.vcs === "git"
}
function projectEntryID(project: PromptProject) {
return `project:${project.server?.key ?? ""}:${project.worktree}`
}
+2 -2
View File
@@ -53,7 +53,7 @@ export default function NewSessionPage(props: { draftId: string }) {
restoreFocus: model.restoreFocus,
project: {
empty: project.empty,
open: () => location(),
open: () => location({ projectsOnly: true }),
},
workspace: {
enabled: workspace.bar.visible,
@@ -67,7 +67,7 @@ export default function NewSessionPage(props: { draftId: string }) {
if (!ready) return
// Search projects opened this draft; the picker returns focus to the composer when it closes.
if (layout.draft.projectSearch.take()) location()
if (layout.draft.projectSearch.take()) location({ projectsOnly: true })
else model.restoreFocus()
}),
)
+13 -10
View File
@@ -37,7 +37,7 @@ import { TitlebarItems, useTitlebarItems } from "@/runtime/extension/titlebar-it
export const newTabCommand = { id: "tab.new", keybind: "mod+t,mod+n" }
/** A draft registers its own Search projects; everywhere else it opens a new draft with the project picker open. */
/** Search projects: a draft searches in place; everywhere else it opens a new draft with the project picker open. */
export const projectSearchCommand = { id: "project.select", keybind: "mod+shift+o" }
export const goBackCommand = { id: "common.goBack", keybind: "mod+[" }
@@ -556,16 +556,19 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
tabsStoreActions.closeTab(tabsStore.findIndex((tab) => current === tab))
},
},
{
...projectSearchCommand,
title: language.t("session.new.project.search"),
category: language.t("command.category.project"),
onSelect: () => {
layout.draft.projectSearch.request()
// A draft registers its own, which searches projects in place.
layout.route().type === "draft"
? undefined
: {
...projectSearchCommand,
title: language.t("session.new.project.search"),
category: language.t("command.category.project"),
onSelect: () => {
layout.draft.projectSearch.request()
if (!openNewTab()) layout.draft.projectSearch.take()
},
},
if (!openNewTab()) layout.draft.projectSearch.take()
},
},
{
id: "tab.reopenClosed",
category: language.t("command.category.file"),