History

Isolate command palette renders

Cursor Agent committed 5 months ago

Commit fdf22101 parent d1e85c41 file · +90 −37Browse files

Unified · Split

apps/web/src/components/CommandPalette.tsx+90−37
@@ -121,6 +121,8 @@ import type { ChatComposerHandle } from "./chat/ChatComposer"; · 120 unmodified lines
121121
122122 const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = [];
123123 const BROWSE_STALE_TIME_MS = 30_000;
124+type CommandPaletteOpenIntent = ReturnType<typeof useCommandPaletteStore.getState>["openIntent"];
125+type BrowseEntry = FilesystemBrowseResult["entries"][number];
124126
125127 function getLocalFileManagerName(platform: string): string {
126128 if (isMacPlatform(platform)) {
@@ -327,11 +329,31 @@ function errorMessage(error: unknown): string { · 200 unmodified lines
327329 }
328330
329331 export function CommandPalette({ children }: { children: ReactNode }) {
332+ const composerHandleRef = useRef<ChatComposerHandle | null>(null);
333+
334+ return (
335+ <ComposerHandleContext value={composerHandleRef}>
336+ {children}
337+ <CommandPaletteController />
338+ </ComposerHandleContext>
339+ );
340+}
341+
342+function CommandPaletteController() {
330343 const open = useCommandPaletteStore((store) => store.open);
331344 const setOpen = useCommandPaletteStore((store) => store.setOpen);
345+ useCommandPaletteShortcut();
346+
347+ return (
348+ <CommandDialog open={open} onOpenChange={setOpen}>
349+ <CommandPaletteDialog />
350+ </CommandDialog>
351+ );
352+}
353+
354+function useCommandPaletteShortcut() {
332355 const toggleOpen = useCommandPaletteStore((store) => store.toggleOpen);
333356 const keybindings = useServerKeybindings();
334− const composerHandleRef = useRef<ChatComposerHandle | null>(null);
335357 const routeTarget = useParams({
336358 strict: false,
337359 select: (params) => resolveThreadRouteTarget(params),
@@ -362,32 +384,82 @@ export function CommandPalette({ children }: { children: ReactNode }) { · 24 unmodified lines
362384 window.addEventListener("keydown", onKeyDown);
363385 return () => window.removeEventListener("keydown", onKeyDown);
364386 }, [keybindings, terminalOpen, toggleOpen]);
365−
366− return (
367− <ComposerHandleContext value={composerHandleRef}>
368− <CommandDialog open={open} onOpenChange={setOpen}>
369− {children}
370− <CommandPaletteDialog />
371− </CommandDialog>
372− </ComposerHandleContext>
373− );
374387 }
375388
376389 function CommandPaletteDialog() {
377390 const open = useCommandPaletteStore((store) => store.open);
378391 const setOpen = useCommandPaletteStore((store) => store.setOpen);
392+ useCloseCommandPaletteOnUnmount(setOpen);
393+
394+ if (!open) {
395+ return null;
396+ }
397+
398+ return <OpenCommandPaletteDialog />;
399+}
379400
401+function useCloseCommandPaletteOnUnmount(setOpen: (open: boolean) => void) {
380402 useEffect(() => {
381403 return () => {
382404 setOpen(false);
383405 };
384406 }, [setOpen]);
407+}
385408
386− if (!open) {
387− return null;
388− }
409+function usePrefetchBrowsePaths({
410+ exactBrowseEntry,
411+ filteredBrowseEntryCount,
412+ highlightedBrowseEntry,
413+ isBrowsing,
414+ prefetchBrowsePath,
415+ query,
416+}: {
417+ readonly exactBrowseEntry: BrowseEntry | null;
418+ readonly filteredBrowseEntryCount: number;
419+ readonly highlightedBrowseEntry: BrowseEntry | null;
420+ readonly isBrowsing: boolean;
421+ readonly prefetchBrowsePath: (partialPath: string) => void;
422+ readonly query: string;
423+}) {
424+ // Prefetch the parent and the most likely next child so browse navigation
425+ // stays warm without scanning every child directory in large trees.
426+ useEffect(() => {
427+ if (!isBrowsing || filteredBrowseEntryCount === 0) return;
389428
390− return <OpenCommandPaletteDialog />;
429+ if (canNavigateUp(query)) {
430+ prefetchBrowsePath(getBrowseParentPath(query)!);
431+ }
432+
433+ const nextChild = highlightedBrowseEntry ?? exactBrowseEntry;
434+ if (nextChild) {
435+ prefetchBrowsePath(appendBrowsePathSegment(query, nextChild.name));
436+ }
437+ }, [
438+ exactBrowseEntry,
439+ filteredBrowseEntryCount,
440+ highlightedBrowseEntry,
441+ isBrowsing,
442+ prefetchBrowsePath,
443+ query,
444+ ]);
445+}
446+
447+function useOpenAddProjectIntent({
448+ clearOpenIntent,
449+ openAddProjectFlow,
450+ openIntent,
451+}: {
452+ readonly clearOpenIntent: () => void;
453+ readonly openAddProjectFlow: () => void;
454+ readonly openIntent: CommandPaletteOpenIntent;
455+}) {
456+ useLayoutEffect(() => {
457+ if (openIntent?.kind !== "add-project") {
458+ return;
459+ }
460+ clearOpenIntent();
461+ openAddProjectFlow();
462+ }, [clearOpenIntent, openAddProjectFlow, openIntent]);
391463 }
392464
393465 function OpenCommandPaletteDialog() {
@@ -587,27 +659,14 @@ function OpenCommandPaletteDialog() { · 193 unmodified lines
587659 [browseEnvironmentId, currentProjectCwdForBrowse, fetchBrowseResult, queryClient],
588660 );
589661
590− // Prefetch the parent and the most likely next child so browse navigation
591− // stays warm without scanning every child directory in large trees.
592− useEffect(() => {
593− if (!isBrowsing || filteredBrowseEntries.length === 0) return;
594−
595− if (canNavigateUp(query)) {
596− prefetchBrowsePath(getBrowseParentPath(query)!);
597− }
598−
599− const nextChild = highlightedBrowseEntry ?? exactBrowseEntry;
600− if (nextChild) {
601− prefetchBrowsePath(appendBrowsePathSegment(query, nextChild.name));
602− }
603− }, [
662+ usePrefetchBrowsePaths({
604663 exactBrowseEntry,
605− filteredBrowseEntries.length,
664+ filteredBrowseEntryCount: filteredBrowseEntries.length,
606665 highlightedBrowseEntry,
607666 isBrowsing,
608667 prefetchBrowsePath,
609668 query,
610− ]);
669+ });
611670
612671 const openProjectFromSearch = useMemo(
613672 () => async (project: (typeof projects)[number]) => {
@@ -971,13 +1030,7 @@ function OpenCommandPaletteDialog() { · 357 unmodified lines
9711030 startAddProjectSourceSelection,
9721031 ]);
9731032
974− useLayoutEffect(() => {
975− if (openIntent?.kind !== "add-project") {
976− return;
977− }
978− clearOpenIntent();
979− openAddProjectFlow();
980− }, [clearOpenIntent, openAddProjectFlow, openIntent]);
1033+ useOpenAddProjectIntent({ clearOpenIntent, openAddProjectFlow, openIntent });
9811034
9821035 const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = [];
9831036