fix(renderer): paint reverse-video cell background with default colors#5
Merged
Merged
Conversation
A reverse-video cell (SGR 7) whose foreground is the default color rendered with no background fill. `renderCellBackground` treated `fgIsDefault` as "use theme background" and skipped the paint, so the cell showed the dark theme background instead of the inverted (theme.foreground) block. `renderCellText` had the mirror-image bug, falling back to theme.foreground instead of the swapped theme.background. This is exactly how reverse-video TUIs — and program-drawn block cursors like Claude Code's, which hides the hardware cursor (DECTCEM) and draws its own inverted cell — render their cursor. The result was an empty/dark cursor block in ghostty-web where native ghostty shows a solid block. Inverse cells now always paint their background (theme.foreground when the source foreground is default); inverse text falls back to theme.background. Adds an e2e regression test: a default-color reverse-video run must paint a solid light background. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Problem
Reverse-video cells (SGR
7) with a default foreground rendered with no background fill — the cell showed the darktheme.backgroundinstead of a solid inverted block.Most visible as a disappearing/empty cursor: TUIs that draw their own cursor by hiding the hardware cursor (
?25l) and inverting a cell — e.g. Claude Code — rendered an empty/dark block in ghostty-web where native ghostty shows a solid one.Root cause
In
renderCellBackground:For an inverse cell the effective background is the cell's foreground. When that foreground is default, the effective background should resolve to
theme.foreground— but the code treatedfgIsDefaultas "use theme background" and skipped the fill entirely, letting the dark line-leveltheme.backgroundshow through.renderCellTexthad the mirror-image bug: it fell back totheme.foregroundinstead of the swappedtheme.background.Fix
theme.foregroundwhen the source foreground is default, otherwise the foreground RGB.theme.background(the swapped theme color), so the glyph stays legible against the inverted block.Test
01-rendering: a default-color reverse-video run must paint a solid light background (sampled via canvasgetImageData). The suite had no reverse-video-with-default-colors coverage.rendererunit tests,typecheck,lint(biome) andfmt(prettier) all clean. The full WASM-backed unit suite needs CI — it fetchesghostty-vt.wasmover HTTP, which isn't reachable in my sandbox.How it was found
Isolated from a downstream app where Claude Code's cursor wasn't rendering a full block. Bisected through the server-side PTY host (passes bytes through unchanged) and Claude's output (emits no DECSCUSR — it relies on the default cursor, then hides it and draws its own reverse-video cell), which localized the defect to ghostty-web's inverse-cell background fill. Confirmed the fix live by patching the built dist into the app.
🤖 Generated with Claude Code