hermes-agent/apps/desktop/src/store/tool-diffs.ts
Brooklyn Nicholson e30174fa17 perf(desktop): scope tool-diff subscriptions + narrow profile query invalidation
Two structural fixes from the Desktop performance audit (P2 tier):

1. Scope live tool-diff subscriptions. `ToolEntry` subscribed to the whole
   `$toolDiffs` map via `useStore`, so one `recordToolDiff` re-rendered every
   mounted tool row. Add a cached per-toolCallId derived atom
   (`$toolInlineDiff(id)`, mirroring the existing `$toolDisclosureOpen` pattern);
   computed() only notifies when that id's diff string changes, so a live patch
   re-renders one row.

2. Narrow profile / gateway-switch query invalidation. Both the active-profile
   subscription and `wipeSessionListsForGatewaySwitch` called keyless
   `queryClient.invalidateQueries()`, refetching account/marketplace/onboarding
   caches on every switch. Add `invalidateProfileScopedQueries()` with a
   denylist of profile-independent roots (billing, marketplace-themes,
   onboarding-model-options, contrib-logs-tail). A denylist is correctness-safe:
   a root we forget just refetches (cheap), whereas an allowlist that misses a
   profile-scoped key would paint the previous profile's data.

Tests: per-tool notify isolation, and real-QueryClient invalidation partition
(profile-scoped invalidated, global left intact, unknown keys invalidated).
2026-07-18 19:15:57 -04:00

38 lines
1.1 KiB
TypeScript

import { atom, computed, type ReadableAtom } from 'nanostores'
const $toolDiffs = atom<Record<string, string>>({})
// Per-tool derived atoms, cached by toolCallId. A `ToolEntry` subscribes only
// to its own id's diff, so recording a diff for one tool re-renders that one
// row -- not every mounted tool row. computed() only notifies when the derived
// string actually changes, so unrelated writes to the map are inert here.
const inlineDiffCache = new Map<string, ReadableAtom<string>>()
export function recordToolDiff(toolCallId: string, diff: string) {
if (!toolCallId || !diff) {
return
}
const current = $toolDiffs.get()
if (current[toolCallId] === diff) {
return
}
$toolDiffs.set({ ...current, [toolCallId]: diff })
}
export function getToolDiff(toolCallId: string): string {
return toolCallId ? $toolDiffs.get()[toolCallId] || '' : ''
}
export function $toolInlineDiff(toolCallId: string): ReadableAtom<string> {
let cached = inlineDiffCache.get(toolCallId)
if (!cached) {
cached = computed($toolDiffs, diffs => (toolCallId ? diffs[toolCallId] || '' : ''))
inlineDiffCache.set(toolCallId, cached)
}
return cached
}