Skip to content

feat(terminal): take the width from Claude Code's COLUMNS and remove the Terminal Width options - #765

Merged
sirmalloc merged 5 commits into
sirmalloc:mainfrom
eric-engberg:feat/terminal-width-columns
Oct 30, 2026
Merged

sirmalloc merged 5 commits into
sirmalloc:mainfrom
eric-engberg:feat/terminal-width-columns

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

Relates to #498 and replaces #702.

What

  • Width from Claude Code. ccstatusline takes the terminal width from the COLUMNS variable Claude Code sets for the status line, after the CCSTATUSLINE_WIDTH override and before any probing. This is feat(terminal): read COLUMNS from Claude Code before probing #612 by lancemarks: the first commit is that PR's commit, cherry-picked with its authorship kept.
  • No more Terminal Width options. Full width minus 40, Full width until compact and its threshold, the Terminal Width entry under Terminal Options, and the Main Menu's "Some lines are truncated" warning that pointed there are removed. Every line renders the way the default, Full width always, did: the detected width minus 6 columns, in Claude Code and in the TUI preview alike.
  • Old settings still load. A settings file that still has flexMode or compactThreshold loads without an error or warning, and the next save leaves them out.

Why

On #702, sirmalloc wrote:

I'm considering removing these settings entirely as the newer claude code builds keep the statusline full-width, and in theory set the COLUMNS env var when invoking it now.

and then:

Sure, if you want to put up the COLUMNS change and the full removal of the terminal width options I'm good with it. I think there might be another PR with the columns change

That other PR is #612, so this builds on it rather than redoing it.

What Claude Code passes: since 2.1.153 ("Status line commands now receive COLUMNS and LINES environment variables so scripts can size output to the terminal width", in its changelog), and in 2.1.310 the status line command runs through the same runner as hooks, which sets both from Claude Code's own terminal size. The status line docs say to rely on it:

Claude Code captures your script's output instead of connecting it directly to the terminal, so tput cols and language-level width detection cannot read the terminal size from inside the script. Read the COLUMNS and LINES environment variables instead.

Until now ccstatusline found the width by walking up the process tree for a tty and running stty on it. On macOS that's several ps/stty processes per render (lancemarks measured about 57 ms per render), and on Windows, where probing is disabled, there was no width at all unless CCSTATUSLINE_WIDTH was set. That PR has a Windows confirmation: with COLUMNS, flex separators fill the line there for the first time.

The width modes existed because the status line couldn't know when the auto-compact message would appear next to it; the removed menu said "Claude code does not currently provide an available width variable for the statusline". With the real width passed in, Full width always is the only mode left that makes sense. Claude Code fits its own notifications on that row: outside fullscreen rendering they still share it, and per the docs, "On narrow terminals, these notifications may truncate your status line output".

How

Width order (getTerminalWidth() in terminal.ts):

  1. CCSTATUSLINE_WIDTH, if it's a positive integer
  2. COLUMNS, if it's a positive integer. Empty, 0 and non-numeric values fall through, like the override's.
  3. The per-session "no TTY" cache, then the probe: /proc on Linux, the ps/stty walk elsewhere, nothing on Windows. This is unchanged and still covers Claude Code builds that don't set COLUMNS.

lancemarks's commit applies as it is. Its only conflicts were in the docs, because main has since dropped tput from the fallbacks; the resolution keeps main's text and adds that commit's sentences. A second commit updates the Windows troubleshooting entry in docs/WINDOWS.md, which still said Windows has no width without CCSTATUSLINE_WIDTH.

Removed:

  • resolveEffectiveTerminalWidth() in renderer.ts now subtracts a fixed 6 columns, in piped renders and the preview. The 40-column reserve and the context-percentage switch are gone, so renderer.ts no longer reads the context percentage.
  • From settings: the FlexMode type, and flexMode and compactThreshold in the schema (current and v1) and in the fields the v1 migration copies.
  • From the TUI:
    • TerminalWidthMenu and its tests, and the Terminal Width entry in Terminal Options, which keeps Color Level.
    • The Main Menu truncation warning, which only showed with Full width minus 40, and the preview's truncation callback that only fed it.
    • The terminalWidth screen in App and in the dev-reload screens it can restore.
  • The two keys from configTemplates/, and the modes from docs/USAGE.md (the section is now "Terminal Width"), docs/DEVELOPMENT.md, AGENTS.md and the README's TUI feature list.

