Skip to contentWolf-Rayet

Decision records

ADR-0045 A turned corner says you may act, and costs the budget nothing

Accepted2026-09-05Phase 4, reopened

#Context

The spatial tier generates three scales — type, space, border — plus the stacking and target constants. There is no radius axis, and there never has been. Every rectangular perimeter in the component set has hard right-angled corners by omission rather than by decision, which is the shape of a thing nobody argued about rather than a thing anybody chose.

Radius is not absent from the system, though, and the distinction matters. Twelve border-radius: 50% declarations across eight stylesheets draw the discs and rings of the shape vocabulary — the indicator's ok, the radio's selected dot, the alert's and toast's leading marks. radio-button.css says so in terms: *"ADR-0028 records why a radius is a decision."* Those are marks, and a disc is a silhouette, not a corner treatment. What has no vocabulary is the perimeter — the edge of the button, the field, the row, the panel.

The reason to hesitate is the reason this system exists. §11's word for a component without a receipt is scenery, and a visual property that encodes nothing is the same failure one tier down. Every other spatial axis earns its place by carrying meaning: type carries reading hierarchy, space carries density, border carries both the ladder's weight axis and ADR-0013's stacking, targets carries the interactive minimum. A radius scale that exists because rounded corners are fashionable would be the first decorative axis in the tier, and the argument for adding it would be an argument for adding anything.

ADR-0013 is the precedent and it cuts both ways. It refused elevation, and its reason was not that shadows are ugly: under ADR-004 the budget prices deviation from the substrate in either direction, so *"an elevation ramp is a standing tax on every raised surface, paid whether or not anything demands attention."* Elevation was refused for spending the attention budget to carry information the structural encoding already carried. That is the test a new visual axis has to pass, and it is a test about cost as much as about meaning.

Radius passes it from an unusual direction. A turned corner removes pixels from a mark rather than adding them, so it spends nothing — it is very slightly *cheaper* than the square it replaces. It is geometry, so it survives field-night, where ADR-0006 locks the hue angle and §4 says the non-hue encoding *is* the design. And §2 closes the attention channels at three — light, language, motion — which means radius cannot carry intensity and must never encode a level. That is not a limitation to work around. It is the property that makes radius safe: it is available precisely because it is not an attention channel, so it can carry a distinction of *kind* without competing with the ladder that carries distinctions of *degree*.

Which leaves the real question: what kind. The system encodes kind today through shape alone — ADR-0028's six polygons, a scarce and deliberately closed vocabulary. One distinction it has no channel for at all is the one an operator needs fastest under time pressure: can I act on this, or am I reading it? Measured from the component sources this pass, the roster splits cleanly and mechanically — nine components render an interactive element and twelve do not:

components
accepts inputaccordion, button, checkbox, input-field, navigation-item, number-input, radio-button, select, text-area
does notalert, data-table, inline-loading, loading, meter, modal, queue-item, status-indicator, table-row, tag, toast, tooltip

modal and data-table sit on the inert side and the reading is deliberate rather than an oversight: Modal.tsx records that *"dismissal is the caller's to wire"*, and DataTable's sort mark is an indicator whose sorting the caller supplies. Both are Scopes that host controls without being one.

#Decision

The spatial tier acquires a radius scale, and a turned corner means exactly one thing: this component accepts input.

It is binary, not a ramp. The distinction being encoded has two states, so the scale supplies one non-zero value and the absence of radius is the other. A graded radius ramp would imply a gradation that encodes nothing — the same defect ADR-0013 named when it refused a numeric elevation ramp for implying unbounded depth the layer model forbids.

It applies to perimeters and never to marks. The twelve existing 50% declarations are the shape vocabulary and are untouched by this record: a disc is a silhouette under ADR-0028, decided per shape and checked by discipline.test.js. This axis governs the outer edge of a component's own box. A component may therefore carry a square perimeter and a round mark at once, which is what the indicator and the tag already do and what they will keep doing.

It is declared, not inferred. Each component states interactive in component.config.json beside its selector and layer, and the declaration is checked in both directions: a component declaring it must render an interactive element, and a component rendering one must declare it. Neither half can go quiet on its own, which is ADR-0041's requirement and the same two-sided shape ADR-0044 gave adverseVariants.

It is generated and density-selected, like every other spatial primitive under ADR-0012 — a scale in generator.config.json with a base and an increment, mode-less, with comfortable and compact selecting from it. A tighter control takes a tighter corner, which is the one place density has an opinion about a corner.

It carries no level and never will. §2 closes the channels at three, and a radius that varied by level would be a fourth channel entering through the spatial tier. The mark table stays keyed on variant and level; the radius token is keyed on neither.

#Rejected options

No radius axis — leave every perimeter square. The status quo, and it has the best argument any status quo in this repository has: the tier is small, every axis in it carries meaning, and the cheapest way to keep that true is to add nothing. It lost because the property it leaves unencoded is real and currently has no carrier at all. Interactivity is signalled today by position and copy, and in field-night — the theme every component is designed in first, where hue carries nothing — a button and a status chip at the same level are the same rectangle. That is a distinction the system makes constantly and draws never.

