Skip to content

fix: 30 bugs you can see on screen, each confirmed with a before/after screenshot - #61

Merged
axisrow merged 5 commits into
mainfrom
claude/visual-bug-verification-2fsvti
Oct 15, 2026
Merged

axisrow merged 5 commits into
mainfrom
claude/visual-bug-verification-2fsvti

Conversation

@axisrow

@axisrow axisrow commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

A bug hunt with one rule: a bug only counts if you can see it. Every item below was reproduced in the real program and screenshotted before any code changed:

  • Piped status line output was rendered through xterm.js (with a real JetBrainsMono Nerd Font).
  • The TUI was driven in tmux at 120×40.

The same scenario was re-run after the fix for the "after" screenshot. Candidates that did not show up on screen were dropped (see below).

Each fix has a regression test, and each of those tests fails on the code before the fix.

Closes #63, which describes every bug with steps and links to the pre-fix code. The Powerline font-detection bug stays open in #62.

Status line rendering — src/utils/renderer.ts, src/utils/colors.ts

# What you saw Trigger Fix
A TUI main menu: "⚠ Some lines are truncated" when nothing is cut full-minus-40 plus any ... in widget text: CWD segments .../a/b, a Custom Command preview over 20 chars, a Link, a max width The renderer reports real truncation (wasTruncated from both the regular and the Powerline paths) instead of searching the line for ...
B A blue "hole": the widget background stops after the command's own \x1b[0m Regular mode, Custom Command with preserve colors and a background (own or override) Restore the background after resets, as Powerline mode already does (1a6e588)
R9 Separators and every later widget become bold and underlined A preserve-colors command that leaves styles open (printf '\033[1;4mX') Close the content with \x1b[0m when it carries SGR
R1 Opus 4.6 | $0.42: a dangling | Separator, then a widget that renders empty, then a flex separator Drop a visible separator when everything up to the flex rendered empty. An explicit sep, flex pair and spacing-only separators are kept as before
R6 A | | B, and an empty block in Powerline A widget whose output is only color codes (a script printing an empty variable in color) Output with zero visible width counts as empty
R4 Two separate widgets share one palette color Regular theme, merge blocked by a separator or flex Advance the palette like Powerline does: share a color only when the widget really glues to the next rendered one. This includes gluing across an empty widget whose separator the merge drops
R7 A line starts or ends with | Terminal width unknown (always on Windows), flex at a line edge A flex at an edge renders nothing
R8 Separators are white next to themed widgets Regular theme plus Inherit separator colors Inherit the resolved, themed foreground
R5 (ui)⎇ fix(ui): link lost, text leaks, line wider than measured Git Branch with a link, dim: parens, branch fix(ui) applyParensDim never puts a style code inside an OSC sequence. A (...) span that wraps a whole link is still dimmed

Widgets

# What you saw Fix
W15 Context Bar 199k/200k (100%) next to Context % 99.6% Cap the whole-percent display below 100 while tokens are still free. A used count that would round up to the total gets one more digit (199.6k/200k (99%))
W12 Ctx: 699.9999999999999 (fractional used_percentage, no current_usage) Round the derived token count
W5 Cached: 1246.4M Add a B tier, promoted the same way as k→M
W13 Weekly Reset: [Loading] forever once usage has loaded at 0% with no reset time Show [Not started] (docs/USAGE.md updated)
W2 Editor: the long Context Bar has no modifier, the default shows (medium bar) Add (long bar)
W8 Editor shows (icon-space-number), output is ↻2 Change the label only, to icon-and-number. The stored id is unchanged

TUI

# What you saw Fix
D Terminal Width: "Full width minus 40 (default)" sits under "Full width always (active)". The text also says "minus 4" (default) moves to Full width always (the schema default), and the text says "minus 6"
E Width-detection note squeezed into two side-by-side columns flexDirection='column'
F ☰ Line2 (3 widgets) and (s)how separators:OFF Add the missing spaces
T3 After toggling powerline in Powerline Setup, ▶ lands on Terminal Options / Line Themes Re-resolve the main-menu index. Install and uninstall return targets also account for the hidden Theme entry
T4 Presets keeps the first-run welcome and hides the "replaces your entire configuration" warning Clear the first-run flag once the picker is answered
T8 Edit Colors lists Flex Separator; its color never renders Exclude it
T10 Delete the last line, ESC, re-enter: ▶ on "← Back" Clamp the remembered line once, on entry from the main menu
T11 "Onedark", "Tokyonight", "Nord-aurora", and a hint that points to "Powerline Configuration" Use the picker names ("One Dark") and "Powerline Setup"
T13 Color-level warning shows Yes/No with no question Print "Continue?"
T15 Add widget → All: 93 rows, the selection and header scroll off the terminal A 12-row window with "Showing x-y of n". getVisibleRange moved to src/utils/visible-range.ts
T16 Global Overrides: "Compact Labels:" is one column off Align it
T9 npm missing: after cancelling, the package step opens on "← Back" Map the remembered manager to its index among the enabled entries
T1 "Current pinned version:" followed by a binary path "Relaunch the pinned version with:"

Before / after

Each image shows the same scenario before and after the fix.

A
A — before and after

B
B — before and after

R9
R9 — before and after

R1
R1 — before and after

R6
R6 — before and after

R4
R4 — before and after

R7
R7 — before and after

R8
R8 — before and after

R5
R5 — before and after

W15
W15 — before and after

W12
W12 — before and after

W5
W5 — before and after

W13
W13 — before and after

W2
W2 — before and after

W8
W8 — before and after

D
D — before and after

E
E — before and after

F
F — before and after
F (separators) — before and after

T3
T3 — before and after

T4
T4 — before and after

T8
T8 — before and after

T10
T10 — before and after
T10 — follow-up: the highlight no longer moves after deleting another line

T11
T11 — before and after

T13
T13 — before and after

T15
T15 — before and after

T16
T16 — before and after

T9
T9 — before and after

T1
T1 — before and after

Self-review follow-up (42bcffc)

An independent review of this PR's own diff (reviewers per area, then a skeptic per finding) found four cases where a fix made something else wrong. All four are fixed in 42bcffc, and each has a test that fails on the previous head:

  • T10: clamping on every render moved the highlight after deleting a line other than the remembered one. Reproduced in the real TUI, then re-verified after the fix.
  • W15: 200k/200k (99%) at 199,500–199,999 tokens.
  • R4: a merge glued across an empty widget got two palette colors.
  • R5: a (...) span wrapping a whole link was no longer dimmed.

Documented only, not changed (owner's call)

Powerline arrows turn into | although a capable font is installed, tracked in #62. Detection in src/utils/powerline.ts is a top-level file-name scan:

  • It matches /nerd font/i with a space, so it misses Nerd Fonts v3 names like JetBrainsMonoNerdFont-Regular.ttf.
  • It never looks in subfolders.
  • Terminals with built-in glyphs (Ghostty, kitty, WezTerm) have no font file at all.

The same font file renders arrows under its old v2 name and | under its v3 name.

Dropped

Not visible on screen:

  • Speed widgets double-count rows that belong to one API call.
  • Turn Number counts interrupt markers as turns.
  • The daemon-only env fallback.
  • The 1024M memory rounding, which needs controlled RAM to reproduce.
  • The Windows font-install message color.

Visible, but intended:

  • H: the ▶ indicator looked misaligned in code, but Ink compensates, so the rows line up on screen.
  • W9: the 0hr 0m placeholder is a style choice.
  • W10: the glyph on raw-mode placeholders is locked in as intended by JjWidgetSharedBehavior.test.ts.

Test plan

  • bun run lint is clean.
  • CI is green: Lint & Type Check, Test, Build.
  • Local bun test: 3605 pass, 9 fail. The same 9 fail on the base commit in this container and pass in CI: 8 daemon shared-mode tests, and the proxy-CONNECT deadline test.
  • Every regression test fails on the pre-fix code (checked by stashing the fix).
  • Each scenario was re-run in the real program after the fix (before/after screenshots).

🤖 Generated with Claude Code

https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS

Each of these was reproduced in real piped output and screenshotted
before the change:

- Report truncation from the renderer instead of searching the line for
  "...": a CWD segments path, a long Custom Command preview or a Link made
  the TUI warn "Some lines are truncated" when nothing was cut.
- Regular mode now treats preserve-colors widgets like Powerline mode
  does: the widget background is restored after a reset inside the
  content, and styles the content leaves open (bold, underline) are closed
  before the rest of the line.
- Drop a visible separator left dangling before a flex gap when every
  widget between them renders empty ("Opus |      $0.42").
- Treat output with no visible text (a script wrapping an empty variable
  in color codes) as empty, so it keeps no separators and no Powerline
  segment.
- A merge blocked by a separator or flex separator no longer makes two
  widgets share a regular-theme palette slot.
- With the terminal width unknown, a flex separator at a line edge renders
  nothing instead of a stray " | ".
- inheritSeparatorColors follows the regular-mode theme color the widget
  was actually drawn with.
- Dim-parens styling leaves OSC 8 hyperlinks alone; a branch like
  "fix(ui)" no longer breaks its link and prints part of the URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS
… editor

Each was reproduced in real output and screenshotted before the change:

- Context Bar no longer shows "(100%)" while tokens are still free
  (199k/200k next to Context % reading 99.6%).
- Tokens derived from a fractional used_percentage are rounded instead of
  printing "699.9999999999999".
- Token counts past a billion use a B suffix ("1.2B") instead of
  "1246.4M", with the same round-up promotion as k to M.
- Block and Weekly Reset Timer show "[Not started]" when usage has loaded
  at 0% but the window has no reset time yet, instead of "[Loading]"
  forever.
- The editor labels the long Context Bar "(long bar)", like the usage
  widgets; it showed no modifier while the default said "(medium bar)".
- Compaction Counter's editor label reads "icon-and-number": the stored
  "icon-space-number" id stays for existing configs, but the space it
  names was dropped from the output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS
…ion screens

Each was reproduced in the real TUI (tmux, 120x40) and screenshotted
before the change:

- Terminal Width marks "Full width always" as the default, matching the
  schema, README and USAGE; its description says the renderer's actual
  6-column reserve instead of 4.
- The width-detection note in Edit Line stacks its two lines instead of
  laying them out as two squeezed columns.
- The delete-line dialog reads "☰ Line 2", and Edit Colors
  "(s)how separators: OFF", with the missing spaces.
- Leaving Powerline Setup after toggling powerline keeps the highlight on
  Powerline Setup; install/uninstall return targets also account for the
  Theme entry powerline hides.
- After the first-run preset picker is answered, Presets shows its own
  title and the "replaces your entire configuration" warning.
- Edit Colors no longer lists Flex Separators, whose colors never render.
- Re-entering a line list after deleting the last line no longer opens on
  "← Back".
- Theme names show as the picker names ("One Dark", not "Onedark"), and
  the theme hint points to "Powerline Setup", the screen's real name.
- The color-level confirmation prints its "Continue?" question.
- The widget picker shows a 12-row window with "Showing x-y of n"
  instead of drawing all 90+ widgets off the top of the terminal.
- Global Overrides aligns "Compact Labels:" with the other rows.
- The install package step reopens on the remembered manager when npm is
  missing instead of on "← Back".
- The pinned-mismatch screen labels the relaunch command as one ("Relaunch
  the pinned version with:") instead of as the "Current pinned version".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS
The test ran a real printf through Custom Command, which other test files
mock in the full bun test run; stub the widget's output instead so the
color-only case is exercised deterministically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS
An independent review of this PR's own changes found four cases where
a fix made something else visibly wrong:

- Edit Lines: the remembered line was clamped on every render, so
  deleting a line other than the remembered one moved the highlight to
  a different line. It is now clamped once, on entry from the main menu.
- Context Bar: with the percent capped below 100, 199,600 of 200,000
  tokens read "200k/200k (99%)". The used count gets one more digit
  ("199.6k/200k (99%)") when it would round up to the total.
- Regular themes: a merge glued across an empty widget (the dropped
  separator after it belongs to the merge) gave the two glued widgets
  different palette colors. The palette now follows the same glue rule.
- Dim parens: a (...) span wrapping a whole hyperlink was no longer
  dimmed. Spans may contain complete OSC sequences again, while no
  style code is ever inserted inside one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TuwfSyvahcufah5vLXqVwS

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — no changes required.

Reviewed the full diff against main (renderer, colors, widgets, TUI) plus the new regression tests.

  • The rendering changes are internally consistent. I traced the new resolvedForegrounds map, the merge-glue rule (mergesWithNextRenderedWidget) and the shouldAddSeparator check across combinations: merge blocked by separator/flex, merge glued across an empty widget, dangling separator before a flex gap, trailing and doubled separators — the palette, the dropped separators and the default-separator suppression agree with each other.
  • The dangling-separator scan deliberately skips spacing-only separators (they remain the boundary when a fully occupied line leaves the flex gap at zero columns); visible separators are the ones dropped. Both sides are covered by tests.
  • formatTokens B-promotion mirrors the documented k-to-M boundary arithmetic (999949999 -> 999.9M, 999950000 -> 1.0B).
  • ContextBar percent cap and the extra-digit used count interact correctly. At 199999/200000 the line reads 200.0k/200k (99%): the fractional digit keeps it distinguishable from the total and the percent stays honest. No change requested.
  • The bar itself still fills fully at 99.96 percent while the text shows 99 percent — an explicit test pins that as intended.
  • findLastIndex in the flex fallback is fine for the stated Node >=18 engine (added in Node 18.0.0).
  • TUI changes check out against the components: the remembered line is clamped once on entry (no highlight drift on later deletes), main-menu index re-resolution accounts for the powerline-hidden Theme entry, InstallMenu maps the remembered manager onto enabled entries, and the inline ConfirmDialog indeed never renders its message prop, so printing Continue? outside it is correct.

CI is green and each fix carries a test that fails on the pre-fix code. Optional nit, summary only: the helper set duplicated in renderer-visible-output.test.ts and renderer-separator-collapse.test.ts could be extracted someday; not worth the churn now.

@axisrow
axisrow merged commit 1a8d21e into main Oct 15, 2026
6 checks passed
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.

30 visible bugs in the status line renderer, widgets and TUI

2 participants