Old settings: the settings schema is a zod object, which drops keys it doesn't define. So a file with flexMode and compactThreshold parses cleanly whatever their values; out-of-range values used to make the whole file invalid and fall back to defaults. Loading doesn't rewrite the file, and the next save writes it without the keys. The legacy git-pr widget type works the same way: it's rewritten in memory and the change lands on the next save. Nothing needs converting, so there's no version bump. Imports ignore the keys too: they aren't in the present-key list a merge applies.

Unchanged: for anyone on the default mode the output is byte-identical (runtime-check.sh matches main). Also unchanged: CCSTATUSLINE_WIDTH, the probe and its terminalWidthCacheTtlSeconds cache, and the Terminal Width widget. The visible change is for anyone who picked Full width minus 40 or Full width until compact: their lines now use the full width.

Overlapping open PRs: #612's commit is the first commit here, so merging this one covers it. #629 and #728 speed up the ps/stty probe, which no longer runs when COLUMNS is set.

Demo

The TUI on the same config in both: saved with Full width minus 40, with a flex separator before a "right edge" text widget. Preview values are the widgets' sample data, so all of it is fake. Before (main), the preview stops 40 columns short and truncates, the Main Menu warns about it, and Terminal Options → Terminal Width lists the three modes. After, the same file renders at the full width with "right edge" at the end, there's no warning, and Terminal Options has only Color Level.

Powerline: before

Main Menu and Terminal Width menu on main, Powerline mode

Powerline: after

Main Menu and Terminal Options with the options removed, Powerline mode

Plain: before

Main Menu and Terminal Width menu on main, plain mode

Plain: after

Main Menu and Terminal Options with the options removed, plain mode

Testing

  • bun run lint: clean.

  • bun test: 3690 pass, 0 fail (Bun 1.4.2), on main as of 2.2.33.

    • Renderer: the 6-column reserve in piped renders, the preview and Powerline, no narrowing at 99% context, and zero-width truncation.
    • Config: files with old keys load with no error or warning, including values the old menu rejected, and lose the keys on the next save. An import with them is valid and doesn't carry them.
    • The six COLUMNS cases from lancemarks's commit, the updated migration test, and the menu tests.
  • The suite under Node v26.10.0 (vitest run), compared with main: no new failing files or tests, and every new or changed test passes.

  • The two Ink test files whose key sequences changed (App, ImportPreviewDialog): 10 runs, all passing.

  • runtime-check.sh: plain and Powerline piped renders are byte-identical across Bun 1.4.2 and Node v26.10.0 and the same as main. The TUI smoke test passes on both.

  • Piped renders of a line with a flex separator. The table gives the visible width of line 1; plain and Powerline give the same widths, under both Bun and Node.

    Settings Environment main This branch
    full no COLUMNS (the probe finds a 245-column tty) 239 239
    full COLUMNS=100 239 (ignored, probes) 94
    full COLUMNS=100 CCSTATUSLINE_WIDTH=120 114 114
    full-minus-40 CCSTATUSLINE_WIDTH=100 60 94
    full-until-compact, context at 80% CCSTATUSLINE_WIDTH=100 60 94
    flexMode: "not-a-mode", compactThreshold: 500 CCSTATUSLINE_WIDTH=100 invalid-config badge, defaults 94
  • The TUI in tmux under Node and Bun, from a settings file with flexMode: "full-minus-40" and compactThreshold: 75. It loads with no warning, and Terminal Options lists only Color Level. ctrl+s writes the file without the two keys and keeps everything else (colorLevel, globalBold, the lines).

lancemarks and others added 4 commits October 30, 2026 00:44
Claude Code 2.1.153+ sets COLUMNS (and LINES) to the terminal size when it
runs the status line. Reading it after CCSTATUSLINE_WIDTH and before the
session cache skips the ps/stty ancestor walk on macOS/BSD and gives
Windows a width without an override. Invalid values fall through to probing.
The troubleshooting entry for wrapped status lines still said Windows has
no width unless CCSTATUSLINE_WIDTH is set. Claude Code 2.1.153 and later
pass the width in COLUMNS, which is now read there too, so the override
is only needed with older builds.
Claude Code keeps the status line full-width and, since the previous
commits, its width comes from the COLUMNS it passes in. The two modes that
held 40 columns back for the auto-compact message no longer have anything
to choose between, so every line now renders the way the default, Full
width always, did: the detected width minus 6 columns, in piped output and
the TUI preview alike. Notifications that share the row outside fullscreen
rendering are Claude Code's to fit; on narrow terminals it cuts the status
line short.

Removed: the flexMode and compactThreshold settings, the Terminal Width
entry under Terminal Options (which keeps Color Level), and the Main Menu
warning that pointed there when Full width minus 40 truncated the preview,
together with the preview's truncation callback that only fed it.

Settings files that still carry flexMode or compactThreshold load without
an error or warning, whatever their values: the schema no longer defines
them, so parsing drops them and the next save writes the file without
them, the way a legacy git-pr widget type is rewritten. The v1 migration
no longer copies them, and imports ignore them. The two config templates
lose them too.
@sirmalloc

Copy link
Copy Markdown
Owner

Tweaked the right side to pad one less space to make it symmetrical on both sides.

@sirmalloc
sirmalloc merged commit 157a2b4 into sirmalloc:main Oct 30, 2026
3 checks passed
eric-engberg added a commit to eric-engberg/ccstatusline that referenced this pull request Oct 31, 2026
With sirmalloc#765 Terminal Options lists only Color Level, so the steps that went
through Terminal Width land on Color Level when the menu opens.
eric-engberg added a commit to eric-engberg/ccstatusline that referenced this pull request Oct 31, 2026
…he first row

With sirmalloc#765 Terminal Options lists only Color Level, so the two tests start
there instead of stepping down from Terminal Width.
eric-engberg added a commit to eric-engberg/ccstatusline that referenced this pull request Oct 31, 2026
…ade moot

With sirmalloc#765, Color Level is Terminal Options' only setting and the
custom-colors warning opens only from it, so the menu comes back on that
row without help.
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Nov 2, 2026
Upstream: weekly limit timers (sirmalloc#768), prompt cache miss widgets (sirmalloc#770), context window size env override (sirmalloc#352, sirmalloc#407), windowed speed fixes (sirmalloc#769, sirmalloc#771), separator/renderer fixes (sirmalloc#773), custom-command styling (sirmalloc#775), clickable cwd links (sirmalloc#744), account email from live credentials (sirmalloc#724), cache timer reported expiry + short toggle (sirmalloc#576, sirmalloc#592), claude-status lock (sirmalloc#635, sirmalloc#772), dedup token metrics (sirmalloc#622, sirmalloc#725, sirmalloc#735).
Not adopted: sirmalloc#765 terminal width from COLUMNS with fixed reserve - the fork keeps flexMode/compactThreshold and tmux pane priority.

Hand edits outside conflicts:
- README.md: kept fork Configure flex separator behavior bullet and older-updates details wrapper; dropped upstream Related Projects additions (claude-eta)
- AGENTS.md: kept fork terminal width wording (sirmalloc#765 not adopted)
- src/types/FlexMode.ts, src/tui/components/TerminalWidthMenu.tsx, src/tui/components/__tests__/TerminalWidthMenu.test.ts, src/tui/components/TerminalOptionsMenu.tsx, src/tui/components/__tests__/TerminalOptionsMenu.test.ts: restored, fork keeps terminal width settings
- src/tui/App.tsx, src/tui/components/index.ts, src/types/index.ts: re-wired terminal width menu and FlexMode export
- src/types/StatusJSON.ts: dropped fork duplicate prompt_cache key, upstream schema is a superset
- src/utils/terminal.ts: COLUMNS source gated on non-tmux so tmux pane width keeps priority
- src/widgets/WeeklyLimitTimer.ts, src/utils/usage-prefetch.ts, src/widgets/__tests__/WeeklyLimitTimer.test.ts: compat fix - fableUsage/fableResetAt -> weeklyFableUsage/weeklyFableResetAt
- src/widgets/ApiUsage.tsx: compat fix - Context Bar honors effective context window (override, clamp, 1.0M format); Weekly renders a valid value despite a session error
- src/widgets/__tests__/ApiUsage.localModelFallback.test.ts: output tokens no longer counted in context (upstream sirmalloc#730)
- src/utils/__tests__/renderer-flex-width.test.ts, src/utils/__tests__/renderer-ansi.test.ts, src/utils/__tests__/gradient.test.ts, src/utils/__tests__/renderer-separator-collapse.test.ts, src/utils/__tests__/config.test.ts, src/tui/__tests__/App.test.ts: fork deviation - fork width model (flexMode, 6-column full reserve, kept settings, Terminal Width menu entry)
- src/utils/__tests__/default-colors.test.ts: fork deviation - accept ansi256:/hex: defaults
- src/utils/__tests__/usage-prefetch.test.ts: fork deviation - weekly usage comes from the API, fork widget imports from ApiUsage
- src/widgets/__tests__/CurrentWorkingDir.test.ts, src/widgets/__tests__/CustomCommand.test.ts: Settings literal gains flexMode/compactThreshold
- src/utils/__tests__/context-window.test.ts, src/utils/__tests__/jsonl-metrics.test.ts: restored fork tests upstream dropped
- docs/test-retirements.md: ledger for the dev-reload snapshot-path and RemoteControlStatus glyph (sirmalloc#605) tests upstream replaced
sirmalloc pushed a commit that referenced this pull request Nov 5, 2026
… at the configured Color Level (#784)

* fix(tui): keep the main menu's highlight when a save changes its entries

Pressing Ctrl+S on the main menu after making changes moved the
highlight back to the entry that was last opened. Saving swaps Save &
Exit and Exit without saving for a single Exit, and List reset its
highlight to initialSelection whenever the number of items changed. The
main menu's initialSelection is the entry last opened, not the one
highlighted, so the cursor left the entry the user had moved to and the
next Enter opened a different screen.

List gets a keepHighlightOnItemsChange prop, and the main menu sets it.
With it, a new initialSelection still moves the highlight, but when just
the number of items changes, the highlight stays on the item it was on;
if that item is gone, it stays on the same row, inside the list. On the
main menu, Install stays Install after a save, and Save & Exit or Exit
without saving become the Exit that replaces them.

Other lists still move the highlight to initialSelection when their
item count changes. Line selection and Powerline Setup keep their
initialSelection on the highlight and set it to place the highlight:
appending a line highlights the new line, and turning Powerline on
highlights Separator.

* fix(tui): build the preview's color codes for the configured color level

In a terminal that chalk detects at fewer colors than the configured
color level, such as tmux with TERM=screen or ssh with TERM=xterm, the
TUI preview and color menus drew different colors from the real status
line. At the default 256 colors, cyan, magenta and bright black widgets
were drawn black; a truecolor setting previewed 256-color
approximations.

chalk works out a 256-color or hex style's escape code when the style
is created, at the level set at that moment. COLOR_MAP creates its
styles when colors.ts loads, so its codes follow the level chalk
detected for the terminal. The piped render sets chalk.level and then
calls updateColorMap() to rebuild them; the TUI set chalk.level on
load, on import and in Terminal Options, but never rebuilt the map.

The TUI now calls updateColorMap() each time it sets chalk.level, as
the piped render does. The test sandbox rebuilds the map when it puts
chalk's level back, so later test files get the codes they expect.

* docs(tui): say the main menu's initialSelection is the highlighted row

The main menu now records its highlighted row as it moves, so
initialSelection is that row's index rather than the entry last opened.
Keeping the highlight on its entry across a save is still needed: the
save swaps Save & Exit and Exit without saving for one Exit, which moves
the entries below, so the index alone would land on another entry. The
comments on MainMenu and List's keepHighlightOnItemsChange say so.

* test(tui): walk the main menu to an entry rather than through a fixed list

The main menu highlight tests pressed down through a hard-coded list of
entries, so an entry another change adds or makes selectable (Configure
Status Line opening before an install, say) broke them. They now press
down until the target entry is highlighted, waiting for the highlight to
move on each press.

* test(app): open Color Level directly, now Terminal Options' first row

With #765 Terminal Options lists only Color Level, so the steps that went
through Terminal Width land on Color Level when the menu opens.
sirmalloc pushed a commit that referenced this pull request Nov 5, 2026
…fter a sub-screen (#780)

* fix(tui): keep Merge highlighted after cancelling the shell-command confirmation

When an import adds a Custom Command, choosing Replace All or Merge
first asks "Apply this import? It adds a shell command...". Cancel says
"Go back to the preview without importing", but coming back from Merge
put the highlight on Replace All and switched the preview to the
Replace diff. The confirmation doesn't name the mode, so pressing Enter
again and confirming applied Replace All, which resets every setting
the file leaves out to its default.

The confirmation replaces the preview's List, and the List mounted
again at its first row, then reported that row as the new selection.
Mount it on the mode the preview was showing instead.

* fix(tui): let Escape leave Import Preview and its shell-command confirmation

Escape did nothing on Import Preview, on both the diff and the "Apply
this import? It adds a shell command..." confirmation. The only way out
was to arrow down to Cancel and press Enter. The Import Config path
dialog one step earlier, and the other menus, go back on Escape, and
docs/USAGE.md already says Escape returns from the confirmation to the
preview.

The dialog had no input handler of its own, and its List only handles
the arrows and Enter. Escape now steps back one screen: from the
confirmation to the preview, where the mode stays highlighted, and from
the preview out of the import, as Cancel does.

* fix(tui): keep Color Level highlighted after the custom-colors warning

In Terminal Options you press Enter on Color Level to cycle through the
levels. With an ansi256 or hex widget color, leaving 256 Color or
Truecolor first warns "Custom colors detected!". After Yes, No or
Escape the highlight was back on Terminal Width, so the next Enter
opened the Terminal Width screen instead of cycling on.

The warning replaces the List, and the List mounted again at its first
row. Remember the highlighted row, as Powerline Setup does around its
confirmations, and mount the List on it.

* fix(tui): keep the cancelled setting highlighted in Configure Status Line

Pressing Escape on a Refresh Interval or cache TTL prompt brought the
list back with the highlight on the first row, Refresh Interval (or Git
Cache TTL when Refresh Interval needs a newer Claude Code). Pressing
Enter again opened a different setting from the one just left. Saving
returns to the Main Menu, so only cancelling showed it.

Each prompt replaces the List, and the List mounted again at its first
row. Remember the highlighted row and mount the List on it.

* fix(tui): keep the chosen update style highlighted when going back in Install

In Install to Claude Code, going back from the package manager step
(Escape or "← Back") highlighted Pinned global install again, with its
description, even after choosing Auto-update. Pressing Enter then led
into the pinned global install commands instead of the npx/bunx ones.

The style List mounts again on the way back, and it always started on
its first row. Start it on the style still held from the first pick.

* test(import-preview): mark the command import with colorLevel instead of flexMode

flexMode is going away with the Terminal Width options; any non-default key
the import carries serves this fixture.

* test(terminal-options): start the warning tests on Color Level, now the first row

With #765 Terminal Options lists only Color Level, so the two tests start
there instead of stepping down from Terminal Width.

* fix(tui): drop the Terminal Options row change, which #765 made moot

With #765, Color Level is Terminal Options' only setting and the
custom-colors warning opens only from it, so the menu comes back on that
row without help.
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.

3 participants