Skip to content

feat(ui): adaptive colors, terminal width handling, and review fixes#23

Merged
josephgoksu merged 3 commits intomainfrom
improvements/tui-and-tux
Mar 8, 2026
Merged

feat(ui): adaptive colors, terminal width handling, and review fixes#23
josephgoksu merged 3 commits intomainfrom
improvements/tui-and-tux

Conversation

@josephgoksu
Copy link
Owner

@josephgoksu josephgoksu commented Mar 8, 2026

Summary

  • Migrate all hardcoded lipgloss.Color literals to lipgloss.AdaptiveColor for light/dark terminal support
  • Add terminal-width-aware column shrinking in table renderer via GetTerminalWidth()
  • Address all 6 review comments from PR feat(ui): enhance color styling with adaptive colors and improve term… #22:
    • Restore ANSI escape for spinner line clearing (was hardcoded 80-char)
    • Add post-clamp overflow redistribution in table column sizing
    • Extract GetTerminalWidthFor(*os.File) to support non-stdout streams
    • Fix badge foreground contrast for light-mode terminals
    • Remove unused ColorTableRowEven/ColorTableRowOdd declarations
    • Guard overhead formula against empty widths slice
  • Move Supported Models section up in README for better visibility

Test plan

  • go build ./internal/ui/ passes
  • go test ./internal/ui/ — 24 tests pass
  • Verify spinner clears cleanly on wide terminals (>80 cols)
  • Verify table columns shrink correctly when terminal is narrow
  • Verify badge text is readable on light-mode terminals

Greptile Summary

This PR delivers a broad UI quality pass: migrating ~40 hardcoded lipgloss.Color literals to lipgloss.AdaptiveColor, adding terminal-width-aware column shrinking in the table renderer, and addressing six review items from PR #22. The architecture is sound and the direction is correct — the TerminalColor interface widening in Panel and scoreToColor is the right way to expose adaptive colors through public APIs.

Key issues found:

  • Critical regression in CategoryBadge (light mode): The badge foreground is set to lipgloss.AdaptiveColor{Light: "0", Dark: "0"} — black in both modes. In light-mode terminals, most badge backgrounds are dark or medium-dark (e.g., ColorBlue.Light = "27" = dark blue, ColorSuccess.Light = "28" = dark green, ColorWarning.Light = "172" = dark orange), making black text on those backgrounds essentially unreadable. The fix should use {Light: "255", Dark: "0"} — white on dark backgrounds in light mode, black on bright backgrounds in dark mode.
  • "note" badge is invisible in light mode: ColorText (whose purpose is text foreground) has Light = "235" (#262626 = near-black). Using it as a badge background combined with a black "0" foreground creates near-zero contrast. ColorText should not be a badge background color at all.
  • padRight uses byte-length len(s) instead of visual width: Pre-existing, but this is now a sharper issue since the new proportional shrinking produces tight column widths where off-by-one misalignments become visible.
  • Hardcoded 76-char wrap in explain.go: The new GetTerminalWidth() utility was not applied to the explanation wrap width — a minor missed opportunity from the same PR.

Confidence Score: 3/5

  • Safe to merge for dark-mode users; light-mode terminal users will see multiple badge contrast regressions that directly undermine the PR's stated goal.
  • The table width logic and spinner/panel fixes are solid. The AdaptiveColor migration is well-structured. However, the badge foreground fix is broken — using {Light: "0", Dark: "0"} makes many badge types unreadable in light mode. The "note" badge (black on near-black) is the most severe case. These are user-visible regressions in the specific feature this PR claims to fix.
  • internal/ui/styles.go — specifically the CategoryBadge function's foreground color and the "note" key's mapping to ColorText as a background.

Important Files Changed

Filename Overview
internal/ui/styles.go Migrates all color constants to AdaptiveColor; removes unused ColorTableRowEven/Odd; adds ColorPurple/ColorBarEmpty. Critical bug: badge foreground is always "0" (black) in both light and dark modes, and ColorText.Light="235" (near-black) is misused as the "note" badge background — both causing near-zero contrast in light mode.
internal/ui/table.go Adds terminal-width-aware column shrinking via GetTerminalWidth(), post-clamp overflow redistribution, and GetTerminalWidthFor(*os.File) for non-stdout streams. Logic is sound; padRight uses byte-length len(s) which is a pre-existing issue surfaced more acutely with tight column sizing.
internal/ui/context_view.go Replaces all hardcoded lipgloss.Color literals with named constants and AdaptiveColor pairs; scoreToColor return type correctly updated to lipgloss.TerminalColor. Clean refactor with no functional issues.
internal/ui/explain.go Replaces raw ANSI StyleBold with lipgloss (respects NO_COLOR); adds lipgloss import. Minor: hardcoded 76-char wrap width not updated to use new GetTerminalWidth().
internal/ui/utils.go Updates Panel.BorderColor and WithBorderColor parameter type from lipgloss.Color to lipgloss.TerminalColor to accept AdaptiveColor values. Straightforward, correct interface widening.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Table.ColumnWidths] --> B{MaxWidth > 0?}
    B -- Yes --> C[Apply per-column MaxWidth cap]
    B -- No --> D[GetTerminalWidth via os.Stdout]
    D --> E[Compute overhead: 1 + len-1 * 2]
    E --> F{total > available?}
    F -- No --> G[Return widths as-is]
    F -- Yes --> H[Proportional shrink with min floor = 4]
    H --> I{Post-clamp: postTotal > available?}
    I -- Yes --> J[Find widest column]
    J --> K{widest > 4?}
    K -- No --> L[Break: table may overflow]
    K -- Yes --> M[Shrink widest by excess, floor 4]
    M --> I
    I -- No --> G

    subgraph Color System
        N[lipgloss.AdaptiveColor] --> O{Terminal Background}
        O -- Light --> P[Light value]
        O -- Dark --> Q[Dark value]
        P --> R[CategoryBadge foreground = 0 black]
        Q --> R
        R --> S{Badge BG in Light mode}
        S -- ColorText 235 near-black --> T[⚠ Near-zero contrast]
        S -- ColorBlue 27 dark blue --> T
        S -- ColorSuccess 28 dark green --> T
    end
