Skip to content

fix(tui): normalize tabs for terminal output - #6697

Merged
badlogic merged 1 commit into
earendil-works:mainfrom
xz-dev:patch/tui-tab-terminal-normalization
Jul 16, 2026
Merged

fix(tui): normalize tabs for terminal output#6697
badlogic merged 1 commit into
earendil-works:mainfrom
xz-dev:patch/tui-tab-terminal-normalization

Conversation

@xz-dev

@xz-dev xz-dev commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6696.

Pi lays out visible tabs as three columns, but previously emitted the raw TAB byte. The terminal then expanded it to the next physical tab stop, which could auto-wrap a logically single-row overlay and desynchronize subsequent physical row updates.

This change expands visible tabs to three spaces in normalizeTerminalOutput(), after layout and overlay composition but before terminal output. Logical component/editor content remains unchanged, and recognized terminal control sequences remain byte-identical.

Tests

Added an xterm-backed TUI regression that verifies:

  • a tab-containing overlay remains on one physical terminal row;
  • following rows remain aligned;
  • raw visible TAB bytes do not reach terminal output;
  • TAB bytes inside recognized OSC/APC sequences remain unchanged.
node --test --test-reporter=tap packages/tui/test/*.test.ts
692 passed, 0 failed

npm run check
passed

./test.sh

@badlogic
badlogic merged commit 1c799ce into earendil-works:main Jul 16, 2026
1 check passed
@badlogic

Copy link
Copy Markdown
Collaborator

cheers!

@xz-dev

xz-dev commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for merging this!

While reviewing the fix, I noticed two possible follow-ups and intentionally kept both out of #6697:

  1. Pi's fixed tab width is currently duplicated as 3 / " " in graphemeWidth(), truncateFragmentToWidth(), visibleWidth(), and normalizeTerminalOutput(). Would a small follow-up PR introducing
    internal TAB_WIDTH / TAB_EXPANSION constants be useful, or would you prefer to leave this as-is?

  2. extractAnsiCode() currently recognizes CSI sequences only when they end in one of [mGKHJ], while ECMA-48 defines the CSI final-byte range as 0x40–0x7E. This would be a broader behavioral change
    affecting all parser callers, so I did not include it here. I also do not yet have a valid user-facing reproduction using a standards-compliant CSI sequence.

Would you prefer a separate issue/PR for either of these, or should I leave them alone until there is a concrete bug report?

@xz-dev
xz-dev deleted the patch/tui-tab-terminal-normalization branch July 16, 2026 07:33
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.

TUI emits raw tabs after fixed-width layout, causing overlay autowrap and row corruption

2 participants