Skip to content

feat(ui): one type scale, and one estimate of how wide a character is - #80

Merged
youhide merged 1 commit into
mainfrom
t2-type-scale
Aug 21, 2026
Merged

feat(ui): one type scale, and one estimate of how wide a character is#80
youhide merged 1 commit into
mainfrom
t2-type-scale

Conversation

@youhide

@youhide youhide commented Aug 21, 2026

Copy link
Copy Markdown
Owner

T2. Eight sizes across 249 call sites, and six file-local constants duplicating each other — ITEM_SIZE = 13.0 written out three separate times in three widgets, CODE_SIZE = 12.0 three more. All of them are now steps on a named scale.

Six steps, named for the job

MICRO LABEL CODE BODY LEAD DISPLAY. LABEL carries structure — section headings and secondary text, the commonest size in the interface — and BODY carries content.

The guard is split by scale, not by file

The two scales migrate at different speeds. A widget can be entirely on the type scale while its paddings are still hand-picked, and one list for both would force converting everything about a file at once or nothing at all.

So ON_THE_LAYOUT_SCALE and ON_THE_TYPE_SCALE are separate lists over separate property sets, and a test pins that one guard does not see the other's numbers.

Two sizes deliberately left out — and why that is a finding

14.0 and 16.0 are each a third spelling of "the title of something", a pixel away from LEAD.

The settings panel shows why that is a hierarchy question rather than a rename:

its header is 14.0 while the option labels beneath it are 15.0the heading is smaller than what it heads.

Deciding what those sites should say is a visual decision, not a substitution, so it is not in this change. The six files holding a 14 or a 16 stay off the type list until it is made. That is also why this PR has no visual change at all: every size that moved kept its value, and the ones that could not are excluded rather than approximated.

One estimate of character width

The canvas has no cheap text measurement, so format::truncate and the graph's badge_width both estimate. They estimated differently — 0.62 against 0.58, for the same font.

The smaller one is the graph's, which is the direction that hurts: a badge too narrow for a label that truncation had already called short enough. And badge_width's own doc comment warns that "two copies of this arithmetic would drift" — the ratio was quietly the second copy.

Pinned by a compile-time assertion rather than a test:

const _: () = assert!(CHAR_WIDTH >= crate::format::CHAR_WIDTH_RATIO);

Clippy is right that a runtime assertion over two constants says nothing, and a wrong value here should fail the build, not a run. I wrote it as a test first, clippy caught it, and the const assertion is the better answer.

Sabotage — three, all confirmed

Sabotage What broke
a raw .size(11.0) returns to sidebar.rs migrated_files_carry_no_raw_type_sizes
a raw padding returns to tabs.rs migrated_files_carry_no_raw_layout_numbers, proving the split lists still work
the graph's ratio goes back to 0.58 compile errorevaluation panicked: assertion failed

Docs

UI_SPEC.md's Layout scale section gains a Type subsection: the six steps, the measurement that motivated them, the 14/16 hierarchy problem stated plainly, and the character-width reconciliation.

Gate

cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features --locked -- -D warnings, cargo test --workspace --all-features --locked — 895 tests, all green.

Eight sizes across 249 call sites, and six file-local constants duplicating
each other: `ITEM_SIZE = 13.0` written out three separate times in three
widgets, `CODE_SIZE = 12.0` three more. Every one of them is now a step on a
named scale, and the guard added with the layout scale grows a second list to
keep them there.

The guard is split by scale rather than by file, because the two migrate at
different speeds. A widget can be entirely on the type scale while its paddings
are still hand-picked, and one list for both would force converting everything
about a file at once or nothing at all.

Two sizes are deliberately left out. `14.0` and `16.0` are each a third spelling
of "the title of something", a pixel from `LEAD`, and the settings panel shows
why that is not a naming problem: its header is 14 while the option labels
beneath it are 15, so the heading is smaller than what it heads. Deciding what
those sites should say is a question about hierarchy, so they keep their
literals and the six files holding them stay off the type list until it is
answered.

The character-width estimate is reconciled rather than documented. The canvas
has no cheap text measurement, so `format::truncate` and the graph's
`badge_width` both estimate — and they estimated differently, 0.62 against 0.58,
for the same font. The smaller one is the graph's, which is the direction that
clips: a badge too narrow for a label truncation had already called short
enough. `badge_width`'s own doc comment warns that two copies of its arithmetic
would drift, and the ratio was quietly the second copy.

That one is pinned by a `const _: () = assert!(…)` rather than a test. Both
sides are constants, so clippy is right that a runtime assertion on them says
nothing — and a wrong value should fail the build rather than a run. Putting
0.58 back is a compile error.

No visual change: every size that moved onto the scale kept its value, which is
why the six files that could not are excluded rather than approximated.
@youhide
youhide merged commit d727e27 into main Aug 21, 2026
7 checks passed
@youhide
youhide deleted the t2-type-scale branch August 21, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant