Repository navigation
fix(renderer): truncate at zero width, keep FLEX text, no custom colors at No Color - #678
Merged
Merged
Conversation
Full minus 40, and Full until compact past its threshold, subtract 40 columns from the terminal width; Full subtracts 6. On a narrow terminal that went to 0 or below, and every width check treated a non-positive width as "no width detected": the line printed at full length and wrapped, while 41 columns still truncated to a single ".". The TUI preview cut the line at the terminal edge instead, so it no longer matched what Claude Code received. Clamp the effective width at 0 and treat any detected width, 0 included, as known, in both the plain and Powerline renderers. When the reserve takes the whole terminal the line now truncates to nothing, the continuation of what 41-43 columns already show. An undetected width (null, or 0 from the caller) keeps the fallback behavior.
The plain renderer stood each flex separator in the assembled line as the string 'FLEX' and later split the line on that string. A widget whose rendered text was exactly FLEX with no color codes around it (No Color mode, or a color name the palette doesn't have) matched too: its text vanished and the line got a second flex gap. Push null for a flex separator instead, which no widget output can equal. Powerline mode already uses its own marker and is unchanged.
…olor getColorAnsiCode returned raw 256-color and truecolor escapes for hex: and ansi256: colors at every color level. With No Color selected, which the TUI describes as disabling all color output, a widget colored hex:FF0000 on hex:0000FF still printed \x1b[48;2;0;0;255m\x1b[38;2;255;0;0m; Basic did the same. Return no code for these formats at the ansi16 level (Basic and No Color), as the function already does for gradient specs. This matches the TUI, which only offers these formats at 256 Color and Truecolor and clears them from widgets when switching to Basic or No Color, so they only reach these modes through a hand-edited config. Named colors are unchanged.
The else branch re-checked terminalWidth === null, which the preceding condition (a flex separator and a known width) already implies when a flex separator is present. Chain it as else-if on hasFlexSeparator instead of nesting an if/else inside the else.
On a fresh CI machine the first start of a runtime can take seconds while its binary is read from a cold disk. The capture tests start Node for the first time in their opening case, so that case once ran past its 5s limit (5018 ms) while the same case one test later took 146 ms and the rest about 60 ms. Each runtime now starts once in a setup hook with its own timeout, before the timed tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
FLEXstays on the line. In plain mode, a widget whose output was exactlyFLEXwith no color codes around it disappeared and turned into a second flex gap.hex:andansi256:widget colors were emitted at every color level, so No Color still colored those widgets.Why
Narrow widths. The effective width is the terminal width minus a reserve (40 columns for Full minus 40, 6 for Full). Every width check treated a result of 0 or less as "no width detected". A 60-column line with Full minus 40:
The TUI preview cuts each line at the terminal edge, so it showed a truncated line while Claude Code got the full one.
FLEXtext. The plain renderer stood each flex separator in the assembled line as the string'FLEX', then split the line wherever an element equaled that string.[Custom Text "left", Flex, Custom Text "FLEX", Custom Text "right"]with No Color and no padding renderedleft right. With colors or padding around it, the text didn't match, which is why this went unnoticed.Custom colors at No Color. With
colorLevel: 0, a widget coloredhex:FF0000onhex:0000FFand one coloredansi256:100rendered as:Terminal Options describes No Color as "Disables all color output". Named colors already follow the level (chalk emits nothing at level 0), and gradients already return no code at Basic and No Color; these two formats didn't.
How
...,..,.). An undetected width (null, or 0 passed by a caller) keeps today's fallback. The TUI preview applies the same reserve, so it now matches the piped output. Clamping to 1 column (a lone.) instead would also work.FLEX: a flex separator is pushed into the assembled line asnull, which no widget output can equal. Separator, padding and fallback (|when the width is unknown) handling are unchanged. Powerline mode already used its own marker and isn't touched.getColorAnsiCodereturns no code forhex:andansi256:at theansi16level, which covers both Basic and No Color, as it already does for gradient specs. This matches the TUI: the color menu only offers these formats at 256 Color and Truecolor, and switching to Basic or No Color clears them from widgets. So they only reach these modes through a hand-edited config. One difference remains: the TUI resets a cleared foreground to the widget's default color, while the renderer now prints the text in the terminal's default color. Named colors, and every level above Basic, are unchanged.Demo
Piped status lines from sample data (
scripts/payload.example.json, the width set withCCSTATUSLINE_WIDTH), each wrapped in[ ]so an empty result shows: Full minus 40 from 44 down to 39 columns, a Custom Text widget readingFLEXat No Color, andhex:/ansi256:colors at No Color. TheFLEXcase only affected plain mode, so its Powerline row doesn't change.Powerline: before
Powerline: after
Plain: before
Plain: after
Also: a CI timing fix in the custom command tests
This PR's first CI run failed on a test it doesn't touch:
That case is the first in the job to start
node; the identical case right after it took 146 ms and the rest about 60 ms. The capture itself can't have hung (it kills its helper after 2 s and returns[Timeout]), so the time went to Node's first start on a fresh runner, before its binary was in the disk cache. The last commit starts each runtime once in abeforeAll(30 s timeout) before the timed tests; the tests and their own limits are unchanged.custom-command-process.test.tspasses under Bun (5 runs) and Node 26 Vitest.Testing
mainfirst:FLEXwidget text survives with a known width and with the unknown-width fallback.getColorAnsiCodereturns no code forhex:/ansi256:atansi16and still does at 256 Color and Truecolor. Rendered lines at No Color and Basic contain no38;5/38;2/48;5/48;2escapes in either mode, and still do at 256 Color and Truecolor.bun test: 2789 pass, 0 fail.bun run lintpasses.mainfor the baseline line. The TUI opens and exits cleanly under both.mainand this branch, as in the stills above.