Skip to content

feat(ui): a layout scale, and a guard that keeps files on it - #79

Merged
youhide merged 1 commit into
mainfrom
t1-layout-scale
Aug 21, 2026
Merged

feat(ui): a layout scale, and a guard that keeps files on it#79
youhide merged 1 commit into
mainfrom
t1-layout-scale

Conversation

@youhide

@youhide youhide commented Aug 21, 2026

Copy link
Copy Markdown
Owner

T1 — the foundation the rest of the UI plan stands on.

The measurement

Colour has been a system since M1: twenty named slots, two hand-authored palettes, contrast asserted over both. Everything that is not colour was not a system at all.

Distinct values, before
Padding::from([v,h]) pairs 25 across 68 sites
.spacing(n) 12
Border radii 6

[3,6], [3,8], [3,10] and [3,12] were all in use at once, each picked at its own call site.

That is not a stylistic complaint. Spacing was the crate's largest blind spot — any number could change and all 890 tests still passed.

The guard is the actual deliverable

The scale is eight steps and three radii; that part is easy. What makes the rest of this migration possible is the guard:

metrics.rs holds the list of files already on the scale, reads each one, and fails if a raw number has come back on a line that sets padding, spacing or a radius. A file joins that list in the change that converts it and cannot regress afterwards — so the migration is testable one file at a time instead of trusted in bulk.

It has its own test rather than being trusted because it happens to pass over the files it is pointed at. It must tell .padding([3, 12]) from .padding(metrics::SNUG), and both from radius: iced::Border::default().radius — a line that chooses no value is not choosing a wrong one.

Same shape as the bidirectional shortcut-reference test and xtask bench-check: an assertion about the source, for an invariant no ordinary test can reach.

What the scale deliberately does not govern

A component's own dimensions. TAB_WIDTH is a decision about how much room a repository name needs; it belongs beside the tab as a named local constant, not as a step on a shared ladder. The guard leaves it alone, and a test pins that it does.

Only the numbers a reader compares without meaning to are shared.

Proof, on the smallest file

widget/tabs.rs — 176 lines, eleven layout literals, a representative spread of padding, spacing and both radius spellings.

Two numbers move by a pixel: a 5 that no other padding in the crate used becomes the 4 that many do. The tab bar only appears with two repositories open, so none of the four committed screenshots show it — there is nothing to retake, and I am not attaching a before/after of a one-pixel change to a bar that is off screen in every one of them.

Sabotage — four, all confirmed

Sabotage Test it broke
a literal returns to a migrated file migrated_files_carry_no_raw_layout_numbers
a raw radius returns the same
two scale steps collide the_scale_climbs_and_never_repeats_itself
a step becomes half a pixel every_step_is_a_whole_number_of_pixels

The half-pixel one is worth its keep: a half-pixel gap renders as zero or one depending where it lands, which is how a scale quietly stops being one.

Docs

UI_SPEC.md gains a Layout scale section beside Theming — the measurement, the eight steps, the boundary against component dimensions, and how the guard works. It also states plainly that type sizes are not on a scale yet, with the ITEM_SIZE = 13.0-three-times duplication named, since that is the next step.

Gate

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

Colour has been a system since M1 — twenty named slots, two hand-authored
palettes, contrast asserted over both. Everything that is not colour was not a
system at all.

Measured before writing anything: 25 distinct `Padding` pairs across 68 sites,
twelve `spacing` values, six border radii. `[3,6]`, `[3,8]`, `[3,10]` and
`[3,12]` were all in use, each chosen a call site at a time. That is not a
stylistic complaint — values picked one at a time drift, and nothing in the
suite noticed. Spacing was the crate's largest blind spot: any number could
change and all 890 tests still passed.

So `metrics.rs` carries eight steps and three radii, and — more importantly — a
guard. It holds the list of files already on the scale and reads each one,
failing if a raw number has come back on a line that sets padding, spacing or a
radius. A file joins that list in the change that converts it and cannot regress
afterwards, which is what makes the rest of this migration testable one file at
a time rather than trusted in bulk.

The guard has its own test rather than being trusted because it passes over the
files it is pointed at. It has to tell `.padding([3, 12])` from
`.padding(metrics::SNUG)`, and both from `radius: iced::Border::default().radius`
— a line that chooses no value is not choosing a wrong one.

What the scale governs is deliberately narrow: space between things and corner
radii, the numbers a reader compares without meaning to. A component's own
dimensions stay local. `TAB_WIDTH` is a decision about how much room a
repository name needs and reads better beside the tab than as a step on a shared
ladder, so the guard leaves it alone.

`widget/tabs.rs` is converted as the proof, being the smallest file with a
representative spread. Two of its numbers move by a pixel: a 5 that no other
padding in the crate used becomes the 4 that many do. The tab bar only appears
with two repositories open, so none of the committed screenshots show it.

Type sizes are not on a scale yet — six file-local constants duplicate each
other, `ITEM_SIZE = 13.0` three times over — and that is its own step.
@youhide
youhide merged commit 6db2bdf into main Aug 21, 2026
7 checks passed
@youhide
youhide deleted the t1-layout-scale branch August 21, 2026 01:33
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