refactor(tui): /clean pass on memory + resize helpers

KISS/DRY sweep — drops ~90 LOC with no behavior change.

- circularBuffer: drop unused pushAll/toArray/size; fold toArray into drain
- gracefulExit: inline Cleanup type + failsafe const; signal→code as a
  record instead of nested ternary; drop dead .catch on Promise.allSettled;
  drop unused forceExit
- memory: inline heapDumpRoot() + writeSnapshot() (single-use); collapse
  the two fd/smaps try/catch blocks behind one `swallow` helper; build
  potentialLeaks functionally (array+filter) instead of imperative
  push-chain; UNITS at file bottom
- memoryMonitor: inline DEFAULTS; drop unused onSnapshot; collapse
  dumpedHigh/dumpedCritical bools to a single Set; single callback
  dispatch line instead of duplicated if-chains
- entry.tsx: factor `dumpNotice` formatter (used twice by onHigh +
  onCritical)
- useMainApp resize debounce: drop redundant `if (timer)` guards
  (clearTimeout(undefined) is a no-op); init as undefined not null
- useVirtualHistory: trim wall-of-text comment to one-line intent; hoist
  `const n = items.length`; split comma-declared lets; remove the
  `;[start, end] = frozenRange` destructure in favor of direct Math.min
  clamps; hoist `hi` init in upperBound for consistency

Validation: tsc clean (both configs), eslint clean on touched files,
vitest 102/102, build produces shebang-preserved dist/entry.js,
performHeapDump smoke-test still writes valid snapshot + diagnostics.
This commit is contained in:
Brooklyn Nicholson 2026-04-20 18:51:12 -05:00
parent 0078f743e6
commit 82b927777c
7 changed files with 89 additions and 182 deletions

View file

@ -18,11 +18,12 @@ const QUANTUM = OVERSCAN >> 1
const FREEZE_RENDERS = 2
const upperBound = (arr: number[], target: number) => {
let lo = 0,
hi = arr.length
let lo = 0
let hi = arr.length
while (lo < hi) {
const mid = (lo + hi) >> 1
arr[mid]! <= target ? (lo = mid + 1) : (hi = mid)
}
@ -42,16 +43,11 @@ export function useVirtualHistory(
const [hasScrollRef, setHasScrollRef] = useState(false)
const metrics = useRef({ sticky: true, top: 0, vp: 0 })
// Resize handling — scale cached heights by oldCols/newCols so post-resize
// offsets stay roughly aligned with (still-unknown) real Yoga heights.
// Clearing the cache instead would force a pessimistic back-walk that mounts
// ~190 rows at once (viewport+overscan at 1-row estimate), each a fresh
// marked.lexer + syntax highlight = ~3ms; ~600ms React commit block. Freeze
// the mount range for FREEZE_RENDERS so warm useMemo results survive while
// the layout effect writes post-resize real heights back into cache.
// skipMeasurement prevents that first post-resize useLayoutEffect from
// poisoning the cache with pre-resize Yoga values (Yoga's stored heights
// are from the frame BEFORE this render's calculateLayout with new width).
// Width change: scale cached heights (not clear — clearing forces a
// pessimistic back-walk mounting ~190 rows at once, each a fresh
// marked.lexer + syntax highlight ≈ 3ms). Freeze mount range for 2
// renders so warm memos survive; skip one measurement so useLayoutEffect
// doesn't poison the scaled cache with pre-resize Yoga heights.
const prevColumns = useRef(columns)
const skipMeasurement = useRef(false)
const prevRange = useRef<null | readonly [number, number]>(null)
@ -122,34 +118,32 @@ export function useVirtualHistory(
return out
}, [estimate, items, ver])
const total = offsets[items.length] ?? 0
const n = items.length
const total = offsets[n] ?? 0
const top = Math.max(0, scrollRef.current?.getScrollTop() ?? 0)
const vp = Math.max(0, scrollRef.current?.getViewportHeight() ?? 0)
const sticky = scrollRef.current?.isSticky() ?? true
const frozenRange = freezeRenders.current > 0 ? prevRange.current : null
let start = 0,
end = items.length
let start = 0
let end = n
if (frozenRange) {
// Columns just changed. Reuse the pre-resize mount range so already-mounted
// MessageRows keep their warm memos (marked.lexer, syntax highlight). Clamp
// to n in case messages were removed (/clear, compaction) mid-freeze.
;[start, end] = frozenRange
start = Math.min(start, items.length)
end = Math.min(end, items.length)
} else if (items.length > 0) {
// Clamp in case items shrank (/clear, compaction) mid-freeze.
start = Math.min(frozenRange[0], n)
end = Math.min(frozenRange[1], n)
} else if (n > 0) {
if (vp <= 0) {
start = Math.max(0, items.length - coldStartCount)
start = Math.max(0, n - coldStartCount)
} else {
start = Math.max(0, Math.min(items.length - 1, upperBound(offsets, Math.max(0, top - overscan)) - 1))
end = Math.max(start + 1, Math.min(items.length, upperBound(offsets, top + vp + overscan)))
start = Math.max(0, Math.min(n - 1, upperBound(offsets, Math.max(0, top - overscan)) - 1))
end = Math.max(start + 1, Math.min(n, upperBound(offsets, top + vp + overscan)))
}
}
if (end - start > maxMounted) {
sticky ? (start = Math.max(0, end - maxMounted)) : (end = Math.min(items.length, start + maxMounted))
sticky ? (start = Math.max(0, end - maxMounted)) : (end = Math.min(n, start + maxMounted))
}
if (freezeRenders.current > 0) {
@ -173,9 +167,6 @@ export function useVirtualHistory(
let dirty = false
if (skipMeasurement.current) {
// First render after a column change — Yoga heights still reflect the
// pre-resize layout. Writing them into cache would overwrite the scaled
// estimates with stale pre-resize values. Next render's Yoga is correct.
skipMeasurement.current = false
} else {
for (let i = start; i < end; i++) {