hermes-agent/apps/desktop/src/hooks/use-resize-observer.ts
Brooklyn Nicholson 7c226cc57f perf(desktop): isolate streaming re-renders & cut layout thrash
During a token stream $messages is replaced ~30x/s. Subscribing the whole
chat view to it re-rendered the composer, runtime boundary, and every
message on every delta.

- Derive coarse facts (empty thread? tail is user?) via nanostores
  `computed` atoms so per-token flushes don't re-render their consumers.
- Move the $messages subscription + runtime wiring into a dedicated
  ChatRuntimeBoundary; the composer reads $messages imperatively.
- Drive message rows off stable useAuiState selectors and a lazy
  getMessageText getter instead of eagerly materialized text.
- Feed ResizeObserver entry sizes into measureClamp / FadeText and dedupe
  the style writes, killing the read-write-read reflow cascade.
2026-06-12 21:07:33 -05:00

47 lines
1 KiB
TypeScript

import { type RefObject, useLayoutEffect, useRef } from 'react'
/**
* Observe element resizes. The callback receives the ResizeObserver entries
* (empty on the initial synchronous call and in non-RO environments) so
* callers can read the observed size off the entry instead of forcing a
* fresh layout read.
*/
export function useResizeObserver(
onResize: (entries: readonly ResizeObserverEntry[]) => void,
...refs: readonly RefObject<Element | null>[]
) {
const refsRef = useRef(refs)
refsRef.current = refs
useLayoutEffect(() => {
if (typeof ResizeObserver === 'undefined') {
onResize([])
return
}
const observer = new ResizeObserver(entries => onResize(entries))
let observed = false
for (const ref of refsRef.current) {
const element = ref.current
if (!element) {
continue
}
observer.observe(element)
observed = true
}
if (!observed) {
observer.disconnect()
return
}
onResize([])
return () => observer.disconnect()
}, [onResize])
}