Skip to content

20.0.0 — UX primitives, five interface guards, and a whole-app island gallery

Choose a tag to compare

@sebyx07 sebyx07 released this 08 Sep 17:35
· 108 commits to main since this release
0c02227

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. empty and ready are required props, so forgetting the empty state is a type error; empty is structurally unreachable from pending, so "No results" before the first page arrives is unconstructible. A refetch keeps the previous data dimmed under aria-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.

  1. DataTable keeps stale rows while reloading. A first load still renders skeletons; a reload dims the existing rows under aria-busy.
  2. Button.loading no longer sets native disabled. It uses aria-disabled + preventDefault(). A disabled control 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.

Full detail: CHANGELOG · #427 · #428 · #429