docs: N-3 tab-bar label design spec (and the defect measuring it turned up) - #100
Merged
Conversation
…ed up P4b excluded tab-bar labels with one line — "requires rebuilding tab width and hit computation off measured text" — and P4e repeated it. Measuring the tab bar first changes the shape of the work in both directions. The stated blocker is smaller than it reads: the hit computation is already downstream of the width. `tab_hit_rects`, `tab_close_hit_rects` and `tab_tearout_hit_rects` are published by the renderer into `ClientState` every frame and only read by `mouse.rs`, so there is no mirrored formula to keep in step — the P4c footer-link problem does not exist here. Underneath it is a defect that has nothing to do with typography: a tab is sized by `label.chars().count() * cell_w` and drawn by `UnicodeWidthChar`, so a full-width character takes two cells of pill it was never given. A Japanese title overflows its own tab, its click region is the narrow one, and the close and tear-out buttons land inside the drawn text. The spec carries the measurement, four decisions (measure don't count; truncate to a width budget; the Nerd Font process icon leaves the label string because it resolves through the terminal font's fallback chain rather than the chrome face; tabs stay content-sized — equal-width tabs and a scrolling strip are a layout redesign, not a typography change), six gates and a three-PR breakdown. Two open questions are left for the maintainer rather than assumed: the minimum tab width once there is no natural cell, and whether the `●` activity dot is safe to keep inside a chrome-measured run. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Coverage reportGenerated by |
…e a font Checking the claim before anyone builds on it: the spec said a Nerd Font glyph "resolves through the terminal font's fallback chain" while `measure_run` asks "the chrome face". There is no such split. `FontManager::chrome_attrs` builds from `self.family` out of the same `FontSystem` as `rasterize_char`; chrome and terminal differ only in size and weight, which is what P4b §5.3 already said. More decisively, `measure_run` and `add_run_verts` both consult `chrome_advance`, so whatever a font answers is used for the measurement and the draw alike — a run cannot overflow its own measurement. What survives is narrower and still worth D3: `rasterize_chrome_char` boxes a glyph to its advance without cropping to ink, so a Nerd Font icon that overhangs is clipped on the chrome path. D3 is rescoped to clipping, and the activity dot question in §7 becomes "what should a zero-advance glyph do", which is the only failure the shared-measurement argument leaves open. `G-width` is rephrased with it. A probe in the devcontainer returns the same advance (8.43 px at 14 px) for `A`, `あ`, `●`, `↗` and two Nerd Font private-use codepoints — one substituting face answering for everything — so a gate that assumed double-width CJK would have passed vacuously in CI. The gate now asserts that the sizing path and the drawing path agree, which holds whatever the font reports. §6 records the probe. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Coverage reportGenerated by |
This was referenced Aug 29, 2026
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.
Docs only — no code. Requesting sign-off on the two open questions in §7 before any implementation PR.
Why the spec exists
P4b excluded tab-bar labels with one line — "requires rebuilding tab width and hit computation off measured text" — and P4e repeated it. Measuring the tab bar first, as P4c–P4f each did, moves the work in both directions.
1. The stated blocker is smaller than it reads
The hit computation is already downstream of the width.
tab_hit_rects,tab_close_hit_rectsandtab_tearout_hit_rectsare published by the renderer intoClientStateevery frame (ui_verts.rs) and only read bymouse.rs:422, 503, 1396. There is no mirrored formula to keep in step — the P4c footer-link problem does not exist here. Correcting the width corrects all seven of its consumers at once.2. There is a live defect underneath it
A tab is sized by character count:
and drawn by display width (
add_string_vertsadvances viaUnicodeWidthChar, so a full-width char takes two cells).chars().count()buildビルドToday, with no ramp involved:
tab_hit_rectsrecords the narrow width, so clicking the text you are reading activates the next pane;The 24-character truncation cap has the same bug — 24 chars is 24 cells of Latin or 48 of Japanese.
This is not a regression the ramp would introduce; it is a defect the ramp forces into the open, because once glyphs advance by their own measured width no character count could ever be right.
Decisions in the spec
truncate_run_to_width, not to 24 characters.measure_runasks the chrome face, and a glyph present in one and absent in the other measures wrong in exactly the direction that overflows.Six gates (
G-width,G-fit,G-hit,G-single,G-truncate,G-icon) and a three-PR breakdown (N-3a pure width + tests → N-3b adopt in the builder → N-3c the icon run).G-hitlooks tautological and is worth writing precisely because it fails today.Open questions — §7, for the maintainer
label_w < cell_w * 2.0. Measured text has no natural cell, so the floor becomes either a fixed pixel minimum or "room for the ellipsis plus padding". Spec recommends the ellipsis rule (same ruletruncate_run_to_widthalready applies).●activity dot stay inside the label? It is the same font-fallback question D3 moves the process icon out of the string to avoid. More common glyph, so it probably measures — but "probably measures" is what the CJK defect is made of. Spec recommends measuring it once on a real font stack before deciding.🤖 Generated with Claude Code