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+).
- Terminal.app with this flag on bills each of those 2 cells.
- TUIs built on Ink resolve width via
string-width, which treats Ambiguous as 1 cell. - Net effect: a 2x column drift on every decorated line.
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.
defaults writewhile Terminal runs → silently lost.- AppleScript
(
tell app "Terminal" to set font name of settings set ...) → goes through Terminal, updates memory and disk. Reliable, but exposes only font name/size/antialias and colors — there is no AppleScript property forEastAsianAmbiguousWide. osascriptover SSH cannot reach a GUI Aqua session (observed: hangs).
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
EastAsianAmbiguousWide→falseon every profile, explicitly (deterministic; absent key was verified equivalent to false via theBasicprofile, but explicit is future-proof).- 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
- rdmbair13m5 was offline (
.localunresolvable; both Tailscale IPs 100.106.221.104 and 100.75.186.59 timed out). It has NOT been fixed and is not staged. Re-run when it is back. - Nerd Fonts were kept throughout, as preferred. Only the variant changed (Propo → Mono).