Skip to content

fix(separators): clear colors a new Color Level can't show, and invert left glyphs entered by hex - #786

Merged
sirmalloc merged 2 commits into
sirmalloc:mainfrom
eric-engberg:fix/separator-colors
Nov 5, 2026
Merged

sirmalloc merged 2 commits into
sirmalloc:mainfrom
eric-engberg:fix/separator-colors

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

What

Two fixes to separator colors:

  1. Color Level clears separator colors the new level can't show. Widget colors were already reset this way, but separators kept theirs. A separator given a hex color at Truecolor no longer prints a 24-bit escape after Color Level is cycled on to 256 Color. For colors set with this version, the "Switching color modes will reset custom ansi256 or hex colors to defaults" warning is now true when only separators have custom colors: confirming it resets them. A configuration that an earlier version already saved at 256 Color with a hex separator isn't repaired straight away (see "Left as is").
  2. A left-facing Powerline separator entered by hex code is inverted. In Powerline Setup > Separator, typing E0B2 (Triangle Left) or E0B6 (Round Left) with (h) now turns the background inversion on, which is what picking them with ← → already did. The arrow is drawn the right way round, and the list shows [Inverted]. Typing E0B0 or E0B4 turns inversion off, as ← → does. A custom code point keeps the slot's inversion as before.

