@ponchia/ui 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,945 @@
1
+ # Changelog
2
+
3
+ > **Versioning:** pre-1.0, breaking changes ship in the _minor_. Pin
4
+ > `~0.x`; `^0.x` does **not** protect you. See README → Versioning, and
5
+ > the deprecation policy in CONTRIBUTING.md.
6
+
7
+ ## 0.4.0 — 2026-05-31
8
+
9
+ The color-system release — [ADR-0001](docs/adr/0001-color-system.md) steps 1–7.
10
+ A governed evolution beyond pure monochrome: the tier model is written down and
11
+ **enforced** (`check:color-policy`), and the "Nothing" look is proven to be a
12
+ _skin, not the architecture_ — opt-in **colorways** (amber CRT · phosphor green ·
13
+ e-ink), a **data-viz palette** for dashboards (colourblind-safe, gated under
14
+ simulated protan/deutan/tritan vision), Tier-3 dot-matrix display tokens, OKLCH
15
+ authoring, and an APCA advisory contrast track all ship. Plus a reframed README
16
+ + a rendered docs site, and the curated CHANGELOG is now the GitHub Release body.
17
+
18
+ **The default build is unchanged** — the red accent, every retained token name,
19
+ and both theme palettes render identically; colorways and data-viz are opt-in
20
+ entrypoints, never in the default bundle. The only breaking change is the
21
+ removal of the orphan `--orange` token (undocumented, unused) — see below.
22
+
23
+ **BREAKING (orphan token removed)** — **`--orange` / `--orange-soft`** are
24
+ removed. They were defined in every token mirror (`css/tokens.css`,
25
+ `tokens/index.js`, `tokens.dtcg.json`, `resolved.json`, `index.d.ts`,
26
+ `reference.md`) but **referenced by no component and documented nowhere**, and
27
+ untiered under the new color model. As a provably-unreferenced token this is
28
+ removed in a single minor under the CONTRIBUTING.md deprecation-policy
29
+ exception (no working call-site to wind down). Removed rather than adopted; if
30
+ categorical color lands later it ships as a governed, opt-in data-viz module
31
+ (ADR-0001 tier 4), not a stray top-level token. _Migration:_ if you referenced
32
+ `--orange`/`--orange-soft`, define it yourself in a consumer override.
33
+
34
+ ### Added
35
+
36
+ - **The `--accent-1..6` ramp is now perceptually even (OKLCH).** Steps 1–4 mix
37
+ the accent toward the background `in oklch` instead of `in srgb` (ADR-0001
38
+ step 8), so the ramp reads as evenly-spaced — better for charts / multi-state
39
+ surfaces. `scripts/gen-resolved.mjs` learned to resolve `color-mix(in oklch,…)`
40
+ → hex (bit-for-bit matching browser `color-mix`), so `tokens/resolved.json`,
41
+ the DTCG export, and `docs/reference.md` all carry the new values. These are
42
+ token **values** (non-contractual under the 0.x policy) and the ramp is not
43
+ consumed by any shipped component, so there is **no change to any component's
44
+ rendering** — only consumers using `var(--accent-1..4)` directly see the
45
+ (intended) shift.
46
+ - **Framework bindings — `@ponchia/ui/react` + `@ponchia/ui/solid`.** Optional,
47
+ thin hooks over the SSR-safe `init*` behaviors (run on mount, clean up on
48
+ unmount/dispose): `useDialog`, `useTabs`, `useMenu`, `useCombobox`,
49
+ `usePopover`, `useDisclosure`, `useFormValidation`, `useTableSort`,
50
+ `useCarousel`, `useDismissible`, `useThemeToggle`, `useDotGlyph`,
51
+ `useToast()`, and the generic `useBrontoBehavior(init, opts)`; `cls`/`ui`/`cx`
52
+ re-exported for one-import DX. `react` / `solid-js` are **optional peer
53
+ dependencies** — the core stays zero-runtime-dependency. Thin adapters over
54
+ the canonical CSS/behaviors layer, not a component library (architecture ADR).
55
+ - **Glyphs: a one-node icon-at-scale render path + a `.ui-icon` wrapper + 5
56
+ circle-family glyphs.** `renderGlyph(name, { render: 'mask', size })` now
57
+ returns a **single** `.ui-icon` element masked by the glyph bitmap (one DOM
58
+ node instead of 256 cells) — for an icon in every table row. It scales with
59
+ the text (`--icon-size`, default `1em`) and inherits `currentColor`. New
60
+ `.ui-icon` CSS primitive (`cls.icon`) drives it. Vocabulary grows to 48 with
61
+ `circle`, `check-circle`, `x-circle`, `plus-circle`, `minus-circle`.
62
+ - **Data-viz colour module — `@ponchia/ui/css/dataviz.css` + `@ponchia/ui/charts.json`.**
63
+ An opt-in Tier-4 chart palette for dashboards (ADR-0001 step 7). **Hybrid
64
+ accent-led**: series 1 is the live `var(--accent)` (the brand stays series 1);
65
+ series 2–8 are the Okabe-Ito colourblind-safe set; plus a sequential ramp
66
+ (heatmaps) and a diverging ramp (gains/losses). **Colour is never the sole
67
+ signal** — each series ships a matching `--chart-pattern-*` dot-matrix fill
68
+ (WCAG 1.4.1). The categorical palette is **gated for distinguishability under
69
+ normal + simulated protan/deutan/tritan vision** (`check:charts`, OKLab ΔE),
70
+ not just eyeballed. Resolved hex per theme in `tokens/charts.json` for
71
+ canvas/SVG/charting libs; typed `ChartTokenName` (`./charts`). **Charts-only,
72
+ never UI chrome** (`check:color-policy` fails on `var(--chart-*)` in core CSS)
73
+ and **never in the default bundle** (separate entrypoint). Sourced from
74
+ `tokens/charts.js` (generated → drift-checked by `check:charts`).
75
+ - **Display colorways — `@ponchia/ui/css/skins.css`.** Opt-in
76
+ `data-bronto-skin="amber-crt | phosphor-green | e-ink"`, a **root-level**
77
+ choice like `data-theme` (apply on `<html>`). Each re-points the one accent
78
+ to a different single hue — the derived accent family, focus ring, dot-matrix
79
+ and glyphs follow automatically; status colours and the neutral canvas are
80
+ untouched. Authored in **OKLCH**, per-theme (a dark + light accent, like the
81
+ core red), and **every skin accent is contrast-gated** to the same WCAG AA /
82
+ 3:1 floors as the core (see `docs/contrast.md`). Shipped as a **separate
83
+ entrypoint, never in the default `dist/bronto.css`** — zero cost unless you
84
+ import it. Sourced from `tokens/skins.js` (generated → drift-checked by
85
+ `check:skins`).
86
+ - **Tier-3 dot-matrix "display expression" tokens** (`css/dots.css`):
87
+ `--dotmatrix-glow` (phosphor bloom on lit cells), `--dotmatrix-pulse-min`
88
+ (the `--pulse` floor), documented `--dotmatrix-reveal-step` (scan cadence).
89
+ All default to a **no-op**, so the default render is unchanged; the phosphor
90
+ colorways use the glow in dark.
91
+ - **APCA advisory contrast** in `docs/contrast.md`. Every pairing (core **and**
92
+ every colorway) now shows an APCA-W3 `Lc` column beside the WCAG ratio — a
93
+ perceptual cross-check. **Advisory only**: WCAG 2.1 AA stays the hard gate
94
+ (`check:contrast`). Implemented dependency-free, alongside an OKLCH→sRGB
95
+ converter so OKLCH-authored values can be measured.
96
+ - **`check:color-policy` gate** (`scripts/check-color-policy.mjs`, wired into
97
+ `npm run check`). Enforces the color constitution: every color-defining token
98
+ (across `global`/`light`/`dark`) must be classified into a tier (this is what
99
+ would have caught `--orange`); the `--chart-*` / `--cat-*` / `--data-*`
100
+ data-viz namespace is reserved; and component CSS may not use raw chromatic
101
+ color (only tiered tokens, with neutral grays / `color-mix()` endpoints).
102
+ - **`check:skins` gate** — `css/skins.css` can't drift from `tokens/skins.js`,
103
+ every skin defines `--accent`, and colorways stay out of the default bundle.
104
+ - **[ADR-0001 — Color system](docs/adr/0001-color-system.md)** — the five-tier
105
+ color constitution and the backward-compatible roadmap. Steps 1–6 are
106
+ implemented in this release (gate + colorways + Tier-3 tokens + OKLCH for new
107
+ work + APCA advisory); the data-viz categorical module and any core-ramp
108
+ OKLCH migration are deliberately deferred.
109
+
110
+ ## 0.3.6 — 2026-05-31
111
+
112
+ Display glyphs: a small `@ponchia/ui/glyphs` subpath of dot-matrix bitmaps
113
+ rendered on the existing `.ui-dotmatrix` primitive — no SVG, no icon font,
114
+ re-skinned by the same `--field-dot*` tokens. All additive — a new optional
115
+ JS subpath plus one backward-compatible CSS var → a patch under the 0.x
116
+ policy.
117
+
118
+ ### Added
119
+
120
+ - **`@ponchia/ui/glyphs` — a 43-glyph dot-matrix display-icon set.** A frozen
121
+ 16×16 bitmap registry (`GLYPHS`, `GLYPH_NAMES`, `GLYPH_SIZE`) with `glyph()`,
122
+ `glyphCells()`, and `renderGlyph(name, { label, grid, solid, anim, dot, gap })`
123
+ — an SSR-safe HTML string, decorative (`aria-hidden`) by default and
124
+ `role="img"` when labelled. Covers navigation (`arrow-*`, `chevron-*`),
125
+ actions (`check`, `close`, `plus`, `minus`, `search`, `menu`, `gear`,
126
+ `edit`, `trash`, `download`, `upload`, `link`, `refresh`), media (`play`,
127
+ `pause`), state (`eye`, `eye-off`), theming (`sun`, `moon`), objects
128
+ (`mail`, `file`, `folder`, `clock`, `home`, `user`, `heart`, `star`,
129
+ `bell`, `lock`, `info`, `warning`), layout (`grid`, `more-vertical`,
130
+ `more-horizontal`) and `spark` (the two-tone accent demo). Three knobs from
131
+ one source: the default dot look for **display** sizes; **`solid: true`**
132
+ (or `data-bronto-glyph-solid`) which fuses the cells into a square, gapless
133
+ pixel glyph legible as an **inline icon down to ~16px**; and opt-in
134
+ **`anim`** (`reveal` powers the cells on in a scan, `pulse` makes the glyph
135
+ breathe) — decorative only, disabled under `prefers-reduced-motion`, with
136
+ the meaning kept in the static frame + label. `renderGlyph` returns a
137
+ `<span>` (valid inline / inside a `<button>`) and accepts any string for
138
+ dynamic dispatch (`GlyphNameInput = GlyphName | (string & {})`) — unknown
139
+ names hit the documented `''`/`[]`/`undefined` fallback — while the
140
+ `GlyphName` union itself stays strict (typos in annotations are errors). The
141
+ union is generated and CI-drift-checked from the runtime, like the
142
+ `cls`/token maps.
143
+ - **`initDotGlyph()` behavior.** Expands `[data-bronto-glyph]` placeholders
144
+ into a `.ui-dotmatrix` grid in place (optional `data-bronto-glyph-label`),
145
+ idempotent, with a cleanup that fully reverts — the DOM counterpart to
146
+ `renderGlyph`.
147
+
148
+ ### Changed
149
+
150
+ - **`.ui-dotmatrix` gains `--dotmatrix-dot` and `--dotmatrix-dot-radius`
151
+ knobs** — the former for intrinsic dot sizing (`grid-template-columns`
152
+ falls back to the previous `minmax(0, 1fr)` when unset), the latter to
153
+ square off the cells (`--dotmatrix-dot-radius: 0`) for the solid pixel-glyph
154
+ look. Both default to the prior behaviour, so existing matrices are
155
+ unchanged. Adds opt-in `ui-dotmatrix--reveal` / `ui-dotmatrix--pulse`
156
+ animation modifiers (`cls.dotmatrixReveal` / `cls.dotmatrixPulse`), both
157
+ reduced-motion-aware; `--dotmatrix-reveal-step` tunes the reveal scan speed
158
+ (per-cell delay, default `3ms`).
159
+
160
+ ## 0.3.5 — 2026-05-29
161
+
162
+ A consumer-evidence pass: six small primitives that adopters were
163
+ repeatedly hand-rolling, added as pure CSS in
164
+ `@layer bronto` reusing existing tokens. All additive — no new token, no
165
+ new gated contrast pairing, no breaking change → a patch under the 0.x
166
+ policy.
167
+
168
+ ### Added
169
+
170
+ - **`ui-pagehead` — the in-page title bar.** `ui-pagehead` +
171
+ `__title` + `__actions`: eyebrow/breadcrumb + title on the start,
172
+ an actions cluster on the end, one hairline beneath. Composes the
173
+ existing `ui-eyebrow` / `ui-breadcrumb` rather than duplicating them.
174
+ Parts-only (no recipe), like `ui-panel`.
175
+ - **`ui-steps` — a stepper for multi-step flows.** `ui-steps` (an `<ol>`)
176
+ + `__item` + `__item--done`, auto-numbered by CSS counter with hairline
177
+ connectors. State is ARIA-driven: the active step is `aria-current="step"`
178
+ (no class); completed steps take `--done`.
179
+ - **`ui-timeline` — a vertical event list.** `ui-timeline` (an `<ol>`) +
180
+ `__item` + `__time`, on a hairline spine with a per-item marker dot;
181
+ `aria-current` marks the live event (accent marker).
182
+ - **`ui-meter` — a labelled proportion / threshold bar.** `ui-meter` +
183
+ `__fill` + tone modifiers `--accent` / `--success` / `--warning` /
184
+ `--danger`, with a `ui.meter({ tone })` recipe + `MeterOpts`. Distinct
185
+ from `ui-progress` (task progress, can be indeterminate): a meter shows
186
+ a measured static value (coverage, capacity, a KPI). Width is the same
187
+ shared `--value` knob progress uses; author `role="meter"` +
188
+ `aria-valuenow/min/max`.
189
+ - **`ui-kbd` — an inline keyboard-key glyph.** Wrap a `<kbd>`; one per key
190
+ for a shortcut.
191
+ - **`ui-input-icon` — a leading/trailing icon *inside* a control.**
192
+ `ui-input-icon` + `__icon` + `--end`, with a `ui.inputIcon({ end })`
193
+ recipe + `InputIconOpts`. Distinct from `ui-input-group` (an *adjacent*
194
+ affix): the icon overlays inside one `.ui-input`, which keeps full width
195
+ and gains padding on the icon side. Closes the "no framework primitive"
196
+ gap consumers were filling with a bespoke absolute overlay.
197
+ - **`ui-carousel` + `initCarousel` — an image gallery / carousel, and a
198
+ lightbox built on it.** `ui-carousel` (`__stage` / `__viewport` /
199
+ `__slide` / `__prev` / `__next` / `__thumbs` / `__thumb` / `__status`)
200
+ is a **scroll-snap** track, so touch + trackpad swipe (with momentum)
201
+ are the browser's, not hand-rolled. `initCarousel` adds prev/next,
202
+ keyboard (Arrow/Home/End), a thumbnail strip with `aria-current` sync,
203
+ the position counter, and carousel ARIA, keeping a JS index in sync with
204
+ the scroll position both ways (`IntersectionObserver` where available);
205
+ `data-bronto-carousel-loop` wraps; it emits `bronto:change`
206
+ (`{ index }`). A **full-screen lightbox** is the same markup inside a
207
+ native `<dialog class="ui-lightbox">` (+ `ui-lightbox__close`) opened by
208
+ the existing `initDialog` — so the top layer, focus-trap, Escape and
209
+ focus-return come from `<dialog>` with zero new focus code. Closes the
210
+ gallery/lightbox/carousel gap consumers were filling by hand (one
211
+ adopter had shipped three overlapping hand-rolled versions). SSR-safe,
212
+ idempotent, returns a cleanup; declared in `behaviors/index.d.ts` and
213
+ demoed.
214
+ - Demo gains a **"0.3.5 additions"** kitchen-sink section; `docs/usage.md`
215
+ gains meter-vs-progress and steps/timeline/kbd/input-icon guidance; the
216
+ generated `docs/reference.md`, `classes/index.d.ts`,
217
+ `classes/vscode.css-custom-data.json` and `dist/*` pick the new surface
218
+ up automatically.
219
+
220
+ ### Changed
221
+
222
+ - **Bundle gzip budget 12 kB → 13 kB** (raw cap unchanged at 76 kB). The
223
+ six new primitives plus the carousel/lightbox grew `dist/bronto.css` to
224
+ ~73 kB raw / ~12.7 kB gzip (against the 76 kB / 13 kB caps); the gzip cap
225
+ is recalibrated per the bump-deliberately rule in
226
+ `scripts/build-dist.mjs`.
227
+
228
+ ### Packaging & docs
229
+
230
+ - **`fonts/OFL.txt`** — the bundled Doto font is now shipped with its SIL
231
+ Open Font License 1.1 text and attribution (© 2024 The Doto Project
232
+ Authors), as required to redistribute it; README gained a font-license
233
+ note. No code change.
234
+ - **README rewritten** for the npm package page (de-duplicated, accurate
235
+ install/quick-start/theming, absolute links); `package.json` gained a
236
+ compelling `description` + `keywords`; `ROADMAP.md` reconciled to 0.3.5.
237
+ - **Visual regression reworked** to component-scoped per-section snapshots
238
+ (`data-shot` slugs, auto-discovered) so adding a primitive no longer
239
+ drifts every baseline; the `visual-baselines` workflow's change-gate now
240
+ detects brand-new (untracked) baselines.
241
+
242
+ ## 0.3.4 — 2026-05-17
243
+
244
+ A review-driven accessibility + adoption pass (three external reviews →
245
+ independent Opus review → AgentMix deep multi-POV review). All additive:
246
+ new gated artifact, two new shipped docs, one new token. No breaking
247
+ change → a patch under the 0.x policy.
248
+
249
+ ### Added
250
+
251
+ - **`docs/contrast.md` — a published, CI-gated WCAG 2.1 contrast
252
+ matrix.** Every contractual token pairing, the conformance level it is
253
+ *held to*, and its measured sRGB ratio per theme. Generated from the
254
+ resolved token model (`scripts/gen-contrast.mjs`) so it cannot drift
255
+ from the palette, and **gated**: the new `check:contrast` fails
256
+ `npm run check` (and so release) if any gated pairing drops below its
257
+ floor. Text pairings are held to AA 4.5:1; non-text UI to 3:1;
258
+ decorative hairlines are reported but WCAG-1.4.11-exempt by design
259
+ (the low ratio is published, not hidden). New `exports`/`files`
260
+ entry; indexed in `llms.txt` + README.
261
+ - **`docs/usage.md` — the decision guide.** When to use which primitive
262
+ (badge vs chip vs status dot), the unset density default and its two
263
+ presets, prose-in-card, when to reach for a behavior. Hand-written and
264
+ stable (contract, like `theming.md`); ships in the tarball for offline
265
+ agents/consumers.
266
+ - **`info` status tone — token wired through the full status family.**
267
+ New `--info` / `--info-soft` tokens (+ `--bronto-color-info` semantic
268
+ alias), dual-theme, **and** the consumers that make it real:
269
+ `ui-badge--info`, `ui-alert--info`, `ui-toast--info`, `ui-dot--info`,
270
+ with `ui.badge`/`ui.alert`/`ui.dot` recipes, `BadgeOpts`/`DotOpts`/
271
+ `Tone` types, the toast `ToastOpts` tone, the generated reference and
272
+ the demo. This deliberately reverses the 0.3.2 `ui-badge--info`
273
+ deferral: shipping a token no component consumes would be the exact
274
+ dead-token defect this release exists to remove. Its gated contrast
275
+ row is now a real indicator (measured ≥3:1 on surface — light 5.77:1,
276
+ dark 8.41:1). Status hues (`success`/`warning`/`danger`/`info`) are
277
+ outside the rationed-accent rule by design.
278
+ - **`docs/theming.md` — a full re-skin recipe.** Demonstrates that the
279
+ "Nothing" identity is a token skin, not the architecture: a per-theme
280
+ override block (measured AA-passing accents) restyles the whole system
281
+ with no fork.
282
+
283
+ ### Changed
284
+
285
+ - `.ui-dotbar i` radius `1px` → `var(--radius-sm)` (byte-identical
286
+ render — `--radius-sm` *is* `1px` — but now responds to a radius
287
+ re-skin; no visual-baseline drift).
288
+ - **`--dot-font` is now a live knob.** It and `--display` were defined
289
+ identically and nothing consumed `--dot-font`, yet README / Astro
290
+ guide / `fonts.css` all advertised it as a self-host/re-skin override
291
+ (a documented dead token — the exact defect this release exists to
292
+ remove). `--display` now derives from `--dot-font`
293
+ (`--display: var(--dot-font)`): byte-identical render today, but
294
+ overriding `--dot-font` as documented finally works. Removing the
295
+ token instead would have been breaking (names are contractual), so
296
+ the patch-safe fix is to make the advertised knob real.
297
+ - Contrast gate now also covers text on `--surface-muted`
298
+ (`--text-soft`, `--text-dim`) — that surface is rendered as a fill by
299
+ forms/disclosure and `--text-dim` there is the tightest real margin
300
+ (~4.7:1). Passing today; now gated so a future palette nudge can't
301
+ silently regress it. (Found by the full-codebase audit.)
302
+ - ROADMAP reconciled against shipped reality: the entire original 0.3.1
303
+ checklist has been delivered for several releases. CHANGELOG is now
304
+ the stated source of truth.
305
+
306
+ ### Fixed
307
+
308
+ - `scripts/gen-contrast.mjs` colour parser now rejects non-finite
309
+ channels and handles percent alpha, so a `NaN` ratio can no longer
310
+ silently pass the gate (`check:contrast` also fails explicitly on a
311
+ non-finite ratio). Found by the AgentMix review.
312
+ - `demo/theme-playground.html` WCAG linearisation threshold aligned to
313
+ the canonical `0.04045` (was `0.03928`); points at the gated
314
+ implementation as the source of truth.
315
+ - `check-classes` now strips CSS comments before scraping selectors. It
316
+ is the one contract check that scrapes CSS rather than diffing a
317
+ generator, so a `.ui-*` named only inside a comment could previously
318
+ satisfy the `cls`⇄selector contract — a real (if currently
319
+ unexercised) drift hole. Closed by the full-codebase audit.
320
+ - **`ToastOpts` was runtime-public but type-private.** `toast()` has
321
+ always accepted (and tested) `assertive` / `closable`, but the typed
322
+ `ToastOpts` stopped at `tone`/`title`/`duration`, so a TypeScript
323
+ consumer couldn't pass documented options. Added both (+ a type
324
+ test). `check:dts` covers only the *generated* classes/tokens `.d.ts`,
325
+ not the hand-written behaviors one — this drift was invisible to the
326
+ gate.
327
+ - **`initFormValidation` suppressed native bubbles too late.**
328
+ `form.noValidate` was set inside the submit/blur handlers, so the
329
+ *first* invalid real-browser submit of a pre-existing form showed the
330
+ UA bubble instead of the Bronto error summary (the demo masked it
331
+ with authored `novalidate`). It is now set at init for matched forms
332
+ and restored on cleanup; dynamically-added forms stay covered by the
333
+ in-handler set.
334
+ - **Combobox could select a filtered-out option.** Typing a query that
335
+ hid the active option left `aria-activedescendant` stale; Enter then
336
+ selected the hidden option. `filter()` now clears stale active state
337
+ and Enter is visibility-guarded (WAI-ARIA APG correctness).
338
+ - `check-dist` now also asserts the on-disk `dist/**/*.css` set exactly
339
+ matches the generated bundles — since the whole `dist/` ships, a
340
+ stale/renamed leaf would otherwise reach npm undetected.
341
+ - README bundle-size figures refreshed (~64 kB raw / ~11 kB gzip; the
342
+ enforced ceiling remains `check-dist`'s budget). Prior `~54/~10`
343
+ prose had drifted since 0.3.2.
344
+
345
+ ## 0.3.3 — 2026-05-16
346
+
347
+ A second consumer-evidence pass ("felt it twice") plus
348
+ agent-discoverability. All additive — new classes/recipes, a new
349
+ generated artifact, a widened input type, a freed primitive with a
350
+ permanent alias. No breaking change → a patch under the 0.x policy.
351
+
352
+ ### Added
353
+
354
+ - **`@ponchia/ui/tokens/resolved.json`** — every colour token resolved
355
+ to a static `#rrggbb` / `rgba(...)` per theme, with `var()` and sRGB
356
+ `color-mix()` evaluated at build time. For render targets that cannot
357
+ read CSS custom properties: MapLibre GPU paint, `<canvas>`, WebGL,
358
+ SVG, server-side image gen. Generated + drift-checked (new
359
+ `check:resolved` gate); no new colours introduced (`check:shiki`
360
+ unaffected). Resolves the deferred charts-palette gap now that a 2nd,
361
+ non-charting consumer has hit it.
362
+ - **`ui-button--sm` / `ui-button--lg`** — token-driven size scale for
363
+ dense tooling (toolbars, pagination, table actions) and hero CTAs.
364
+ `ui.button({ size: 'sm' | 'lg' })`. Default size unchanged.
365
+ - **`ui-empty-state`** — the empty-state primitive is now
366
+ shell-agnostic; `ui-app-empty-state` remains a permanent grouped alias
367
+ (byte-identical render, no baseline drift). Same playbook as 0.3.2
368
+ `ui-stat`.
369
+ - **`ui-app-rail__account`** — a framework-blessed identity / sign-out
370
+ slot, so admin shells stop hand-rolling it; stays visible when the
371
+ rail collapses on mobile.
372
+ - **`ui-modal` controlled (`is-open`) path** — a portal/React modal that
373
+ can't be a native `<dialog>` wears the same skin + open layout via
374
+ `is-open` (`ui.modal({ open: true })`). Backdrop and focus-trap remain
375
+ the consumer's responsibility (the `<dialog>` path still gets them
376
+ free).
377
+ - **`llms.txt`** at the package root — a self-contained agent entrypoint
378
+ (typed contract, the `@layer bronto` override rule, import surface,
379
+ shipped offline references).
380
+ - **`docs/reference.md` and `docs/theming.md` now ship in the npm
381
+ tarball** — an offline coding agent gets the full class catalog and
382
+ token contract from `node_modules/@ponchia/ui/`. The reference also
383
+ now documents the table-local `is-num`/`is-pos`/`is-neg` state classes
384
+ and the ARIA-driven composition/state model. New `exports` subpaths:
385
+ `./llms.txt`, `./docs/reference.md`, `./docs/theming.md`,
386
+ `./tokens/resolved.json`.
387
+
388
+ ### Changed
389
+
390
+ - **`ClassValue` widened to clsx parity** (`string | number | boolean |
391
+ null | undefined | …`). The idiomatic React `reactNode && 'cls'` guard
392
+ (where the node may be `0` / `''`) now type-checks; the runtime `cx`
393
+ already skipped every falsy value, so this is a non-breaking type
394
+ relaxation.
395
+ - `check-pack.mjs` relaxed from a blanket `docs/` ship-block to a
396
+ **curated allowlist** (`docs/reference.md`, `docs/theming.md` only); a
397
+ consistency assertion fails if that set drifts from `package.json`
398
+ `files`. The rest of `docs/` stays dev-only by design — a deliberate,
399
+ documented narrowing of the earlier runtime-only stance.
400
+
401
+ ### Deferred (deliberately not shipped)
402
+
403
+ - **React/Solid binding layer.** The duplicated form/badge "glue" two
404
+ consumers feel is ARIA-driven by design (`aria-invalid`, `aria-busy`,
405
+ `aria-describedby`), so the agnostic surface is already complete; the
406
+ remaining duplication is the framework-binding layer, still deferred.
407
+ The reference now documents the composition model explicitly.
408
+ - **`ui-app-rail__foot` mobile `display:none`.** Whether the foot should
409
+ survive the mobile breakpoint is a visual/possibly-breaking call; the
410
+ new `__account` slot (which does survive) covers the actual need
411
+ without changing existing behaviour.
412
+
413
+ ## 0.3.2 — 2026-05-16
414
+
415
+ Re-skin-proven adoption pass: a real content-site consumer rebuilt
416
+ several idioms bespoke because a primitive was missing or shell-locked.
417
+ This promotes the genuinely generic, token-only ones upstream. **All
418
+ additions are non-breaking** (additive classes/recipes; the metric tile
419
+ keeps its admin-shell name as a permanent alias) — a patch per the 0.x
420
+ policy.
421
+
422
+ ### Added
423
+
424
+ - **`ui-stat` / `ui-statgrid`** — the metric tile is now shell-agnostic
425
+ (label + display value + signed delta). The admin-shell
426
+ `ui-app-metric*` / `ui-app-metrics` names remain as permanent aliases
427
+ grouped on the same rules (byte-identical output, no baseline drift).
428
+ - **`ui-link--cta`** — the eyebrow-faced action link (accent · display ·
429
+ uppercase + arrow glyph), composed from the same tokens as
430
+ `ui-eyebrow`. `ui.link({ cta: true })`.
431
+ - **`ui-badge--dot`** — leading state dot; composes with any badge tone
432
+ (`is`-tone tints the dot). `ui.badge({ tone, dot: true })`.
433
+ - **`ui-eyebrow--sm`** — restores the dense size step (was
434
+ `eyebrow--tight` in 0.2.2). `ui.eyebrow({ sm: true })`.
435
+ - **`ui-container--wide`** — documented wide preset (`--container-wide`,
436
+ default 82rem) for app/marketing shells. `ui.container({ wide: true })`.
437
+ - **`ui-siteheader--sticky`** — structural sticky only (the floating-card
438
+ skin stays consumer identity, deliberately not shipped).
439
+ - **`ui-dotmatrix`** (+`__cell`, `--hot`, `--accent`) — data-bound dot
440
+ grid, the on-brand counterpart to the decorative `ui-dotgrid`; the
441
+ data→cell mapping stays the consumer's.
442
+ - **`ui-num`** (+`--pos`, `--neg`, `--muted`) — the tabular / end-aligned
443
+ / P&L-tone numeric vocabulary the table has shipped since 0.1.0, freed
444
+ from `.ui-table` so cards, stats and inline figures share one
445
+ contract. `ui.num({ tone })`. (Two independent admin consumers were
446
+ reinventing `.text-green`/right-align outside a table.)
447
+ - **`ui-badge--muted`** — the idle / unknown / "no signal yet" status
448
+ tone, distinct from the default tinted badge. `ui.badge({ tone:
449
+ 'muted' })`. Token-safe, no new hue.
450
+
451
+ ### Fixed
452
+
453
+ - **`ui-card--interactive`** had no keyboard cue (cards typically wrap a
454
+ single link): added `:focus-within` border + a `--shadow-raised` ring
455
+ on hover. a11y completeness fix.
456
+
457
+ ### Changed
458
+
459
+ - dist raw size budget recalibrated 64 kB → 76 kB (post-0.3.2 bundle is
460
+ ~64.5 kB raw / ~11.1 kB gzip; ~18% raw headroom restored). The gzip
461
+ cap (12 kB) is unchanged — the wire-size contract still holds.
462
+
463
+ ## 0.3.1 — 2026-05-16
464
+
465
+ Adoption + gap-closing pass driven by a 12-perspective review (two Opus
466
+ analyses + two five-model AgentMix deep runs). **All additions are
467
+ non-breaking** (additive classes/tokens/behaviors; short token names
468
+ kept as permanent aliases) — so this is a patch, per the 0.x policy
469
+ (only breaking changes bump the minor); the 0.3.0 legacy removal
470
+ stands. Tracked scope: ROADMAP.md.
471
+
472
+ ### Added
473
+
474
+ - **Components/behaviors** (all SSR-safe, idempotent, cleanup-returning,
475
+ dependency-free): `ui-combobox` + `initCombobox` (WAI-ARIA APG
476
+ combobox); `ui-popover` + `initPopover` (collision-aware, native
477
+ top-layer when available); `initFormValidation` (Constraint
478
+ Validation → `aria-invalid`/`aria-describedby` + error summary);
479
+ `initTableSort` (sortable `aria-sort` headers + row selection,
480
+ `bronto:selectionchange`); `.ui-button[aria-busy]` loading state;
481
+ toast dismiss button + separate assertive region for `danger`.
482
+ - **Forms:** `ui-input-group`(+`__addon`), `ui-file`, `ui-range`,
483
+ `ui-error-summary`.
484
+ - **Layout:** `ui-sidebar`, `ui-switcher`, `ui-center`, `ui-ratio`;
485
+ opt-in `ui-cq` container queries for `ui-grid` / `ui-app-metrics`.
486
+ - **Tokens:** semantic `--bronto-color-*` tier, `--accent-1..6` /
487
+ `--surface-1..6` ramps, `--z-*` stacking scale (every framework
488
+ `z-index` now resolves through it; values unchanged).
489
+ - **DX:** generated drift-checked `docs/reference.md`; VS Code
490
+ `classes/vscode.css-custom-data.json` (token IntelliSense, exported
491
+ as `./vscode.css-custom-data.json`); per-framework integration guides
492
+ + Tailwind interop recipe; `examples/{vanilla-vite,astro,sveltekit}`
493
+ + consumer-smoke CI matrix; README badges; `ROADMAP.md`;
494
+ `MIGRATIONS.json` + `docs/migrations/0.2-to-0.3.md`;
495
+ `demo/theme-playground.html` (live contrast checker).
496
+ - **a11y:** `role="switch"` contract + forced-colors switch cues;
497
+ `@supports(anchor-name)` tooltip un-clipping.
498
+
499
+ ### Fixed
500
+
501
+ - **Release hygiene:** the `0.3.0` section was still labelled
502
+ `unreleased` after `v0.3.0` shipped to npm `latest`. Dated it
503
+ correctly and added a `check:release` gate (in `npm run check`) that
504
+ fails when `package.json`'s version maps to an `unreleased` changelog
505
+ heading, so a published version can never again be marked unreleased.
506
+
507
+ ### Changed
508
+
509
+ - `npm run check` is now 14 gates (+`check:release`, `check:reference`,
510
+ `check:vscode`); `prepack` regenerates the reference + VS Code data.
511
+ - CONTRIBUTING.md documents a deprecate-one-minor policy so
512
+ "minor may break" is predictable.
513
+
514
+ ## 0.3.0 — 2026-05-16
515
+
516
+ ### Multi-POV review hardening
517
+
518
+ A six-perspective review (DX/contract, CSS architecture, a11y,
519
+ release/supply-chain, plus AgentMix) drove this pass.
520
+
521
+ **BREAKING (token / contract level)**
522
+
523
+ - **New `--accent-text` token** (= `var(--accent-strong)`). Everywhere
524
+ the accent was used as _foreground text_ (links on hover, active
525
+ nav/tab/accordion, prose markers, eyebrows, chips, breadcrumb,
526
+ pagination) now resolves through `--accent-text`, not raw `--accent`,
527
+ so a pale re-brand no longer silently fails text contrast.
528
+ _Migration:_ none for default themes (visually identical). If you
529
+ re-brand `--accent` to a light hue, also set `--accent-text` to a
530
+ dark-enough value (see docs/theming.md).
531
+ - **`--focus-ring` is now solid `var(--accent)`** (was an unused
532
+ 50%/55% transparent mix) and every focus outline is wired to it.
533
+ Default focus appearance is unchanged; the `[data-contrast=high]` /
534
+ `prefers-contrast` promotion and per-theme `--focus-ring` overrides
535
+ now actually take effect. _Migration:_ none unless you relied on the
536
+ (previously dead) token value.
537
+ - **`classes/index.d.ts` / `tokens/index.d.ts` are now generated
538
+ literal types.** `cls` exposes literal keys+values; token views use
539
+ `ColorKey`/`ScaleKey`/`*TokenName` unions; `themeColor` takes
540
+ `ThemeName`. Mistyped keys are now compile errors. _Migration:_ fix
541
+ any code that relied on the old `Record<string,string>` (e.g. reading
542
+ a non-existent key and getting `string` instead of an error). JS
543
+ token keys are kebab-case — `themeColor('dark')['accent-soft']`.
544
+
545
+ **Fixed**
546
+
547
+ - **a11y (WCAG AA):** `.ui-chip--accent`, legacy `.eyebrow` group and
548
+ `.tag-list--compact` first child no longer use raw `--accent` as
549
+ small text (was ~3.9:1 in light).
550
+ - **a11y:** native `<dialog>` returns focus to its trigger on _every_
551
+ close path (Esc, close button, backdrop light-dismiss, programmatic).
552
+ - **a11y:** the toast `aria-live` stack is a persistent region — no
553
+ longer created-then-destroyed per drain, fixing dropped first /
554
+ post-drain screen-reader announcements.
555
+ - **a11y:** `.ui-tab:focus-visible` is now visually distinct from the
556
+ active-tab underline (inset ring).
557
+ - **DX:** the `.` export is conditional (`style`/`default`) instead of a
558
+ bare `.css` string, so type-aware tooling no longer mis-resolves the
559
+ package root. The root is CSS-only (documented).
560
+
561
+ **Changed (non-breaking)**
562
+
563
+ - Behavior initializers (`initThemeToggle`, `dismissible`, `initDialog`,
564
+ `initDisclosure`, `initTabs`) are now idempotent — re-init (HMR,
565
+ framework remount, repeat calls) replaces rather than stacking
566
+ duplicate listeners. Tab ids use a module-global counter so separate
567
+ islands never collide on `bronto-tab-1`.
568
+ - New drift gate `check:dts` (generated `.d.ts` ⇄ JS runtime), wired
569
+ into `npm run check` and `prepack`. `docs/architecture.md` drift table
570
+ and release-gating section corrected to the real four-job DAG
571
+ (`validate` + `e2e` → `publish-npm` → `release-notes`).
572
+ - README: explicit "do not mix a bundle with a raw leaf import" hazard
573
+ warning; a Versioning section; size/`@import`-depth prose de-drifted.
574
+ - Tests: +3 unit tests (dialog focus-return, initializer idempotency,
575
+ global-unique tab ids); +5 e2e a11y tests (RTL axe pass, dialog
576
+ focus-return on Escape, persistent toast live region, disclosure
577
+ toggle, modal computed-contrast instead of a blanket rule disable);
578
+ demo gained a `[data-bronto-disclosure]` instance (was untested).
579
+ _Release note:_ the visual-snapshot baselines (`test/e2e/__screenshots__`)
580
+ are intentionally stale after the contrast / focus-ring / legacy-removal
581
+ changes (cross-OS rasterisation means they can only be authored in the
582
+ pinned container, not on a dev machine). Regenerate them with one click:
583
+ run the **“Update visual baselines”** workflow (`workflow_dispatch`,
584
+ `.github/workflows/visual-baselines.yml`) from this branch — it rebuilds
585
+ them in `mcr.microsoft.com/playwright:v1.60.0-jammy` and commits them
586
+ back, after which the `e2e` gate goes green on its own. Red by design
587
+ until that runs.
588
+
589
+ **BREAKING (legacy vocabulary removed / migrated)**
590
+
591
+ The whole non-`ui-*` surface is gone; everything shipped is now under the
592
+ `.ui-*` contract and the `check-classes` drift gate.
593
+
594
+ - **Deleted (had `ui-*` equivalents):** `css/layout.css`, `css/cards.css`,
595
+ `css/typography.css` and their entire vocabulary — `.hero`,
596
+ `.project-*`, `.post-card`, `.essay-*`, `.metric-tile`, `.callout`,
597
+ `.eyebrow`, bare `.button`, `.section-head`, `.tag-list`,
598
+ `.profile-link-list`, `.page-*`, `.home-*`, `.signal-panel`,
599
+ `.worklog-summary`, … _Migration:_ use the `ui-*` content layer —
600
+ `.ui-prose`/`.ui-quote` (long-form), `.ui-card`, `.ui-eyebrow`,
601
+ `.ui-button`, `.ui-tag`/`.ui-tags`, `.ui-grid`/`.ui-stack`, the
602
+ `ui-site*` shell. `.skip-link` → `.ui-skiplink`; `.site-nav` →
603
+ `.ui-sitenav`; `.site-menu` → `.ui-sitemenu` (responsive nav) or the
604
+ new `.ui-menu-host` (a `<details>` + `.ui-menu` dropdown wrapper).
605
+ - **Renamed → first-class:** the admin shell `.app-*` → **`.ui-app-*`**
606
+ (`ui-app-shell`/`-rail`/`-topbar`/`-toolbar`/`-nav`/`-panel`/
607
+ `-content`/`-main`/`-metrics`/`-metric`/`-empty-state`, with the same
608
+ `__part` / `--mod` suffixes). The theme toggle `.theme-toggle__*` →
609
+ **`.ui-themetoggle__*`**. _Migration:_ rename these class strings in
610
+ consumer markup (or use the new `cls.app*` / `cls.themetoggle*` /
611
+ `cls.menuHost` entries). They are now typed and drift-checked.
612
+ - **Bundle collapsed:** `css/responsive.css` and `css/index.css` removed.
613
+ `ui-*` components own their breakpoints, so there is no core/full
614
+ split: `@ponchia/ui/css` now resolves to `css/core.css` (one bundle),
615
+ `@ponchia/ui` → the single `dist/bronto.css` (~54 kB / ~10 kB gzip,
616
+ was ~70/12). _Removed exports:_ `./css/index.css`,
617
+ `./css/responsive.css`, `./dist/bronto-core.css`, and the deleted
618
+ leaves' `./css/{layout,typography,cards}.css`. _Migration:_ import
619
+ `@ponchia/ui` or `@ponchia/ui/css`.
620
+
621
+ **BREAKING (per-leaf imports are now layer-safe)**
622
+
623
+ - Every `@ponchia/ui/css/<leaf>.css` export now resolves to a
624
+ self-`@layer bronto`-wrapped build (`dist/css/<leaf>.css`), so a
625
+ direct leaf import is layered by default and safe to mix with the
626
+ bundle — the silent cascade-inversion footgun is gone. The raw,
627
+ full-specificity source is still available as a deliberate escape
628
+ hatch at the explicit **`@ponchia/ui/css/unlayered/<leaf>.css`**
629
+ path. _Migration:_ none if you import the bundle. If you imported a
630
+ raw leaf *expecting* unlayered/full-specificity behaviour, switch
631
+ that import to the `css/unlayered/*` path; otherwise the now-layered
632
+ leaf is the correct (safe) default. Drift-checked by `check-dist`.
633
+
634
+ ### Tooling (external-review triage)
635
+
636
+ Adopted what fits the CSS-first / zero-runtime-dep / curated-artifact
637
+ ADR; declined what doesn't (recorded so the decision isn't re-litigated).
638
+
639
+ **Added**
640
+
641
+ - **TypeScript type gate** — `tsconfig.json` + `test/types.test-d.ts`
642
+ + `check:types` (in `npm run check`). Compiles the published `.d.ts`
643
+ and asserts, via `@ts-expect-error`, that the generated literal
644
+ `cls`/token types reject typos and `themeColor` rejects non-`ThemeName`.
645
+ Completes the review's "auto-generate .d.ts, kill drift" item
646
+ (generation + `check-dts` landed earlier this minor; this proves the
647
+ result). `typescript` is devDep-only — no runtime/types-export change.
648
+ - **Prettier** — `.prettierrc` + `check:format`/`format`, in
649
+ `npm run check`. Scoped to hand-authored non-CSS source; CSS stays
650
+ Stylelint-owned, generated artifacts and the curated Markdown/`demo`
651
+ are `.prettierignore`d so formatters never fight generators.
652
+ - **GitHub issue/PR templates** — collect `@ponchia/ui` version,
653
+ consuming framework, and surface; PR template carries the
654
+ contract/SemVer/a11y checklist.
655
+ - **Bundle-size budget tightened** — `check-dist` `BUDGET` recalibrated
656
+ 90 kB→64 kB raw / 16 kB→12 kB gzip (bundle is ~54/~10 post-cleanup),
657
+ so regrowth is gated, not just catastrophic blowouts. (Dependabot was
658
+ already added earlier this minor.)
659
+
660
+ **Declined (rationale)** — Storybook (heavy React/Vite toolchain vs the
661
+ framework-agnostic zero-dep ADR; `demo/index.html` is the self-driving
662
+ surface); Style Dictionary as a dependency (the shipped
663
+ `tokens.dtcg.json` *is* the deliberate bring-your-own platform interop;
664
+ no native consumers exist — consumers run SD themselves);
665
+ standard-version/auto-changelog (the curated narrative CHANGELOG is an
666
+ asset; commits are already conventional); Renovate (Dependabot chosen);
667
+ Lighthouse CI (the axe a11y gate + size budget already cover the
668
+ regression vectors).
669
+
670
+ ### Post-review fixes (independent Opus + AgentMix pass on this branch)
671
+
672
+ - **Fixed (HIGH, regression introduced here):** the persistent-toast
673
+ rAF deferral could resurrect an already-dismissed first toast into the
674
+ `aria-live` region (dismiss within the first frame). Now guarded by a
675
+ `dismissed` flag; `dismiss()` is idempotent. +2 unit tests polyfilling
676
+ rAF (the jsdom env had no `requestAnimationFrame`, so the path was
677
+ previously untested).
678
+ - **Fixed (HIGH, regression introduced here):** the layered per-leaf
679
+ `@ponchia/ui/css/fonts.css` (now `dist/css/fonts.css`) referenced
680
+ `url(../fonts/*)`, which from `dist/css/` resolves to the unshipped
681
+ `dist/fonts/`. `build-dist` now rewrites `../fonts/` → `../../fonts/`
682
+ for the deeper per-leaf files (the flattened bundle at `dist/` is
683
+ unaffected and unchanged). `check-dist` now also resolves every
684
+ `url(...)` in each generated file against its own location, so this
685
+ class of depth bug can't recur.
686
+ - **Fixed (docs):** README SemVer guidance was wrong — at `0.x` npm
687
+ resolves `^0.3.0` and `~0.3.0` identically (`>=0.3.0 <0.4.0`); both
688
+ hold back the breaking `0.4.0`. Corrected. Removed the stale
689
+ "legacy `site-*`/`.tag-list` kept as back-compat" line (they were
690
+ deleted this release). De-duplicated the `check-dist` paragraph in
691
+ architecture.md.
692
+ - **Hardened:** the release `publish-npm` step uses
693
+ `npm publish --ignore-scripts`, so `NODE_AUTH_TOKEN` is never exposed
694
+ to the prepack/prepublishOnly lifecycle; it ships the artifacts already
695
+ byte-verified by `validate` on the same commit.
696
+
697
+ ### Further discovered-issue cleanup
698
+
699
+ Bounded, sensible items surfaced by the reviews, now closed:
700
+
701
+ - **a11y:** new `initMenu` behavior — Escape / outside-click /
702
+ close-on-activate (with focus return to `<summary>`) for a native
703
+ `<details data-bronto-menu>` `.ui-menu` dropdown. Deliberately a
704
+ disclosure of buttons, not an over-claimed ARIA menu (review M3).
705
+ Wired in the demo; unit-tested.
706
+ - **a11y:** the active tab's selected state is re-asserted under
707
+ `forced-colors: active` (`border/colour: Highlight`) — it was
708
+ invisible in Windows High Contrast (review L3).
709
+ - **a11y (demo):** the pagination "previous" control is now a real
710
+ `disabled` button (was a focusable/clickable `aria-disabled`,
711
+ misleading on the axe-gated integration surface); arrow controls
712
+ gained accessible names; active page uses `aria-current="page"`
713
+ (review M1).
714
+ - **CI:** GitHub Pages now deploys only after the `CI` workflow
715
+ concludes **successfully** on `main` (`workflow_run` trigger, not a
716
+ bare push) — a red-e2e/broken demo can no longer be published
717
+ independently of the gates.
718
+ - **docs:** theming.md documents the one accent surface the framework
719
+ can't tune — native control `accent-color` under a pale re-brand
720
+ (review css M2).
721
+
722
+ ### Content-site layer
723
+
724
+ Promotes the proven, hand-rolled site shell into the first-class typed
725
+ contract so consumers stop reimplementing it. (The legacy `site-*` /
726
+ `.tag-list` back-compat classes referenced here were **removed** in the
727
+ same release — see the "legacy vocabulary removed / migrated" BREAKING
728
+ section above; they are not shipped.)
729
+
730
+ - **`site.css`**: `ui-container` (+`--narrow`), `ui-siteheader`
731
+ (`__brand`/`__actions`), `ui-sitenav` (active via `aria-current`, dot
732
+ cue, responsive collapse into `ui-sitemenu` — native `<details>`, no
733
+ JS), `ui-sitefooter` (`__links`), `ui-skiplink`, `ui-tags`/`ui-tag`
734
+ (`--accent`; neutral content labels, distinct from interactive
735
+ `ui-chip`), `ui-meta` (dot-separated meta row).
736
+ - **`ui-quote`** (+ `__cite`) in `content.css`: a pull-quote companion
737
+ to `.ui-prose` — emphasis by scale + a short accent rule, not a box.
738
+ - **Shiki**: `@ponchia/ui/shiki/nothing.json` — a documented optional
739
+ VS Code/TextMate theme (rationed: brand accent + greyscale),
740
+ drift-checked (`check:shiki` keeps it on-palette and in sync with the
741
+ dark `--accent`). Bring-your-own-highlighter, like `tokens.dtcg.json`.
742
+ - Full contract treatment: 18 classes + `ui.container`/`ui.tag` recipes
743
+ + `.d.ts` (guarded), cascade/exports/dist wired, demo + docs. The
744
+ `not-a-gap` items from the source review (theme-toggle CSS already
745
+ exists in `navigation.css`; `ui-timeline` too consumer-specific) were
746
+ deliberately excluded.
747
+
748
+ ## 0.2.2 — 2026-05-15
749
+
750
+ Component + mobile expansion, then a framework-grade hardening pass
751
+ (RTL, a11y, theming contract, Markdown content layer). Additive — no
752
+ existing token/selector values changed except documented WCAG fixes.
753
+
754
+ ### Framework hardening
755
+
756
+ - **RTL / logical properties**: every `css/*` physical property is now
757
+ logical (`*-inline/-block-*`), enforced by `stylelint-use-logical`
758
+ (`csstools/use-logical: always`). Render-neutral in LTR; RTL mirrors
759
+ cleanly (verified, incl. the drawer).
760
+ - **A11y**: `initTabs` behavior (WAI-ARIA Tabs keyboard pattern);
761
+ `forced-colors` (Windows High Contrast) support in `base.css`; WCAG
762
+ contrast fixes — light `--text-dim`/`--warning`, dark `--text-dim`
763
+ now ≥ 4.5:1. Badge variants drop tone-on-tinted-tone text (failed AA
764
+ at small bold) — tone now rides the border + tint, text inherits the
765
+ high-contrast neutral. `.ui-eyebrow` uses `--accent-strong` so it
766
+ clears 4.5:1 on soft surfaces too. (All surfaced by the new axe gate.)
767
+ - **Theming contract**: `--accent` is one knob — the whole accent family
768
+ is `color-mix`-derived (ratios tuned to the prior hex, ≈ zero default
769
+ drift). `data-density` (compact/comfortable) and `data-contrast=high`
770
+ + `@media (prefers-contrast: more)` presets. See `docs/theming.md`.
771
+ - **content.css**: `.ui-prose` (+ `--compact`) styles raw
772
+ Markdown-renderer HTML — headings, lists, quote, code, tables, media,
773
+ figures — with **zero per-element classes**, keeping documents
774
+ semantic and machine-readable.
775
+ - **CI regression safety**: Playwright visual snapshots of the demo
776
+ (dark / light / RTL / modal) + `@axe-core/playwright` WCAG 2.1 A/AA
777
+ gates (both themes, modal, tab keyboard pattern), as a new `visual`
778
+ CI job pinned to the Playwright container the baselines were authored
779
+ in (byte-stable, no cross-OS font flake). Catches CSS/markup
780
+ regressions structure-checks can't.
781
+ - **Prebuilt bundles**: `dist/bronto.css` + `dist/bronto-core.css` —
782
+ the `@import` graph flattened + conservatively minified into one
783
+ `@layer bronto` file (~62 kB / ~11 kB gzip), no load waterfall.
784
+ Exposed as `@ponchia/ui` (`.`) and `./dist/*`; `check:dist` keeps it
785
+ byte-fresh and in a size budget; built in `prepack`. README documents
786
+ the evergreen support floor (Chrome 111+/Safari 16.4+/Firefox 121+).
787
+
788
+ ### Multi-agent review response
789
+
790
+ Acted on a deep read-only review (AgentMix `deep`), verifying each
791
+ finding against the code first:
792
+
793
+ - **Public TS contract**: `classes/index.d.ts` was stale — added the 9
794
+ missing `ui.*` recipes (`alert`, `toast`, `progress`, `dotspinner`,
795
+ `dotbar`, `modal`, `tab`, `avatar`, `prose`) + option types, and a new
796
+ drift guard in `check-classes` so a recipe without a declaration now
797
+ fails `npm run check`.
798
+ - **RTL completeness**: `[dir='rtl']` mirrors for the cases the lint
799
+ plugin can't convert — switch thumb, theme-toggle thumb, `<select>`
800
+ marker (`background-position`), arrow-link hover nudge.
801
+ - **Bug**: `.ui-progress__bar` transitioned the (now non-existent)
802
+ physical `width`; animate `inline-size`.
803
+ - **Release gating**: `release.yml` now runs the containerised
804
+ visual/a11y suite and `publish-npm`/`release-notes` depend on it — a
805
+ tagged release can't skip what every branch push runs.
806
+ - **Hardening**: `initTabs` scopes to its own group (nested-safe);
807
+ `initDialog` root semantics documented (dialogs are document-global
808
+ by design); `scripts/serve.mjs` binds loopback + strict path
809
+ containment; fixed an invalid selector in `docs/theming.md`.
810
+
811
+ ### Second review pass (PR #3)
812
+
813
+ Independent Opus review said SHIP; the AgentMix `deep` mix flagged
814
+ more — verified each, fixed the real ones:
815
+
816
+ - **Release hygiene**: `package-lock.json` synced to `0.2.2`;
817
+ `release.yml` `release-notes` now `needs: publish-npm` (no GitHub
818
+ Release for a version that failed to publish — no split-brain).
819
+ - **RTL**: `.ui-toast-stack` used physical `inset` → logical
820
+ `inset-block`/`inset-inline` so the stack mirrors in RTL.
821
+ - **`initTabs`**: a `root` that *is* the `[data-bronto-tabs]` element is
822
+ now initialised (querySelectorAll only sees descendants); tabs are
823
+ cross-linked to panels via `aria-controls`/`aria-labelledby`
824
+ (APG-complete), ids minted only where absent.
825
+ - **Toast**: dropped the per-item `role="status"` nested inside the
826
+ `aria-live` stack (double-announcement risk).
827
+ - **DTCG**: `--shadow: none` was typed `color` (name matched the colour
828
+ regex, value failed the shadow test) — shadows are now classified by
829
+ name first → `$type: "shadow"`.
830
+ - **Tests**: new `ui.*` recipe-output coverage + a `.d.ts`-declaration
831
+ assertion; `initTabs` root-self/APG test; `tokens.dtcg.json` shape
832
+ test (shadow typing + the null-+-extension invariant); `serve.mjs`
833
+ `safePath` traversal unit test. The RTL e2e now waits for the
834
+ mirrored end-state instead of racing a fixed sleep (the CSS was
835
+ correct; the old test read mid-transition).
836
+
837
+ ### Discovered follow-ups
838
+
839
+ Closed the remaining items surfaced across the review/verification:
840
+
841
+ - **Regression tests for the review fixes**: `serve.mjs` refactored to a
842
+ pure exported `safePath()` with a traversal/sibling-prefix unit test;
843
+ `initTabs` nested-isolation test — which **caught a real bug in the
844
+ first nested fix** (the outer group's delegated click still fired for
845
+ nested tabs); now gated on owned membership, not DOM containment. New
846
+ e2e assertion that RTL truly mirrors the switch transform + select
847
+ marker (not just box model).
848
+ - **Print** (`@media print` in `base.css`): ink-on-white, chrome hidden,
849
+ `break-inside` guards, prose link URLs surfaced, `@page` margin.
850
+ - **prefers-reduced-data**: points `--display`/`--dot-font` at the mono
851
+ stack so the Doto webfont is never fetched for data-saver users.
852
+ - **DTCG export**: `@ponchia/ui/tokens.dtcg.json` (W3C Design Tokens
853
+ format) generated from the model, drift-checked (`check:dtcg`), built
854
+ in `prepack`. Runtime-derived tokens are spec-shaped with
855
+ `$value:null` + `$extensions` rather than fabricated numbers.
856
+
857
+ ### Earlier in this cycle
858
+
859
+ Component + mobile expansion. No token/selector changes to existing
860
+ classes — purely additive; existing consumers are unaffected.
861
+
862
+ - **Dot loaders**: new orbital `ui-dotspinner` (the Nothing-signature
863
+ ring loader, `--sm`/`--lg`), `ui-dotbar--indeterminate` sweep, and a
864
+ linear `ui-progress` (determinate via `--value`, plus
865
+ `ui-progress--indeterminate`).
866
+ - **feedback.css**: `ui-alert` / callout (tones + dismissible), `ui-toast`
867
+ + `ui-toast-stack`, CSS-only `ui-tooltip`.
868
+ - **overlay.css**: `ui-modal` + `ui-modal--drawer` on native `<dialog>`
869
+ (bottom-sheet on mobile), `ui-menu` dropdown.
870
+ - **disclosure.css**: `ui-tabs` (ARIA + `.is-active` contract, scrollable
871
+ on mobile), `ui-accordion` (styled `<details>`), `:has()`-driven
872
+ `ui-segmented`, `ui-breadcrumb`, `ui-pagination`, `ui-avatar` /
873
+ `ui-avatar-group`.
874
+ - **Behaviors**: `initDialog` (native `<dialog>` open/close + backdrop
875
+ light-dismiss) and `toast()`. SSR-safe; covered by the test suite.
876
+ - **Mobile**: 44px touch targets for buttons/inputs/checkboxes on coarse
877
+ pointers; component-level breakpoints for modal/drawer/menu/tabs.
878
+ - **Contract**: 53 new classes added to the typed `cls` registry + recipes
879
+ (`ui.alert`, `ui.toast`, `ui.progress`, `ui.dotspinner`, `ui.modal`,
880
+ `ui.tab`, `ui.avatar`); `npm run check` and the 20-test suite stay green.
881
+ - **Docs**: removed the stale "not published yet" install note (the
882
+ package is live on npm); documented the new layers and behaviors.
883
+
884
+ ## 0.2.1 — 2026-05-15
885
+
886
+ - Remove private project names and personal paths from docs and CSS
887
+ comments (no code/selector/token changes). Supersedes 0.2.0, which
888
+ carried those references in shipped CSS comments.
889
+
890
+ ## 0.2.0 — 2026-05-15
891
+
892
+ Architecture: keep plain CSS as the universal substrate, add thin optional
893
+ layers on top (see `docs/architecture.md`). No `css/*` selector/token values
894
+ changed — existing consumers are visually unaffected.
895
+
896
+ - **Cascade**: the whole framework now ships inside `@layer bronto`, so
897
+ un-layered consumer CSS overrides it without specificity fights. Applied
898
+ only at the bundle entrypoints (`core.css`, `index.css`); source files
899
+ unchanged. _Behavioural change for consumers that override via specificity._
900
+ - **Fonts**: `@font-face` moved from `tokens.css` to `css/fonts.css` with
901
+ package-relative URLs (`../fonts/*`) — no more absolute `/fonts`
902
+ assumption. Bundled into `core.css`/`index.css`; exported standalone.
903
+ - **`@ponchia/ui/tokens`**: design tokens as data (`index.js` canonical,
904
+ `index.json` generated, `themeColor()` helper, typed).
905
+ - **`@ponchia/ui/classes`**: typed class-name contract — `cls` registry,
906
+ `ui.*` recipe builders, `cx()`. Framework-agnostic, returns strings.
907
+ - **`@ponchia/ui/behaviors`**: vanilla, SSR-safe, dependency-free helpers —
908
+ `applyStoredTheme`, `initThemeToggle`, `dismissible`, `initDisclosure`.
909
+ - **Drift control**: `npm run check` adds `check-tokens` and
910
+ `check-classes`; the demo now drives itself via the shipped modules.
911
+ - **Packaging**: `exports` for the new entrypoints (with `types`),
912
+ `sideEffects` for tree-shaking, `files` widened.
913
+ - **Distribution decided**: published to npm as **`@ponchia/ui`** (the
914
+ `@bronto` scope isn't ownable; the `@layer bronto` / `data-bronto-*`
915
+ namespace is unchanged). `private` removed, `publishConfig`
916
+ (`access: public`, `provenance: true`), `repository`/`homepage`/`bugs`
917
+ added. `release.yml` now gates on a real `npm publish` job
918
+ (`validate` → `publish-npm`). Rationale + pre-publish blockers
919
+ (LICENSE, `NPM_TOKEN`, version bump) in `docs/architecture.md`.
920
+
921
+ ## 0.1.0 — 2026-05-15
922
+
923
+ First standalone release. Extracted into its own standalone package.
924
+
925
+ - Re-skinned to a Nothing-inspired design language: monochrome dual
926
+ light/dark palette, single red accent, Doto dot-matrix display type, flat
927
+ hairline surfaces, sharp radii, no soft shadows.
928
+ - New `motion.css` — keyframes (migrated from the old responsive layer so
929
+ core-only consumers keep their animations) plus reveal / stagger /
930
+ skeleton / spinner / caret utilities and full reduced-motion handling.
931
+ - New `dots.css` — dot-grid surfaces, dotted rule, status dot (with live
932
+ pulse), dot loader, dot progress bar, matrix-reveal.
933
+ - New `forms.css` — input, select, textarea, search, switch, checkbox.
934
+ - New `table.css` — `ui-table` with dense / comfortable / lined variants and
935
+ numeric helpers for admin dashboards.
936
+ - Expanded `app.css` into a full admin shell: sidebar rail with dot nav,
937
+ sticky blurred topbar, toolbar, panel, metric tiles, empty state, mobile
938
+ rail collapse.
939
+ - Re-skinned primitives, navigation (dot active indicator), and the
940
+ semantic typography/eyebrows.
941
+ - Renamed `theme.css` → `tokens.css`; dropped the `components.css`
942
+ indirection; `core.css` now bundles the full set; `index.css` =
943
+ core + responsive. `package.json` exports updated accordingly.
944
+ - Doto fonts vendored into `fonts/` as the canonical home.
945
+ - Added `demo/index.html` kitchen sink.