Skip to content

Commit 517188b

Browse files
fix(web): show "No project" near the top of the new thread picker (#16628)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 4b5c604 commit 517188b

1 file changed

Lines changed: 90 additions & 107 deletions

File tree

‎apps/web/src/components/CommandPalette.tsx‎

Lines changed: 90 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ import {
163163
findHighlightedCommandPaletteItem,
164164
type CommandPaletteActionItem,
165165
type CommandPaletteOpenIntent,
166+
type CommandPaletteProject,
166167
type CommandPaletteSubmenuItem,
167168
type CommandPaletteView,
168169
filterCommandPaletteGroups,
@@ -1309,89 +1310,94 @@ function OpenCommandPaletteDialog(props: {
13091310
],
13101311
);
13111312

1312-
const projectThreadItems = useMemo(
1313-
() =>
1314-
enumerateCommandPaletteItems([
1315-
...buildProjectActionItems({
1316-
// The no-project home shows once, as the "No project" item below.
1317-
projects: pickerProjects.filter(
1318-
(project) => !isScratchProject(project, scratchWorkspaceRootFor(project.environmentId)),
1319-
),
1320-
valuePrefix: "new-thread-in",
1321-
searchTerms: (project) => {
1322-
const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1323-
const location = projectEnvironmentLocationById.get(project.environmentId);
1324-
return [
1325-
...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ??
1326-
[]),
1327-
...(location ? [location.label] : []),
1328-
];
1329-
},
1330-
renderDescription: (project) => {
1331-
const location = projectEnvironmentLocationById.get(project.environmentId) ?? {
1332-
kind: "remote",
1333-
label: "Remote",
1334-
machine: "server" as const,
1335-
};
1336-
return (
1337-
<span className="flex min-w-0 items-center gap-1">
1338-
<span className="inline-flex min-w-0 items-center gap-1">
1339-
{location.kind === "remote" ? (
1340-
<EnvironmentMachineIcon
1341-
aria-hidden
1342-
kind={location.machine}
1343-
className={COMMAND_PALETTE_META_ICON_CLASS}
1344-
/>
1345-
) : null}
1346-
<span className="truncate">{location.label}</span>
1347-
</span>
1348-
<CommandPaletteMetaDot />
1349-
<span className="truncate">{project.workspaceRoot}</span>
1313+
const projectThreadItems = useMemo(() => {
1314+
const isScratch = (project: CommandPaletteProject) =>
1315+
isScratchProject(project, scratchWorkspaceRootFor(project.environmentId));
1316+
const projectItems = enumerateCommandPaletteItems(
1317+
buildProjectActionItems({
1318+
// The no-project home shows once, as the "No project" item below.
1319+
projects: pickerProjects.filter((project) => !isScratch(project)),
1320+
valuePrefix: "new-thread-in",
1321+
searchTerms: (project) => {
1322+
const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1323+
const location = projectEnvironmentLocationById.get(project.environmentId);
1324+
return [
1325+
...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ??
1326+
[]),
1327+
...(location ? [location.label] : []),
1328+
];
1329+
},
1330+
renderDescription: (project) => {
1331+
const location = projectEnvironmentLocationById.get(project.environmentId) ?? {
1332+
kind: "remote",
1333+
label: "Remote",
1334+
machine: "server" as const,
1335+
};
1336+
return (
1337+
<span className="flex min-w-0 items-center gap-1">
1338+
<span className="inline-flex min-w-0 items-center gap-1">
1339+
{location.kind === "remote" ? (
1340+
<EnvironmentMachineIcon
1341+
aria-hidden
1342+
kind={location.machine}
1343+
className={COMMAND_PALETTE_META_ICON_CLASS}
1344+
/>
1345+
) : null}
1346+
<span className="truncate">{location.label}</span>
13501347
</span>
1348+
<CommandPaletteMetaDot />
1349+
<span className="truncate">{project.workspaceRoot}</span>
1350+
</span>
1351+
);
1352+
},
1353+
icon: projectFaviconIcon,
1354+
runProject: async (project) => {
1355+
const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1356+
const contextualRefBelongsToGroup =
1357+
contextualProjectRef !== null &&
1358+
group?.memberProjectRefs.some(
1359+
(projectRef) =>
1360+
projectRef.environmentId === contextualProjectRef.environmentId &&
1361+
projectRef.projectId === contextualProjectRef.projectId,
13511362
);
1352-
},
1353-
icon: projectFaviconIcon,
1354-
runProject: async (project) => {
1355-
const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1356-
const contextualRefBelongsToGroup =
1357-
contextualProjectRef !== null &&
1358-
group?.memberProjectRefs.some(
1359-
(projectRef) =>
1360-
projectRef.environmentId === contextualProjectRef.environmentId &&
1361-
projectRef.projectId === contextualProjectRef.projectId,
1362-
);
1363-
await handleNewThread(
1364-
contextualRefBelongsToGroup
1365-
? contextualProjectRef
1366-
: scopeProjectRef(project.environmentId, project.id),
1367-
);
1368-
},
1369-
}),
1370-
...(scratchTargetEnvironmentId === null
1371-
? []
1372-
: [
1373-
{
1374-
kind: "action" as const,
1375-
value: "new-thread-in:no-project",
1376-
searchTerms: ["no project", "without project", "none"],
1377-
title: "No project",
1378-
icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />,
1379-
shortcutCommand: "chat.newWithoutProject" as const,
1380-
run: () => startScratchThread(scratchTargetEnvironmentId),
1381-
},
1382-
]),
1383-
]),
1384-
[
1385-
contextualProjectRef,
1386-
handleNewThread,
1387-
pickerProjects,
1388-
projectEnvironmentLocationById,
1389-
projectGroupByTargetKey,
1390-
scratchTargetEnvironmentId,
1391-
scratchWorkspaceRootFor,
1392-
startScratchThread,
1393-
],
1394-
);
1363+
await handleNewThread(
1364+
contextualRefBelongsToGroup
1365+
? contextualProjectRef
1366+
: scopeProjectRef(project.environmentId, project.id),
1367+
);
1368+
},
1369+
}),
1370+
);
1371+
if (scratchTargetEnvironmentId === null) return projectItems;
1372+
1373+
// "No project" goes right after the current project: visible without
1374+
// scrolling past every project, while Enter still starts in the current
1375+
// one. When the current thread has no project, it is the current entry and
1376+
// goes first. It keeps its own shortcut, so the projects' mod+1..9 hold.
1377+
const noProjectIndex = pickerProjects[0] !== undefined && isScratch(pickerProjects[0]) ? 0 : 1;
1378+
return [
1379+
...projectItems.slice(0, noProjectIndex),
1380+
{
1381+
kind: "action" as const,
1382+
value: "new-thread-in:no-project",
1383+
searchTerms: ["no project", "without project", "none"],
1384+
title: "No project",
1385+
icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />,
1386+
shortcutCommand: "chat.newWithoutProject" as const,
1387+
run: () => startScratchThread(scratchTargetEnvironmentId),
1388+
},
1389+
...projectItems.slice(noProjectIndex),
1390+
];
1391+
}, [
1392+
contextualProjectRef,
1393+
handleNewThread,
1394+
pickerProjects,
1395+
projectEnvironmentLocationById,
1396+
projectGroupByTargetKey,
1397+
scratchTargetEnvironmentId,
1398+
scratchWorkspaceRootFor,
1399+
startScratchThread,
1400+
]);
13951401

13961402
const allThreadItems = useMemo(
13971403
() =>
@@ -1848,35 +1854,12 @@ function OpenCommandPaletteDialog(props: {
18481854
setNewProjectFlow(null);
18491855
setViewStack([]);
18501856
setQuery("");
1851-
const currentPrefix =
1852-
currentProjectEnvironmentId && currentProjectId
1853-
? `new-thread-in:${currentProjectEnvironmentId}:${currentProjectId}`
1854-
: null;
1855-
const prioritized = currentPrefix
1856-
? [
1857-
...projectThreadItems.filter((item) => item.value === currentPrefix),
1858-
...projectThreadItems.filter((item) => item.value !== currentPrefix),
1859-
]
1860-
: projectThreadItems;
1857+
// projectThreadItems already lists the current project first.
18611858
pushPaletteView({
18621859
addonIcon: <SquarePenIcon className={ADDON_ICON_CLASS} />,
1863-
groups: [
1864-
{
1865-
value: "projects",
1866-
label: "Projects",
1867-
items: enumerateCommandPaletteItems(prioritized),
1868-
},
1869-
],
1860+
groups: [{ value: "projects", label: "Projects", items: projectThreadItems }],
18701861
});
1871-
}, [
1872-
clearOpenIntent,
1873-
browseNavigation,
1874-
currentProjectEnvironmentId,
1875-
currentProjectId,
1876-
openIntent,
1877-
projectThreadItems,
1878-
pushPaletteView,
1879-
]);
1862+
}, [clearOpenIntent, browseNavigation, openIntent, projectThreadItems, pushPaletteView]);
18801863

18811864
const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = [];
18821865

0 commit comments

Comments
 (0)