Fleet changelogs · dev.ecs0.net
rdmbair15m5-changelog-20260821-0212-terminal-nerdfont-cellwidth-fix-fleetwide

Terminal.app TUI rendering corruption — root-caused and fixed fleet-wide

Summary: Claude Code / Codex rendered with overlapping status lines, gapped box-drawing rules and ghost text after resize on Apple Terminal.app. Root cause was NOT the Nerd Fonts. It was a cell-width units disagreement (EastAsianAmbiguousWide) plus use of the Nerd Font Propo (proportional) build. Both fixed across the fleet; Nerd Fonts retained as preferred.

Root cause 1 — EastAsianAmbiguousWide = true (primary)

UAX #11 classifies as Ambiguous width: box drawing U+2500 U+2502 U+2501, blocks U+2588 U+2581, arrows U+2190, U+25B6, U+2605, middot U+00B7, Greek, and the entire Nerd Font Private Use Area (U+E000–U+F8FF, U+F0000+).

Measured with a DSR (ESC[6n) cursor-position probe inside Terminal.app:

String (10 chars) TUI assumes Terminal used
AAAAAAAAAA 10 10 OK
────────── U+2500 10 20 MISMATCH
││││││││││ U+2502 10 20 MISMATCH
██████████ U+2588 10 20 MISMATCH
·········· U+00B7 10 20 MISMATCH
←←←←←←←←←← U+2190 10 20 MISMATCH
▶▶▶▶▶▶▶▶▶▶ U+25B6 10 20 MISMATCH
★★★★★★★★★★ U+2605 10 20 MISMATCH
PUA U+E0B0 / U+F07B / U+F0219 10 20 MISMATCH

That single 2x drift explains every artifact at once: the gapped — — — — rules (2 cells reserved, 1-cell glyph drawn), the wrapped/ghosted status line, and why resizing worsened it (a wrong column count only becomes visible when a redraw must re-wrap).

Root cause 2 — Nerd Font Propo (NFP) variant

Active font was JetBrainsMonoNFP-Regular. Propo is the proportional build Nerd Fonts ships for editors/UI, not for cell grids. Measured advance widths in cell units (1.000 = one cell):

Variant M ─ █ U+E0B0 U+F07B U+F0219
NFP (Propo) — was active 1.000 1.000 1.000 0.998 1.538 1.113
NF (plain) 1.000 1.000 1.000 1.000 1.000 1.000
NFM (Mono) — now active 1.000 1.000 1.000 1.000 1.000 1.000

Critical mechanism — why defaults write does NOT work here

Terminal.app holds its settings in memory and rewrites com.apple.Terminal.plist from that memory during normal operation — verified: a defaults write landed on disk, was ignored by the running app, and had reverted to the old value before a deliberate flush was even triggered.

Therefore the only reliable route for the width flag is the GUI checkbox, or editing the plist while Terminal is not running. Hence --stop-terminal.

Changes applied

  1. EastAsianAmbiguousWide → false on every profile, explicitly (deterministic; absent key was verified equivalent to false via the Basic profile, but explicit is future-proof).
  2. Font PostScript name remapped *NFP-* → *NFM-*, same typeface and size. Only profiles whose NFM counterpart is actually installed are touched; others are reported and skipped.

Script: ~/scripts/terminal_render_fix.zsh v1.0.0 — dry-run by default, refuses to apply while Terminal runs unless --stop-terminal, timestamped plist backup, self-verifying, logs to ~/scripts/terminal_render_fix_<ts>.log.

Backups: ~/scripts/terminal_render_fix_backups/com.apple.Terminal.plist.<ts> per host, plus ~/Library/Preferences/com.apple.Terminal.plist.bak-<ts> on rdmbair15m5.

Undo

Quit Terminal.app, restore the backup, relaunch:

cp ~/scripts/terminal_render_fix_backups/com.apple.Terminal.plist.<ts> \
   ~/Library/Preferences/com.apple.Terminal.plist && killall cfprefsd

Verification

~/scripts/width_probe.zsh re-runs the DSR probe and prints OK/MISMATCH per character class. Post-fix on rdmbair15m5 active profile: 11/11 OK.

Outstanding owner actions