Why

  1. sanitizeLinesForColorLevel resets a widget's foreground to getDefaultColor(). Separators and flex separators have no widget implementation and so no default, and resetWidgetForegroundToDefault returned them unchanged. Their backgrounds were cleared, but their foregrounds were not. getColorAnsiCode only drops hex and ansi256 colors at Basic and No Color, so at 256 Color a hex color still prints 38;2. The TUI only offers hex at Truecolor, so the level change is the one place that keeps hex off a 256-color line, and separators skipped it.

    Line [Model hex:FF00AA, separator hex:00FF00, Custom Text], sanitized for each step from Truecolor round to 256 Color (No Color, Basic, 256 Color) on main:

    level 0 [["model","cyan"],["separator","hex:00FF00"],["custom-text",null]]
    level 1 [["model","cyan"],["separator","hex:00FF00"],["custom-text",null]]
    level 2 [["model","cyan"],["separator","hex:00FF00"],["custom-text",null]]
    

    Piping a payload with that saved line at colorLevel: 2 printed (cat -v):

    ^[[0m^[[38;5;30mModel: Sonnet 4.5^[[39m^[[38;2;0;255;0m | ^[[39m^[[38;5;188mX^[[39m
    

    hasCustomWidgetColors counts separators, so with a hex color on a separator only, it returned true and the warning showed. Sanitizing for No Color then returned the line unchanged, so confirming the warning reset nothing.

  2. The hex Enter branch of PowerlineSeparatorEditor called updateSeparators(newSeparators) with no inversion list. updateSeparators then kept the slot's old flag (invertBgs[i] ?? false). Only the ← → branch set the flag from the glyph's direction. On main, with the editor's saved settings:

    • (h) E0B2 or E0B6 on the default Triangle Right saved separatorInvertBackground: [false]. ← → saves [true] for the same glyph.
    • (h) E0B0 over a Triangle Left that ← → had inverted saved [true].

    The renderer picks the foreground and background order from that flag alone. Between a red and a blue segment (bgRed, then bgBlue, Basic colors), the E0B2 separator printed ^[[31m^[[44m with the flag off: the previous segment's red arrow on the next segment's blue, pointing into the wrong segment. With the flag on it printed ^[[34m^[[41m.

How

  1. resetWidgetForegroundToDefault clears a separator's or flex separator's foreground (color: undefined), the same way backgrounds are cleared. A separator without a color renders in the separator default, or takes the previous widget's colors when separators inherit colors. The existing test that kept a separator's hex foreground at Basic now expects it cleared. One new test covers separators at 256 Color and Truecolor, and another checks that the separator colors that trigger the warning are reset.
  2. When (h) enters one of the four separator presets, the hex branch sets that slot's inversion the way ← → does: on for E0B2 and E0B6, off for E0B0 and E0B4. Any other code point keeps the slot's inversion. Its direction isn't known, and (t) still toggles it. A new it.each in PowerlineSetup.test.ts types each code into the editor and checks the saved separators and inversion.

Left as is:

  • Other Nerd Font glyphs that face left, such as E0BA or E0BE, aren't separator presets, so they count as custom codes and keep the slot's inversion. The editor has no list of glyph directions beyond its presets.
  • Typing E0B0 again over a Triangle Right that (t) inverted turns its inversion off. ← → does the same when it lands on Triangle Right.
  • A configuration that an earlier version saved at 256 Color with a hex separator color keeps that color. This is what the Truecolor → No Color → Basic → 256 Color cycle above produced. The fix acts only when Color Level changes, and nothing rewrites saved settings on load. On this branch, the saved line from "Why" at "colorLevel": 2 still prints the separator as ^[[38;2;0;255;0m | . The color is cleared the next time Color Level reaches No Color, two presses away (256 Color → Truecolor → No Color). Saved at Basic or No Color, the same hex color prints no escape, and the next Color Level step clears it. A hex color set at 256 Color by editing settings.json by hand behaves the same way. Mapping hex to the nearest 256 color when rendering would cover both cases, but that is a separate change. The TUI only offers hex at Truecolor.
  • The warning still checks for any custom color, not just the colors the next level drops. For colors set with this version these are the same: 256 Color → Truecolor drops the ansi256 colors the 256 Color editor sets, and Truecolor → No Color drops every custom color. For the saved configurations in the bullet above they differ. 256 Color → Truecolor warns, but hex is valid at Truecolor, so confirming resets nothing. The next press, Truecolor → No Color, warns again and clears the color. Warning only when the next level would change the lines would close that gap, and is left for a separate change.

Demo

Each case only shows up in one mode, so each was recorded once.

Powerline

A separator entered by hex code. The line uses the Nord Aurora theme with one Triangle Right separator. (h), E0B2, Enter turns it into a Triangle Left. Before, the list shows Triangle Left and the preview draws each arrow in the previous segment's color, notched into the next one. After, it shows Triangle Left [Inverted] and the arrows are the right way round. Plain mode isn't recorded: Separator is disabled in Powerline Setup while Powerline is off.

Before:

Separator entered by hex code in Powerline mode before the fix

After:

Separator entered by hex code in Powerline mode after the fix

Plain

Color Level from Truecolor round to 256 Color. Model, Context Length and Session Cost have hex colors, and the two | separator items are hex 00FF00. Terminal Options > Color Level warns, Yes goes to No Color, and two more presses reach 256 Color. The widgets go back to their default colors on the way. Before, the separators stay bright green in the preview. After, they are back to the separator default. The configuration is then saved, and the piped line is checked: color-codes.js lists each visible piece of it with its foreground escape. Before, both | print ESC[38;2;0;255;0m (24-bit) at "colorLevel": 2. After, they print in the default color. Powerline isn't recorded: Powerline mode draws no separator items.

Before:

Color Level change with hex-colored separators in plain mode before the fix

After:

Color Level change with hex-colored separators in plain mode after the fix

Testing

  • On main as of fix: use env override and settings for usable context ratio calculation #407 (71e8b42): merges cleanly, bun run lint is clean, and bun test gives 3944 pass, 0 fail. The built CLI under Bun 1.4.4 and Node 26.13.0 matches main in plain and Powerline mode with the baseline configs, and the TUI opens and exits cleanly. It adds no JavaScript or Node API newer than Node 14, the build's target.
  • bun run lint is clean.
  • bun test: 3174 pass, 0 fail across 178 files, against 3168 pass, 0 fail on main. 6 tests are new, and one existing assertion changed.
  • On main, the 2 new color-sanitize.test.ts tests and the changed one fail. 3 of the 4 new editor cases fail there too. The fourth checks that a custom code keeps its inversion, which main already does.
  • The two changed test files pass under Node (v26.10.0, Vitest): 19 of 19 tests, against 13 of 13 on main.
  • Built dist/ under Bun 1.4.2 and Node v26.10.0: the baseline plain and Powerline renders agree across runtimes and match main. The TUI opens and exits cleanly under both.

Changing Color Level resets each widget's ansi256 or hex color that the
new level can't show to the widget's default, and clears such
backgrounds. A separator's foreground was skipped, because separators
have no widget default to reset to, so it kept its color. A separator
given a hex color at Truecolor still had it after cycling on to 256
Color, and the 256-color line printed a 24-bit escape for it
(\x1b[38;2;0;255;0m) next to the widgets' 38;5 codes.

The reset warning counts separator colors as well, so with only a
colored separator it promised a reset, and confirming it changed
nothing.

Clear a separator foreground the new level can't show, as backgrounds
already are. Without a color, a separator renders in the separator
default, or takes the previous widget's colors when separators inherit
colors.
Picking Triangle Left or Round Left with the arrow keys turns on the
separator's background inversion, which a left-facing glyph needs to
draw the next segment's color on the previous segment's background.
Entering the same glyph with (h), as E0B2 or E0B6, kept the slot's old
inversion instead. On the default separator that is off, so the arrow
was drawn reversed (\x1b[31m\x1b[44m between a red and a blue segment,
instead of \x1b[34m\x1b[41m), and the list showed no [Inverted] to hint
that (t) was needed. Typing E0B0 over an inverted Triangle Left
likewise left the right triangle inverted.

When (h) enters one of the preset glyphs, set its inversion as the
arrow keys do: on for the left-facing ones, off for the right-facing
ones. A custom code point keeps the slot's inversion, since its
direction is unknown, and (t) still toggles it.
@axisrow

axisrow commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Reproduced on current main (66e9a08):

Red: 6 failing / 19 total — separator colors survive a Color Level downgrade (a hex separator keeps printing a 24-bit escape at 256 Color), the "reset custom colors" warning resets nothing when only separators have custom colors, and hex-entered left glyphs (E0B2/E0B6) don't set background inversion. Matches the PR's 2+1+3 split; the guard case passes on main as stated.

Green: with the full PR head checked out, 19 passing / 0 failing.

bun test on macOS.

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