Skip to content

feat(terminal): Migrate to Bun 1.4 native terminal APIs and add live TUI resize [EPIC] #140

Description

@shouze

Context / Problem

The TUI relies on three fragile in-house helpers in src/render.ts:

  • stripAnsi(str): a regex limited to SGR codes that misses OSC 8 terminal hyperlinks and cursor movement sequences (ESC[A, ESC[H...).
  • clipAnsi(str, maxVisible): a manual character loop that treats every code point as exactly 1 column wide. Emoji, CJK characters, and multi-code-point grapheme clusters (flags, skin tone modifiers) are miscounted, causing line overflow or premature truncation.
  • src/render/team-pick.ts uses .length directly to measure team name widths in the sliding window, with the same measurement flaw.

Additionally, termHeight is captured once at startup (const termHeight = process.stdout.rows ?? 40). Resizing the terminal window has no effect until the process is restarted.

Bun 1.4 ships Bun.stringWidth(), Bun.stripANSI(), Bun.sliceAnsi(), and Bun.wrapAnsi() as native built-in APIs, covering exactly these needs with SIMD-backed implementations (more than 6,000x faster than the equivalent npm packages according to the Bun 1.4 release notes). Bun 1.4 also adds SIGWINCH support on Windows, where it was previously unavailable.

Solution and Architecture Principles

  1. Pure module src/render/terminal.ts: a single facade between application code and Bun 1.4 native APIs. No other source file calls Bun.stringWidth, Bun.sliceAnsi, or Bun.stripANSI directly.
  2. Remove in-house helpers: stripAnsi and clipAnsi in src/render.ts are deleted after migration. No parallel implementations are kept.
  3. Mutable terminal dimensions in tui.ts: termHeight and termWidth become let variables refreshed from process.stdout.rows and process.stdout.columns; a SIGWINCH handler triggers an immediate redraw.
  4. Bun.Terminal is out of scope: this API attaches a PTY to a child process. It does not represent the current interactive terminal. It may be used in a future integration test.
  5. Bun.wrapAnsi for multi-line content only: TUI rows must remain single-line; Bun.sliceAnsi is the right tool for truncation. Bun.wrapAnsi is only relevant if the help overlay is made responsive (not in this EPIC).
  6. Bun 1.4+ required: these APIs are called without a fallback. The minimum runtime version is documented in README.md and AGENTS.md.
  7. Existing layering preserved: src/render/terminal.ts is a pure module (no I/O), consumed through the existing src/render.ts facade.

Sub-issue Dependency Graph

flowchart LR
    I01["01 - terminal.ts\nno dependencies"]
    I02["02 - render.ts\nmigration"]
    I03["03 - team-pick.ts\nmigration"]
    I04["04 - SIGWINCH\nlive resize"]
    I05["05 - Documentation"]

    I01 --> I02
    I01 --> I03
    I02 --> I04
    I03 --> I04
    I04 --> I05
Loading

02 and 03 can be worked in parallel once 01 is merged.

Acceptance Criteria

  • Bun.stringWidth, Bun.stripANSI, and Bun.sliceAnsi are called exclusively from src/render/terminal.ts.
  • The stripAnsi and clipAnsi helpers are deleted from src/render.ts.
  • src/render/team-pick.ts no longer uses .length to measure team name widths.
  • Resizing the terminal window during a TUI session triggers an immediate redraw without any keypress.
  • bun test, bun run lint, bun run format:check, bun run knip, and bun run build.ts all pass.
  • docs/architecture/ and AGENTS.md reflect the new terminal.ts layer.
  • README.md explicitly states Bun 1.4+ as the minimum runtime requirement.

Definition of Done

  • All sub-issues 01 through 05 are merged into branch feat/bun14-terminal.
  • The feature branch is merged into main via a reviewed PR.
  • A minor release is published following AGENTS.md - Release process.

Sub-issues (execution order)

Branch

feat/bun14-terminal

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions