perf(desktop): gate hot store subscriptions + memoize overlay nav + palette rows

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)
This commit is contained in:
Brooklyn Nicholson 2026-07-28 18:51:34 -05:00
parent 7bfbfa3e34
commit d65b226497
5 changed files with 207 additions and 169 deletions

View file

@ -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<StatusResponse | null>(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 (
<OverlayView closeLabel={cc.close} onClose={onClose}>
<OverlaySplitLayout>
<OverlayNav
groups={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)
}))}
/>
<OverlayNav groups={navGroups} />
<OverlayMain>
<header className="mb-4 flex items-center justify-between gap-3 max-[47.5rem]:mb-2">

View file

@ -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<string, string[]>
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 (
<CommandItem
className={cn(HUD_ITEM, HUD_TEXT)}
keywords={item.keywords}
onMouseDown={onSelectMods}
onSelect={() => onSelectItem(item)}
value={paletteValue(item)}
>
<Icon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">{item.label}</span>
{combo && <KbdCombo className="ml-auto opacity-55" combo={combo} size="sm" />}
{item.to && (
<ChevronRight className={cn('size-3.5 shrink-0 text-muted-foreground/70', !combo && 'ml-auto')} />
)}
{item.active && (
<Check className={cn('size-3.5 shrink-0 text-primary', !combo && !item.to && 'ml-auto')} />
)}
</CommandItem>
)
})
// Hermes session ids: <YYYYMMDD>_<HHMMSS>_<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 (
<CommandItem
className={cn(HUD_ITEM, HUD_TEXT)}
key={item.id}
keywords={item.keywords}
onMouseDown={noteSelectMods}
onSelect={() => handleSelect(item)}
value={paletteValue(item)}
>
<Icon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">{item.label}</span>
{combo && <KbdCombo className="ml-auto opacity-55" combo={combo} size="sm" />}
{item.to && (
<ChevronRight
className={cn('size-3.5 shrink-0 text-muted-foreground/70', !combo && 'ml-auto')}
/>
)}
{item.active && (
<Check
className={cn('size-3.5 shrink-0 text-primary', !combo && !item.to && 'ml-auto')}
/>
)}
</CommandItem>
)
})}
{group.items.map(item => (
<PaletteRow
bindings={bindings}
item={item}
key={item.id}
onSelectItem={handleSelect}
onSelectMods={noteSelectMods}
/>
))}
</CommandGroup>
))}
</>

View file

