From 399d9c83194252ce03aa8ddcc37889189fd76813 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:56:03 +0200 Subject: [PATCH 1/3] fix(web): choose a server for grouped projects --- .../src/components/CommandPalette.logic.ts | 8 +- apps/web/src/components/CommandPalette.tsx | 135 ++++++++++++------ apps/web/src/components/Sidebar.logic.test.ts | 6 +- apps/web/src/components/Sidebar.logic.ts | 8 +- apps/web/src/components/Sidebar.tsx | 39 ++--- apps/web/src/environmentGrouping.test.ts | 10 +- apps/web/src/routes/_chat.tsx | 27 ++-- apps/web/src/sidebarProjectGrouping.ts | 10 ++ 8 files changed, 157 insertions(+), 86 deletions(-) diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index 688a8a8ea791..d6cd3bcc9d28 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -126,7 +126,13 @@ export interface CommandPaletteView { export function enumerateCommandPaletteItems( items: ReadonlyArray, -): CommandPaletteActionItem[] { +): CommandPaletteActionItem[]; +export function enumerateCommandPaletteItems( + items: ReadonlyArray, +): Array; +export function enumerateCommandPaletteItems( + items: ReadonlyArray, +): Array { return items.map((item, index) => { const shortcutCommand = THREAD_JUMP_KEYBINDING_COMMANDS[index]; if (shortcutCommand) return { ...item, shortcutCommand }; diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index c5ec3f095167..d569c30300d6 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -42,6 +42,7 @@ import { FolderPlusIcon, LinkIcon, MessageSquareIcon, + MonitorIcon, PaletteIcon, ServerIcon, SettingsIcon, @@ -165,6 +166,7 @@ import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore" import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, + type SidebarProjectGroupMember, } from "../sidebarProjectGrouping"; import type { Project } from "../types"; @@ -665,7 +667,7 @@ function OpenCommandPaletteDialog(props: { { kind: isLocal ? "local" : "remote", label: isPrimary - ? "Local" + ? environment.label : isLocal ? `${environment.label} (Local)` : environment.label, @@ -1024,61 +1026,102 @@ function OpenCommandPaletteDialog(props: { const projectThreadItems = useMemo( () => enumerateCommandPaletteItems( - buildProjectActionItems({ - projects: pickerProjects, - valuePrefix: "new-thread-in", - searchTerms: (project) => { - const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); - const location = projectEnvironmentLocationById.get(project.environmentId); - return [ - ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? - []), - ...(location ? [location.label] : []), - ]; - }, - renderDescription: (project) => { - const location = projectEnvironmentLocationById.get(project.environmentId) ?? { - kind: "remote", + projectPickerEntries.map( + ({ group, targetProject }): CommandPaletteActionItem | CommandPaletteSubmenuItem => { + const projectByEnvironmentId = new Map(); + for (const member of group.memberProjects) { + if ( + !projectByEnvironmentId.has(member.environmentId) || + (member.environmentId === targetProject.environmentId && + member.id === targetProject.id) + ) { + projectByEnvironmentId.set(member.environmentId, member); + } + } + const locations = [...projectByEnvironmentId.values()].map((member) => { + const location = projectEnvironmentLocationById.get(member.environmentId) ?? { + kind: "remote" as const, + label: member.environmentLabel ?? member.environmentId, + }; + return { location, member }; + }); + const searchTerms = group.memberProjects.flatMap((member) => [ + member.title, + member.workspaceRoot, + member.environmentLabel ?? member.environmentId, + ]); + const icon = projectFavicon(targetProject); + + if (locations.length > 1) { + return { + kind: "submenu", + value: `new-thread-in:${targetProject.environmentId}:${targetProject.id}`, + searchTerms, + title: group.displayName, + description: `${locations.length} servers`, + icon, + addonIcon: , + groups: [ + { + value: `new-thread-in-servers:${group.projectKey}`, + label: "Run on", + items: locations.map(({ location, member }) => { + const LocationIcon = location.kind === "local" ? MonitorIcon : ServerIcon; + return { + kind: "action" as const, + value: `new-thread-in-server:${member.environmentId}:${member.id}`, + searchTerms: [location.label, member.workspaceRoot, member.title], + title: location.label, + description: member.workspaceRoot, + icon: , + run: async () => { + await handleNewThread(scopeProjectRef(member.environmentId, member.id)); + }, + }; + }), + }, + ], + }; + } + + const activeLocation = locations[0]?.location ?? { + kind: "remote" as const, label: "Remote", }; - return ( - - - {location.kind === "remote" ? ( - - ) : null} - {location.label} - - - {project.workspaceRoot} - - ); - }, - icon: projectFavicon, - runProject: async (project) => { - const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); const contextualRefBelongsToGroup = contextualProjectRef !== null && - group?.memberProjectRefs.some( + group.memberProjectRefs.some( (projectRef) => projectRef.environmentId === contextualProjectRef.environmentId && projectRef.projectId === contextualProjectRef.projectId, ); - await handleNewThread( - contextualRefBelongsToGroup - ? contextualProjectRef - : scopeProjectRef(project.environmentId, project.id), - ); + const LocationIcon = activeLocation.kind === "local" ? MonitorIcon : ServerIcon; + return { + kind: "action", + value: `new-thread-in:${targetProject.environmentId}:${targetProject.id}`, + searchTerms, + title: group.displayName, + description: ( + + + {activeLocation.label} + + {targetProject.workspaceRoot} + + ), + icon, + run: async () => { + await handleNewThread( + contextualRefBelongsToGroup + ? contextualProjectRef + : scopeProjectRef(targetProject.environmentId, targetProject.id), + ); + }, + }; }, - }), + ), ), - [ - contextualProjectRef, - handleNewThread, - pickerProjects, - projectEnvironmentLocationById, - projectGroupByTargetKey, - ], + [contextualProjectRef, handleNewThread, projectEnvironmentLocationById, projectPickerEntries], ); const allThreadItems = useMemo( diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index ba75f2eaaf54..ab5b0661af7c 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -462,15 +462,15 @@ describe("isSidebarNestedLinkClick", () => { }); describe("shouldCreateNewThreadInCurrentProject", () => { - it("creates directly on shift+click in a multi-project setup", () => { + it("creates directly on shift+click with multiple destinations", () => { expect(shouldCreateNewThreadInCurrentProject(true, 2)).toBe(true); }); - it("opens the picker on a plain click in a multi-project setup", () => { + it("opens the picker on a plain click with multiple destinations", () => { expect(shouldCreateNewThreadInCurrentProject(false, 2)).toBe(false); }); - it("creates directly on any click with a single project", () => { + it("creates directly on any click with a single destination", () => { expect(shouldCreateNewThreadInCurrentProject(false, 1)).toBe(true); expect(shouldCreateNewThreadInCurrentProject(true, 1)).toBe(true); }); diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index 747fc07d3daf..34781de5447a 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -300,14 +300,14 @@ export function isSidebarNestedLinkClick(target: EventTarget | null): boolean { } // Shift+click on the new thread button creates directly in the current -// project, skipping the command palette's project picker. With a single -// project there is nothing to pick, so a plain click already creates +// project, skipping the command palette's destination picker. With a single +// destination there is nothing to pick, so a plain click already creates // immediately and the modifier changes nothing. export function shouldCreateNewThreadInCurrentProject( shiftKey: boolean, - projectGroupCount: number, + destinationCount: number, ): boolean { - return shiftKey || projectGroupCount <= 1; + return shiftKey || destinationCount <= 1; } export function orderItemsByPreferredIds(input: { diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 971ead810f07..5ac3b14f79dd 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -93,6 +93,7 @@ import { readLocalApi } from "../localApi"; import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; import { buildSidebarProjectSnapshots, + countNewThreadDestinations, type SidebarProjectSnapshot, } from "../sidebarProjectGrouping"; import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; @@ -1868,6 +1869,10 @@ export default function Sidebar() { () => sortLogicalProjectsForSidebar(unsortedProjectGroups, threads, sidebarProjectSortOrder), [sidebarProjectSortOrder, threads, unsortedProjectGroups], ); + const newThreadDestinationCount = useMemo( + () => countNewThreadDestinations(projectGroups), + [projectGroups], + ); const serverConfigs = useAtomValue(environmentServerConfigsAtom); // Threads on non-primary environments (T3 Connect, hosted) resolve their // provider entry from their own environment's config: default instance ids @@ -3332,14 +3337,16 @@ export default function Sidebar() { // New thread defaults to the project you're in (active thread's project, // falling back to the top project) — same resolution the command palette - // uses. The command palette already offers a "New thread in..." submenu - // for multi-project setups. + // uses. A grouped project that exists on multiple environments still has + // multiple destinations, so route it through the picker too. const handleNewThreadClick = useCallback( (event?: ReactMouseEvent) => { - // One project: nothing to pick, create immediately. Shift+click creates - // directly in the current project even with several projects, skipping - // the palette picker. - if (shouldCreateNewThreadInCurrentProject(event?.shiftKey ?? false, projectGroups.length)) { + // One destination: nothing to pick, create immediately. Shift+click + // creates directly in the current project even with several destinations, + // skipping the palette picker. + if ( + shouldCreateNewThreadInCurrentProject(event?.shiftKey ?? false, newThreadDestinationCount) + ) { if (isMobile) setOpenMobile(false); void startNewThreadFromContext({ activeDraftThread: newThreadContext.activeDraftThread, @@ -3352,20 +3359,18 @@ export default function Sidebar() { if (isMobile) setOpenMobile(false); openCommandPalette({ open: "new-thread-in" }); }, - [isMobile, newThreadContext, projectGroups.length, setOpenMobile], + [isMobile, newThreadContext, newThreadDestinationCount, setOpenMobile], ); - // The button mirrors chat.new: in multi-project setups both route through - // the command palette's "New thread in..." picker, and in single-project - // setups both create immediately. In multi-project setups the label is only - // the picker's shortcut: falling back to chat.newLocal would advertise the - // same shortcut for both the picker and direct create. In single-project - // setups both commands create directly, so chat.newLocal is a valid - // fallback. The second tooltip line (multi-project only) advertises - // shift+click and its keyboard twin chat.newLocal for direct create. + // The button mirrors chat.new: with multiple destinations both route through + // the command palette picker, and with one destination both create + // immediately. The second tooltip line advertises shift+click and its + // keyboard twin chat.newLocal for direct create. const newThreadShortcutLabel = shortcutLabelForCommand(keybindings, "chat.new") ?? - (projectGroups.length <= 1 ? shortcutLabelForCommand(keybindings, "chat.newLocal") : undefined); + (newThreadDestinationCount <= 1 + ? shortcutLabelForCommand(keybindings, "chat.newLocal") + : undefined); const newThreadInProjectShortcutLabel = shortcutLabelForCommand(keybindings, "chat.newLocal"); return ( <> @@ -3444,7 +3449,7 @@ export default function Sidebar() { /> - {projectGroups.length > 1 ? ( + {newThreadDestinationCount > 1 ? ( {newThreadShortcutLabel diff --git a/apps/web/src/environmentGrouping.test.ts b/apps/web/src/environmentGrouping.test.ts index 9029f1204d36..1c9df7931676 100644 --- a/apps/web/src/environmentGrouping.test.ts +++ b/apps/web/src/environmentGrouping.test.ts @@ -12,6 +12,7 @@ import { buildPhysicalToLogicalProjectKeyMap, buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, + countNewThreadDestinations, } from "./sidebarProjectGrouping"; import { orderItemsByPreferredIds } from "./components/Sidebar.logic"; import { legacyProjectCwdPreferenceKey } from "./uiStateStore"; @@ -63,7 +64,7 @@ describe("environment grouping", () => { expect(deriveLogicalProjectKey(remote)).toBe(repositoryIdentity.canonicalKey); }); - it("counts cross-environment copies as one new-thread project choice", () => { + it("counts each server copy as a new-thread destination", () => { const primary = makeProject({ repositoryIdentity }); const remote = makeProject({ id: ProjectId.make("project-remote"), @@ -71,14 +72,15 @@ describe("environment grouping", () => { repositoryIdentity, }); - const projectGroupCount = buildSidebarProjectSnapshots({ + const groups = buildSidebarProjectSnapshots({ projects: [primary, remote], settings: defaultGroupingSettings, primaryEnvironmentId, resolveEnvironmentLabel: () => null, - }).length; + }); - expect(projectGroupCount).toBe(1); + expect(groups).toHaveLength(1); + expect(countNewThreadDestinations(groups)).toBe(2); }); it("keeps projects without repository identity physically scoped", () => { diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx index e084e22c2cbb..372303300761 100644 --- a/apps/web/src/routes/_chat.tsx +++ b/apps/web/src/routes/_chat.tsx @@ -8,7 +8,10 @@ import { openCommandPalette } from "../commandPaletteBus"; import { useProjects } from "../state/entities"; import { usePrimaryEnvironmentId } from "../state/environments"; import { selectProjectGroupingSettings } from "../logicalProject"; -import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping"; +import { + buildSidebarProjectSnapshots, + countNewThreadDestinations, +} from "../sidebarProjectGrouping"; import { dispatchPreviewAction } from "../components/preview/previewActionBus"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { startNewThreadFromContext } from "../lib/chatThreadActions"; @@ -32,14 +35,16 @@ function ChatRouteGlobalShortcuts() { const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); const projects = useProjects(); const primaryEnvironmentId = usePrimaryEnvironmentId(); - const projectGroupCount = useMemo( + const newThreadDestinationCount = useMemo( () => - buildSidebarProjectSnapshots({ - projects, - settings: projectGroupingSettings, - primaryEnvironmentId, - resolveEnvironmentLabel: () => null, - }).length, + countNewThreadDestinations( + buildSidebarProjectSnapshots({ + projects, + settings: projectGroupingSettings, + primaryEnvironmentId, + resolveEnvironmentLabel: () => null, + }), + ), [primaryEnvironmentId, projectGroupingSettings, projects], ); const terminalOpen = useTerminalUiStateStore((state) => @@ -94,8 +99,8 @@ function ChatRouteGlobalShortcuts() { event.stopPropagation(); // The default sidebar routes creation through the command palette // whenever there is a real choice to make; the legacy sidebar (and - // single-project setups) keep the immediate contextual create. - if (!legacySidebarEnabled && projectGroupCount > 1) { + // single-destination setups) keep the immediate contextual create. + if (!legacySidebarEnabled && newThreadDestinationCount > 1) { openCommandPalette({ open: "new-thread-in" }); return; } @@ -164,7 +169,7 @@ function ChatRouteGlobalShortcuts() { keybindings, defaultProjectRef, previewOpen, - projectGroupCount, + newThreadDestinationCount, routeThreadRef, selectedThreadKeysSize, legacySidebarEnabled, diff --git a/apps/web/src/sidebarProjectGrouping.ts b/apps/web/src/sidebarProjectGrouping.ts index be92fcaee849..a007425e6359 100644 --- a/apps/web/src/sidebarProjectGrouping.ts +++ b/apps/web/src/sidebarProjectGrouping.ts @@ -31,6 +31,16 @@ export interface SidebarProjectPickerEntry { isPreferred: boolean; } +export function countNewThreadDestinations( + groups: ReadonlyArray>, +): number { + return groups.reduce( + (count, group) => + count + new Set(group.memberProjects.map((project) => project.environmentId)).size, + 0, + ); +} + export function buildPhysicalToLogicalProjectKeyMap(input: { projects: ReadonlyArray; settings: ProjectGroupingSettings; From 52713599f648c3235f218a30155169ede97b3089 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:20:12 +0200 Subject: [PATCH 2/3] fix(web): keep server choices consistent --- apps/web/src/components/CommandPalette.tsx | 41 +++++++++------------- apps/web/src/environmentGrouping.test.ts | 4 +++ apps/web/src/sidebarProjectGrouping.ts | 27 ++++++++++++++ 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index d569c30300d6..67077e168bd7 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -42,7 +42,6 @@ import { FolderPlusIcon, LinkIcon, MessageSquareIcon, - MonitorIcon, PaletteIcon, ServerIcon, SettingsIcon, @@ -166,7 +165,7 @@ import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore" import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, - type SidebarProjectGroupMember, + listNewThreadProjectDestinations, } from "../sidebarProjectGrouping"; import type { Project } from "../types"; @@ -667,7 +666,11 @@ function OpenCommandPaletteDialog(props: { { kind: isLocal ? "local" : "remote", label: isPrimary - ? environment.label + ? resolveEnvironmentOptionLabel({ + isPrimary, + environmentId: environment.environmentId, + runtimeLabel: environment.label, + }) : isLocal ? `${environment.label} (Local)` : environment.label, @@ -1028,23 +1031,15 @@ function OpenCommandPaletteDialog(props: { enumerateCommandPaletteItems( projectPickerEntries.map( ({ group, targetProject }): CommandPaletteActionItem | CommandPaletteSubmenuItem => { - const projectByEnvironmentId = new Map(); - for (const member of group.memberProjects) { - if ( - !projectByEnvironmentId.has(member.environmentId) || - (member.environmentId === targetProject.environmentId && - member.id === targetProject.id) - ) { - projectByEnvironmentId.set(member.environmentId, member); - } - } - const locations = [...projectByEnvironmentId.values()].map((member) => { - const location = projectEnvironmentLocationById.get(member.environmentId) ?? { - kind: "remote" as const, - label: member.environmentLabel ?? member.environmentId, - }; - return { location, member }; - }); + const locations = listNewThreadProjectDestinations(group, targetProject).map( + (member) => { + const location = projectEnvironmentLocationById.get(member.environmentId) ?? { + kind: "remote" as const, + label: member.environmentLabel ?? member.environmentId, + }; + return { location, member }; + }, + ); const searchTerms = group.memberProjects.flatMap((member) => [ member.title, member.workspaceRoot, @@ -1066,14 +1061,13 @@ function OpenCommandPaletteDialog(props: { value: `new-thread-in-servers:${group.projectKey}`, label: "Run on", items: locations.map(({ location, member }) => { - const LocationIcon = location.kind === "local" ? MonitorIcon : ServerIcon; return { kind: "action" as const, value: `new-thread-in-server:${member.environmentId}:${member.id}`, searchTerms: [location.label, member.workspaceRoot, member.title], title: location.label, description: member.workspaceRoot, - icon: , + icon: , run: async () => { await handleNewThread(scopeProjectRef(member.environmentId, member.id)); }, @@ -1095,7 +1089,6 @@ function OpenCommandPaletteDialog(props: { projectRef.environmentId === contextualProjectRef.environmentId && projectRef.projectId === contextualProjectRef.projectId, ); - const LocationIcon = activeLocation.kind === "local" ? MonitorIcon : ServerIcon; return { kind: "action", value: `new-thread-in:${targetProject.environmentId}:${targetProject.id}`, @@ -1103,7 +1096,7 @@ function OpenCommandPaletteDialog(props: { title: group.displayName, description: ( - + {activeLocation.label} {targetProject.workspaceRoot} diff --git a/apps/web/src/environmentGrouping.test.ts b/apps/web/src/environmentGrouping.test.ts index 1c9df7931676..dff7e410e4da 100644 --- a/apps/web/src/environmentGrouping.test.ts +++ b/apps/web/src/environmentGrouping.test.ts @@ -13,6 +13,7 @@ import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, countNewThreadDestinations, + listNewThreadProjectDestinations, } from "./sidebarProjectGrouping"; import { orderItemsByPreferredIds } from "./components/Sidebar.logic"; import { legacyProjectCwdPreferenceKey } from "./uiStateStore"; @@ -81,6 +82,9 @@ describe("environment grouping", () => { expect(groups).toHaveLength(1); expect(countNewThreadDestinations(groups)).toBe(2); + expect( + listNewThreadProjectDestinations(groups[0]!, remote).map((project) => project.id), + ).toEqual([remote.id, primary.id]); }); it("keeps projects without repository identity physically scoped", () => { diff --git a/apps/web/src/sidebarProjectGrouping.ts b/apps/web/src/sidebarProjectGrouping.ts index a007425e6359..aa57b68a68c8 100644 --- a/apps/web/src/sidebarProjectGrouping.ts +++ b/apps/web/src/sidebarProjectGrouping.ts @@ -41,6 +41,33 @@ export function countNewThreadDestinations( ); } +export function listNewThreadProjectDestinations( + group: Pick, + targetProject: Pick, +): SidebarProjectGroupMember[] { + const projectByEnvironmentId = new Map(); + for (const member of group.memberProjects) { + if ( + !projectByEnvironmentId.has(member.environmentId) || + (member.environmentId === targetProject.environmentId && member.id === targetProject.id) + ) { + projectByEnvironmentId.set(member.environmentId, member); + } + } + + const destinations = [...projectByEnvironmentId.values()]; + const targetIndex = destinations.findIndex( + (project) => project.environmentId === targetProject.environmentId, + ); + if (targetIndex <= 0) return destinations; + + return [ + destinations[targetIndex]!, + ...destinations.slice(0, targetIndex), + ...destinations.slice(targetIndex + 1), + ]; +} + export function buildPhysicalToLogicalProjectKeyMap(input: { projects: ReadonlyArray; settings: ProjectGroupingSettings; From db5c47c9839a3fe4dc906205e592ffa8ec685abb Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:36:02 +0200 Subject: [PATCH 3/3] fix(web): distinguish local project destinations --- apps/web/src/components/CommandPalette.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 67077e168bd7..994abd703b8c 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -42,6 +42,7 @@ import { FolderPlusIcon, LinkIcon, MessageSquareIcon, + MonitorIcon, PaletteIcon, ServerIcon, SettingsIcon, @@ -1061,13 +1062,14 @@ function OpenCommandPaletteDialog(props: { value: `new-thread-in-servers:${group.projectKey}`, label: "Run on", items: locations.map(({ location, member }) => { + const LocationIcon = location.kind === "local" ? MonitorIcon : ServerIcon; return { kind: "action" as const, value: `new-thread-in-server:${member.environmentId}:${member.id}`, searchTerms: [location.label, member.workspaceRoot, member.title], title: location.label, description: member.workspaceRoot, - icon: , + icon: , run: async () => { await handleNewThread(scopeProjectRef(member.environmentId, member.id)); }, @@ -1082,6 +1084,7 @@ function OpenCommandPaletteDialog(props: { kind: "remote" as const, label: "Remote", }; + const ActiveLocationIcon = activeLocation.kind === "local" ? MonitorIcon : ServerIcon; const contextualRefBelongsToGroup = contextualProjectRef !== null && group.memberProjectRefs.some( @@ -1096,7 +1099,7 @@ function OpenCommandPaletteDialog(props: { title: group.displayName, description: ( - + {activeLocation.label} {targetProject.workspaceRoot}