Radius encodes the layer. Tempting, because the layer model is the system's spine and a visual reading of it would be elegant: Scopes square, Emitters turned. It lost on the roster. status-indicator, tag, meter, alert and toast are Emitters that accept nothing, and button and input-field are Emitters that accept everything; one radius for all of them encodes the layer and hides the thing a reader actually needs. A visual encoding of a taxonomy the reader cannot act on is decoration with a justification attached.

Radius encodes the level. Rejected on §2, which closes the channel list at three and rations all three on one ladder. A fourth channel entering through the spatial tier would be exactly the drift §2 exists to prevent, and it would be invisible to the budget engine, which measures light and has no term for geometry.

A graded radius ramp — four steps, per component taste. The conventional design-system answer and the most familiar. Rejected because a ramp with no encoding is decoration by construction: with two meanings there is nothing for steps two through four to say, and the steps would immediately be spent on per-component preference, which is the creep ADR-0013 refused when it kept stacking a closed set of three.

Revisit elevation alongside it. Considered because both are "modern surface treatment" and it would be tidy to settle them together. Refused, and ADR-0013 stands unamended. Its argument is undamaged by anything in this record: a shadow is deviation the budget prices, so it is a standing tax on every raised surface, it collapses toward invisibility on dark substrates precisely where field-night needs structure most, and a graded depth ramp contradicts a layer model that nests Scopes at most two deep. Radius is admitted *because* it fails to resemble elevation in the one way that matters — it costs nothing. Naming that here is the point: this record is not a crack in ADR-0013, it is a demonstration of the test ADR-0013 set.

#Consequences

Nine stylesheets consume the token and twelve must not. The check is two-sided, so a tenth component quietly taking a radius fails, and so does one of the nine dropping it.

Emission moves slightly down, or not at all. A turned corner removes pixels from a mark's bounding area, so a control's contribution can only fall. It is a small term against a 2px-to-3px border on a 54px target, and the pass measures it rather than predicting it. Both baselines move and linux-x64 goes stale in the shape every such pass takes.

ADR-0028 is untouched and the boundary is now stated. Shape governs marks, radius governs perimeters, and a component may carry both. The twelve 50% declarations remain shape decisions and remain checked by discipline.test.js; none of them becomes a token by this record.

The spatial tier gains its fourth scale and its receipt discipline extends to it for free. resolveSpatial derives its collision and unreachable-step checks from the scale registry rather than a list, so the new scale joins both without either being edited — which is the property ADR-0012 was written to produce and the first time it has been exercised.

One rule for every component built after this. The remaining 26 on ADR-0021's roster declare interactive when they land, and the two-sided check means neither a missing declaration nor a mistaken one can ship quietly.

#Measured

All darwin-arm64, this pass.

The split held and the exemption is one, not a class. Nine components declare interactive, twelve declare it false, and eight consume radius-control — radio-button takes the shape exemption, because its actable perimeter is .wr-radio-button__ring at border-radius: 50%, a complete turn ADR-0028 already owns. The exemption is verified rather than trusted: a component claiming it must actually draw that 50% radius, and may not also declare the token.

The check was observed failing in three directions before it was trusted green (ADR-0041): button declared not-interactive while rendering a <button>, tag declared interactive while rendering nothing actable, and meter with the declaration removed entirely. Each fired, and the first two fired twice — once on the source and once on the token — because the declaration and the token are checked against each other as well as against the component.

The emission prediction was exact. 60 Scope values moved and every one of them got quieter — zero louder — 15 unthemed at net −0.000465 and 45 themed at net −0.002170. The record predicted "emission moves slightly down, or not at all", and a turned corner removing pixels from a mark is the whole of the mechanism. field-night's themed variants moved by exactly zero, because the seven of them carry no interactive component.

Every declared verdict unchanged, both engines exit 0, and all sixteen suites plus both site builds pass.

The scale's third step is unreachable and the receipt says so. comfortable selects index 1 and compact index 0; index 2 is reported by resolveSpatial's unreachable-step check without that check being edited, which is the ADR-0012 property this record exercised for the first time. It is headroom named rather than a ramp shipped.

#A correction, and the gap it exposed

This record was committed with a tree that failed tokens:verify, and the failure was this record's own prose. Its first revision wrote "hard 90-degree corners" using the degree sign, U+00B0, which is outside the shipped font subset ADR-0035 declares. The character is now spelled out; nothing else changed.

The interesting half is why the local sweep passed. The subset scan reads git ls-files, so it sees tracked files — and a record being written is untracked until the commit that introduces it. The check was therefore blind to exactly the file that introduced the codepoint: green on the pass that caused the problem, red on the next one, which is where CI found it. That is the third instance in this sequence of a check reading a set that excludes its own subject — ADR-0043 found it in Rule A's pair set, ADR-0044 in the token-completeness check, and this is the same shape in the family scan. The file list now reads --cached --others --exclude-standard, so it admits an untracked record and still excludes anything ignored, and it was observed catching a fresh untracked codepoint before being trusted *(darwin-arm64, this pass)*.

ADR-0041's rule was written about checks whose passing condition an absence satisfies. Three instances in three consecutive records suggest the narrower restatement worth carrying forward: a check that derives its own input set is only as good as that set, and the set is the first place to look when a check has never failed.