Skip to content

feat(tui) 04 - Live TUI resize via SIGWINCH #144

Description

@shouze

EPIC: #140
Branch: feat/bun14-terminal
Depends on: #142 and #143 (correct column measurements must be in place before activating dynamic redraws)
Blocks: #145


Context / Problem

runInteractive() in src/tui.ts captures the terminal height once at startup:

const termHeight = process.stdout.rows ?? 40;

This value is never updated. If the user resizes the window during a session:

  • Content overflows vertically when the window shrinks.
  • Empty space accumulates at the bottom when the window expands.
  • No redraw is triggered; the user must press a key before anything changes.

termWidth is already re-read on every redraw() call via process.stdout.columns ?? 80, but without an active resize handler the width is only picked up on the next keypress.

SIGWINCH was unavailable on Windows before Bun 1.4. It is now supported on all platforms.

Solution

1. Make dimensions mutable

Change const to let for both dimension variables:

let termHeight = process.stdout.rows ?? 40;
let termWidth  = process.stdout.columns ?? 80;

getViewportHeight() reads termHeight via its closure. No change to the viewport or scroll logic is needed beyond this declaration change.

2. Install the SIGWINCH handler before the event loop

const onResize = () => {
  const newH = process.stdout.rows ?? 40;
  const newW = process.stdout.columns ?? 80;
  if (newH === termHeight && newW === termWidth) return;
  termHeight = newH;
  termWidth  = newW;
  redraw();
};
process.on("SIGWINCH", onResize);

3. Remove the handler on every exit path

process.off("SIGWINCH", onResize) must be called in every exit path inside runInteractive:

  • Ctrl+C in each mode handler (normal, filter, team pick, repick)
  • q in normal mode
  • Enter / confirm output
  • Natural end of the for await loop

Recommended: extract a local exit() function that centralizes process.off, setRawMode(false), and process.stdout.write(ANSI_CLEAR), then substitute it for all process.exit(0) calls inside the loop.

Resize event flow

sequenceDiagram
    participant OS as OS / terminal emulator
    participant TUI as tui.ts (runInteractive)
    participant Render as renderGroups()
    participant Stdout as process.stdout

    Note over TUI: process.on("SIGWINCH", onResize)

    OS->>TUI: SIGWINCH

    TUI->>Stdout: read .rows / .columns
    Stdout-->>TUI: newH, newW

    alt dimensions changed
        TUI->>TUI: termHeight = newH, termWidth = newW
        TUI->>Render: redraw()
        Render-->>Stdout: ANSI_CLEAR + re-rendered frame
    else no change
        TUI->>TUI: return early (no redraw)
    end
Loading

Acceptance Criteria

  • termHeight is declared as let and updated by the SIGWINCH handler.
  • process.on("SIGWINCH", onResize) is installed before the for await loop.
  • process.off("SIGWINCH", onResize) is called in every exit path of runInteractive.
  • After a resize, getViewportHeight() and isCursorVisible() remain consistent: no cursor outside the viewport, no negative scrollOffset.
  • Manual test results documented in the PR description:
    1. Run bun github-code-search.ts query --org <org> <query> with at least one result.
    2. Shrink the window to approximately 40 columns: lines truncate without overflowing.
    3. Expand to approximately 200 columns: lines extend to fill the new width.
    4. Reduce height to 10 lines: the footer stays at the bottom and the viewport shrinks.
    5. Restore the original window size: the rendering returns to normal without any keypress.
  • bun test passes (unit tests do not simulate SIGWINCH but must remain green).
  • bun run lint and bun run format:check are clean.

Definition of Done

  • PR reviewed and merged into feat/bun14-terminal.
  • Zero lint errors, zero format diff, test suite green.
  • Results of the five-step manual test documented in the PR description.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions