Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

419. The slot size is named where the icon is built

Date: 2026-09-19

Status

Accepted. Closes the TODO entry left by ADR-0143 — “nothing says so at the door” — and is the first record to apply ADR-0394’s rule to a diagnostic written after it.

Context

An Icon is a path built at a size. Icon.of multiplies Lucide’s 24×24 coordinates by size / 24 at parse time and scales the stroke with them, so there is no transform at draw time and no way to change the answer afterwards (ADR-0043): “drawing the same symbol at two sizes is two Icons”. ADR-0143 then decided that an icon larger than the box it is given is centred in it, which turned a misalignment into a mere overhang. Its own consequence is the entry this record closes:

The toolkit cannot resize an Icon, so an application that wants its menu icons to fit the column builds them at 16 — which is what §3 sizes a glyph at, and what the showcase should have been doing.

The interesting part is not the overflow. It is that there is exactly one slot in the catalog this can happen in. Surveying every widget that places an icon — button, chip, tab, crumb, menu heading, timeline marker, segmented option, link marker, image error glyph — every one of them does

content.add(Box.icon(icon, style.color()));

with no .style(style) on that box. Box.icon sizes the box to the glyph, so there is no slot and 20 or 24 is ordinary. Only ItemLead applies the style after the icon:

return Box.icon(icon, style.color()).style(style);

and Box.style assigns style.width() and style.height() wholesale, so item-lead { width: 16px; height: 16px } wins and the glyph overhangs a box it did not size. (The comment on that line claimed the override went the other way. It did not, and that is a small part of why an oversized glyph looked like nobody’s decision rather than a wrong number.)

So the entry reduces to: the number 16 was written in controls.css, and the person who has to choose it is writing Java and has no reason to open a stylesheet. Icons — the registry an application hands its icons to — knew nothing about sizes at all; its only diagnostic was for an unregistered name.

Why the obvious diagnostic is the one ADR-0394 forbids

The reflex is a warning when a glyph is bigger than its slot. ADR-0394 spent a whole record on why that class of thing is worthless, and this case fails its sharpest test in advance: the showcase builds its menu icons at 20, in a 16px column, on purpose, and menu-icon-oversized.png is a golden that pins that drawing as correct. A WARN would therefore fire on the toolkit’s own demo, on every row of every menu, once per paint, and claim a defect where ADR-0143 recorded a deliberate decision. “A WARN claims something is broken; nothing is.”

Nor can the check live at the factory. Icon.bundled("palette", 20) is unimpeachable — it is what a button wants — and the factory has no idea which slot, if any, the icon is headed for.

Decision

Three things, and the diagnostic is the smallest of them.

1. Icons.SLOT names the number, in the class an application already goes through. Sixteen, documented as what the catalog’s fixed slots are and explicitly not as a maximum, because a button’s icon sizes the button’s own box and 20 and 24 are right there.

2. Icons.bind(String) makes the correct call the shortest one. icons.bind("folder") registers Icon.bundled("folder", Icons.SLOT). This is the half that actually closes the entry: a constant nobody finds is a stylesheet with extra steps, and the only thing that reliably competes with the shortest call is a shorter correct one. Icons.resolve’s unknown-name message points at it too.

3. ItemLead says so at debug, once per (name, size, column) triple. The repo’s established dedup shape — a ConcurrentHashMap.newKeySet(), a REPORT_LIMIT of 256 past which everything goes through rather than falling silent, and reportedOverhang() / forgetReportedOverhang() so a test can assert once rather than merely at all, there being no appender on the classpath. The message names the icon, both numbers, ADR-0043’s reason, and the fix.

It fires only against an explicit Length.Points width. A percentage or an auto column has no number to be bigger than, and inventing one is how a diagnostic starts reporting arithmetic. There is no tolerance beyond an epsilon for the double: unlike ADR-0394’s overflow watch, which was summing insets and found 92% of its reports inside two pixels, these are two numbers a human typed.

The count, because ADR-0394 asks for it

Every Icon that reaches a menu Item in this repository: MenuGoldenTest builds two at 16 and one at 20; ItemAlignmentTest builds one at 20; the showcase’s AppMenu uses the 20 from Showcase.ICON_SIZE. Everything else that calls Icon.bundled at 20, 24 or 48 goes to a button, a tray raster, or a test of the path scaler, and never reaches a slot.

So across :widgets:test this diagnostic produces one report — palette/20.0/16.0 — reached from two test classes, plus the same one triple in :example:test. Not 688 shading into arithmetic: one, and it is exactly the case ADR-0143 named and the showcase never fixed. A distribution of one is not a distribution, which is itself the argument that the check is measuring what it meant to.

Consequences

The showcase still reports. Deliberately, and this is the test that says the diagnostic is not tuned to be quiet. ADR-0143 decided the drawing is acceptable and a golden pins it; the showcase is not changed here, because changing it would delete the one true positive and leave a check that fires on nothing.

debug means it is off by default, and that is the cost. Nobody is told unless they go looking. ADR-0394 took the same trade for the nested-viewport notice and stated it the same way: the failure mode is mild — a centred, slightly large glyph — and the false positive would have been constant. What is bought is that when somebody does ask “why is this icon large”, the answer is one log line away instead of a bisect through controls.css.

Icons.SLOT and controls.css can drift, so a test pins them together. ItemLeadOverhangTest renders a menu the way Menus builds one and asserts the item-lead box is Length.points(Icons.SLOT) square. If somebody widens the column and leaves the constant, the door starts handing out the wrong number and this is what says so.

This does nothing for the other nine slots, because there is nothing to do. They size to their icon. If one ever gains a fixed width in a stylesheet it will overflow silently, and the check will have to move somewhere both it and item-lead can see — most likely Box.icon, which is where BoxInk.of already computes the overhang for culling and says nothing about it.

ItemLead.render does one comparison per iconed row per paint. A Length pattern match against a double, and the set is touched only when it already overflowed. The rows that do not overflow — which is the intended world — pay one branch.

A stale comment was corrected on the way. ItemLead claimed Box.icon’s size overrode the column’s. It is the other way round, it has been since the line was written, and it only ever looked harmless because both numbers were 16.