Loading

Comments Outside Diff (2)

  1. internal/ui/table.go, line 154-158 (link)

    padRight uses byte length, not visual (rune) width

    len(s) returns byte count, not visual column width. For multi-byte Unicode characters (e.g., the "…" ellipsis added during truncation, or any non-ASCII cell content), len(s) will exceed the visual width, causing padRight to return early and produce misaligned columns.

    Concrete example: after truncation, val ends in "…" (3 UTF-8 bytes, 1 visual column). len(val) = widths[i]+2, so padRight correctly skips padding in that case — but this depends on accidentally correct math. For other multi-byte characters in original cell values (e.g., emoji), column alignment would break silently.

    Consider using lipgloss.Width(s) or uniseg.StringWidth(s) for accurate visual width measurements. This is a pre-existing limitation surfaced more sharply now that ColumnWidths performs terminal-width-aware shrinking (making tight column sizing more common).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/ui/table.go
    Line: 154-158
    
    Comment:
    **`padRight` uses byte length, not visual (rune) width**
    
    `len(s)` returns byte count, not visual column width. For multi-byte Unicode characters (e.g., the `"…"` ellipsis added during truncation, or any non-ASCII cell content), `len(s)` will exceed the visual width, causing `padRight` to return early and produce misaligned columns.
    
    Concrete example: after truncation, `val` ends in `"…"` (3 UTF-8 bytes, 1 visual column). `len(val)` = `widths[i]+2`, so `padRight` correctly skips padding in that case — but this depends on accidentally correct math. For other multi-byte characters in original cell values (e.g., emoji), column alignment would break silently.
    
    Consider using `lipgloss.Width(s)` or `uniseg.StringWidth(s)` for accurate visual width measurements. This is a pre-existing limitation surfaced more sharply now that `ColumnWidths` performs terminal-width-aware shrinking (making tight column sizing more common).
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. internal/ui/explain.go, line 119 (link)

    Hardcoded 76-char wrap width not updated to use GetTerminalWidth()

    This PR adds GetTerminalWidth() specifically for terminal-width-aware layout, but RenderExplainExplanation still wraps at a hardcoded 76 characters. On narrow terminals (<80 cols) this can cause text to overflow, and on wide terminals it wastes readable width.

    The -4 accounts for the " " (3-space) prefix applied to each line below.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/ui/explain.go
    Line: 119
    
    Comment:
    **Hardcoded 76-char wrap width not updated to use `GetTerminalWidth()`**
    
    This PR adds `GetTerminalWidth()` specifically for terminal-width-aware layout, but `RenderExplainExplanation` still wraps at a hardcoded 76 characters. On narrow terminals (<80 cols) this can cause text to overflow, and on wide terminals it wastes readable width.
    
    
    
    The `-4` accounts for the `"   "` (3-space) prefix applied to each line below.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Last reviewed commit: 3a83f9b

Greptile also left 2 inline comments on this PR.

…ling

- Restore ANSI escape for spinner line clearing instead of hardcoded 80-char width
- Add post-clamp overflow redistribution in table column sizing
- Extract GetTerminalWidthFor(f *os.File) to support non-stdout streams
- Fix badge foreground contrast for light-mode terminals (white -> black)
- Remove unused ColorTableRowEven/ColorTableRowOdd declarations
- Guard overhead formula against empty widths slice
- Move Supported Models section up in README
- Badge foreground: Light:255 (white) for dark backgrounds, Dark:0 (black) for bright backgrounds
- Replace ColorText with dedicated neutral gray for "note" badge background
@josephgoksu josephgoksu merged commit bdfcc5f into main Mar 8, 2026
8 checks passed
@josephgoksu josephgoksu deleted the improvements/tui-and-tux branch March 8, 2026 21:32
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.

1 participant