Keep the update -i prompt frame on screen and skip no-op redraws

Closed
RO
1mo ago
+152-39

Problem

  • In bun update -ri, every mouse press and release adds one more copy of ? Select packages to update - Space to toggle, Enter to confirm, ... to the terminal scrollback (#39679).
  • The input loop in src/runtime/cli/update_interactive_command.rs redraws the whole frame on every input byte, a no-op mouse click included. The viewport height (get_terminal_size, old line 1117) reserves 4 rows but ignores the 2 lines each dependency-group header takes, so with -r the frame is taller than the terminal. The redraw's cursor-up clamps at the screen top and each repaint scrolls the top of the previous frame into scrollback.

Fix

  • compute_viewport_height sizes the package viewport from the raw terminal height minus the frame's fixed lines: the help line, 2 lines per dependency group, the bottom scroll indicator, and the row the cursor parks on below the frame. The frame now always fits, so an in-place redraw never scrolls.
  • The loop redraws only when input changed visible state (needs_redraw). A click press or release changes nothing and no longer repaints, which also removes flicker.
  • The resize check now reacts to height changes too, and a resize redraws in place instead of printing a new frame below the old one.
  • Verified: two new tests in test/cli/update_interactive_formatting.test.ts (both fail on the current release: 7 prompt copies after 3 clicks, and a 25-line cursor-up on a 24-row terminal). Also ran update_interactive_install, update_interactive_snapshots, regression/issue/update-interactive-formatting, and bun-update-transitive (174 pass).

Background

  • The prompt enables SGR mouse reporting (\x1b[?1000h, \x1b[?1006h), so a click sends \x1b[<0;col;rowM on press and ...m on release. Only wheel buttons (64/65) scroll. Button 0 is parsed and discarded.
  • A redraw moves the cursor up over the previous frame with \x1b[{n}A and repaints. A terminal clamps that cursor move at row 1. When the frame has more lines than the screen, the repaint pushes the overflow into scrollback, once per redraw.
  • -i vs -ri is not the real switch. The real switch is "frame taller than the terminal". -r just makes the list taller. Plain -i showed the same leak in a 9-row terminal.
Notes

Reproduction was driven through a pseudo-terminal: run bun update -ri in a 16x140 PTY against a workspace with 13 outdated dependencies, send 3 SGR clicks (press + release), then Ctrl+C, and feed the raw bytes through @xterm/headless to count help lines in screen plus scrollback. Unfixed: 7 (1 real + 6 duplicates, one per mouse event). Fixed: 0 extra (the one on screen). The 9-row -i case also went from 7 to 0.

The tests run without a PTY, so get_terminal_size falls back to 24x80. The frame-height test builds 20 outdated rows across 2 dependency groups (10 workspaces with one dependency and one devDependency each, -r), then parses the \x1b[{n}A\x1b[1G cursor-up the prompt emits: n must stay under 24. Unfixed, the fallback viewport was 20 rows and n was 25. The click test counts occurrences of the help line in raw stdout after 3 clicks: 1 fixed, 7 unfixed. A wheel event in the frame-height test asserts that a real scroll still repaints (the more package above indicator appears).

The fallback terminal size changes from 20x80 (already minus the old 4-row reserve) to a raw 24x80, and the reserve moves into compute_viewport_height. On a real terminal the numbers only shrink when more than one dependency group is visible, by 2 lines per extra group plus 1 for the resting row.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/update_interactive_formatting.test.ts