@ -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 (
<button
className={cn(
@ -121,7 +128,7 @@ export function OverlayNavItem({ active, icon: Icon, label, nested, onClick, tra
{trailing}
</button>
)
}
})
export interface OverlayNavLink {
active: boolean

View file

@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { codiconIcon } from '@/components/ui/codicon'
@ -134,123 +134,124 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
}
}
const navGroups: OverlayNavGroup[] = [
const navGroups: OverlayNavGroup[] = useMemo(() => [
...SECTIONS.map(s => {
const view = `config:${s.id}` as SettingsViewId
const view = `config:${s.id}` as SettingsViewId
return {
active: activeView === view,
icon: s.icon,
id: view,
label: t.settings.sections[s.id] ?? s.label,
onSelect: () => setActiveView(view)
return {
active: activeView === view,
icon: s.icon,
id: view,
label: t.settings.sections[s.id] ?? s.label,
onSelect: () => setActiveView(view)
}
}),
{
active: activeView === 'notifications',
icon: Bell,
id: 'notifications',
label: t.settings.nav.notifications,
onSelect: () => setActiveView('notifications')
},
{
active: activeView === 'billing',
icon: BarChart3,
id: 'billing',
label: t.settings.nav.billing,
onSelect: () => setActiveView('billing')
},
{
active: activeView === 'providers',
children: [
{
active: activeView === 'providers' && providerView === 'accounts',
icon: codiconIcon('account'),
id: 'pview:accounts',
label: t.settings.nav.providerAccounts,
onSelect: () => openProviderView('accounts')
},
{
active: activeView === 'providers' && providerView === 'keys',
icon: KeyRound,
id: 'pview:keys',
label: t.settings.nav.providerApiKeys,
onSelect: () => openProviderView('keys')
},
{
active: activeView === 'providers' && providerView === 'custom-endpoints',
icon: Globe,
id: 'pview:custom-endpoints',
label: t.settings.nav.providerCustomEndpoints,
onSelect: () => openProviderView('custom-endpoints')
}
],
gapBefore: true,
icon: Zap,
id: 'providers',
label: t.settings.nav.providers,
onSelect: () => setActiveView('providers')
},
{
active: activeView === 'gateway',
icon: Globe,
id: 'gateway',
label: t.settings.nav.gateway,
onSelect: () => setActiveView('gateway')
},
{
active: activeView === 'keybinds',
icon: Keyboard,
id: 'keybinds',
label: t.settings.nav.keybinds,
onSelect: () => setActiveView('keybinds')
},
{
active: activeView === 'keys',
children: [
{
active: activeView === 'keys' && keysView === 'tools',
icon: Wrench,
id: 'kview:tools',
label: t.settings.nav.keysTools,
onSelect: () => openKeysView('tools')
},
{
active: activeView === 'keys' && keysView === 'settings',
icon: Settings2,
id: 'kview:settings',
label: t.settings.nav.keysSettings,
onSelect: () => openKeysView('settings')
}
],
icon: KeyRound,
id: 'keys',
label: t.settings.nav.apiKeys,
onSelect: () => setActiveView('keys')
},
{
active: activeView === 'plugins',
icon: Package,
id: 'plugins',
label: t.settings.nav.plugins,
onSelect: () => setActiveView('plugins')
},
{
active: activeView === 'sessions',
icon: Archive,
id: 'sessions',
label: t.settings.nav.archivedChats,
onSelect: () => setActiveView('sessions')
},
{
active: activeView === 'about',
gapBefore: true,
icon: Info,
id: 'about',
label: t.settings.nav.about,
onSelect: () => setActiveView('about')
}
}),
{
active: activeView === 'notifications',
icon: Bell,
id: 'notifications',
label: t.settings.nav.notifications,
onSelect: () => setActiveView('notifications')
},
{
active: activeView === 'billing',
icon: BarChart3,
id: 'billing',
label: t.settings.nav.billing,
onSelect: () => setActiveView('billing')
},
{
active: activeView === 'providers',
children: [
{
active: activeView === 'providers' && providerView === 'accounts',
icon: codiconIcon('account'),
id: 'pview:accounts',
label: t.settings.nav.providerAccounts,
onSelect: () => openProviderView('accounts')
},
{
active: activeView === 'providers' && providerView === 'keys',
icon: KeyRound,
id: 'pview:keys',
label: t.settings.nav.providerApiKeys,
onSelect: () => openProviderView('keys')
},
{
active: activeView === 'providers' && providerView === 'custom-endpoints',
icon: Globe,
id: 'pview:custom-endpoints',
label: t.settings.nav.providerCustomEndpoints,
onSelect: () => openProviderView('custom-endpoints')
}
],
gapBefore: true,
icon: Zap,
id: 'providers',
label: t.settings.nav.providers,
onSelect: () => setActiveView('providers')
},
{
active: activeView === 'gateway',
icon: Globe,
id: 'gateway',
label: t.settings.nav.gateway,
onSelect: () => setActiveView('gateway')
},
{
active: activeView === 'keybinds',
icon: Keyboard,
id: 'keybinds',
label: t.settings.nav.keybinds,
onSelect: () => setActiveView('keybinds')
},
{
active: activeView === 'keys',
children: [
{
active: activeView === 'keys' && keysView === 'tools',
icon: Wrench,
id: 'kview:tools',
label: t.settings.nav.keysTools,
onSelect: () => openKeysView('tools')
},
{
active: activeView === 'keys' && keysView === 'settings',
icon: Settings2,
id: 'kview:settings',
label: t.settings.nav.keysSettings,
onSelect: () => openKeysView('settings')
}
],
icon: KeyRound,
id: 'keys',
label: t.settings.nav.apiKeys,
onSelect: () => setActiveView('keys')
},
{
active: activeView === 'plugins',
icon: Package,
id: 'plugins',
label: t.settings.nav.plugins,
onSelect: () => setActiveView('plugins')
},
{
active: activeView === 'sessions',
icon: Archive,
id: 'sessions',
label: t.settings.nav.archivedChats,
onSelect: () => setActiveView('sessions')
},
{
active: activeView === 'about',
gapBefore: true,
icon: Info,
id: 'about',
label: t.settings.nav.about,
onSelect: () => setActiveView('about')
}
]
]
, [activeView, keysView, providerView, t])
const navFooter = (
<>

View file

@ -23,6 +23,7 @@ import {
import { useI18n } from '@/i18n'
import { isDesktopToolsetVisible } from '@/lib/desktop-toolsets'
import { compactNumber } from '@/lib/format'
import { useStoreSelector } from '@/lib/use-session-slice'
import { queryClient, writeCache } from '@/lib/query-client'
import { invalidateSlashCompletions } from '@/lib/slash-completion-cache'
import { normalize } from '@/lib/text'
@ -184,8 +185,10 @@ interface SkillsViewProps extends React.ComponentProps<'section'> {
export function SkillsView({ setStatusbarItemGroup: _setStatusbarItemGroup, ...props }: SkillsViewProps) {
const { t } = useI18n()
const gateway = useStore($gateway) as HermesGateway | null
const [mode, setMode] = useRouteEnumParam('tab', SKILLS_MODES, 'skills')
// $gateway only feeds the MCP tab — gate the subscription so Skills/Toolsets/Hub
// tabs don't re-render on connect/disconnect/reconnect.
const gateway = useStoreSelector($gateway, g => (mode === 'mcp' ? g : null))
const [query, setQuery] = useState('')