19.x → 20.0.0, entry by entry
Both entries are @ultimat3/ui component behaviour, and neither changes a type — so nothing
fails to compile and nothing throws. They change what a screen DOES, which is why they are a major:
a silent rendering change is worse than a build error, and you would have found these by looking.
| # | Surface | Costs you an edit if |
|---|---|---|
| 1 | DataTable while reloading | you relied on skeletons appearing on every load. A FIRST load (rows: []) still renders skeletons; a RELOAD now keeps the existing rows, dimmed, under aria-busy. Replacing rendered content with placeholders on every refresh is a second layout change for no news, and it is what makes a fast list feel slow — the reader has already read those rows, and taking them away to say "loading" tells them nothing they can act on. No edit if you pass fresh rows on each load, which is the ordinary case. If you genuinely want the placeholder back on a refresh, pass rows: [] while the fetch is in flight |
| 2 | Button with loading | you style button[disabled], or you assert on the disabled attribute in a test. loading no longer sets the native attribute — it sets aria-disabled + aria-busy and refuses the click in onClick with preventDefault(). The click is still refused; the mechanism moved. A disabled control loses focus the moment it becomes disabled — mid-flow, with no announcement — is exempt from the WCAG contrast minimum precisely because nobody is meant to read it, which is wrong for a control you are asking someone to WAIT on, and it does not actually prevent a double submit, because that race is server-side and always was. The edit is one selector: style button[aria-disabled='true'] beside button[disabled], and assert on aria-disabled |
defineTheme() can now refuse your brand — X_UI_CONTRAST_INSUFFICIENT. Listed here rather
than as a third entry because it throws at declaration with the measured ratio, the required one and
the role to move, so it cannot ship silently and needs no search. An override whose RESOLVED
channels put a pairing below WCAG 2.2 AA (4.5:1 text, 3:1 focus ring, 1.4:1 border) fails at boot.
Only pairings your brand can have CHANGED are measured — blaming an app for the framework's own
colours is how a rule gets switched off. The usual defect is half a pairing: a new accent against
the shipped white accent-fg. @ultimat3/ui exports contrastRatio and roleContrast so you can
measure a candidate before shipping it. AA and never APCA: APCA is not a standard, and AA is the
operative legal benchmark.
Nothing else in this release costs an edit. The five new app guards ship in x new only; an
existing app gains one with x g guard <name> (since 23.0.0; x doctor --json lists the shipped
ones it lacks in data.guards.missing), one file per rule, and deleting one drops that rule. x shot --all-islands is additive, and
x g island / x g resource now write a .island.states.ts beside what they generate.