mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
Audit follow-up. ChatBar subscribed to the whole `$statusItemsBySession` (a computed that rebuilds the entire map) + `$previewStatusBySession` maps just to derive a boolean, so every per-item status mutation (a subagent tick, the 5s background poll) and every OTHER session's change re-rendered the ~1.4k component. The queue hook likewise subscribed to the whole `$queuedPromptsBySession` map. - Add `useSessionStatusPresence` — a coarse edge (useSyncExternalStore) that flips only when the stack shows/hides; ChatBar uses it for the styling data-attr instead of the two map subscriptions. - Add generic `useSessionSlice(store, key)` — subscribes to one session's array, bailing out when other sessions churn (the plain atom keeps per-key refs stable). The queue hook now reads its slice through it. Result: ChatBar re-renders only when the stack's presence flips or this session's queue changes — not on background/subagent status streaming or other sessions. Verified: typecheck clean, 0 lint errors, composer tests 39/40 (pre-existing attachments failure unrelated).
31 lines
1.3 KiB
TypeScript
31 lines
1.3 KiB
TypeScript
import { useSyncExternalStore } from 'react'
|
|
|
|
interface SliceStore<T> {
|
|
get(): Record<string, T[] | undefined>
|
|
listen(listener: () => void): () => void
|
|
}
|
|
|
|
// Stable empty result so an absent key never yields a fresh array (which would
|
|
// defeat the snapshot bail-out and re-render on every store write).
|
|
const EMPTY: readonly never[] = []
|
|
|
|
/**
|
|
* Subscribe to ONE session's slice of a `Record<sessionId, T[]>` nanostore,
|
|
* re-rendering only when *that* slice's reference changes — not on writes to
|
|
* other sessions. The map reference churns on every cross-session update, so a
|
|
* plain `useStore(map)` re-renders all consumers globally; reading `map[key]`
|
|
* through `useSyncExternalStore` bails out whenever the keyed array is
|
|
* unchanged (the stores update immutably per key). Returns a shared empty array
|
|
* when the key is null/absent.
|
|
*
|
|
* Note: only helps stores whose per-key arrays are referentially stable across
|
|
* unrelated writes (plain atoms with immutable per-key updates). A `computed`
|
|
* that rebuilds the whole map churns every slice — use a presence/edge selector
|
|
* there instead.
|
|
*/
|
|
export function useSessionSlice<T>(store: SliceStore<T>, key: string | null): T[] {
|
|
return useSyncExternalStore(
|
|
onChange => store.listen(onChange),
|
|
() => (key ? (store.get()[key] ?? (EMPTY as unknown as T[])) : (EMPTY as unknown as T[]))
|
|
)
|
|
}
|