Skip to content

fix(tui): degrade daxnuts image to 256-color on non-truecolor terminals#6362

Merged
jeremymcs merged 6 commits into
gsd-build:mainfrom
jeremymcs:tui-p0/06-daxnuts-256color
May 18, 2026
Merged

fix(tui): degrade daxnuts image to 256-color on non-truecolor terminals#6362
jeremymcs merged 6 commits into
gsd-build:mainfrom
jeremymcs:tui-p0/06-daxnuts-256color

Conversation

@jeremymcs

@jeremymcs jeremymcs commented May 18, 2026

Copy link
Copy Markdown
Contributor

Stack — TUI Phase 0 (7/8)

Builds on #6361. Stacked branch — diff also includes commits below it until they merge.

Problem

The daxnuts easter-egg image emitted 24-bit truecolor SGR sequences (\x1b[38;2;r;g;bm) unconditionally. On 256/16-color terminals (many containers, remote sessions, Apple Terminal) those render incorrectly.

Fix

Add an rgbTo256() quantizer. rgb() now emits 256-color SGR when theme.getColorMode() reports the terminal is not truecolor. (Theme tokens cannot represent an arbitrary 32×32 photo — graceful colour-depth degradation is the correct fix.)

Summary by CodeRabbit

  • Bug Fixes

    • Fixed text input corruption in certain editing scenarios
    • Corrected color output to respect terminal color capabilities
    • Improved nested list and filtered selection behavior
    • Enhanced Unicode character handling in search functionality
    • Fixed overlay dismissal not fully clearing from display
  • Documentation

    • Added comprehensive system audit with phased repair roadmap

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@jeremymcs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 31 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4b511d7-9dc8-4a7d-b0b9-8cb1c0b89e01

📥 Commits

Reviewing files that changed from the base of the PR and between a253e93 and 0b52798.

📒 Files selected for processing (7)
  • packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts
  • packages/pi-tui/src/__tests__/tui.test.ts
  • packages/pi-tui/src/components/input.ts
  • packages/pi-tui/src/components/markdown.ts
  • packages/pi-tui/src/components/select-list.ts
  • packages/pi-tui/src/fuzzy.ts
  • packages/pi-tui/src/tui.ts
📝 Walkthrough

Walkthrough

This PR documents a comprehensive audit of the TUI system's structural and correctness issues, then implements six Phase 0 "stop-the-bleeding" fixes: clamping selection indices to prevent out-of-bounds navigation, preventing negative-slice text corruption, using Unicode code points for string matching, emitting terminal-compatible color escapes, refactoring markdown list rendering to use structural metadata, and disabling render-caching short-circuits when overlays were present in the prior frame.

Changes

TUI Audit and Phase 0 Stop-the-Bleeding Fixes

Layer / File(s) Summary
Audit Document & Remediation Roadmap
docs/tui-audit.md
Comprehensive audit identifying P0 broken issues (stale render caching, selection underflow, terminal escape incompatibility, width overflow), P1 risks (timer leaks, paste edge cases, design fragmentation), P2 inconsistencies, and P3 cleanup. Defines five-phase repair strategy with explicit checkpoints and exit criteria. Recommends prioritizing overlay/render short-circuits (B2) and color escape fixes (B3) in Phase 0.
Selection & Input Boundary Safety
packages/pi-tui/src/components/select-list.ts, packages/pi-tui/src/components/input.ts
SelectList.handleInput guards against empty filtered items by short-circuiting navigation logic. Input.yankPop() clamps deletion start index to zero to prevent negative slicing and text corruption when yanking multiple times.
Unicode Code-Point String Matching
packages/pi-tui/src/fuzzy.ts
fuzzyMatch now iterates over Unicode code points via Array.from() instead of UTF-16 code units, fixing length and boundary checks for queries and text containing multi-byte characters, emoji, and other non-ASCII content.
Terminal Color Escape Compatibility
packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts
Adds rgbTo256() quantizer and updates rgb() helper to emit 256-color SGR sequences (...;5;<index>m) when theme.getColorMode() is not "truecolor", replacing unconditional truecolor escapes that were incompatible with limited-palette terminals.
Markdown List Structural Rendering
packages/pi-tui/src/components/markdown.ts
Introduces ListItemLine interface with nested flag. renderList() and renderListItem() now use this structural metadata to decide indentation/bullet placement, replacing fragile ANSI-color regex detection of nested list lines.
Overlay-Aware Render Caching
packages/pi-tui/src/tui.ts
Adds _lastFrameHadOverlays flag to track prior-frame overlay presence. Disables early-exit optimization that skips post-processing when base output is unchanged, forcing redraw when overlays were dismissed so stale overlay content does not remain on screen.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • gsd-build/gsd-2#6131: Both PRs modify TUI.doRender() render/post-processing logic for overlay handling, though this PR uses overlay-presence tracking while #6131 addresses viewport/flicker repaint behavior.
  • gsd-build/gsd-2#5478: Both PRs modify TUI.doRender()'s redraw/short-circuit logic to force redraws under specific prior-frame conditions.
  • gsd-build/gsd-2#6186: Both PRs address terminal display glitches in TUI render/write logic—this PR prevents stale overlay rendering via caching disablement, while the related PR wraps terminal writes with synchronized-output escape sequences.

Suggested labels

bug, needs-review

Poem

🐰 The audit hops deep through bugs both great and small,
Overlays persist, boundaries sprawl, UTF-16 fails,
Phase zero hops fast: clamp, redraw, code points true,
Color palettes dance, list nesting structured anew—
Stop the bleeding fast, the roadmap lights the way! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary fix: adding 256-color degradation for the daxnuts image on non-truecolor terminals, which is the final and main change in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

🟡 PR Risk Report — MEDIUM

Files changed 7
Systems affected 1
Overall risk 🟡 MEDIUM

Affected Systems

Risk System
🟡 medium TUI Components
File Breakdown
Risk File Systems
🟡 packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts TUI Components
🟡 packages/pi-tui/src/components/input.ts TUI Components
🟡 packages/pi-tui/src/components/markdown.ts TUI Components
🟡 packages/pi-tui/src/components/select-list.ts TUI Components
🟡 packages/pi-tui/src/fuzzy.ts TUI Components
🟡 packages/pi-tui/src/tui.ts TUI Components
packages/pi-tui/src/__tests__/tui.test.ts (unclassified)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/tui-audit.md`:
- Around line 158-159: Update the Phase 0 doc entry B4 to reflect the actual
remediation for daxnuts.ts: instead of instructing to replace RGB escapes with
theme.fg() tokens, state that daxnuts should perform RGB quantization/fallback
(emit truecolor when the terminal supports it, otherwise map to the best
256-color approximation) and preserve the existing quantization logic; reference
the file/name "daxnuts.ts" and the term "RGB escapes" in the doc so future
changes implement truecolor-first with 256-color fallback rather than replacing
colors with theme.fg().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 506057f7-6013-4a0c-b505-24487d9b4b20

📥 Commits

Reviewing files that changed from the base of the PR and between 498fe33 and a253e93.

📒 Files selected for processing (7)
  • docs/tui-audit.md
  • packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts
  • packages/pi-tui/src/components/input.ts
  • packages/pi-tui/src/components/markdown.ts
  • packages/pi-tui/src/components/select-list.ts
  • packages/pi-tui/src/fuzzy.ts
  • packages/pi-tui/src/tui.ts

Comment thread docs/tui-audit.md
Comment on lines +158 to +159
4. **B4** `daxnuts.ts` — replace RGB escapes with `theme.fg()` tokens.
5. **B5** `user-message.ts` — gate OSC 133 emission behind a terminal-capability

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Phase 0 item B4 currently recommends the wrong remediation.

This line says to replace RGB escapes with theme.fg() tokens, but the current fix strategy for daxnuts is RGB quantization/fallback (truecolor when available, 256-color otherwise). Please update this item to match that behavior so future work doesn’t regress image rendering.

Suggested doc patch
-4. **B4** `daxnuts.ts` — replace RGB escapes with `theme.fg()` tokens.
+4. **B4** `daxnuts.ts` — keep RGB source colors, but emit 256-color SGR via
+   quantization when terminal color mode is not truecolor.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
4. **B4** `daxnuts.ts` — replace RGB escapes with `theme.fg()` tokens.
5. **B5** `user-message.ts` — gate OSC 133 emission behind a terminal-capability
4. **B4** `daxnuts.ts` — keep RGB source colors, but emit 256-color SGR via
quantization when terminal color mode is not truecolor.
5. **B5** `user-message.ts` — gate OSC 133 emission behind a terminal-capability
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tui-audit.md` around lines 158 - 159, Update the Phase 0 doc entry B4 to
reflect the actual remediation for daxnuts.ts: instead of instructing to replace
RGB escapes with theme.fg() tokens, state that daxnuts should perform RGB
quantization/fallback (emit truecolor when the terminal supports it, otherwise
map to the best 256-color approximation) and preserve the existing quantization
logic; reference the file/name "daxnuts.ts" and the term "RGB escapes" in the
doc so future changes implement truecolor-first with 256-color fallback rather
than replacing colors with theme.fg().

jeremymcs added 6 commits May 18, 2026 08:45
The doRender() short-circuit skipped all post-processing when component
output was byte-identical to the previous frame. If the previous frame
composited an overlay onto the screen, an identical next frame with no
overlay would short-circuit and never erase it, leaving the overlay
stuck on screen.

Track _lastFrameHadOverlays and force a redraw when the previous frame
drew an overlay, even if base component output is unchanged.
When a filter matched no items, selectUp set selectedIndex to
filteredItems.length - 1 (i.e. -1), corrupting list state until the
next setFilter() reset it. Skip navigation and selection entirely when
the filtered list is empty; still honor cancel.
renderList() identified nested-list lines with the regex
/^\s+\x1b\[36m[-\d]/ — assuming the list bullet is always ANSI cyan.
Any theme with a different bullet color silently broke nested-list
indentation.

renderListItem() now returns ListItemLine[] with a 'nested' flag set on
lines produced by recursive renderList() calls, so renderList() keys off
structure instead of pattern-matching theme output.
yankPop() computed the deletion start as cursor - prevText.length with
no floor. A negative index passed to String.slice() counts from the end
of the string, so an underflow would silently corrupt the input value
rather than delete nothing. Clamp the start index to 0.
fuzzyMatch() indexed strings with [i], which yields UTF-16 code units.
Astral-plane characters (emoji, rare CJK) occupy two code units, so a
single such character was compared as two lone surrogates and never
matched. Iterate over Array.from() code-point arrays instead.
The daxnuts easter-egg image emitted 24-bit truecolor SGR sequences
(\x1b[38;2;r;g;bm) unconditionally. On 256/16-color terminals (many
containers, remote sessions, Apple Terminal) those render incorrectly.

Add an rgbTo256() quantizer; rgb() now emits 256-color SGR when
theme.getColorMode() reports the terminal is not truecolor.
@jeremymcs jeremymcs force-pushed the tui-p0/06-daxnuts-256color branch from a253e93 to 0b52798 Compare May 18, 2026 13:46
@jeremymcs jeremymcs merged commit 44bbdb3 into gsd-build:main May 18, 2026
11 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.

1 participant