diff --git a/apps/web/src/components/CommandPalette.logic.test.ts b/apps/web/src/components/CommandPalette.logic.test.ts index 8cf712b113cf7..5295d27c67ba5 100644 --- a/apps/web/src/components/CommandPalette.logic.test.ts +++ b/apps/web/src/components/CommandPalette.logic.test.ts @@ -9,6 +9,7 @@ import { buildLinkedThreadActionItems, filterPinnedBrowseEntries, filterCommandPaletteGroups, + resolveNewThreadProjectGroups, reduceCommandPaletteUiState, resolveAddProjectEntry, createCommandPaletteSearchState, @@ -920,6 +921,48 @@ describe("returning from file and content search", () => { }); }); +describe("open new-thread project choices", () => { + it("uses current choices and actions while preserving other submenus", async () => { + const staleRun = vi.fn(async () => undefined); + const currentRun = vi.fn(async () => undefined); + const action = (value: string, run = staleRun) => ({ + kind: "action" as const, + value, + title: value, + searchTerms: [value], + icon: null, + run, + }); + const stored = [ + { + value: "new-thread-in", + label: "Projects", + items: [action("scratch"), action("checkout-a"), action("checkout-b")], + }, + ]; + const current = [action("grouped-repository"), action("arriving-project", currentRun)]; + const groups = resolveNewThreadProjectGroups(stored, current); + const visible = filterCommandPaletteGroups({ + activeGroups: groups, + query: "", + isInSubmenu: true, + projectSearchItems: [], + threadSearchItems: [], + }); + expect(visible.flatMap((group) => group.items.map((item) => item.title))).toEqual([ + "grouped-repository", + "arriving-project", + ]); + const arriving = visible[0]!.items[1]!; + if (arriving.kind !== "action") throw new Error("Expected a project action"); + await arriving.run(); + expect(currentRun).toHaveBeenCalledOnce(); + expect(staleRun).not.toHaveBeenCalled(); + const other = [{ ...stored[0]!, value: "projects" }]; + expect(resolveNewThreadProjectGroups(other, current)).toBe(other); + }); +}); + it("resolves project metadata and browse roots within the active environment", () => { const local = { title: "Local project", workspaceRoot: "/local/repo" }; const remote = { title: "Remote project", workspaceRoot: "/remote/repo" }; diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index 96d439252ac53..d2f455dbd55fd 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -223,6 +223,13 @@ export interface CommandPaletteGroup { readonly items: ReadonlyArray; } +export function resolveNewThreadProjectGroups( + groups: ReadonlyArray, + items: ReadonlyArray, +): ReadonlyArray { + return groups[0]?.value === "new-thread-in" ? [{ ...groups[0], items }] : groups; +} + export interface CommandPaletteView { readonly addonIcon: ReactNode; readonly groups: ReadonlyArray; diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index deb9092726641..b0111a3d2aadf 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -175,6 +175,7 @@ import { type CommandPaletteSubmenuItem, type CommandPaletteView, filterCommandPaletteGroups, + resolveNewThreadProjectGroups, buildNestedSearchItems, filterPinnedBrowseEntries, getCommandPaletteInputPlaceholder, @@ -244,7 +245,7 @@ import { threadRecentUse } from "@t3tools/shared/paletteSearch"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; const ADD_PROJECT_ENTRY_GROUP = "add-project"; -const NEW_THREAD_IN_GROUP = "projects"; +const NEW_THREAD_IN_GROUP = "new-thread-in"; /** The submenu an open intent starts in; its groups resolve while rendering. */ function openIntentView(intent: CommandPaletteOpenIntent | null): CommandPaletteView | null { @@ -1607,18 +1608,16 @@ function OpenCommandPaletteDialog(props: { ) : currentView?.groups[0]?.value === ADD_PROJECT_ENTRY_GROUP ? addProjectEntryGroups() - : currentView?.groups[0]?.value === NEW_THREAD_IN_GROUP - ? [{ value: NEW_THREAD_IN_GROUP, label: "Projects", items: projectThreadItems }] - : currentView?.groups[0]?.value === "themes" - ? changeThemeItem.groups - : currentView?.groups[0]?.value === "appearance" - ? changeAppearanceItem.groups - : (currentView?.groups ?? rootGroups); + : currentView?.groups[0]?.value === "themes" + ? changeThemeItem.groups + : currentView?.groups[0]?.value === "appearance" + ? changeAppearanceItem.groups + : (currentView?.groups ?? rootGroups); const isLinkedThreadSearch = linkedThreadSearch?.linkedThreads !== undefined && deferredQuery === linkedThreadSearch.query; const filteredGroups = filterCommandPaletteGroups({ - activeGroups, + activeGroups: resolveNewThreadProjectGroups(activeGroups, projectThreadItems), query: deferredQuery, isInSubmenu: currentView !== null, ...(deferredQuery.trim().length > 0