Skip to content

fix(tui): stop perf-buffer leak and react to terminal resize#718

Merged
admarble merged 1 commit into
mainfrom
fix/tui-perf-leak-and-width-reactivity
Jun 4, 2026
Merged

fix(tui): stop perf-buffer leak and react to terminal resize#718
admarble merged 1 commit into
mainfrom
fix/tui-perf-leak-and-width-reactivity

Conversation

@admarble

@admarble admarble commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Two fixes for the sequant run dashboard.

1. perf_hooks leak (MaxPerformanceEntryBufferExceededWarning)

ink pulls in react-reconciler, which loads its development bundle whenever NODE_ENV !== "production" (the normal dogfooding case). That bundle emits an uncleared performance.measure() per component render; driven by the TUI's 10 Hz poll it overflows Node's ~1,000,000-entry global perf buffer and writes a raw warning to stderr — which also corrupts the in-place redraw (stderr scrolls the terminal between frames, so the erase undershoots and frames stack).

Fix: new src/ui/tui/load.tsloadTui() brackets the TUI's single dynamic import() with NODE_ENV=production (only when unset), caching the production reconciler (zero performance.measure calls), then restores NODE_ENV so spawned phase children don't inherit it (e.g. npm install skipping devDependencies). Both call sites (run.ts, ready.ts) now go through it.

Verified: same render workload went from 206 → 0 measure entries; NODE_ENV confirmed restored afterward.

2. Width corruption on resize (duplicate / garbled frames)

ink's resize handler re-renders the existing React tree but does not re-run App, and App read stdout.columns imperatively — so after a resize it repainted boxes at the stale, too-wide width until the next 100 ms poll. Those over-wide lines wrapped while short lines didn't, misaligning the box borders into the garbled/duplicate frames.

Fix (App.tsx): track columns in state, updated from the stdout resize event (plus a 1 Hz fallback for terminals that don't emit it), and clamp boxWidth to the live width.

Tests

  • load.test.ts — pins the NODE_ENV restore behavior for every prior state.
  • App.resize.test.ts — renders a real ink tree, shrinks 120 → 60, and asserts the box width follows within one frame (under the poll). Confirmed it fails on the un-fixed code and passes with the fix.

typecheck clean; 224 tests pass in the touched areas.

Two fixes for the run dashboard:

1. perf_hooks leak (MaxPerformanceEntryBufferExceededWarning). ink pulls in
   react-reconciler, which loads its dev bundle when NODE_ENV != "production".
   The dev bundle emits an uncleared performance.measure() per render; at the
   TUI's 10 Hz poll it overflows Node's ~1M-entry perf buffer and writes a raw
   stderr warning (which also corrupts the in-place redraw). New loadTui()
   brackets the TUI's dynamic import with NODE_ENV=production (only when unset)
   then restores it so spawned phase children don't inherit it. Verified:
   206 -> 0 measure entries for the same render workload.

2. Width corruption on resize. ink's resize handler re-renders the existing
   tree but does not re-run App, and App read stdout.columns imperatively — so
   after a resize it repainted boxes at the stale, too-wide width until the next
   poll, wrapping lines and misaligning borders. App now tracks columns from the
   stdout 'resize' event (plus a 1 Hz fallback) and clamps boxWidth to the live
   width.

Tests: load.test.ts pins the NODE_ENV restore; App.resize.test.ts renders a
real ink tree, shrinks 120->60, and asserts the box follows within one frame
(fails on the un-fixed code).

Entire-Checkpoint: 2b39247ae40a
@admarble admarble merged commit 9d99287 into main Jun 4, 2026
4 checks passed
@admarble admarble deleted the fix/tui-perf-leak-and-width-reactivity branch June 4, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant