Skip to content

Constrain PickerMenu description width to the terminal's real width - #988

Closed
Ref34t wants to merge 1 commit into
PostHog:mainfrom
Ref34t:fix/picker-menu-description-width-986-clean
Closed

Constrain PickerMenu description width to the terminal's real width#988
Ref34t wants to merge 1 commit into
PostHog:mainfrom
Ref34t:fix/picker-menu-description-width-986-clean

Conversation

@Ref34t

@Ref34t Ref34t commented Jul 27, 2026

Copy link
Copy Markdown

Fixes #986.

(Reopened from #987, which was accidentally branched from wordpress-framework-support instead of main and dragged in unrelated files. This branch is based on current main and contains only the fix below.)

The bug

Multi-select options with a description wrapped that text in a Box fixed at width={56}, regardless of the actual terminal width. On a narrow terminal this let the row render wider than the physical screen. The terminal's own line-wrapping (which Ink doesn't control) then misaligned Ink's cursor bookkeeping for every row after it — producing the overlapping/corrupted text in #986: checkbox labels merging into their descriptions (No + Skip custom scouts...NoSkip custom scouts...), the Confirm button overlapping the last option's wrapped tail, and — on a second checklist in the same run — fragments of multiple different tool names bleeding together (FrillrebaseghtVM reads like "Frill" + "Firebase" + something ending "...VM" overlapping).

How I found it

Reproduced headlessly with ink-testing-library, patching its Stdout prototype to drive real widths (100 → 40 columns) since the library hardcodes 100 columns with no override. The message text reflowed correctly at every width; the description did not — identical wrap points from 100 down to 40 columns, confirming the fixed width never adapted.

The fix

descriptionWidth() in PickerMenu.tsx reads the live terminal width via Ink's useStdout() and clamps to it — floor of 20 characters, cap of 56 (the original constant, kept as an upper bound on wide terminals), accounting for the outer margin, the per-column share when the grid has more than one column, and the description's own indent.

Verified the fix directly: re-ran the same headless harness after the change — longest rendered line now tracks the terminal width exactly (100 cols → 62, 60 cols → 60, 40 cols → 40), instead of staying near-constant regardless of width.

Also found while investigating

  • The playground demo for PickerMenu (InputDemo.tsx) never exercises the description prop at all — the exact code path that broke had zero coverage in the one place built to catch this visually.
  • ink-testing-library is a real devDependency but was unused anywhere in this repo before this fix. This is the first .tsx test file — vitest.config.ts was already configured for it (esbuild: { jsx: 'automatic' }), just never exercised.

Testing

  • pnpm build — clean, including smoke + warlock security tests
  • pnpm test — 1544/1544 passing against current main (1541 existing + 3 new)
  • pnpm lint — 0 errors
  • New test: src/ui/tui/primitives/__tests__/PickerMenu.test.tsx, using the same monkeypatch-the-Stdout-prototype technique used to find the bug, kept as a permanent regression test

Opened as a draft — background on how this surfaced (a real self-driving run against a Laravel/Vue app, not a wizard-workbench fixture) is in #986.

Multi-select options with a description wrapped that text in a Box
fixed at 56 columns, regardless of the actual terminal width. On a
narrow terminal this let the row render wider than the physical
screen, and the terminal's own line-wrapping (which Ink doesn't
control) then misaligned Ink's cursor bookkeeping for every row
after it, producing overlapping/corrupted text.

Reads the live terminal width via Ink's useStdout() and clamps the
description width to it (floor 20, cap 56 — the original constant,
kept as an upper bound), accounting for the outer margin, grid
columns, and the description's own indent.

Adds a regression test that drives real Ink renders at several
terminal widths by patching ink-testing-library's Stdout prototype,
since the library hardcodes 100 columns with no override.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Ref34t

Ref34t commented Jul 27, 2026

Copy link
Copy Markdown
Author

Closing this. On investigation the premise is wrong — see #986 (comment) for the detail.

Short version: Output clips every line to the terminal width (renderer.js builds the buffer at the root's computed width and slices with sliceAnsi), so an over-wide description Box can't cause the terminal-level wrap this patch assumed. The corruption also shows up on screens where the description Box never renders at all, and my terminal was ≥88 columns — where descriptionWidth() returns exactly 56, identical to the current hardcoded value. So this would have been a no-op for the reported bug.

The actual trigger looks to be resizing the terminal window mid-run: Ink's resized() only clears when width decreases.

Not reopening as a hardening change either — I'd rather not add a speculative patch to a component I no longer think is at fault. If narrow-terminal support for option descriptions is something you want, happy to raise it separately on its own merits.

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.

Self-driving setup: checkbox labels and Confirm button overlap wrapped description text

2 participants