Skip to content

Glyph rendering: the command-line path has no fallback, and column width is measured with the wrong ruler #1292

Description

@santoshkumarradha

An audit of every non-ASCII character this product draws, prompted by users reporting that they see boxes or unrecognisable characters in some terminals.

What is already right, so nobody rebuilds it

The glyph system is deliberate and well built, and none of the work below should replace it.

  • internal/tui2/tokens holds one vocabulary with three spellings of every mark: a Nerd Font icon, a geometric Unicode shape, and one ASCII character. GlyphSet.Glyph (internal/tui2/tokens/glyphset.go:351) is the single door.
  • tokens.DetectGlyphSet (internal/tui2/tokens/detect.go:75) vetoes the rich tier on no TERM, TERM=dumb, the Linux console, Apple Terminal, a CJK locale and the legacy Windows console.
  • The design position is stated outright at internal/tui2/tokens/detect.go:6-13: no terminal reliably reports its font, so detection may only veto, never confirm. The rich default is a decision, not an oversight.
  • TestTierWidthParity (internal/tui2/tokens/parity_test.go:84) renders the same surfaces in both tiers and fails if a tier moves a column.
  • internal/tui3/cellwidth.go performs a real runtime capability handshake, reading DEC mode 2027 and switching the ruler only when the terminal answers.
  • internal/iconlaw/iconlaw_test.go:122 forbids any surface from spelling an icon itself.

1. The command-line path has no detection and no fallback

cmd/codeaf holds no palette, does no detection, and is absent from the icon law's surface list (internal/iconlaw/iconlaw_test.go:47-51). It writes non-ASCII characters straight to stdout.

cmd/codeaf/do.go:1615 writes U+23F3 HOURGLASS WITH FLOWING SAND. That character is on this product's own banned list at internal/tui2/tokens/glyph.go:568, recorded there as two cells. It is also default-emoji presentation, so most fonts draw it as a colour emoji. It ships to any terminal, including TERM=dumb in a C locale, and nothing catches it.

Also drawn raw from that package: U+2500, U+2014, U+00B7, U+2192, U+25B6, U+2713, U+2717, U+21BB, U+22EF.

Fix: correct the hourglass, then extend the icon law to cover cmd/codeaf so this class cannot return.

2. The correct width ruler is used in 2 places out of 561

internal/tui3/cellwidth.go exists because a rail sat two columns left on rows carrying emoji. Its own comment at cellwidth.go:54-57 says every layout decision that has to line up with a drawn column belongs on cellRuler.cells rather than on ansi.StringWidth.

cellRuler.cells has two callers: internal/tui3/task.go:5572 and internal/tui3/taskstable.go:78. ansi.StringWidth has 566 non-test call sites, 559 of them in internal/tui3, including every padding and truncation primitive: fitWidth (internal/tui3/render.go:4275), fitPad (internal/tui3/hop.go:1648), rightPad (hop.go:1661).

The file was written to fix this class and is wired into almost none of it.

3. Seventy-three drawn characters are East Asian Ambiguous

Measured as one cell, drawn as two on a terminal configured ambiguous-wide. The set includes every box-drawing character, every block element, every arrow, the middle dot separator used roughly two thousand times, and five of the six state marks: U+25CB, U+25D0, U+25A0, U+25B2, U+25CF.

Every column after such a character on that row shifts. Multiplied by depth in the work tree, where the connectors ├─, └─ and │ are all ambiguous (internal/tui3/task.go:2241-2247).

ansi.StringWidth is narrow-fixed for ambiguous runes and there is no East Asian width configuration anywhere in the tree.

4. The users most exposed are the ones the vetoes miss

Two independent mitigations exist and neither covers this case.

  • tokens.DetectGlyphSet drops the icon tier to plain on a CJK locale (internal/tui2/tokens/detect.go:90). It does not touch box drawing, separators or rails.
  • detectASCII (internal/tui3/styles.go:1199) drops the whole surface to ASCII, but only when the locale is not UTF-8.

So a user on ja_JP.UTF-8, the exact population most likely to run a terminal with ambiguous-wide on, keeps all 73 ambiguous characters in the layout. Neither gate fires.

5. The rich tier is never rendered in a terminal test

Every end-to-end suite forces the plain tier (internal/e2e/harness_test.go:154-159, questions_e2e_test.go:1306, secondwindow_e2e_test.go:206, tmux_test.go:132). The Nerd Font path has unit coverage and no terminal-level assertion. Nothing renders under an ambiguous-wide terminal at all; TestAmbiguousWidthFlags records the property without exercising it.

6. Smaller findings

  • U+276F is on the banned list at internal/tui2/tokens/glyph.go:570 as font-fragile, and is drawn at internal/tui3/task.go:2381.
  • internal/standing/standing.go:576 returns U+25B2, U+25CF, U+2219, U+25E6 as bare literals with no tier. internal/tui3 wraps it defensively; internal/session/tools_standing.go:1366 consumes it raw.
  • Characters with both text and emoji presentation are drawn with no variation selector, so each is a font-dependent one-or-two-cell outcome: U+25B6, U+2197, U+23FA, U+2699, U+2692, U+2696.
  • Likely absent from common monospace fonts, so a visible miss rather than a width bug: U+27F2, U+29C9, U+233E, U+2315 (used for both search and filter, so a miss is constant), U+21E1, U+25F7, U+22D4.

Suggested order

  1. The hourglass, and the icon law extended over cmd/codeaf.
  2. A decision on ambiguous width, with a test that renders under it. This is the one that silently bends columns for a whole class of users.
  3. Move the layout primitives onto cellRuler.cells, starting with fitPad and rightPad.
  4. One terminal-level test of the rich tier.

One thing that is not a bug

A tab draws the plain question mark for needs-you even when the rest of the screen is on the rich tier, so one screen can show two shapes for one meaning. That is deliberate: tabSignalGlyph (internal/tui3/tabsignal.go:199) takes the ASCII-safe character because it needs no fallback. Worth a decision rather than a fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:chatThe v3 surface a person sits in front of (internal/tui3)bugSomething the code does that it should notsev:papercutA wording, a hint, a small wrongness that costs a moment

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions