perf(ui-tui): narrow overlay subscriptions to focused selectors

Subscribe overlay components to computed theme/session selectors instead of the full UI store so unrelated UI state updates trigger fewer overlay renders.
This commit is contained in:
Brooklyn Nicholson 2026-05-05 15:30:27 -05:00 committed by Teknium
parent ee502e5640
commit 00d25595c1
2 changed files with 27 additions and 23 deletions

View file

@ -1,4 +1,4 @@
import { atom } from 'nanostores'
import { atom, computed } from 'nanostores'
import { MOUSE_TRACKING } from '../config/env.js'
import { ZERO } from '../domain/usage.js'
@ -30,6 +30,9 @@ const buildUiState = (): UiState => ({
export const $uiState = atom<UiState>(buildUiState())
export const $uiTheme = computed($uiState, state => state.theme)
export const $uiSessionId = computed($uiState, state => state.sid)
export const getUiState = () => $uiState.get()
export const patchUiState = (next: Partial<UiState> | ((state: UiState) => UiState)) =>

View file

@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react'
import { useGateway } from '../app/gatewayContext.js'
import type { AppOverlaysProps } from '../app/interfaces.js'
import { $overlayState, patchOverlayState } from '../app/overlayStore.js'
import { $uiState } from '../app/uiStore.js'
import { $uiSessionId, $uiTheme } from '../app/uiStore.js'
import { FloatBox } from './appChrome.js'
import { MaskedPrompt } from './maskedPrompt.js'
@ -24,12 +24,12 @@ export function PromptZone({
onSudoSubmit
}: Pick<AppOverlaysProps, 'cols' | 'onApprovalChoice' | 'onClarifyAnswer' | 'onSecretSubmit' | 'onSudoSubmit'>) {
const overlay = useStore($overlayState)
const ui = useStore($uiState)
const theme = useStore($uiTheme)
if (overlay.approval) {
return (
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
<ApprovalPrompt onChoice={onApprovalChoice} req={overlay.approval} t={ui.theme} />
<ApprovalPrompt onChoice={onApprovalChoice} req={overlay.approval} t={theme} />
</Box>
)
}
@ -46,7 +46,7 @@ export function PromptZone({
return (
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
<ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={ui.theme} />
<ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={theme} />
</Box>
)
}
@ -59,7 +59,7 @@ export function PromptZone({
onAnswer={onClarifyAnswer}
onCancel={() => onClarifyAnswer('')}
req={overlay.clarify}
t={ui.theme}
t={theme}
/>
</Box>
)
@ -68,7 +68,7 @@ export function PromptZone({
if (overlay.sudo) {
return (
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
<MaskedPrompt cols={cols} icon="🔐" label="sudo password required" onSubmit={onSudoSubmit} t={ui.theme} />
<MaskedPrompt cols={cols} icon="🔐" label="sudo password required" onSubmit={onSudoSubmit} t={theme} />
</Box>
)
}
@ -82,7 +82,7 @@ export function PromptZone({
label={overlay.secret.prompt}
onSubmit={onSecretSubmit}
sub={`for ${overlay.secret.envVar}`}
t={ui.theme}
t={theme}
/>
</Box>
)
@ -101,7 +101,8 @@ export function FloatingOverlays({
}: Pick<AppOverlaysProps, 'cols' | 'compIdx' | 'completions' | 'onModelSelect' | 'onPickerSelect' | 'pagerPageSize'>) {
const { gw } = useGateway()
const overlay = useStore($overlayState)
const ui = useStore($uiState)
const sid = useStore($uiSessionId)
const theme = useStore($uiTheme)
const hasAny = overlay.modelPicker || overlay.pager || overlay.picker || overlay.skillsHub || completions.length
@ -119,40 +120,40 @@ export function FloatingOverlays({
return (
<Box alignItems="flex-start" bottom="100%" flexDirection="column" left={0} position="absolute" right={0}>
{overlay.picker && (
<FloatBox color={ui.theme.color.border}>
<FloatBox color={theme.color.border}>
<SessionPicker
gw={gw}
onCancel={() => patchOverlayState({ picker: false })}
onSelect={onPickerSelect}
t={ui.theme}
t={theme}
/>
</FloatBox>
)}
{overlay.modelPicker && (
<FloatBox color={ui.theme.color.border}>
<FloatBox color={theme.color.border}>
<ModelPicker
gw={gw}
onCancel={() => patchOverlayState({ modelPicker: false })}
onSelect={onModelSelect}
sessionId={ui.sid}
t={ui.theme}
sessionId={sid}
t={theme}
/>
</FloatBox>
)}
{overlay.skillsHub && (
<FloatBox color={ui.theme.color.border}>
<SkillsHub gw={gw} onClose={() => patchOverlayState({ skillsHub: false })} t={ui.theme} />
<FloatBox color={theme.color.border}>
<SkillsHub gw={gw} onClose={() => patchOverlayState({ skillsHub: false })} t={theme} />
</FloatBox>
)}
{overlay.pager && (
<FloatBox color={ui.theme.color.border}>
<FloatBox color={theme.color.border}>
<Box flexDirection="column" paddingX={1} paddingY={1}>
{overlay.pager.title && (
<Box justifyContent="center" marginBottom={1}>
<Text bold color={ui.theme.color.primary}>
<Text bold color={theme.color.primary}>
{overlay.pager.title}
</Text>
</Box>
@ -163,7 +164,7 @@ export function FloatingOverlays({
))}
<Box marginTop={1}>
<OverlayHint t={ui.theme}>
<OverlayHint t={theme}>
{overlay.pager.offset + pagerPageSize < overlay.pager.lines.length
? `↑↓/jk line · Enter/Space/PgDn page · b/PgUp back · g/G top/bottom · Esc/q close (${Math.min(overlay.pager.offset + pagerPageSize, overlay.pager.lines.length)}/${overlay.pager.lines.length})`
: `end · ↑↓/jk · b/PgUp back · g top · Esc/q close (${overlay.pager.lines.length} lines)`}
@ -174,23 +175,23 @@ export function FloatingOverlays({
)}
{!!completions.length && (
<FloatBox color={ui.theme.color.primary}>
<FloatBox color={theme.color.primary}>
<Box flexDirection="column" width={Math.max(28, cols - 6)}>
{completions.slice(start, start + viewportSize).map((item, i) => {
const active = start + i === compIdx
return (
<Box
backgroundColor={active ? ui.theme.color.completionCurrentBg : undefined}
backgroundColor={active ? theme.color.completionCurrentBg : undefined}
flexDirection="row"
key={`${start + i}:${item.text}:${item.display}:${item.meta ?? ''}`}
width="100%"
>
<Text bold color={ui.theme.color.label}>
<Text bold color={theme.color.label}>
{' '}
{item.display}
</Text>
{item.meta ? <Text color={ui.theme.color.muted}> {item.meta}</Text> : null}
{item.meta ? <Text color={theme.color.muted}> {item.meta}</Text> : null}
</Box>
)
})}