Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 31 additions & 57 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -371,16 +371,12 @@ function notifyThemeSaveFailure(): void {
);
}

// These helpers keep "use memo" until the palette's item building moves out of this
// component: inlined, each result reaches the returned <CommandPaletteContent> through
// the action and thread item arrays built after the browse hooks, and the compiler
// leaves all of them uncached.
function useThemeCards(
customThemes: ReturnType<typeof useCustomThemes>,
environmentThemes: ReturnType<typeof useEnvironmentThemeDefinitions>,
) {
// oxlint-disable-next-line t3code/no-manual-memo -- Kept until the palette's item building moves out of this component (see above).
"use memo";
// Each helper reads its own source hook so the compiler caches the derived value on that
// source. Taking the source as an argument instead leaves the helper hook-free, and the
// compiler then treats it as a plain function and caches nothing.
function useThemeCards() {
const customThemes = useCustomThemes();
const environmentThemes = useEnvironmentThemeDefinitions();
const seen = new Set<string>();
return [
...STANDARD_THEME_CARDS.map((card) => ({ ...card, id: null })),
Expand All @@ -394,21 +390,23 @@ function useThemeCards(
];
}

function useThreadContentMatchByKey(matches: ReturnType<typeof useThreadSearch>["matches"]) {
// oxlint-disable-next-line t3code/no-manual-memo -- Kept until the palette's item building moves out of this component (see above).
"use memo";
return new Map(
matches.flatMap((match) =>
function useThreadSearchWithContentMatches(
environmentIds: ReadonlyArray<EnvironmentId>,
query: string,
) {
const threadSearch = useThreadSearch(environmentIds, query);
const contentMatchByKey = new Map(
threadSearch.matches.flatMap((match) =>
match.source === "user" || match.source === "assistant"
? [[threadSearchMatchKey(match), match] as const]
: [],
),
);
return { threadSearch, contentMatchByKey };
}

function useEnvironmentLookups(environments: ReturnType<typeof useEnvironments>["environments"]) {
// oxlint-disable-next-line t3code/no-manual-memo -- Kept until the palette's item building moves out of this component (see above).
"use memo";
function useEnvironmentLookups() {
const { environments } = useEnvironments();
const environmentLabelById = new Map(
environments.map((environment) => [environment.environmentId, environment.label] as const),
);
Expand All @@ -429,9 +427,8 @@ function useEnvironmentLookups(environments: ReturnType<typeof useEnvironments>[
return { environmentLabelById, projectEnvironmentLocationById };
}

function useProjectLookups(projects: ReturnType<typeof useProjects>) {
// oxlint-disable-next-line t3code/no-manual-memo -- Kept until the palette's item building moves out of this component (see above).
"use memo";
function useProjectLookups() {
const projects = useProjects();
const projectCwdById = new Map<ProjectId, string>(
projects.map((project) => [project.id, project.workspaceRoot]),
);
Expand All @@ -450,25 +447,13 @@ const selectCommandPaletteClientSettings = (settings: ClientSettings) => ({
sidebarProjectSortOrder: settings.sidebarProjectSortOrder,
sidebarThreadSortOrder: settings.sidebarThreadSortOrder,
});
type CommandPaletteClientSettings = ReturnType<typeof selectCommandPaletteClientSettings>;

function useSortedProjectGroups({
projects,
projectOrder,
clientSettings,
primaryEnvironmentId,
environmentLabelById,
threads,
}: {
readonly projects: ReturnType<typeof useProjects>;
readonly projectOrder: ReturnType<typeof useUiStateStore.getState>["projectOrder"];
readonly clientSettings: CommandPaletteClientSettings;
readonly primaryEnvironmentId: ReturnType<typeof usePrimaryEnvironmentId>;
readonly environmentLabelById: ReadonlyMap<EnvironmentId, string>;
readonly threads: ReturnType<typeof useThreadShells>;
}) {
// oxlint-disable-next-line t3code/no-manual-memo -- Kept until the palette's item building moves out of this component (see above).
"use memo";

function useSortedProjectGroups(environmentLabelById: ReadonlyMap<EnvironmentId, string>) {
const projects = useProjects();
const threads = useThreadShells();
const projectOrder = useUiStateStore((store) => store.projectOrder);
const clientSettings = useClientSettings(selectCommandPaletteClientSettings);
const primaryEnvironmentId = usePrimaryEnvironmentId();
const projectGroupingSettings = {
sidebarProjectGroupingMode: clientSettings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: clientSettings.sidebarProjectGroupingOverrides,
Expand Down Expand Up @@ -806,7 +791,6 @@ function OpenCommandPaletteDialog(props: {
);
}
};
const projectOrder = useUiStateStore((store) => store.projectOrder);
const threads = useThreadShells();
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const {
Expand All @@ -818,9 +802,7 @@ function OpenCommandPaletteDialog(props: {
setTheme,
setThemeHalf,
} = useTheme();
const customThemes = useCustomThemes();
const environmentThemes = useEnvironmentThemeDefinitions();
const themeCards = useThemeCards(customThemes, environmentThemes);
const themeCards = useThemeCards();
const providers = useAtomValue(primaryServerProvidersAtom);
const providersByEnvironment = useEnvironmentProviders();
const providerEntryByEnvironmentAndInstanceId = new Map<string, ProviderInstanceEntry>(
Expand All @@ -844,8 +826,8 @@ function OpenCommandPaletteDialog(props: {
.filter((environment) => environment.connection.phase === "connected")
.map((environment) => environment.environmentId);
const threadSearchQuery = currentView === null && !isActionsOnly ? deferredQuery : "";
const threadSearch = useThreadSearch(environmentIds, threadSearchQuery);
const threadContentMatchByKey = useThreadContentMatchByKey(threadSearch.matches);
const { threadSearch, contentMatchByKey: threadContentMatchByKey } =
useThreadSearchWithContentMatches(environmentIds, threadSearchQuery);
const [browseGeneration, setBrowseGeneration] = useState(0);
const [browseNavigation] = useState(createBrowseNavigationCoordinator);
const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>(
Expand All @@ -868,16 +850,8 @@ function OpenCommandPaletteDialog(props: {
const cloneLookupGeneration = useRef(0);
const [isRemoteProjectLookingUp, setIsRemoteProjectLookingUp] = useState(false);
const [isRemoteProjectCloning, setIsRemoteProjectCloning] = useState(false);
const { environmentLabelById, projectEnvironmentLocationById } =
useEnvironmentLookups(environments);
const projectGroups = useSortedProjectGroups({
projects,
projectOrder,
clientSettings,
primaryEnvironmentId,
environmentLabelById,
threads,
});
const { environmentLabelById, projectEnvironmentLocationById } = useEnvironmentLookups();
const projectGroups = useSortedProjectGroups(environmentLabelById);
const contextualProjectRef = resolveThreadActionProjectRef({
activeDraftThread,
activeThread: activeThread ?? undefined,
Expand Down Expand Up @@ -1000,7 +974,7 @@ function OpenCommandPaletteDialog(props: {
return ensureBrowseDirectoryPath(baseDirectory);
};

const { projectCwdById, projectByKey, projectTitleById } = useProjectLookups(projects);
const { projectCwdById, projectByKey, projectTitleById } = useProjectLookups();

const activeThreadId = activeThread?.id;
const supportsThreadFind =
Expand Down
4 changes: 3 additions & 1 deletion docs/internals/web-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ Every file under `apps/web/src` must compile. Lint makes those rules errors ther
no upstream rule bans them outright. It also rejects `"use memo"`. The compiler withholds a cache
scope when a value reaches plain function calls on both sides of a hook call. A `"use memo"`
helper forces one, but hides the ordering problem. Fix the order instead: plain uses of a value go
before the hook, or the helper is inlined. Lint misses some bailouts, notably `try` with `finally` and
before the hook, or the helper is inlined. A `use*` helper that calls no hook is compiled as a
plain function and caches nothing, so a derived-value helper reads its source hook itself instead
of taking the value as an argument. Lint misses some bailouts, notably `try` with `finally` and
`throw` inside `try`, so [`reactCompilerCoverage.test.ts`](../../apps/web/src/reactCompilerCoverage.test.ts)
compiles every file and fails on any skip. Async handlers rarely need `try`: an
atom command settles into a result, so cleanup can follow the `await`. `useStableCallback` still
Expand Down
Loading