coder / coder/ghostty-web

Powerline prompts render as smudged/banded segments: font-rasterized separators + unclamped fallback glyphs

Open
#184 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
2.8k
Forks
172
PR merge metrics
No merged PRs in 30d

Description

## Symptom

Embedding ghostty-web (0.4.0) with a starship Powerline prompt (segment backgrounds, fading-arrow separators `U+E0B0`, rounded caps `U+E0B4/E0B6`, Nerd icons), the prompt renders as soft "watercolor" blobs with ragged edges and horizontal banding inside segments — while plain ASCII output stays crisp. Reproduced on a 2x display at 13px with JetBrains Mono + Symbols Nerd Font Mono.

## Root causes (two, compounding)

**1. Powerline separators are font-rasterized.**
`renderCellText` draws `U+E0B0-U+E0B7` via `fillText` like any glyph. Two physical problems:
- the diagonal edges keep anti-aliasing fringe on both sides, which visually smears against adjacent solid segment backgrounds;
- the glyph's ink height is smaller than the cell height (font ascent/descent box ≠ cell box), so the neighboring background bleeds through above/below every separator — with stacked "fading arrow" themes this shows as banding stripes.

This is why WezTerm and xterm.js (`customGlyphs`) both special-case these codepoints and draw them as exact vector geometry covering the cell box, bypassing the font entirely.

**2. Oversized fallback glyphs are not clamped to their cells.**
Cell metrics come from the primary font's `M`, but glyphs served by fallback fonts carry their own advance — e.g. Symbols Nerd Font Mono icons advance 1em (13px = 1.67 cells at a 7.8px cell). `fillText` never scales, so the ink bleeds into neighbor cells. Since rows repaint by background fill (no `clearRect`), the cursor-row repaint shaves that overhanging ink asymmetrically every blink — accumulating the ragged artifacts.

`renderCellText` already computes the cell budget (`cellWidth = metrics.width * cell.width`) but doesn't pass it to `fillText`, whose 4th argument (`maxWidth`) does exactly this clamping.

## Fix

PR incoming that (a) draws `U+E0B0-U+E0B7` as vector paths (solid triangles / angle outlines / half-circles) using the cell's fillStyle, and (b) passes `cellWidth` as `fillText`'s `maxWidth` for everything else. Verified pixel-level against WezTerm rendering the same prompt.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.