feat(udg): type the press vars — @property carries the contract to the client #158
No reviewers
Labels
No labels
build-dist
priority
P1
priority
P2
priority
P3
priority
P4
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Toilville/toilville-design!158
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/udg-typed-properties"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The UDG is well-typed right up to the CSS boundary, and then stops. A custom property is untyped: an unset or wrong-typed one is invalid-at-computed-value-time and silently takes the property's initial value —
autoforinset, which collapses the box and the press vanishes.It doesn't load unhappily. It loads plausibly, which is worse — and it's where every bug in this tier has lived.
skin.cssnow registers all 116 press vars with@propertybefore:root, so each carries a real type and a declared fallback.Scope
Verified in Chrome and Firefox, identically, before any of this was written:
0×0box — the bug as it ships todayinitial-valueinitial-value(not IACVT)var()indirectionsyntax: "*"+ unsetinitial-value— no type check, but the fallback, which is the half that mattersDemonstrated end to end: a client feeding
bananaintodx,not-a-numberintoopacityandwobbleintoblendnow renders identically to one that adheres — 12.47px band, mask intact, in both engines.The generator already knew every type (
dxis a<length>,opacitya<number>,blenda keyword set), so this is emitted, not authored. Zero new burden on anyone writing a press.inherits: true, and that is not a detailPress vars are declared at
:rootand consumed anywhere in the tree. Withinherits: falsea registered property is not visible below its declaring element, so every alias in every recipe read theinitial-valueinstead — the anchored band filled its whole panel rather than sitting at the edge.The typing feature shipped the exact class of silent failure it exists to prevent, for about ten minutes.
just checkwas green throughout; it was caught by rendering it and reading the computed values. Its test was verified to fail wheninherits: falseis reintroduced.Testing
just checkgreen · 276 tests · both engines agree ·skin.css260 → 346 lines, all generated.New tests pin: every consumed var is registered or explicitly untyped; every registration inherits; every registration declares a fallback; the geometry vars specifically; registrations land before
:root; and the emitted skin registers every press var it declares.Impact Statement
Additive and inert for an adhering client. Every value resolves exactly as before — the two shipped presses render byte-identically. What changes is the behaviour of a client that gets something wrong.
dist/churn isskin.cssfor the one product with presses, plus the review artifact. Versions bumped.What this does NOT fix, stated plainly: a dropped mask. An unset
platetypes tonone, which is a valid value meaning "no mask" — so the element still paints raw pigment.@propertymakes that failure predictable, not loud. Making absence loud is the review artifact's self-check, not the type system, and that remains the open half.Only the press tier is typed here. It is the one tier whose vars a consumer wires into layout, where a miss collapses a box rather than just losing a colour. Extending registration to colour/space/radius is a reasonable follow-up but a much larger diff for a much smaller failure mode.
Browser support:
@propertyis Chrome 85+, Safari 16.4+, Firefox 128+. An engine without it ignores the at-rule and behaves exactly as today — so this degrades to the status quo rather than breaking.The UDG is well-typed right up to the CSS boundary, and then stops. A custom property is untyped: an unset or wrong-typed one is invalid-at-computed-value-time and silently takes the PROPERTY's initial value — `auto` for inset, which collapses the box and the press vanishes. It does not load unhappily. It loads plausibly, which is worse, and it is where every bug in this tier has lived. skin.css now registers all 116 press vars with @property before :root, so each carries a real type and a declared fallback. A client that adheres gets its value; one that does not gets the fallback instead of a collapse. Verified in Chrome AND Firefox, identically, BEFORE any of this was written: unregistered + unset -> 0x0 box (the bug as it ships today) registered + unset -> initial-value registered + WRONG type -> initial-value (not IACVT) registered + var() indirection -> resolves (token defaults survive) syntax "*" + unset -> initial-value (no type check, but the fallback, which is the half that matters) Demonstrated end to end: a client feeding `banana` into dx, `not-a-number` into opacity and `wobble` into blend now renders IDENTICALLY to one that adheres. The generator already knew every type — dx is a <length>, opacity a <number>, blend a keyword set — so this is emitted, not authored. Zero new burden. `inherits: true`, and that is not a detail. Press vars are declared at :root and consumed anywhere in the tree; with inherits:false a registered property is not visible below its declaring element, so every alias in every recipe read the initial-value instead and the anchored band filled its whole panel. The typing feature shipped the exact class of silent failure it exists to prevent, for about ten minutes, and was caught by rendering it rather than by the gate. Its test was verified to fail when inherits:false is reintroduced. What this does NOT fix, stated plainly: a dropped mask. An unset plate types to `none`, which is a VALID value meaning "no mask" — so the element still paints raw pigment. @property makes that failure predictable, not loud. Making absence loud is the review artifact's self-check, not the type system. Verified: just check green; 276 tests; both engines agree; skin.css 260 -> 346 lines, all generated.