authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-06 17:17:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-06 17:17:32-07:00
log517188b3caa135bc09ec0a4f93474ef17c8b5c4f
tree168e75832316b9dc2ee7a16801527485f349aee2
parent4b5c60485366421794787aee7513913f92eced29
signature Signed by PGP key B5690EEEBB952194

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 files changed, 90 insertions(+), 107 deletions(-)

apps/web/src/components/CommandPalette.tsx+90-107
...@@ -163,6 +163,7 @@ import {...@@ -163,6 +163,7 @@ import {
163 findHighlightedCommandPaletteItem,163 findHighlightedCommandPaletteItem,
164 type CommandPaletteActionItem,164 type CommandPaletteActionItem,
165 type CommandPaletteOpenIntent,165 type CommandPaletteOpenIntent,
166 type CommandPaletteProject,
166 type CommandPaletteSubmenuItem,167 type CommandPaletteSubmenuItem,
167 type CommandPaletteView,168 type CommandPaletteView,
168 filterCommandPaletteGroups,169 filterCommandPaletteGroups,
...@@ -1309,89 +1310,94 @@ function OpenCommandPaletteDialog(props: {...@@ -1309,89 +1310,94 @@ function OpenCommandPaletteDialog(props: {
1309 ],1310 ],
1310 );1311 );
13111312
1312 const projectThreadItems = useMemo(1313 const projectThreadItems = useMemo(() => {
1313 () =>1314 const isScratch = (project: CommandPaletteProject) =>
1314 enumerateCommandPaletteItems([1315 isScratchProject(project, scratchWorkspaceRootFor(project.environmentId));
1315 ...buildProjectActionItems({1316 const projectItems = enumerateCommandPaletteItems(
1316 // The no-project home shows once, as the "No project" item below.1317 buildProjectActionItems({
1317 projects: pickerProjects.filter(1318 // The no-project home shows once, as the "No project" item below.
1318 (project) => !isScratchProject(project, scratchWorkspaceRootFor(project.environmentId)),1319 projects: pickerProjects.filter((project) => !isScratch(project)),
1319 ),1320 valuePrefix: "new-thread-in",
1320 valuePrefix: "new-thread-in",1321 searchTerms: (project) => {
1321 searchTerms: (project) => {1322 const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);
1322 const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);1323 const location = projectEnvironmentLocationById.get(project.environmentId);
1323 const location = projectEnvironmentLocationById.get(project.environmentId);1324 return [
1324 return [1325 ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ??
1325 ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ??1326 []),
1326 []),1327 ...(location ? [location.label] : []),
1327 ...(location ? [location.label] : []),1328 ];
1328 ];1329 },
1329 },1330 renderDescription: (project) => {
1330 renderDescription: (project) => {1331 const location = projectEnvironmentLocationById.get(project.environmentId) ?? {
1331 const location = projectEnvironmentLocationById.get(project.environmentId) ?? {1332 kind: "remote",
1332 kind: "remote",1333 label: "Remote",
1333 label: "Remote",1334 machine: "server" as const,
1334 machine: "server" as const,1335 };
1335 };1336 return (
1336 return (1337 <span className="flex min-w-0 items-center gap-1">
1337 <span className="flex min-w-0 items-center gap-1">1338 <span className="inline-flex min-w-0 items-center gap-1">
1338 <span className="inline-flex min-w-0 items-center gap-1">1339 {location.kind === "remote" ? (
1339 {location.kind === "remote" ? (1340 <EnvironmentMachineIcon
1340 <EnvironmentMachineIcon1341 aria-hidden
1341 aria-hidden1342 kind={location.machine}
1342 kind={location.machine}1343 className={COMMAND_PALETTE_META_ICON_CLASS}
1343 className={COMMAND_PALETTE_META_ICON_CLASS}1344 />
1344 />1345 ) : null}
1345 ) : null}1346 <span className="truncate">{location.label}</span>
1346 <span className="truncate">{location.label}</span>
1347 </span>
1348 <CommandPaletteMetaDot />
1349 <span className="truncate">{project.workspaceRoot}</span>
1350 </span>1347 </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,
1351 );1362 );
1352 },1363 await handleNewThread(
1353 icon: projectFaviconIcon,1364 contextualRefBelongsToGroup
1354 runProject: async (project) => {1365 ? contextualProjectRef
1355 const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`);1366 : scopeProjectRef(project.environmentId, project.id),
1356 const contextualRefBelongsToGroup =1367 );
1357 contextualProjectRef !== null &&1368 },
1358 group?.memberProjectRefs.some(1369 }),
1359 (projectRef) =>1370 );
1360 projectRef.environmentId === contextualProjectRef.environmentId &&1371 if (scratchTargetEnvironmentId === null) return projectItems;
1361 projectRef.projectId === contextualProjectRef.projectId,1372
1362 );1373 // "No project" goes right after the current project: visible without
1363 await handleNewThread(1374 // scrolling past every project, while Enter still starts in the current
1364 contextualRefBelongsToGroup1375 // one. When the current thread has no project, it is the current entry and
1365 ? contextualProjectRef1376 // goes first. It keeps its own shortcut, so the projects' mod+1..9 hold.
1366 : scopeProjectRef(project.environmentId, project.id),1377 const noProjectIndex = pickerProjects[0] !== undefined && isScratch(pickerProjects[0]) ? 0 : 1;
1367 );1378 return [
1368 },1379 ...projectItems.slice(0, noProjectIndex),
1369 }),1380 {
1370 ...(scratchTargetEnvironmentId === null1381 kind: "action" as const,
1371 ? []1382 value: "new-thread-in:no-project",
1372 : [1383 searchTerms: ["no project", "without project", "none"],
1373 {1384 title: "No project",
1374 kind: "action" as const,1385 icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />,
1375 value: "new-thread-in:no-project",1386 shortcutCommand: "chat.newWithoutProject" as const,
1376 searchTerms: ["no project", "without project", "none"],1387 run: () => startScratchThread(scratchTargetEnvironmentId),
1377 title: "No project",1388 },
1378 icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />,1389 ...projectItems.slice(noProjectIndex),
1379 shortcutCommand: "chat.newWithoutProject" as const,1390 ];
1380 run: () => startScratchThread(scratchTargetEnvironmentId),1391 }, [
1381 },1392 contextualProjectRef,
1382 ]),1393 handleNewThread,
1383 ]),1394 pickerProjects,
1384 [1395 projectEnvironmentLocationById,
1385 contextualProjectRef,1396 projectGroupByTargetKey,
1386 handleNewThread,1397 scratchTargetEnvironmentId,
1387 pickerProjects,1398 scratchWorkspaceRootFor,
1388 projectEnvironmentLocationById,1399 startScratchThread,
1389 projectGroupByTargetKey,1400 ]);
1390 scratchTargetEnvironmentId,
1391 scratchWorkspaceRootFor,
1392 startScratchThread,
1393 ],
1394 );
13951401
1396 const allThreadItems = useMemo(1402 const allThreadItems = useMemo(
1397 () =>1403 () =>
...@@ -1848,35 +1854,12 @@ function OpenCommandPaletteDialog(props: {...@@ -1848,35 +1854,12 @@ function OpenCommandPaletteDialog(props: {
1848 setNewProjectFlow(null);1854 setNewProjectFlow(null);
1849 setViewStack([]);1855 setViewStack([]);
1850 setQuery("");1856 setQuery("");
1851 const currentPrefix =1857 // projectThreadItems already lists the current project first.
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;
1861 pushPaletteView({1858 pushPaletteView({
1862 addonIcon: <SquarePenIcon className={ADDON_ICON_CLASS} />,1859 addonIcon: <SquarePenIcon className={ADDON_ICON_CLASS} />,
1863 groups: [1860 groups: [{ value: "projects", label: "Projects", items: projectThreadItems }],
1864 {
1865 value: "projects",
1866 label: "Projects",
1867 items: enumerateCommandPaletteItems(prioritized),
1868 },
1869 ],
1870 });1861 });
1871 }, [1862 }, [clearOpenIntent, browseNavigation, openIntent, projectThreadItems, pushPaletteView]);
1872 clearOpenIntent,
1873 browseNavigation,
1874 currentProjectEnvironmentId,
1875 currentProjectId,
1876 openIntent,
1877 projectThreadItems,
1878 pushPaletteView,
1879 ]);
18801863
1881 const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = [];1864 const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = [];
18821865