20.0.0 — UX primitives, five interface guards, and a whole-app island gallery
Ultimate made apps fast and correct. It did not make them good to use — and the reason turned out not to be missing components. @ultimat3/ui has 52 careful ones, a motion token scale with a global prefers-reduced-motion guard, and a strong a11y layer. The behavioural half was built and never wired.
Built, exported, documented — and never once executed
| Mechanism | State before |
|---|---|
| Optimistic UI | the apply is gated on if (store && local && !collapsed), LocalStore is optional, and no app in the repo passed one. The claim that at tier 2 "my own click feels instant" was false as every shipped example configured it |
| Toasts | Toast + ToastRegion — zero consumers repo-wide and no queue behind them |
announce() |
zero callers, and a live region must exist before its content or the first message of a session is silent |
useForm |
zero call sites outside its own directory, and x g resource emitted a hand-rolled createSignal + fetch |
examples/dummy's like button now passes a MemoryLocalStore and a RebaseLog. Both the apply and the rollback are proved by mutation: removing the store fails 4 tests, removing the rebase log fails exactly the one asserting a refused write comes back off the screen.
New mechanism
AsyncRegion+asyncBranch— the four-state decision as a discriminated union.emptyandreadyare required props, so forgetting the empty state is a type error;emptyis structurally unreachable frompending, so "No results" before the first page arrives is unconstructible. A refetch keeps the previous data dimmed underaria-busy.- A toast store — token dwell, a visible cap, dedupe, and a pause on hover, focus-within and
document.hidden. The last is the one everybody forgets and the only one that loses the message outright. - Form submit state, dirty/touched, focus-first-invalid.
defineTheme()refuses a palette failing WCAG 2.2 AA. It caught a pre-existing unreadable fixture on its first run.x shot --all-islands+.x/shot/island/index.md— one command, a picture of every component state in the app, one file to open.
Five interface guards, and the mechanism's first real test
semantic-interactive, focus-visible, image-dimensions, animated-layout-property, island-without-states.
No tracked app had ever carried a guards/ directory. Bringing all nine into examples/dummy proved two of the original four were broken: raw-colour reported all 87 uses of rgb(var(--color-…)), which is the token form the framework emits, and untranslated-string's JSX mask does not nest, so every t() call with an interpolation object read as prose — the guard flagged the exact thing it exists to require. Both fixed; all nine now report zero.
Breaking
Two, both @ultimat3/ui behaviour and neither a type change — nothing fails to compile, which is why they need a major.
DataTablekeeps stale rows while reloading. A first load still renders skeletons; a reload dims the existing rows underaria-busy.Button.loadingno longer sets nativedisabled. It usesaria-disabled+preventDefault(). Adisabledcontrol loses focus mid-flow, is exempt from the contrast minimum, and never prevented the double submit — that race is server-side.
Upgrading — 19.x → 20.0.0, entry by entry
Also
persist: true was instructed by four documents and exists nowhere — query() has never accepted it. A cropMarginPx defaulted with ??, so NaN reached the clip arithmetic. The x new scaffold pinned TypeScript ^6.0.3 while the framework is gated on 7.0.2. New wiki/Interface-Rules.md states each rule with what refuses it, or marks it judgement — a rule with nothing enforcing it is named as unenforced rather than implied.