Repository navigation
[low] fix(custom-command): truncate maxWidth output by display width, not string length - #626
Merged
Conversation
The widget cut its output with `output.substring(0, maxWidth - 3)`, which counts UTF-16 code units, not terminal columns. With preserveColors on, the output still holds its escape sequences, so: - escape bytes use up the budget (`\x1b[38;5;208mfeature/login-refactor` at maxWidth 20 showed 6 characters), - the cut can land inside an escape sequence, and the terminal then swallows the characters that follow, - the command's trailing reset is dropped, so the colour bleeds into the separators and widgets after it. Without preserveColors, CJK text took twice its maxWidth, and a cut could split a surrogate pair. The renderer's ANSI-aware maxWidth pass runs only for preserveColors widgets, and only after the widget has already damaged the text. So keep the widget-level cut, but use the shared applyMaxWidth helper (truncateStyledText with an ellipsis), as GitBranch and GitRootDir do. It keeps the '...' ellipsis. truncateStyledText does not re-emit a reset after it cuts, so append one when the truncated output still holds SGR styling. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Sep 30, 2026
Open
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.
BLUF
maxWidthcut counts UTF-16 code units, not terminal columns.preserveColors, escape bytes use up the budget. The cut can land inside an escape sequence, and it drops the command's trailing reset, so the colour bleeds into the separators and widgets after it. WithoutpreserveColors, CJK output takes twice itsmaxWidthand a cut can split a surrogate pair.applyMaxWidthhelper (truncateStyledTextwith the...ellipsis), as GitBranch and GitRootDir already do. After a cut, append an SGR reset when the output still holds styling.mainand all pass with the fix.Details
src/widgets/CustomCommand.tsxranoutput.substring(0, item.maxWidth - 3) + '...'on the raw output.getVisibleTextonly strips escapes whenpreserveColorsis off.main:\x1b[38;5;208mfeature/login-refactor\x1b[0mat maxWidth 20 gives\x1b[38;5;208mfeatur.... That is 9 visible columns, and the colour is never reset, so the following|separator is painted orange.\x1b[38;5;...), and the terminal swallows the following text.'中'.repeat(20)at maxWidth 20 is not truncated at all and takes 40 columns.'😀'.repeat(15)at maxWidth 20 leaves a lone surrogate.maxWidthpass (getVisibleWidth+truncateStyledText(..., { ellipsis: false })) only runs for preserve-colors widgets. Non-preserve output relies on the widget. For preserve-colors output the renderer pass runs too late, because the widget has already damaged the text. The cut has been in the widget since it was added, and there is no reason to keep raw-length semantics.truncateStyledTextcloses an open OSC 8 link, but it does not re-emit an SGR reset after it cuts. The widget therefore adds\x1b[0mwhen the truncated text still has SGR codes. The reset goes last, so the renderer's outer background, bold and dim wrapping is unaffected.maxWidth≤ 3 the widget now returns'.'.repeat(maxWidth)(the shared helper's rule) instead of a 3-column...that overflowed the limit.ansi.tsis untouched. This is independent of [low] fix(ansi): never truncate to a line wider than maxWidth #625, which changes cluster handling insidetruncateStyledText. This widget benefits from that fix automatically when it lands.Checks
bun run lint(tsc + eslint--max-warnings=0): clean.bun run build: clean.main(colour cut, cut inside an escape, CJK width, surrogate pair). 12/12 pass with the fix. CustomCommand, renderer*, ansi*, GitBranch and GitRootDir suites: 165 pass, 0 fail.bun teston a heavily loaded machine (load average ~23): only unrelated, pre-existing failures. These are 5 s timeouts infetchUsageData error handling,custom command captureprocess tests and TUI component tests. The same 12 files run on unmodifiedmaingave 54 failures, against 41 on this branch. None of them import the changed widget.🤖 Generated with Claude Code