v0.4.1
Patch hardening for the public framework surface, plus the first step of the
modern-platform motion direction (see ADR-0002).
Added
- Static report kit —
@ponchia/ui/css/report.css+docs/reporting.md.
An opt-in, PDF-first report layer for LLM-authored and hand-authored HTML:
report covers, headers, section numbering, summaries, findings, evidence
blocks, source/appendix/footnote blocks, chart wrappers/legends/fallback
tables, and print utilities (ui-print-only,ui-screen-only,
ui-break-before,ui-break-after,ui-keep,ui-print-exact). It stays
out of the default bundle, ships with the offline LLM docs, and is covered by
a report fixture, package/export checks, and class-contract validation.
The layer also includes compact covers, unnumbered report sections, simple
static chart-bar primitives, and evidence-table framing rules so generated
reports need less private CSS. - Zero-JS enter and exit motion for native-
<dialog>overlays. Modal and
drawer (and their backdrop) now fade/scale both ways via@starting-styletransition-behavior: allow-discrete— previously they only animated in and
vanished on close. Pure CSS, reduced-motion-aware (snaps with no flash), scoped
todialog.ui-modalso the controlled.is-openpath is unchanged.
- Enter/exit motion extended to popover, toast, and accordion (ADR-0002
"next, same approach"):- Popover (
.ui-popover) fades + slides both ways via the same
@starting-style+allow-discreterecipe, covering both the native
[popover]top-layer path and the.is-openfallback. Zero JS,
reduced-motion-aware. - Toast (
.ui-toast) now plays a CSS fade-out on dismiss instead of being
yanked from the DOM. Thetoast()behavior adds.is-leavingand removes
the node ontransitionend(with a timeout fallback); it falls back to
instant removal under reduced-motion or where no transition is computed, so
the persistentaria-liveregion is undisturbed. - Accordion (
.ui-accordion, native<details>) animates auto-height
open/close via::details-content+interpolate-size: allow-keywords+
content-visibility … allow-discrete. Strict progressive enhancement —
gated on@supports selector(::details-content); engines without it (today,
Firefox/Safari) simply snap, exactly as before.
- Popover (
- Scroll-driven motion (progressive enhancement).
.ui-scroll-progress(a
reading-progress bar on ascroll(root block)timeline, RTL-aware) and
.ui-scroll-reveal(a JS-free, IntersectionObserver-free reveal on aview()
timeline). Both are gated on@supports (animation-timeline: …)and
prefers-reduced-motion: no-preference, so engines without scroll timelines
(today, Firefox/Safari) keep a static end-state and reduced-motion users get
no movement. - View Transitions (progressive enhancement). A
.ui-vthelper
(view-transition-name: var(--ui-vt-name)) to morph an element across a
same-documentstartViewTransition()or a cross-document navigation, an
on-brand default for the::view-transition-*(root)cross-fade, and a
reduced-motion kill-switch for the::view-transition-*pseudo-tree
(which the platform does not quiet automatically). Cross-document nav stays
a documented one-liner you add yourself (@view-transition { navigation: auto }
is document-global, so it can't be layered or scoped by the framework). - Optional Qwik bindings —
@ponchia/ui/qwik. Same thin-adapter shape as
the React/Solid bindings (useDialog,useToast, …useBrontoBehavior, plus
thecls/ui/cx+applyStoredThemere-exports), wrapping the SSR-safe
behaviors in Qwik'suseVisibleTask$(run on visible, cleanup on dispose) so a
resumable page stays zero-JS until interaction. Scope a behavior with a Qwik
signal:useDialog({ root: useSignal() }).@builder.io/qwikis an optional
peer dependency, so the core stays zero-dependency. Newexamples/qwik-vite
builds it through the real Qwik optimizer. - OLED true-black surface variant —
data-surface="oled". The dark base is
now a readable elevated near-black (see Changed); this opt-in root attribute
restores pure black for OLED power-saving and the original "Nothing" look.
CSS-only preset (likedata-density/data-contrast), scoped to the dark
theme. Documented indocs/theming.md. - APCA advisory for dark text.
check:contrastnow emits a non-failing
warning when a dark text pairing falls below its perceptual APCA target (WCAG
stays the hard gate) — the early-warning that would have caught the illegible
dim text. The kitchen-sink demo gains a unified theme picker (theme × colorway
× surface, all persisted). - ADR-0003 records the theme model: a binary
light/dark base × one-knob derivation × orthogonal axes (colorway, surface,
contrast, density), and why a flat named-theme catalog is rejected.
Changed
- Dark theme re-tuned for readability. The dark base moved off pure
#000
to an elevated near-black (--bg #121212, panels#1c1c1c/#222/#242424,
lines#383838/#555); body text eased#f2f2f2 → #e6e6e6(APCA Lc 99 → ~91,
removing halation) and dim/meta text raised#858585 → #a0a0a0(APCA
Lc ~36 → ~49 — the actual "hard to read" fix). WCAG 2.x over-rates contrast on
pure black, so pairings "passed" while reading poorly; the re-tune clears WCAG
AA on every pairing and lifts perceptual (APCA) contrast. Accent and status
colours are unchanged; true black stays available viadata-surface="oled". - Browser floor raised to Chrome/Edge 125+, Safari 18+, Firefox 129+
(early–mid 2025). A deliberate greenfield stance (ADR-0002) so the framework
can build natively on@starting-style,transition-behavior: allow-discrete,
oklch()/relative color, andlight-dark(). No fallbacks ship below the
floor; not-yet-cross-engine features (View Transitions, scroll-driven
animations) are enhancement-only and degrade to a static end-state. - Bundle budget nudged for the new motion: gzip 13.0 → 13.5 kB (for the dialog
enter/exit work) and raw 76 → 77 kB (for the popover/toast/accordion motion
plus the scroll-driven + view-transition CSS). Gzip held at ~13.1 kB — it
compresses well — so the compressed payload still has headroom.
Fixed
- React and Solid bindings now resolve scoped roots on mount, so
{ root: ref }
and resolver callbacks work after framework refs are assigned. Nullish resolver
results normalize to default behavior instead of crashing destructuring
behavior initializers. - Scoped behavior roots now resolve controlled ids root-first, then
document-wide. This keeps existing body/portal-mounted dialogs, popovers, and
disclosure panels working while preventing earlier duplicate ids outside an
island from shadowing the in-root target. data-bronto-dismiss="<selector>"ignores malformed selectors instead of
throwing during event handling.- The one-node glyph mask path now includes a WebKit-prefixed mask declaration,
and the OKLCH accent ramp uses an explicit white/black neutral endpoint for
cross-engine browser parity.
Added
- React and Solid Vite examples, CI/release matrix coverage for those examples,
runtime binding tests, public API stability docs, a release runbook, and
npm run size:report.