From 216464d07e411812dda93dd670d76f92900fba80 Mon Sep 17 00:00:00 2001 From: iamjr15 Date: Tue, 25 Aug 2026 16:13:43 +0530 Subject: [PATCH] fix(platform): close production performance gaps Use lateral joins for latest project threads so sidebar and search navigation resolve correctly. Defer profile, skill, and terminal reads until their surfaces open. This keeps authenticated home startup at three requests. --- .../composer/model-menu-controller.ts | 13 +++++++-- .../components/composer/model-menu-view.tsx | 12 ++++++-- .../components/composer/use-composer-menu.ts | 11 ++++---- .../components/home/home-computer-pane.tsx | 4 ++- apps/web/src/lib/hooks/use-profile.ts | 4 +-- packages/db/src/navigation-bootstrap.ts | 28 ++++++++++--------- packages/db/src/search.ts | 22 ++++++++++----- 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/apps/web/src/components/composer/model-menu-controller.ts b/apps/web/src/components/composer/model-menu-controller.ts index fc207d75..f2dc773d 100644 --- a/apps/web/src/components/composer/model-menu-controller.ts +++ b/apps/web/src/components/composer/model-menu-controller.ts @@ -21,6 +21,7 @@ export interface ModelMenuController { displayOption: AgentModelOption; disabledModels: readonly string[]; isOpen: boolean; + isPolicyLoading: boolean; selectedOption: AgentModelOption; shouldRender: boolean; }; @@ -37,13 +38,14 @@ export function useModelMenuController({ }): ModelMenuController { const agentModelId = useAppStore((state) => state.agentModelId); const setAgentModelId = useAppStore((state) => state.setAgentModelId); - const disabledModels = useProfileQuery().data?.disabledModels ?? []; const menuRef = useRef(null); const menuId = `model-menu-${useId()}`; const [internalOpen, setInternalOpen] = useState(false); const isOpen = open ?? internalOpen; const setIsOpen = onOpenChange ?? setInternalOpen; const shouldRender = useMenuPresence(isOpen); + const profileQuery = useProfileQuery(shouldRender); + const disabledModels = profileQuery.data?.disabledModels ?? []; const selectedOption = agentModelOption(agentModelId); const resolvedOption = resolvedModelId ? agentModelOption(resolvedModelId) : null; const displayOption = @@ -61,7 +63,14 @@ export function useModelMenuController({ toggle: () => setIsOpen(!isOpen), }, meta: { menuId, menuRef }, - state: { displayOption, disabledModels, isOpen, selectedOption, shouldRender }, + state: { + displayOption, + disabledModels, + isOpen, + isPolicyLoading: shouldRender && profileQuery.isLoading, + selectedOption, + shouldRender, + }, }; } diff --git a/apps/web/src/components/composer/model-menu-view.tsx b/apps/web/src/components/composer/model-menu-view.tsx index 8667f00f..30057138 100644 --- a/apps/web/src/components/composer/model-menu-view.tsx +++ b/apps/web/src/components/composer/model-menu-view.tsx @@ -85,7 +85,9 @@ function ModelMenuOption({ controller: ModelMenuController; option: AgentModelOption; }) { - const isDisabled = option.id !== "auto" && controller.state.disabledModels.includes(option.id); + const isDisabled = + option.id !== "auto" && + (controller.state.isPolicyLoading || controller.state.disabledModels.includes(option.id)); const isActive = option.id === controller.state.selectedOption.id; const select = () => controller.actions.select(option.id); return ( @@ -102,7 +104,13 @@ function ModelMenuOption({ select(); }} role="menuitemradio" - title={isDisabled ? "Disabled in Models settings" : option.label} + title={ + controller.state.isPolicyLoading + ? "Loading model settings" + : isDisabled + ? "Disabled in Models settings" + : option.label + } type="button" > fetchComposerSkills(options.getToken, signal), - queryKey: COMPOSER_SKILLS_QUERY, - staleTime: 60_000, - }); const triggers = useComposerTriggers({ onChange: options.onChange, onInsert: (kind, item) => { @@ -59,6 +54,12 @@ export function useComposerMenu(options: UseComposerMenuOptions): ComposerMenuCo textareaRef: options.textareaRef, value: options.value, }); + const skillsCatalog = useQuery({ + enabled: triggers.kind === "mention", + queryFn: ({ signal }) => fetchComposerSkills(options.getToken, signal), + queryKey: COMPOSER_SKILLS_QUERY, + staleTime: 60_000, + }); const fileItems = useProjectFileItems({ enabled: triggers.kind === "slash", projectId: options.projectId, diff --git a/apps/web/src/components/home/home-computer-pane.tsx b/apps/web/src/components/home/home-computer-pane.tsx index f1df370f..9dbb95c2 100644 --- a/apps/web/src/components/home/home-computer-pane.tsx +++ b/apps/web/src/components/home/home-computer-pane.tsx @@ -89,7 +89,9 @@ function HomeComputerBody({ /> : null + activeTab === "files" ? ( + + ) : null } > diff --git a/apps/web/src/lib/hooks/use-profile.ts b/apps/web/src/lib/hooks/use-profile.ts index 7e741fc5..ea311d7d 100644 --- a/apps/web/src/lib/hooks/use-profile.ts +++ b/apps/web/src/lib/hooks/use-profile.ts @@ -8,10 +8,10 @@ import { getProfile, updateProfile } from "@/lib/api/profile"; const PROFILE_QUERY_KEY = ["me-profile"] as const; -export function useProfileQuery() { +export function useProfileQuery(enabled = true) { const { getToken, isSignedIn } = useAuth(); return useQuery({ - enabled: Boolean(isSignedIn), + enabled: Boolean(isSignedIn) && enabled, queryFn: ({ signal }) => getProfile(getToken, signal), queryKey: PROFILE_QUERY_KEY, staleTime: 30_000, diff --git a/packages/db/src/navigation-bootstrap.ts b/packages/db/src/navigation-bootstrap.ts index 3d0c0f58..deefc834 100644 --- a/packages/db/src/navigation-bootstrap.ts +++ b/packages/db/src/navigation-bootstrap.ts @@ -37,13 +37,26 @@ function listNavigationProjects( input: { activeThreadId?: ThreadId; userId: UserId }, ) { const activeProjectId = activeProjectIdExpression(input); + const latestThread = db + .select({ id: threads.id }) + .from(threads) + .where( + and( + eq(threads.projectId, projects.id), + eq(threads.userId, input.userId), + isNull(threads.deletedAt), + ), + ) + .orderBy(desc(threads.updatedAt), desc(threads.id)) + .limit(1) + .as("latest_thread"); return db .select({ activeProjectId, archiveAfter: projects.archiveAfter, createdAt: projects.createdAt, id: projects.id, - latestThreadId: latestThreadIdExpression(input.userId), + latestThreadId: latestThread.id, mode: projects.mode, name: projects.name, overQuota: projects.overQuota, @@ -52,6 +65,7 @@ function listNavigationProjects( workspaceSlug: projects.workspaceSlug, }) .from(projects) + .leftJoinLateral(latestThread, sql`true`) .where(and(eq(projects.userId, input.userId), isNull(projects.deletedAt))) .orderBy( sql`case when ${projects.id} = ${activeProjectId} then 0 else 1 end`, @@ -77,18 +91,6 @@ function activeProjectIdExpression(input: { activeThreadId?: ThreadId; userId: U )`; } -function latestThreadIdExpression(userId: UserId) { - return sql`( - select latest_thread.id - from ${threads} latest_thread - where latest_thread.project_id = ${projects.id} - and latest_thread.user_id = ${userId} - and latest_thread.deleted_at is null - order by latest_thread.updated_at desc, latest_thread.id desc - limit 1 - )`; -} - function navigationProjectFromRow( row: Awaited>[number], ): NavigationProjectRecord { diff --git a/packages/db/src/search.ts b/packages/db/src/search.ts index e422074c..be91f700 100644 --- a/packages/db/src/search.ts +++ b/packages/db/src/search.ts @@ -61,20 +61,28 @@ async function searchProjectRecords( pattern: string, limit: number, ): Promise { + const latestThread = db + .select({ id: threads.id }) + .from(threads) + .where( + and( + eq(threads.projectId, projects.id), + eq(threads.userId, userId), + isNull(threads.deletedAt), + ), + ) + .orderBy(desc(threads.updatedAt), desc(threads.id)) + .limit(1) + .as("latest_thread"); const projectRows = await db .select({ id: projects.id, name: projects.name, updatedAt: projects.updatedAt, - latestThreadId: sql`( - select sub.id - from ${threads} as sub - where sub.project_id = ${projects.id} and sub.deleted_at is null - order by sub.updated_at desc - limit 1 - )`, + latestThreadId: latestThread.id, }) .from(projects) + .leftJoinLateral(latestThread, sql`true`) .where( and( eq(projects.userId, userId),