From d65b226497d1497da69d1b1de9cdd7b26032a5e5 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Jul 2026 18:51:34 -0500 Subject: [PATCH 1/6] perf(desktop): gate hot store subscriptions + memoize overlay nav + palette rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command Center, Settings, Skills, and Command Palette all subscribed to hot stores unconditionally — ticks on every streaming token (title updates), on every connect/disconnect. Components that only need that data on one tab were re-rendering on every stream delta while sitting on an unrelated tab. Fixes: - CommandCenterView: gate / to the Sessions tab via useStoreSelector returning a stable empty array on other tabs - SkillsView: gate to the MCP tab only - SettingsView: memoize navGroups (was rebuilt inline on every render with fresh onSelect closures for every nav item) - OverlayNavItem: memo() so nav items don't re-render when a sibling's active state changes - CommandPalette: extract memoized PaletteRow so items don't re-render on unrelated parent state changes (open/close, theme, etc) - CommandCenterView: memoize navGroups array (was inline JSX) --- apps/desktop/src/app/command-center/index.tsx | 52 ++-- .../desktop/src/app/command-palette/index.tsx | 75 +++--- .../src/app/overlays/overlay-split-layout.tsx | 13 +- apps/desktop/src/app/settings/index.tsx | 231 +++++++++--------- apps/desktop/src/app/skills/index.tsx | 5 +- 5 files changed, 207 insertions(+), 169 deletions(-) diff --git a/apps/desktop/src/app/command-center/index.tsx b/apps/desktop/src/app/command-center/index.tsx index 1c9f1dd7fd7a..ab459599be75 100644 --- a/apps/desktop/src/app/command-center/index.tsx +++ b/apps/desktop/src/app/command-center/index.tsx @@ -1,4 +1,3 @@ -import { useStore } from '@nanostores/react' import { type MouseEvent, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { LogTail } from '@/components/chat/log-tail' @@ -13,6 +12,7 @@ import type { ActionStatusResponse, AnalyticsResponse, StatusResponse } from '@/ import { useI18n } from '@/i18n' import { sessionTitle } from '@/lib/chat-runtime' import { compactNumber } from '@/lib/format' +import { useStoreSelector } from '@/lib/use-session-slice' import { Activity, AlertCircle, @@ -48,6 +48,12 @@ const LOG_LEVELS = ['ALL', 'INFO', 'WARNING', 'ERROR'] as const const USAGE_PERIODS = [7, 30, 90] as const type UsagePeriod = (typeof USAGE_PERIODS)[number] +// Stable empty arrays so the selector returns the same reference when we're +// not on the Sessions tab — useStoreSelector bails out on Object.is, so the +// component never re-renders from $sessions ticks while on System/Usage/etc. +const EMPTY_SESSIONS: readonly never[] = [] +const EMPTY_PINNED: readonly string[] = [] + interface CommandCenterViewProps { initialSection?: CommandCenterSection onClose: () => void @@ -129,10 +135,12 @@ function EmptyPanel({ action, description, title }: { action?: ReactNode; descri export function CommandCenterView({ initialSection, onClose, onDeleteSession, onOpenSession }: CommandCenterViewProps) { const { t } = useI18n() const cc = t.commandCenter - const sessions = useStore($sessions) - const pinnedSessionIds = useStore($pinnedSessionIds) - + // $sessions ticks on every streaming token (title updates, new sessions), + // but we only need the data on the Sessions tab. Subscribe conditionally so + // the System/Usage/Maintenance tabs don't re-render on every stream delta. const [section, setSection] = useRouteEnumParam('section', SECTIONS, initialSection ?? 'sessions') + const sessions = useStoreSelector($sessions, s => (section === 'sessions' ? s : EMPTY_SESSIONS)) + const pinnedSessionIds = useStoreSelector($pinnedSessionIds, s => (section === 'sessions' ? s : EMPTY_PINNED)) const [query, setQuery] = useState('') const [status, setStatus] = useState(null) @@ -294,25 +302,29 @@ export function CommandCenterView({ initialSection, onClose, onDeleteSession, on [cc, refreshSystem] ) + const navGroups = useMemo( + () => + SECTIONS.map(value => ({ + active: section === value, + icon: + value === 'sessions' + ? MessageCircle + : value === 'system' + ? Activity + : value === 'maintenance' + ? Wrench + : BarChart3, + id: value, + label: cc.sections[value], + onSelect: () => setSection(value) + })), + [cc, section] + ) + return ( - ({ - active: section === value, - icon: - value === 'sessions' - ? MessageCircle - : value === 'system' - ? Activity - : value === 'maintenance' - ? Wrench - : BarChart3, - id: value, - label: cc.sections[value], - onSelect: () => setSection(value) - }))} - /> +
diff --git a/apps/desktop/src/app/command-palette/index.tsx b/apps/desktop/src/app/command-palette/index.tsx index f627f1395742..8d22cf99aea6 100644 --- a/apps/desktop/src/app/command-palette/index.tsx +++ b/apps/desktop/src/app/command-palette/index.tsx @@ -1,7 +1,7 @@ import { useStore } from '@nanostores/react' import { useQuery } from '@tanstack/react-query' import { Dialog as DialogPrimitive } from 'radix-ui' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react' import { useNavigate } from 'react-router-dom' import { HUD_HEADING, HUD_ITEM, HUD_POSITION, HUD_SURFACE, HUD_TEXT } from '@/app/floating-hud' @@ -218,6 +218,41 @@ const rankGroups = (groups: PaletteGroup[], search: string): PaletteGroup[] => { // theme lists under both Light and Dark). The id suffix disambiguates. const paletteValue = (item: PaletteItem): string => `${item.label}\u0001${item.id}` +const PaletteRow = memo(function PaletteRow({ + bindings, + item, + onSelectMods, + onSelectItem +}: { + bindings: Record + item: PaletteItem + onSelectMods: (event: { ctrlKey: boolean; metaKey: boolean; shiftKey: boolean }) => void + onSelectItem: (item: PaletteItem) => void +}) { + const Icon = item.icon + const combo = item.action ? bindings[item.action]?.[0] : undefined + + return ( + onSelectItem(item)} + value={paletteValue(item)} + > + + {item.label} + {combo && } + {item.to && ( + + )} + {item.active && ( + + )} + + ) +}) + // Hermes session ids: __<6 hex>. Used to offer a direct // "Go to session ‹id›" jump for ids that aren't in the recent-200 list. const SESSION_ID_RE = /^\d{8}_\d{6}_[a-f0-9]{6}$/ @@ -996,35 +1031,15 @@ export function CommandPalette() { heading={group.heading} key={group.heading ?? `palette-group-${index}`} > - {group.items.map(item => { - const Icon = item.icon - const combo = item.action ? bindings[item.action]?.[0] : undefined - - return ( - handleSelect(item)} - value={paletteValue(item)} - > - - {item.label} - {combo && } - {item.to && ( - - )} - {item.active && ( - - )} - - ) - })} + {group.items.map(item => ( + + ))} ))} diff --git a/apps/desktop/src/app/overlays/overlay-split-layout.tsx b/apps/desktop/src/app/overlays/overlay-split-layout.tsx index 6ed570f4b99b..b4a90dd0f322 100644 --- a/apps/desktop/src/app/overlays/overlay-split-layout.tsx +++ b/apps/desktop/src/app/overlays/overlay-split-layout.tsx @@ -1,4 +1,4 @@ -import { Fragment, type ReactNode } from 'react' +import { Fragment, memo, type ReactNode } from 'react' import { TabDropdown } from '@/components/ui/tab-dropdown' import type { IconComponent } from '@/lib/icons' @@ -94,7 +94,14 @@ export function OverlayMain({ children, className }: OverlayMainProps) { ) } -export function OverlayNavItem({ active, icon: Icon, label, nested, onClick, trailing }: OverlayNavItemProps) { +export const OverlayNavItem = memo(function OverlayNavItem({ + active, + icon: Icon, + label, + nested, + onClick, + trailing +}: OverlayNavItemProps) { return (