authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-08-18 20:33:04+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-19 00:03:04+05:30
logfda740ad7b28334a6c579d801db22ad8f5914b5f
tree3ac73673805d905428e5c15da15e819d8127293d
parent3b5d476eb6af9d14efe037e9d163848d6fd048d7
signature Signed by PGP key B5690EEEBB952194

feat(web): show project location in new thread picker (#7392)

Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>

3 files changed, 66 insertions(+), 12 deletions(-)

apps/web/src/components/CommandPalette.logic.ts+2-1
......@@ -150,6 +150,7 @@ export function buildProjectActionItems(input: {
150150 icon: (project: Project) => ReactNode;
151151 runProject: (project: Project) => Promise<void>;
152152 searchTerms?: (project: Project) => ReadonlyArray<string>;
153 renderDescription?: (project: Project) => ReactNode;
153154 shortcutCommand?: KeybindingCommand;
154155}): CommandPaletteActionItem[] {
155156 return input.projects.map((project) => ({
......@@ -157,7 +158,7 @@ export function buildProjectActionItems(input: {
157158 value: `${input.valuePrefix}:${project.environmentId}:${project.id}`,
158159 searchTerms: [project.title, project.workspaceRoot, ...(input.searchTerms?.(project) ?? [])],
159160 title: project.title,
160 description: project.workspaceRoot,
161 description: input.renderDescription?.(project) ?? project.workspaceRoot,
161162 icon: input.icon(project),
162163 ...(input.shortcutCommand !== undefined ? { shortcutCommand: input.shortcutCommand } : {}),
163164 run: async () => {
apps/web/src/components/CommandPalette.tsx+56-3
......@@ -41,6 +41,7 @@ import {
4141 LinkIcon,
4242 MessageSquareIcon,
4343 PaletteIcon,
44 ServerIcon,
4445 SettingsIcon,
4546 SquarePenIcon,
4647 TextSearchIcon,
......@@ -131,7 +132,11 @@ import { ProjectFavicon } from "./ProjectFavicon";
131132import { ProjectFilePicker } from "./files/ProjectFilePicker";
132133import { ProjectContentSearchDialog } from "./search/ProjectContentSearchDialog";
133134import { toggleThemeEditorForTheme } from "./settings/themeEditorStore";
134import { ThreadCommandSubtitle } from "./ThreadCommandSubtitle";
135import {
136 COMMAND_PALETTE_META_ICON_CLASS,
137 CommandPaletteMetaDot,
138 ThreadCommandSubtitle,
139} from "./ThreadCommandSubtitle";
135140import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators";
136141import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server";
137142import {
......@@ -657,6 +662,27 @@ function OpenCommandPaletteDialog(props: {
657662 ),
658663 [environments],
659664 );
665 const projectEnvironmentLocationById = useMemo(
666 () =>
667 new Map(
668 environments.map((environment) => {
669 const isPrimary = environment.entry.target._tag === "PrimaryConnectionTarget";
670 const isLocal = isPrimary || isDesktopLocalConnectionTarget(environment.entry.target);
671 return [
672 environment.environmentId,
673 {
674 kind: isLocal ? "local" : "remote",
675 label: isPrimary
676 ? "Local"
677 : isLocal
678 ? `${environment.label} (Local)`
679 : environment.label,
680 },
681 ] as const;
682 }),
683 ),
684 [environments],
685 );
660686 const orderedProjects = useMemo(
661687 () =>
662688 orderItemsByPreferredIds({
......@@ -1011,8 +1037,29 @@ function OpenCommandPaletteDialog(props: {
10111037 valuePrefix: "new-thread-in",
10121038 searchTerms: (project) => {
10131039 const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1040 const location = projectEnvironmentLocationById.get(project.environmentId);
1041 return [
1042 ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ??
1043 []),
1044 ...(location ? [location.label] : []),
1045 ];
1046 },
1047 renderDescription: (project) => {
1048 const location = projectEnvironmentLocationById.get(project.environmentId) ?? {
1049 kind: "remote",
1050 label: "Remote",
1051 };
10141052 return (
1015 group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? []
1053 <span className="flex min-w-0 items-center gap-1">
1054 <span className="inline-flex min-w-0 items-center gap-1">
1055 {location.kind === "remote" ? (
1056 <ServerIcon aria-hidden className={COMMAND_PALETTE_META_ICON_CLASS} />
1057 ) : null}
1058 <span className="truncate">{location.label}</span>
1059 </span>
1060 <CommandPaletteMetaDot />
1061 <span className="truncate">{project.workspaceRoot}</span>
1062 </span>
10161063 );
10171064 },
10181065 icon: projectFavicon,
......@@ -1033,7 +1080,13 @@ function OpenCommandPaletteDialog(props: {
10331080 },
10341081 }),
10351082 ),
1036 [contextualProjectRef, handleNewThread, pickerProjects, projectGroupByTargetKey],
1083 [
1084 contextualProjectRef,
1085 handleNewThread,
1086 pickerProjects,
1087 projectEnvironmentLocationById,
1088 projectGroupByTargetKey,
1089 ],
10371090 );
10381091
10391092 const allThreadItems = useMemo(
apps/web/src/components/ThreadCommandSubtitle.tsx+8-8
......@@ -18,20 +18,20 @@ export type ThreadCommandSubtitleVariant =
1818export const THREAD_COMMAND_SUBTITLE_VARIANT: ThreadCommandSubtitleVariant =
1919 "favicon-workspace-harness";
2020
21const META_ICON_CLASS = "size-3 shrink-0 text-muted-foreground/70";
21export const COMMAND_PALETTE_META_ICON_CLASS = "size-3 shrink-0 text-muted-foreground/70";
2222
23function Dot() {
23export function CommandPaletteMetaDot() {
2424 return <span className="shrink-0 text-muted-foreground/50">·</span>;
2525}
2626
2727function WorkspaceIcon(props: { variant: ThreadCommandSubtitleVariant; isWorktree: boolean }) {
2828 if (props.isWorktree) {
29 return <FolderGit2Icon className={META_ICON_CLASS} aria-hidden />;
29 return <FolderGit2Icon className={COMMAND_PALETTE_META_ICON_CLASS} aria-hidden />;
3030 }
3131 if (props.variant === "favicon-branch-harness") {
32 return <GitBranchIcon className={META_ICON_CLASS} aria-hidden />;
32 return <GitBranchIcon className={COMMAND_PALETTE_META_ICON_CLASS} aria-hidden />;
3333 }
34 return <FolderIcon className={META_ICON_CLASS} aria-hidden />;
34 return <FolderIcon className={COMMAND_PALETTE_META_ICON_CLASS} aria-hidden />;
3535}
3636
3737export function ThreadCommandSubtitle(props: {
......@@ -82,7 +82,7 @@ export function ThreadCommandSubtitle(props: {
8282
8383 {branchLabel ? (
8484 <>
85 {projectLabel ? <Dot /> : null}
85 {projectLabel ? <CommandPaletteMetaDot /> : null}
8686 <span className="inline-flex min-w-0 items-center gap-1">
8787 <WorkspaceIcon variant={variant} isWorktree={isWorktree} />
8888 <span className="min-w-0 truncate">{branchLabel}</span>
......@@ -92,7 +92,7 @@ export function ThreadCommandSubtitle(props: {
9292
9393 {showHarness && props.driverKind ? (
9494 <>
95 {projectLabel || branchLabel ? <Dot /> : null}
95 {projectLabel || branchLabel ? <CommandPaletteMetaDot /> : null}
9696 <ProviderInstanceIcon
9797 driverKind={props.driverKind}
9898 displayName={props.providerDisplayName ?? props.driverKind}
......@@ -103,7 +103,7 @@ export function ThreadCommandSubtitle(props: {
103103
104104 {props.isCurrent ? (
105105 <>
106 {projectLabel || branchLabel || showHarness ? <Dot /> : null}
106 {projectLabel || branchLabel || showHarness ? <CommandPaletteMetaDot /> : null}
107107 <span className="shrink-0">Current thread</span>
108108 </>
109109 ) : null}