origam 2.13.0 → 2.14.1

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +2012 -0
  2. package/README.md +4 -4
  3. package/dist/src/components/Avatar/OrigamAvatar.vue +14 -0
  4. package/dist/src/components/ColorPicker/OrigamColorPickerEdit.vue +7 -1
  5. package/dist/src/components/ColorPickerField/OrigamColorPickerField.vue +12 -1
  6. package/dist/src/components/DatePicker/OrigamDatePickerMonth.vue +14 -2
  7. package/dist/src/components/DatePickerField/OrigamDatePickerField.vue +14 -1
  8. package/dist/src/components/Field/OrigamField.vue +9 -7
  9. package/dist/src/components/Messages/OrigamMessages.vue +1 -0
  10. package/dist/src/components/Select/OrigamSelect.vue +16 -1
  11. package/dist/src/components/TextField/OrigamTextField.vue +1 -1
  12. package/dist/src/composables/Commons/defaults.composable.cjs +1 -1
  13. package/dist/src/composables/Commons/defaults.composable.js +1 -1
  14. package/dist/src/composables/Commons/style.composable.cjs +4 -1
  15. package/dist/src/composables/Commons/style.composable.js +4 -1
  16. package/dist/src/composables/Commons/teleport-typography.composable.cjs +38 -0
  17. package/dist/src/composables/Commons/teleport-typography.composable.d.ts +86 -0
  18. package/dist/src/composables/Commons/teleport-typography.composable.js +30 -0
  19. package/dist/src/composables/index.cjs +12 -0
  20. package/dist/src/composables/index.d.ts +1 -0
  21. package/dist/src/composables/index.js +1 -0
  22. package/dist/src/consts/Commons/rounded.const.d.ts +1 -1
  23. package/dist/src/enums/Commons/rounded.enum.cjs +12 -2
  24. package/dist/src/enums/Commons/rounded.enum.d.ts +25 -0
  25. package/dist/src/enums/Commons/rounded.enum.js +10 -0
  26. package/dist/src/themes/origam.theme.cjs +2 -2
  27. package/dist/src/themes/origam.theme.js +2 -2
  28. package/dist/src/types/Commons/rounded.type.d.ts +14 -2
  29. package/dist/src/utils/Commons/color.util.cjs +2 -2
  30. package/dist/src/utils/Commons/color.util.d.ts +24 -1
  31. package/dist/src/utils/Commons/color.util.js +2 -2
  32. package/package.json +9 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,2012 @@
1
+ # origam — Changelog
2
+
3
+ This changelog tracks releases of the `origam` package itself. Token-value
4
+ churn (Tokens Studio sync from Figma, semantic primitive shifts) lives in
5
+ [`tokens/CHANGELOG.md`](./tokens/CHANGELOG.md) — keep them separate so
6
+ "the design moved" never blocks a release of "the code shipped a new
7
+ component".
8
+
9
+ Format inspired by [Keep a Changelog](https://keepachangelog.com).
10
+ This project follows [Semantic Versioning](https://semver.org).
11
+
12
+ ---
13
+
14
+ ## [Unreleased]
15
+
16
+ ## [2.14.1] - 2026-08-13
17
+
18
+ Two fixes, both found while converting variants to props presets on the v3
19
+ line. Neither is caused by that work — they predate it and affect this 2.x
20
+ line, so they are shipped here rather than held back for a major.
21
+
22
+ ### Fixed
23
+
24
+ - **A theme default could be silently ignored.** `usePassedProps` counted a key
25
+ present in `vnode.props` with the value `undefined` as "passed". Vue does not
26
+ drop a dynamically-bound key just because its current value is `undefined`, so
27
+ the most ordinary consumer pattern — `:bg-color="state.bgColor"` — reported
28
+ the prop as declared while it was empty, and the theme default was skipped.
29
+
30
+ The symptom was a component that appeared not to follow its theme, with
31
+ nothing logged and no error. Requiring a non-`undefined` value brings this in
32
+ line with Vue's own `withDefaults()`, where an `undefined` prop falls back to
33
+ its default.
34
+
35
+ - **`color-mix()` was not recognised as a CSS colour.** `isCssColor` matched
36
+ `color(...)` (Color Level 4) but not `color-mix(...)`, which is a full
37
+ `<color>` function and one the design system uses itself. Values passed that
38
+ way fell through silently. Purely additive — no caller could have depended on
39
+ a `color-mix(...)` string being dropped. `useElevation` already listed
40
+ `color-mix` in its equivalent detector; this brings `isCssColor` in line.
41
+
42
+ ### Notes for consumers
43
+
44
+ No API change, no visual change by default. If a component of yours looked like
45
+ it was ignoring a theme default while you bound a possibly-`undefined` value to
46
+ one of its props, that is the first fix above.
47
+
48
+ ## [2.14.0] - 2026-08-07
49
+
50
+ Reported from a real integration, not from a code review. Each item was
51
+ reproduced before being fixed, and the fix verified against the running
52
+ component.
53
+
54
+ ### ⚠️ BREAKING — `<origam-avatar>` is now a circle by default
55
+
56
+ The base theme sized avatar corners with `rounded: 'lg'` — a 16px rounded
57
+ square. Every avatar that does not set `rounded` explicitly now renders as a
58
+ circle (`rounded: 'full'`).
59
+
60
+ **Why**: an avatar is round in the overwhelming majority of interfaces, and
61
+ until now obtaining one required knowing that a second, undocumented radius
62
+ scale existed (see the `rounded` fix below). The mismatch showed: a
63
+ rounded-square avatar dropped into a circular trigger left a visible ring
64
+ around it — reported from the field as *"a square inside a circle"*.
65
+
66
+ **Migration** — restore the previous shape app-wide in one line, through the
67
+ theme layer:
68
+
69
+ ```ts
70
+ createOrigam({ theme: { components: { 'origam-avatar': { rounded: 'lg' } } } })
71
+ ```
72
+
73
+ or per instance with `<origam-avatar rounded="lg">`.
74
+
75
+ ### Fixed — dead `aria-describedby` on input fields (accessibility)
76
+
77
+ A screen reader announced **no validation message at all**. The defect was
78
+ invisible to the eye: the message rendered correctly.
79
+
80
+ `<origam-text-field>` filtered `id` out of the props it forwarded, so
81
+ `<origam-input>` never received it and invented its own. It then derived
82
+ `<id>-messages` and pointed `aria-describedby` at it — but `<origam-messages>`
83
+ declared an `id` prop and applied it to **no element**, giving each message a
84
+ different id instead. The reference resolved to nothing.
85
+
86
+ `<origam-messages>` now applies the received `id` to its root, and
87
+ `<origam-text-field>` no longer filters `id` out. Verified in a browser:
88
+ `aria-describedby` resolves to an element that exists.
89
+
90
+ **Second effect of the same fix**: an `id` passed to a text field now actually
91
+ reaches its `<input>`. `document.getElementById(...)` finds it, and an
92
+ external `<label for>` associates correctly — both previously failed silently.
93
+
94
+ ### Fixed — `<origam-avatar size>` ignored outside a flex container
95
+
96
+ The root element declared no `display`, so with `tag="span"` it inherited the
97
+ browser's `inline` — and an inline box ignores `width`/`height`. The avatar
98
+ collapsed to its content and `size` did nothing, with no warning.
99
+
100
+ It appeared to work only when the parent happened to be a flex container,
101
+ which blockifies its children. Measured on one real page: the same component
102
+ rendered 32×32 under `inline-flex` but **30×30 under `position: relative`**
103
+ for a requested size of 72.
104
+
105
+ The root now declares `display: inline-flex` — `inline-flex` rather than
106
+ `block` because `tag="span"` exists so an avatar can sit in phrasing content,
107
+ inside a `<button>` whose content model rejects block-level elements. Consumers
108
+ who worked around this with `.origam-avatar { display: inline-flex }` can drop
109
+ the override; it remains harmless.
110
+
111
+ ### Fixed — `rounded` rejected the token scale, hiding the circular variant
112
+
113
+ `TRounded` only described the component scale (`x-small` … `x-large`,
114
+ `shaped`), none of which produces a circle. The radius rungs — `none`, `xs`,
115
+ `sm`, `md`, `lg`, `xl` and **`full`**, the one that yields a circle — were
116
+ already honoured at runtime (the brand themes have shipped `rounded: 'md'` for
117
+ a while) but failed to type-check and never appeared in autocompletion.
118
+
119
+ A round avatar was therefore undiscoverable: it required knowing that a second,
120
+ undocumented scale existed. Reported from the field as *"a square inside a
121
+ circle"* — a rounded-square avatar in a circular trigger leaves a visible ring.
122
+
123
+ The rungs are now a first-class `ROUNDED_TOKEN` enum and part of `TRounded`.
124
+ `rounded="full"` type-checks. The two scales stay separate on purpose: one
125
+ names an intent (`shaped`), the other a radius rung — `x-small` and `xs` are
126
+ not interchangeable.
127
+
128
+ ### Fixed — the published package shipped no changelog
129
+
130
+ `files` listed `CHANGELOG.md`, but the file lives at the monorepo root and had
131
+ no counterpart in `packages/ds/`, so npm silently packed nothing. Consumers had
132
+ no way to tell what a release contained. A `prepack` step now copies it in —
133
+ verified: 96 kB of changelog in the tarball.
134
+
135
+ ## [2.13.0] - 2026-08-05
136
+
137
+ ### ⚠️ BREAKING — `<origam-select>` dropdown rows now follow the control
138
+
139
+ A `<origam-select>` opened a menu whose rows measured 48px whatever the
140
+ control was set to. The most common case — a select with **no** `size` prop —
141
+ was the worst: a 48px row under a 36px control. Rows now match the control
142
+ exactly at every size (36/36 unsized, 28/28 small, 44/44 large, 60/60
143
+ x-large + comfortable).
144
+
145
+ **Why this is breaking**: the fix corrects how `density` is applied to
146
+ **every** list, not only dropdowns. Density becomes a coherent ±8px offset
147
+ aligned on the field model, where it previously amounted to −24px in
148
+ `compact` and did nothing at all in `comfortable`:
149
+
150
+ | standalone list row, no `size` | before | after |
151
+ |---|---|---|
152
+ | `default` | 56px | 56px |
153
+ | `compact` | 32px | **48px** |
154
+ | `comfortable` | 56px | **64px** |
155
+
156
+ The old 32px came from counting density three times (−8 on `min-height`,
157
+ −8 on each block padding). `comfortable` had no rule at all and was a no-op.
158
+ Anything relying on `density="compact"` on an `<origam-list>` will render
159
+ 8–16px taller.
160
+
161
+ `ISizeProps` is added to `IListProps` (a forwarding prop — the list paints
162
+ nothing from it) and to `IListItemProps` (which paints the row height).
163
+
164
+ ### ⚠️ BREAKING — `useStyle()` no longer overwrites a consumer `id`
165
+
166
+ `useStyle()` generated its own id and the component put it on its root,
167
+ silently overwriting an `id` passed as a prop. On `<origam-btn>` this broke
168
+ every `<label for>`, `aria-labelledby`, `aria-controls` and
169
+ `aria-describedby` pointing at a button. The consumer's `id` now wins and the
170
+ generated rule follows it.
171
+
172
+ **Why this is breaking**: components previously always carried a generated
173
+ id. Anything keyed on that generated id (a CSS selector, a test locator)
174
+ will no longer match when the consumer supplies an `id`.
175
+
176
+ ### Fixed
177
+
178
+ - **The generated stylesheet was being discarded entirely.** `StyleValue`
179
+ includes `false`, and Vue resolves an unpassed prop whose declared type
180
+ contains Boolean to `false`, never `undefined` — so every component
181
+ exposing the shared `style` prop emitted `#id {false}`, which is not a
182
+ declaration. Browsers dropped it through error recovery; jsdom dropped the
183
+ **whole** sheet. Nested style arrays were also flattened one level only,
184
+ landing an object in the sheet as `[object Object]`.
185
+ - **CSS injection through the `id` prop.** Now that the id comes from the
186
+ consumer and is interpolated verbatim into a `<style>` element, an id such
187
+ as `a { } body { display: none }` appended arbitrary rules to the document.
188
+ A new `escapeCssIdent()` utility delegates to native `CSS.escape`, with a
189
+ spec-conformant fallback for jsdom and SSR, which expose no `CSS` global.
190
+ - **Interpolation placeholders silently rendered empty.** `t()` is variadic
191
+ — `t(key, ...params)` — but five call sites passed it an **array**, so the
192
+ array landed in `params[0]` where the template expected the value itself.
193
+ This stayed invisible while `stringifyParam` accepted any value (`[3]`
194
+ stringified to `"3"`); once it was narrowed to primitives, an array began
195
+ rendering as an **empty string** and the placeholder vanished. Visible
196
+ effects: `<origam-file-field display="counter">` displayed `" files"`
197
+ instead of `"3 files"`, and the `max_length` validation message of
198
+ `<origam-text-field>` / `<origam-textarea-field>` showed an empty limit.
199
+
200
+ ### Security
201
+
202
+ `brace-expansion` overrides were pinned to the **vulnerable** versions
203
+ (`^2.1.2` / `^5.0.8`) — set for an earlier advisory and never moved since.
204
+ They are raised to the patched floors (`^2.1.4` / `^5.0.9`), which clears
205
+ the three `high` DoS advisories (unbounded expansion) that `pnpm audit`
206
+ reported through `minimatch`.
207
+
208
+ Nuxt advisories published during this release cycle are cleared too:
209
+ `@nuxt/devtools` (**critical**), and five `high` on `nuxt` itself
210
+ (server-side RCE via Runtime, unauthenticated OOM crash, CPU exhaustion
211
+ while parsing). `nuxt` moves to `^4.5.1` and `typeorm` to `^0.3.31`,
212
+ staying on the `0.3.x` line — the fix landed there, and `1.x` is a
213
+ breaking change. Every advisory listed above is cleared as of this release.
214
+
215
+ Advisories are a moving target, so this is stated as of a date rather than as
216
+ a standing guarantee: on 2026-08-11, `pnpm audit --prod` surfaced three new
217
+ `high` advisories unrelated to the ones above (`image-size` ICNS and JXL/HEIF
218
+ parsers, `nanoid` custom generators). They arrived after this release and are
219
+ tracked separately — run `pnpm audit --prod` yourself for the current state.
220
+
221
+ Scope note for consumers: the published `origam` package depends only on
222
+ `@mdi/font` and `qrcode-generator`, with `shiki` / `vue` / `vue-i18n` /
223
+ `vue-router` as peers. None of the packages above ship with it — they all
224
+ come from `@origam/marketing`, which is `private` and never published. You
225
+ were never exposed; the deployed site was.
226
+
227
+ One change does reach the published package. Nuxt 4.5 made TypeScript
228
+ unable to name the type `defineNuxtModule` returns, so the default export
229
+ of the `origam/nuxt` sub-export is now explicitly annotated as
230
+ `NuxtModule`. Behaviour is unchanged — it is a declaration-emit fix.
231
+
232
+ ### ⚠️ BREAKING — i18n locale keys migrated to `snake_case`
233
+
234
+ All locale keys shipped by the DS (`packages/ds/src/assets/locales/en.json`,
235
+ `fr.json`) now follow the project-wide i18n convention: every key **segment**
236
+ is `snake_case` (`a-z`, `0-9`, `_`). Nested structure is unchanged — only the
237
+ casing of each segment changes (e.g. `dataTable.ariaLabel.sortBy` →
238
+ `data_table.aria_label.sort_by`).
239
+
240
+ **Why this is breaking**: `createOrigam({ messages })` lets a consumer
241
+ override any locale message by key path. Any override keyed on an old
242
+ camelCase path (e.g. `'origam.dataTable.sortBy'`) will silently stop applying
243
+ — the DS falls back to its own bundled (English) string with **no error**,
244
+ which is easy to miss until a French (or other locale) screen renders in
245
+ English.
246
+
247
+ **All 65 `t()` call sites and their indirect prop-default equivalents were
248
+ updated in the same commit** — verified with a cross-reference script (every
249
+ literal `origam.*` key still referenced in `packages/ds/src` was checked
250
+ against both locale files; zero leftover old-casing key strings remain
251
+ anywhere in `packages/ds/src`). Fallback strings (the second argument to
252
+ `t(key, fallback)`) are unchanged — only the key paths moved.
253
+
254
+ If you override any DS locale key, update the key path using the table below.
255
+
256
+ | Old key | New key |
257
+ |---|---|
258
+ | `origam.bottomNav.ariaLabel` | `origam.bottom_nav.aria_label` |
259
+ | `origam.calendar.ariaLabel` | `origam.calendar.aria_label` |
260
+ | `origam.calendar.monthGrid` | `origam.calendar.month_grid` |
261
+ | `origam.calendar.moreEvents` | `origam.calendar.more_events` |
262
+ | `origam.calendar.viewSwitcher` | `origam.calendar.view_switcher` |
263
+ | `origam.carousel.ariaLabel.delimiter` | `origam.carousel.aria_label.delimiter` |
264
+ | `origam.clipboard.copiedAriaLabel` | `origam.clipboard.copied_aria_label` |
265
+ | `origam.clipboard.copyAriaLabel` | `origam.clipboard.copy_aria_label` |
266
+ | `origam.code.copiedAriaLabel` | `origam.code.copied_aria_label` |
267
+ | `origam.code.copyAriaLabel` | `origam.code.copy_aria_label` |
268
+ | `origam.colorPicker.canvas.ariaLabel` | `origam.color_picker.canvas.aria_label` |
269
+ | `origam.colorPicker.canvas.value` | `origam.color_picker.canvas.value` |
270
+ | `origam.confirmEdit.cancel` | `origam.confirm_edit.cancel` |
271
+ | `origam.confirmEdit.ok` | `origam.confirm_edit.ok` |
272
+ | `origam.dataFooter.firstPage` | `origam.data_footer.first_page` |
273
+ | `origam.dataFooter.itemsPerPageAll` | `origam.data_footer.items_per_page_all` |
274
+ | `origam.dataFooter.itemsPerPageText` | `origam.data_footer.items_per_page_text` |
275
+ | `origam.dataFooter.lastPage` | `origam.data_footer.last_page` |
276
+ | `origam.dataFooter.nextPage` | `origam.data_footer.next_page` |
277
+ | `origam.dataFooter.pageText` | `origam.data_footer.page_text` |
278
+ | `origam.dataFooter.prevPage` | `origam.data_footer.prev_page` |
279
+ | `origam.dataIterator.loadingText` | `origam.data_iterator.loading_text` |
280
+ | `origam.dataIterator.noResultsText` | `origam.data_iterator.no_results_text` |
281
+ | `origam.dataTable.ariaLabel.activateAscending` | `origam.data_table.aria_label.activate_ascending` |
282
+ | `origam.dataTable.ariaLabel.activateDescending` | `origam.data_table.aria_label.activate_descending` |
283
+ | `origam.dataTable.ariaLabel.activateNone` | `origam.data_table.aria_label.activate_none` |
284
+ | `origam.dataTable.ariaLabel.sortAscending` | `origam.data_table.aria_label.sort_ascending` |
285
+ | `origam.dataTable.ariaLabel.sortDescending` | `origam.data_table.aria_label.sort_descending` |
286
+ | `origam.dataTable.ariaLabel.sortNone` | `origam.data_table.aria_label.sort_none` |
287
+ | `origam.dataTable.itemsPerPageText` | `origam.data_table.items_per_page_text` |
288
+ | `origam.dataTable.sortBy` | `origam.data_table.sort_by` |
289
+ | `origam.dataTableRow.collapseRow` | `origam.data_table_row.collapse_row` |
290
+ | `origam.dataTableRow.expandRow` | `origam.data_table_row.expand_row` |
291
+ | `origam.datePicker.header` | `origam.date_picker.header` |
292
+ | `origam.datePicker.input.placeholder` | `origam.date_picker.input.placeholder` |
293
+ | `origam.datePicker.itemsSelected` | `origam.date_picker.items_selected` |
294
+ | `origam.datePicker.range.header` | `origam.date_picker.range.header` |
295
+ | `origam.datePicker.range.title` | `origam.date_picker.range.title` |
296
+ | `origam.datePicker.title` | `origam.date_picker.title` |
297
+ | `origam.datePickerRangeField.text` | `origam.date_picker_range_field.text` |
298
+ | `origam.fileField.browse` | `origam.file_field.browse` |
299
+ | `origam.fileField.counter` | `origam.file_field.counter` |
300
+ | `origam.fileField.counterSize` | `origam.file_field.counter_size` |
301
+ | `origam.fileField.dropzoneSubtitle` | `origam.file_field.dropzone_subtitle` |
302
+ | `origam.fileField.dropzoneTitle` | `origam.file_field.dropzone_title` |
303
+ | `origam.fileInput.counter` | `origam.file_input.counter` |
304
+ | `origam.fileInput.counterSize` | `origam.file_input.counter_size` |
305
+ | `origam.fileUpload.browse` | `origam.file_upload.browse` |
306
+ | `origam.fileUpload.divider` | `origam.file_upload.divider` |
307
+ | `origam.fileUpload.title` | `origam.file_upload.title` |
308
+ | `origam.infiniteScroll.empty` | `origam.infinite_scroll.empty` |
309
+ | `origam.infiniteScroll.loadMore` | `origam.infinite_scroll.load_more` |
310
+ | `origam.input.appendAction` | `origam.input.append_action` |
311
+ | `origam.input.prependAction` | `origam.input.prepend_action` |
312
+ | `origam.media.castToDevice` | `origam.media.cast_to_device` |
313
+ | `origam.media.nextTrack` | `origam.media.next_track` |
314
+ | `origam.media.normalSpeed` | `origam.media.normal_speed` |
315
+ | `origam.media.playbackSpeed` | `origam.media.playback_speed` |
316
+ | `origam.media.previousTrack` | `origam.media.previous_track` |
317
+ | `origam.media.stopCasting` | `origam.media.stop_casting` |
318
+ | `origam.noDataText` | `origam.no_data_text` |
319
+ | `origam.pagination.ariaLabel.currentPage` | `origam.pagination.aria_label.current_page` |
320
+ | `origam.pagination.ariaLabel.first` | `origam.pagination.aria_label.first` |
321
+ | `origam.pagination.ariaLabel.last` | `origam.pagination.aria_label.last` |
322
+ | `origam.pagination.ariaLabel.next` | `origam.pagination.aria_label.next` |
323
+ | `origam.pagination.ariaLabel.page` | `origam.pagination.aria_label.page` |
324
+ | `origam.pagination.ariaLabel.previous` | `origam.pagination.aria_label.previous` |
325
+ | `origam.pagination.ariaLabel.root` | `origam.pagination.aria_label.root` |
326
+ | `origam.rating.ariaLabel.item` | `origam.rating.aria_label.item` |
327
+ | `origam.stepper.progressSteps` | `origam.stepper.progress_steps` |
328
+ | `origam.stepper.stepAriaLabel` | `origam.stepper.step_aria_label` |
329
+ | `origam.timePicker.am` | `origam.time_picker.am` |
330
+ | `origam.timePicker.pm` | `origam.time_picker.pm` |
331
+ | `origam.timePicker.title` | `origam.time_picker.title` |
332
+ | `origam.video.disableCaptions` | `origam.video.disable_captions` |
333
+ | `origam.video.enableCaptions` | `origam.video.enable_captions` |
334
+ | `origam.video.enterFullscreen` | `origam.video.enter_fullscreen` |
335
+ | `origam.video.enterPip` | `origam.video.enter_pip` |
336
+ | `origam.video.exitFullscreen` | `origam.video.exit_fullscreen` |
337
+ | `origam.video.exitPip` | `origam.video.exit_pip` |
338
+
339
+ ### ⚠️ BREAKING — `<origam-title>` now defaults to `h2`, not `h1`
340
+
341
+ Every `<origam-title>` written **without** an explicit `tag` renders an
342
+ `<h2>` instead of an `<h1>`. This changes the rendered DOM, the heading
343
+ outline, any CSS keyed on `h1`, and the result of automated a11y audits.
344
+
345
+ **Why**: a document carries exactly one `h1`, so `h1` is the one level a
346
+ shared component must never emit by default — two untagged titles on a page
347
+ produced two `h1`s and broke both the heading order and the a11y audit. `h2`
348
+ is the deepest level that is always valid under a page-owned `h1`, and
349
+ repeating it is legal. The correct level depends on document position, which
350
+ the component cannot know: pass `tag` explicitly whenever it matters.
351
+
352
+ **Migration** — restore the old behaviour app-wide in one line, through the
353
+ defaults layer:
354
+
355
+ ```ts
356
+ createOrigam({ theme: { components: { 'origam-title': { tag: 'h1' } } } })
357
+ ```
358
+
359
+ ⚠️ **This migration only works from this release onwards.** On any earlier
360
+ version it silently does nothing: `OrigamTitle`'s template bound `:is="tag"`,
361
+ and in `<script setup>` a bare prop name in the template resolves against the
362
+ raw `$props`, never against the `useDefaults()` proxy — so the theme value
363
+ was read and then ignored. Fixed here by binding `:is="props.tag"`, the same
364
+ correction as the seven components in #250. Do not conclude the snippet is
365
+ wrong if you try it on 2.12.x.
366
+
367
+ ### Fixed
368
+
369
+ - **`useStyle()` no longer overwrites a consumer-supplied `id`.** The
370
+ composable returns the id a component puts on its root element *and* the
371
+ selector of the `#<id> { … }` rule it injects — the two must be the same
372
+ value or the rule stops matching. It now accepts that id from the caller
373
+ (`useStyle(styles, () => props.id)`), reactively, so an `id` that only
374
+ appears on a later render is still picked up. Applied to `OrigamBtn`, where
375
+ the generated id silently replaced the consumer's and broke every
376
+ `<label for>` / `aria-labelledby` / `aria-controls` / `aria-describedby`
377
+ association targeting a button, and to `OrigamTitle`, where element and
378
+ rule had diverged. ⚠️ **138 of the 140 components calling `useStyle` are
379
+ still affected**, in three shapes: 14 swallow a consumer `id` outright
380
+ (same defect as Btn); 114 bind no id to their root at all, so where the
381
+ component also declares an `id` prop — 192 of 217 do — that prop is inert
382
+ and a consumer simply cannot set an id (measured on `OrigamCard` and
383
+ `OrigamKbd`, which render no `id` attribute even when one is passed); and
384
+ 10 bind some other id while the rule keeps targeting the generated one,
385
+ the same divergence just fixed on Title. Tracked separately; a blanket
386
+ default was deliberately rejected because `OrigamSnackbarGroup` uses
387
+ `props.id` as a group *name*, not a DOM id.
388
+ - **The injected stylesheet is valid CSS again.** Every rule carried a bare
389
+ `false` in its body (`#id {false}`). Vue's `StyleValue` type includes
390
+ `false`, so the shared `style` prop compiles to a runtime type containing
391
+ `Boolean`, and Vue resolves an unpassed boolean-typed prop to the concrete
392
+ value `false` — which was serialised verbatim. 192 of 217 components
393
+ declare that prop. No visual impact: Chromium, Firefox and WebKit all
394
+ discard the invalid declaration by CSS error recovery and apply the rest
395
+ unchanged. The cost was invalid CSS shipped to consumers, and jsdom
396
+ rejecting every generated sheet under test ("Could not parse CSS
397
+ stylesheet"). Numbers are dropped for the same reason, and an object nested
398
+ inside an array is now expanded instead of reaching the sheet as
399
+ `[object Object]`.
400
+ - **`<origam-title>` reads its theme defaults.** It never called
401
+ `useDefaults()`, so `theme.components['origam-title']` was inert.
402
+
403
+ ### Added
404
+
405
+ - **`escapeCssIdent()`** (`origam/utils`) — escapes an arbitrary string for
406
+ interpolation as a CSS ident. Required now that `useStyle()` builds a real
407
+ stylesheet rule out of a consumer-supplied id: without it an id such as
408
+ `a { } body { display: none }` appends attacker- or typo-controlled rules
409
+ to the document, and ids that are legal in HTML but illegal as a CSS ident
410
+ (leading digit, dots, colons) silently kill their own rule. Delegates to
411
+ the native `CSS.escape` when present, with a spec-equivalent fallback for
412
+ jsdom and SSR — the two were compared over 34 inputs in Chromium and are
413
+ byte-identical.
414
+
415
+ ### Changed
416
+
417
+ - **`useStyle(styles, uniq)`** — `uniq` widened from `string | undefined` to
418
+ `MaybeRefOrGetter<string | undefined>`. Backward compatible: a plain string
419
+ still behaves exactly as before, and an empty value falls back to the
420
+ generated `<name>-<uid>` id rather than emitting the invalid selector `#`.
421
+
422
+ ---
423
+
424
+ ## [2.12.1] — 2026-07-31
425
+
426
+ **Hotfix — `vue-router` 5 is now accepted.** A consuming project could not
427
+ upgrade to Nuxt 4.5.1: Nuxt pulls `vue-router` 5, while origam declared an
428
+ optional peer of `vue-router: "^4.5.0"`. The peer range is widened to
429
+ `"^4.5.0 || ^5.0.0"`.
430
+
431
+ Nothing else changed — no runtime code, no public API, no dependency added.
432
+ The widening is strictly additive: projects on `vue-router` 4 are unaffected,
433
+ projects on 5 stop being blocked.
434
+
435
+ Vue Router 5 is, in the maintainers' own words, "a *boring* release, it
436
+ merges unplugin-vue-router into the core package with no breaking changes",
437
+ and the official migration guide states: "If you're using Vue Router 4
438
+ without unplugin-vue-router, there are no breaking changes". origam does not
439
+ use unplugin-vue-router, nor the iife build (the single documented breaking
440
+ change is that the iife bundle no longer ships `@vue/devtools-api`).
441
+
442
+ Verified on both majors before shipping: every symbol origam imports
443
+ (`useLink`, `RouterLink.useLink`, `RouteLocationRaw`, `Router`,
444
+ `RouteLocationNormalizedLoaded`, `NavigationGuardNext`, `UseLinkOptions`)
445
+ still exists in 5.2.0; type-check of the design system is clean against
446
+ 4.6.4 and 5.2.0 alike; and the built `dist` was exercised at runtime under
447
+ both — href resolution, named routes, click navigation, external hrefs,
448
+ `isActive`, and the tag fallback when no `to` is passed all behave
449
+ identically.
450
+
451
+ **Consumer note.** `vue-router` 5 requires `vue` `^3.5.34 || ^4.0.0`, while
452
+ origam's peer on `vue` remains `^3.5.0`. A project pinned between Vue 3.5.0
453
+ and 3.5.33 that adopts `vue-router` 5 will hit a conflict — between `vue`
454
+ and `vue-router` directly, not with origam.
455
+
456
+ ---
457
+
458
+ ## [2.12.0] — 2026-07-29
459
+
460
+ **Hotfix — `vue-i18n` is optional again.** A consuming project reported that
461
+ origam could not be enabled at all: `createOrigam()` reached `createLocale()`,
462
+ which imported `vue-i18n` at module level, even though the package declares it
463
+ as an **optional** peer. Any project without `vue-i18n` failed at import time.
464
+
465
+ ### Fixed
466
+
467
+ - **`createLocale()` no longer imports `vue-i18n`.** The default locale is now
468
+ backed by a built-in adapter shipped with the DS. Runtime *and* type
469
+ dependencies are gone — `grep vue-i18n` over the published `dist/` returns
470
+ only a string literal (the vue-i18n adapter's own `name` field) and a
471
+ comment. (Reported by a consumer.)
472
+ - The `vue-i18n` **type** imports in `locale.interface.ts` / `locale.util.ts`
473
+ are replaced by locally-declared structural types. An `import type` costs
474
+ nothing at runtime but still breaks `tsc` for a project that does not install
475
+ the package once `skipLibCheck` is `false` — the same class of bug, one step
476
+ further.
477
+
478
+ ### Added
479
+
480
+ - **`createBuiltinAdapter()`**, exported from `origam/utils`. Resolves dotted
481
+ keys, falls back to the fallback locale then to the key itself, and
482
+ interpolates both positional (`{0}`) and named (`{value}`) params — the only
483
+ two forms used by the DS's own messages.
484
+
485
+ It deliberately does **not** reimplement `vue-i18n`: no plurals, no linked
486
+ messages, no date formatting. A consumer needing those keeps `vue-i18n` and
487
+ passes their own adapter.
488
+
489
+ ### Changed — read this if you use `vue-i18n`
490
+
491
+ **The default adapter is no longer `vue-i18n`.** If you relied on
492
+ `createOrigam()` building a `vue-i18n` instance for you, pass it explicitly:
493
+
494
+ ```ts
495
+ import { createOrigam } from 'origam'
496
+ import { createVueI18nAdapter } from 'origam/utils'
497
+ import { createI18n, useI18n } from 'vue-i18n'
498
+
499
+ const i18n = createI18n({ legacy: false, locale: 'en' })
500
+
501
+ createOrigam({
502
+ locale: { adapter: createVueI18nAdapter({ i18n, useI18n }) }
503
+ })
504
+ ```
505
+
506
+ Nothing changes for the DS's own strings — they resolve identically through
507
+ either adapter. This is a **minor** rather than a patch precisely because that
508
+ default changed.
509
+
510
+ ---
511
+
512
+ ## [2.11.0] — 2026-07-28
513
+
514
+ Theming-resolution release. Every fix here has the same root cause: a
515
+ component **declared** theme-able props but never **consumed** them, so an
516
+ `IOrigamTheme.components` block was a silent no-op. Minor rather than patch
517
+ because the public prop surface grew (see *Added*), even though every commit
518
+ is labelled `fix`.
519
+
520
+ ### Added
521
+
522
+ - **`ISelectionControlProps` now extends `IBorderProps` / `IRoundedProps` /
523
+ `IElevationProps`**, inherited by `ICheckboxBtnProps` and `IRadioBtnProps`.
524
+ `OrigamSelectionControl` consumes them through the standard
525
+ `useBorder` / `useRounded` / `useElevation` composables, on the
526
+ state-layer box that owns the control's visible surface — same relay point
527
+ as `OrigamSwitchTrack`. Checkbox and Radio therefore accept `border`,
528
+ `rounded` and `elevation`, from a prop or from a theme. (#241)
529
+
530
+ ::: warning Known limit — `rounded` alone paints nothing at rest
531
+ The state-layer box has no background and no border by default, so a
532
+ `rounded` passed on its own is measurable in the computed style but
533
+ produces no visible difference at rest. It shows on hover (the state layer
534
+ now follows the radius), or combined with `border` / `elevation` / a
535
+ custom background. The glyph itself (`mdi-checkbox-*`, `mdi-radiobox-*`)
536
+ is an icon-font character and keeps its own shape — reshaping the mark
537
+ would be a rendering change, not a prop. Documented in the Checkbox and
538
+ Radio pages.
539
+ :::
540
+
541
+ ### Fixed
542
+
543
+ - **`OrigamBtnGroup`** forwarded `density`/`color`/`bgColor`/`size`/`hover`/
544
+ `active` to its children unconditionally, overwriting theme defaults with
545
+ `undefined`. Now filtered through `usePassedProps()` — the helper that
546
+ detects whether the *consumer* actually passed a prop, since Vue coerces an
547
+ unpassed boolean to `false`, never `undefined`. (#274)
548
+ - **`:is="tag"` bypassed theme resolution** on 7 components — `OrigamAvatar`,
549
+ `OrigamBtnGroup`, `OrigamExpansionPanel`, `OrigamLabel`, `OrigamMessages`,
550
+ `OrigamTab`, `OrigamTabs`. In `<script setup>` the template binds each prop
551
+ from the *raw* `$props`, independently of the local
552
+ `props = useDefaults(_props)`, so a bare `:is="tag"` never saw the theme's
553
+ value. The audit the ticket asked for widened the scope from 1 file to 7.
554
+ (#250)
555
+ - **`OrigamSelectionControl` declared `grid-area: control` unconditionally**,
556
+ assuming it always sits inside a Field/Input named grid. Any consumer
557
+ placing a bare control in its own grid saw every instance collapse onto one
558
+ cell. The declaration was in fact **dead code** on the Field/Input path —
559
+ `.origam-input__control` already owns `grid-area: control` *and* is
560
+ `display: flex`, and a `grid-area` on a flex child has no effect — so it is
561
+ removed rather than scoped. (#247)
562
+ - **The Checkbox/Radio hover halo hardcoded `border-radius: 100%`**, staying
563
+ circular even under `rounded="md"` — while being the only surface the prop
564
+ could paint. Now `inherit`. (#241)
565
+
566
+ ### Internal
567
+
568
+ - Marketing i18n: locale files split one per top-level namespace, an
569
+ `i18n:check` script wired into CI, and 195 missing translation keys filled
570
+ (400 → 205). EN/FR parity reached **0 gap** — the admin backoffice went
571
+ from 0 % to fully translated. No change to the published package.
572
+
573
+ ---
574
+
575
+ ## [2.10.0] — 2026-07-27
576
+
577
+ Field-rendering release. No new API — no component, prop, emit or slot was
578
+ added, and no source file was created. The minor bump is there to flag one
579
+ **visible rendering change** for consumers of `origam/styles` (see *Changed*);
580
+ everything else is a fix.
581
+
582
+ ### Changed
583
+
584
+ - **`--origam-field__input---padding-start` / `-end` now resolve to `0`**
585
+ (was `var(--origam-space---4)`, i.e. 16px) for consumers of the bundled
586
+ `origam/styles` (`main.css`). The inline spacing of a field is owned by the
587
+ **root** token `--origam-field---padding-start`, which is unchanged at
588
+ `{space.4}`; the input wrapper sits inside it, so a second inline padding
589
+ doubled it — and it truncated the value text of narrow controls
590
+ (`elevated` → `ele…`). Consumers importing `origam/tokens/css/{light,dark}`
591
+ are **unaffected**: those sheets already emitted `0` since 2026-07-24.
592
+ Fields keep their inline spacing, supplied by the root token. (#295)
593
+
594
+ ### Fixed
595
+
596
+ - **Tokens** — `field.input.padding-start/end` was `0` in the generated sheets
597
+ but `{space.4}` in the token source, so every `tokens:build` silently
598
+ reverted the fix, and `main.css` (produced by the separate `styles:build`
599
+ step) had drifted to a third value. The source now carries the intent and
600
+ the pipeline is idempotent — a second full build produces no diff. (#295)
601
+ - **`OrigamSliderField`** — the `rounded` prop was inert: radii were
602
+ hardcoded instead of going through `useRounded()`. (#283)
603
+ - **`OrigamApp`** — did not call `useDefaults()`, so
604
+ `theme.components['origam-app']` (`bgColor`, `fullHeight`) was ignored.
605
+ - **`OrigamField`** — inline padding is floored at the corner radius so
606
+ content clears the rounded outline, and the outline's start leg is widened
607
+ to match, the left corner no longer rendering flatter than the right.
608
+ Prepended fields are excluded to avoid a swatch/icon box artifact.
609
+ - **`OrigamField`** — the outlined floating label was pinned to the notch
610
+ corner by a hard `margin: 0 4px` while the input text and the resting label
611
+ both sit at `--origam-field__input---padding-start`, leaving the label
612
+ misaligned with its own value — visibly so on large-radius themes.
613
+ - **`OrigamColorPickerField`** — the swatch sat inside the field's inline
614
+ padding, so any consumer raising `--origam-field---padding-start` opened a
615
+ gap between the colour and the left border. Also fixed: the field wrapping
616
+ onto two lines when given content, and the swatch's square outer corner.
617
+ - **`OrigamColorPicker`** — a class typo on the hue/alpha slider fill
618
+ occluded both gradients.
619
+ - **`OrigamDatePickerField`** — wrapped onto two lines when given content in
620
+ `range` mode.
621
+
622
+ ### Internal
623
+
624
+ - Quality Gate unblocked: the two SonarQube new-code violations that had kept
625
+ the `Build` workflow red on every `develop` push since 2026-07-22 are
626
+ resolved — a nested ternary in `OrigamSliderField` and a duplicate
627
+ `:deep()` selector in `OrigamColorPickerField`. Both were pure
628
+ maintainability findings, no behaviour change. (#296)
629
+
630
+ ---
631
+
632
+ ## [2.9.0] — 2026-07-22
633
+
634
+ Theming-enablement release. This ships the design-token **hooks** and the
635
+ `useDefaults` **wiring** that let a consumer configure a component's
636
+ appearance entirely from an `IOrigamTheme` object (props-first), plus the
637
+ CSS hooks brand themes need for translucency and separation. No breaking
638
+ changes — every addition is opt-in via a new component token with an inert
639
+ (`none`) fallback, so existing renders are byte-identical until a theme
640
+ sets a value.
641
+
642
+ ### Added
643
+
644
+ - **`useDefaults()` now resolves per-component theme defaults on 11 more
645
+ components** that declared theme-configurable props but never read
646
+ `theme.components['origam-<name>']` — their theme config was silently
647
+ inert before this. This is the mechanism that makes props-first theming
648
+ actually apply. (#242)
649
+ - **Translucency & focus hooks on 7 components.** New component tokens,
650
+ each consumed with an inert fallback (`var(--…, none)`):
651
+ `--origam-chip---backdrop-filter`, `--origam-snackbar-item---backdrop-filter`,
652
+ `--origam-field---backdrop-filter` (+ `--origam-field---focus-ring-*`),
653
+ `--origam-switch__track---backdrop-filter`,
654
+ `--origam-selection-control__input---backdrop-filter`,
655
+ `--origam-tooltip---backdrop-filter`, `--origam-overlay-scrim---backdrop-filter`.
656
+ These give brand themes real `backdrop-filter` translucency (glass, frosted
657
+ surfaces) without per-instance `:style` overrides. (#253)
658
+ - **`OrigamAvatarGroup` separation ring.** Overlapping avatars now get a
659
+ legible seam via a dedicated `outline` — a CSS property distinct from
660
+ `box-shadow`, so it coexists with each theme's own avatar shadow with no
661
+ specificity or ordering tricks. Driven by new
662
+ `--origam-avatar-group__item---outline-{color,width,style}` tokens
663
+ (defaults `color.surface.default` / `2px` / `solid`). Only avatars rendered
664
+ inside an `<origam-avatar-group>` get it; a standalone `<origam-avatar>`
665
+ never does. (#263)
666
+ - **`usePassedProps()` composable helper.** Reports which props a consumer
667
+ actually passed by reading `vnode.props`, distinguishing a real value from
668
+ Vue 3 coercing an unpassed boolean prop to `false`. Used so a
669
+ theme-provided value is no longer overwritten by an `undefined → false`
670
+ default. (#263)
671
+
672
+ ### Fixed
673
+
674
+ - **Live theme switching now updates component default *props*, not just CSS
675
+ variables.** The Nuxt module's theme singleton could be duplicated when the
676
+ host app resolved `origam/nuxt` (source) and `origam/composables` (compiled
677
+ `dist`) to two physical module files — two independent `_theme` singletons,
678
+ so the plugin's watcher (which reassigns the resolved per-component
679
+ defaults) listened to a different instance than the one `setTheme()`
680
+ mutated. Props stayed frozen on a live switch while cssVars followed (pure
681
+ CSS cascade). The singleton is now anchored on `globalThis` on the client
682
+ (server stays module-level to avoid leaking state across concurrent SSR
683
+ requests). (#275)
684
+ - **`OrigamRadio`, `OrigamTabs` and `OrigamSliderField` now call
685
+ `useDefaults()`.** They declared theme-configurable props (`activeBgColor`,
686
+ `variant`, `color`/`bgColor`) but never resolved
687
+ `theme.components['origam-<name>']`, so that theme config was silently inert.
688
+ (#279)
689
+ - **`OrigamDialog` `scrim` now renders.** `scrim` was missing from the
690
+ component's `withDefaults(...)`, so Vue 3 coerced the unpassed boolean to
691
+ `false` and forwarded it to `OrigamOverlay`, overriding the overlay's own
692
+ `scrim: true` default — the backdrop never showed. Anchored `scrim: true` in
693
+ `withDefaults` (same pattern as the existing `openOnClick` default). (#279)
694
+ - **`useDefaults` wiring no longer collapses `<origam-code>`'s header zone.**
695
+ The origam base theme carried a `compact: true` default (authored in a bulk
696
+ pass before `useDefaults` was wired, never visually validated); once the
697
+ wiring landed it forced every code block without an explicit `compact` prop
698
+ into the condensed single-line pill layout, dropping the
699
+ header/filename/line-numbers/lang-badge. Removed from the base theme. (#249)
700
+ - **`OrigamBtnGroup` renders as a single rounded surface.** The group applies
701
+ its `rounded`/`elevation` on the outer wrapper with `overflow: hidden`
702
+ clipping children to the inner curvature — no forced child heights, no
703
+ inner-radius tokens; group height derives from `OrigamBtn`'s own border-box
704
+ height calc. (#239)
705
+ - **`OrigamSwitchTrack.error` widened from `boolean` to `string | boolean`**
706
+ to match the canonical Commons `IValidationProps`, so the parent's
707
+ validation surface is type-assignable again (was `vue-tsc` TS2345, CI red).
708
+ Consumed by truthiness only — behaviour unchanged. (#239)
709
+ - **`OrigamLayout` / `OrigamApp` `full-height` no longer clamps content
710
+ taller than one screen.** The full-height wrapper was `height: 100vh`
711
+ (a hard clamp) instead of `min-height: 100vh` ("occupy at least the
712
+ viewport"). Content overflowing past one screen still rendered (nothing
713
+ was clipped — no `overflow: hidden` on that element), but the wrapper's
714
+ own box never grew to match, so any ancestor sizing itself off that box
715
+ (e.g. a themed page background) stopped short of the actual page height.
716
+ `full-height` pages now grow naturally past one screen and the
717
+ background/box always matches the true content height.
718
+ - Fixed a handful of pre-existing `$type: "dimension"` component tokens
719
+ with non-numeric values (`auto`, `calc(...)`) that crashed the
720
+ `size/rem` Style Dictionary transform and silently blocked
721
+ `tokens:build` / the full package build (`btn.width`, `dialog.max-width`,
722
+ `dialog.max-height`, `contextual-menu.max-height`, `menu.max-height`,
723
+ `expansion-panel.header.append.margin-inline-start`,
724
+ `expansion-panel.popout.max-width(-active)`,
725
+ `expansion-panel.inset.max-width-active`) — retyped to `"other"`,
726
+ matching the established pattern already used for equivalent tokens
727
+ elsewhere in the same files. No visual/behavioural change; this only
728
+ unblocks the build pipeline.
729
+
730
+ > The 8 marketing brand themes (glass · cartoon · geek · apple · editorial ·
731
+ > material · ecom) that these hooks enable live in the private
732
+ > `@origam/marketing` package and are **not** part of the npm `origam`
733
+ > release — this changelog tracks the published library only.
734
+
735
+ ---
736
+
737
+ ## [2.8.1] — 2026-07-15
738
+
739
+ ### Fixed
740
+
741
+ - **The npm tarball now ships `README.md` and `LICENSE`.** The publish runs
742
+ from `packages/ds/` exclusively, but the package dir contained neither
743
+ file — both listed in the `files` array yet silently absent from every
744
+ published version (the npm page showed no readme at all). The new
745
+ npm-facing README covers install, quick start (plugin + token sheets +
746
+ Nuxt module), the theming model and the granular exports, and links to
747
+ the upcoming documentation site (https://origam.dev).
748
+
749
+ ---
750
+
751
+ ## [2.8.0] — 2026-07-15
752
+
753
+ ### Added
754
+
755
+ - **Border per-side props are now wired** (`useBorder`). The discrete
756
+ `borderTop` / `borderRight` / `borderBottom` / `borderLeft` props on
757
+ `IBorderProps` — declared but never read until now — each accept a
758
+ number (px), a boolean (legacy thin opt-in) or a free-form
759
+ `"width style color"` string (same grammar as the global `border`
760
+ shorthand). New per-side color overrides ship alongside:
761
+ `borderTopColor` / `borderRightColor` / `borderBottomColor` /
762
+ `borderLeftColor` (`TColor` — semantic intent or raw CSS color).
763
+ Precedence is specific-over-global (global `border`, then standalone
764
+ `borderColor` / `borderStyle`, then per-side width/style, then per-side
765
+ color) and documented on `useBorder`. Emitted declarations are physical
766
+ (`border-top-width`, …). `useStateEffect` forwards the 8 new props, so
767
+ Card, Sheet and every other consumer gets them for free. Pilot
768
+ component: Card (story + doc + e2e).
769
+ - **`useElevation` accepts a free-form custom `box-shadow` value.**
770
+ `elevation` (new `TElevation` type) still takes a named origam rung or
771
+ a Material 0–24 number, and now also any shadow-like CSS string
772
+ (`var()` / `calc()` / `rgba()` / hex / length / `inset` signals),
773
+ emitted verbatim as `box-shadow`. Previously a custom string silently
774
+ produced NO shadow (`parseInt` read a leading `0` and resolved to the
775
+ `none` rung). Pilot component: Card (story + doc + e2e).
776
+
777
+ ### Changed
778
+
779
+ - **`OrigamBlockquote`: `bgColor` renamed to `accentColor` (non-breaking).**
780
+ `bgColor` never painted a surface fill on Blockquote — it drove the
781
+ decorative accent (bar, background quote glyph, author label), which the
782
+ `bgColor` name misrepresented. `accentColor` is now the canonical prop;
783
+ `bgColor` keeps working as a deprecated alias (`accentColor` wins when
784
+ both are set) and logs a console warning once per session. Scope: this
785
+ pass only touches Blockquote — `bgColor` stays canonical and
786
+ non-deprecated on surface-fill components (Btn, Card, Chip, Badge,
787
+ Alert, Pagination, …). Removal of the `bgColor` alias on Blockquote is
788
+ targeted for v3.0.0. See `ROADMAP.md` — "Renommer `bgColor` →
789
+ `accentColor`".
790
+
791
+ ### Fixed
792
+
793
+ - **`useMargin`: 2-value strings work again.** `margin="8px 16px"` silently
794
+ produced no styles (the 2-value case was missing from
795
+ `formatMarginStylesVar`; padding already had it). Both utils now document
796
+ the intentional 4-value order convention: values are grouped by logical
797
+ axis — `block-start`, `inline-start`, `block-end`, `inline-end`
798
+ (top / left / bottom / right) — NOT the native CSS clockwise shorthand.
799
+ RTL-safe by design, arbitrated in #216.
800
+
801
+ ### Internal
802
+
803
+ - Marketing `/theming` — the Theme Builder ships six rich, validated
804
+ controls built from origam components (color/accentColor with intents +
805
+ custom picker, density, rounded with per-corner link/unlink, elevation
806
+ presets + full custom box-shadow, border with per-side width AND color
807
+ link/unlink, padding/margin devtools-style box-model with axis linking).
808
+ - Marketing playground isolation: the `origam` preview theme now carries a
809
+ complete GENERATED reset of every component var (~2700/mode, derived from
810
+ the DS baseline sheets) — ambient brand themes can no longer leak into
811
+ the playground preview.
812
+ - Marketing package restructured to the project architecture (`src/`
813
+ srcDir, locales under `src/assets/locales`, wireframes under
814
+ `wireframes/`).
815
+
816
+ ---
817
+
818
+ ## [2.7.3] — 2026-07-10
819
+
820
+ ### Fixed
821
+
822
+ - **`OrigamField`: the `rounded` prop now drives the whole field chrome.**
823
+ The prop used to round the outer box only (inline `border-radius`), while
824
+ the inner chrome (outline legs, per-corner radii) reads
825
+ `--origam-field---border-radius` — which the prop never touched, so the
826
+ outline corners stayed at the default and mismatched the box (themes had
827
+ to force the var with `!important` hacks). The field now mirrors the
828
+ radius resolved from the `rounded` prop into its component var, keeping
829
+ box and outline in sync for every field type (text, select, textarea,
830
+ number, password, date, file, color). `shaped` / `shaped-invert` are
831
+ unaffected and stay SCSS-owned.
832
+
833
+ ---
834
+
835
+ ## [2.7.2] — 2026-07-09
836
+
837
+ ### Fixed
838
+
839
+ - **Default text (and every `currentColor` icon) rendered browser-black in
840
+ dark mode.** The semantic text token flips correctly
841
+ (`neutral-900 → neutral-50`) and `app.json` has declared
842
+ `app.color` / `app.background-color` all along — but nothing consumed
843
+ them. `OrigamApp` now paints the base pair
844
+ (`color: var(--origam-app---color)`,
845
+ `background-color: var(--origam-app---background-color)`), and
846
+ `OrigamThemeProvider` (`display: contents`) sets the inherited `color`
847
+ so a local `data-mode="dark"` sub-tree gets readable defaults too.
848
+ Verified in both modes (computed styles) with no light-mode change.
849
+
850
+ ---
851
+
852
+ ## [2.7.1] — 2026-07-09
853
+
854
+ ### Fixed
855
+
856
+ - **`origam/styles` CSS entry shipped empty.** `src/assets/css/main.css` was
857
+ 0 bytes in every published version (2.6.x, 2.7.0), so
858
+ `import 'origam/styles'` (the `style`/`import`/`require` conditions)
859
+ delivered no tokens and no utility classes — only the SCSS path
860
+ (`@use 'origam/styles'`) worked. `main.css` is now compiled from
861
+ `main.scss` at build time (`styles:build`): the full aggregate
862
+ (primitives + light + dark + utilities + reset, ~366 KB) ships in the
863
+ package.
864
+
865
+ ### Internal
866
+
867
+ - New brand identity: the "cube sonobe" logo (modular origami — identical
868
+ folded units assembling into a structure) replaces the legacy mark across
869
+ the repo (docs/stories/marketing assets; not part of the npm tarball).
870
+ - npm publishing is handled by CI again (release workflow auth fixed).
871
+
872
+ ---
873
+
874
+ ## [2.7.0] — 2026-07-08
875
+
876
+ ### Added
877
+
878
+ - `OrigamThemeProvider` now applies the active theme's **component default
879
+ props** to its sub-tree (props-first theming cascades into sub-trees via
880
+ `provideDefaults`), not just the `data-theme` / `data-mode` attributes. A
881
+ `<origam-theme-provider theme="brand-x">` wrapper therefore gives every
882
+ descendant that brand's per-component defaults.
883
+ - Glass theme: `OrigamAlert` glassmorphism (transparency + blur).
884
+
885
+ ### Changed
886
+
887
+ - **`shiki` is now an OPTIONAL peer dependency** (removed from `dependencies`).
888
+ `OrigamCode` degrades gracefully to plain, un-highlighted code when `shiki`
889
+ is not installed (a one-time console warning is emitted; line numbers,
890
+ highlight-lines and copy keep working). **Migration:** add `shiki` (`^4.3.1`)
891
+ to your app's dependencies to keep syntax highlighting. This drops a heavy
892
+ (~3 MB) hard dependency from apps that don't render code blocks.
893
+
894
+ ### Fixed
895
+
896
+ - `OrigamPagination` colored mode (`color` / `bgColor`) now fills the page
897
+ buttons. The theme's default `text` btn variant
898
+ (`background-color: transparent !important`) was swallowing the intent fill,
899
+ leaving resting buttons transparent; the row now forces the `flat` variant
900
+ when a colour is set.
901
+ - Cartoon dark `/theming`: fields adopt the theme and the primary btn contrast
902
+ is corrected.
903
+
904
+ ### Internal
905
+
906
+ - Restored the green `vue-tsc` type-check gate under TypeScript 6.
907
+ - E2E suite stabilised: sharded CI plus deterministic fixture-drift fixes
908
+ (variant-index / navigation realignment) — the "flaky" bucket was
909
+ deterministic drift, not timing.
910
+
911
+ ---
912
+
913
+ ## [2.6.3] — 2026-07-03
914
+
915
+ ### Changed
916
+
917
+ - Internal code-quality cleanup to green the SonarQube quality gate
918
+ (`new_violations`): removed redundant type assertions, merged duplicate
919
+ imports, extracted nested template literals, `||` → `??`, dropped a redundant
920
+ `| undefined`, converted a call-signature interface to a function type, and
921
+ removed a leftover CSS comment. **No API or runtime behaviour change** — pure
922
+ refactor (PR #87).
923
+ - Marketing site (private, not part of the published `origam` package):
924
+ `@nuxtjs/i18n` 9 → 10 and `@nuxtjs/seo` 2 → 5 (locales moved to `i18n/locales/`;
925
+ v10 config breaking changes handled) — PR #88.
926
+
927
+ ---
928
+
929
+ ## [2.6.2] — 2026-07-03
930
+
931
+ ### Changed
932
+
933
+ - Dependency maintenance (Dependabot): TypeScript 5.8 → 6.0, ESLint 9 → 10
934
+ (+ `typescript-eslint`, `eslint-plugin-vue`), Style Dictionary 4 → 5
935
+ (+ `@tokens-studio/sd-transforms` 1 → 2), Shiki 3 → 4 (VitePress docs), and a
936
+ dev-tooling batch (`jsdom` 25 → 29, `happy-dom`, `sass`, `@types/node`,
937
+ `unplugin-vue-components`, `lint-staged`, `globals`…). **Vite and Vue stay
938
+ pinned** (`vite ~7.3.6`, `vue 3.5.35`) — their bumps were declined (Vite ≥ 7.3.5
939
+ broke the build; the Vue override neutralises the bump). `react-dom` bump
940
+ closed (not a dependency of a Vue design system).
941
+
942
+ ### Fixed
943
+
944
+ - CI publish (`release.yml`): the npm publish step failed with **E404** because
945
+ `pnpm publish` did not expand the `${NODE_AUTH_TOKEN}` placeholder from
946
+ setup-node's `.npmrc` → the PUT went out unauthenticated. The resolved token is
947
+ now written to the `.npmrc` pnpm reads before publish (PR #82). 2.5.0/2.5.1/2.6.0
948
+ had failed here; 2.6.1 was published manually as a stop-gap.
949
+ - Test robustness: `transition.composable.spec.ts` used an invalid CSS sentinel
950
+ (`transformOrigin: 'original'`) that jsdom 29 now rejects; switched to a valid
951
+ value `top left` (PR #84).
952
+
953
+ ---
954
+
955
+ ## [2.6.1] — 2026-07-02
956
+
957
+ ### Added
958
+
959
+ - `OrigamCode` — `compact` and `prompt` display modes (commit `5ebc6702`).
960
+ Compact mode collapses the component to a single line (no gutter, no
961
+ filename bar); `prompt` mode renders a shell-style `$` prefix. Two-axis
962
+ theming support: the component now tracks both `data-theme` and
963
+ `data-mode` on the host `<html>` element so it correctly switches in
964
+ dark/light sub-trees (`fix(ds): OrigamCode two-axis theming` — commit
965
+ `e2e716f2`). Scroller fill repaired in the same pass.
966
+ - `backdrop-filter` glassmorphism tokens on `Card` / `Sheet` / `Toolbar` /
967
+ `Menu` — new token group `component.{card,sheet,toolbar,menu}.backdrop-filter`
968
+ emitted as `--origam-{cmp}---backdrop-filter` CSS vars. Allows consumers
969
+ to build frosted-glass surfaces without overrides (commit `71647594`).
970
+
971
+ ### Changed
972
+
973
+ - Brand themes removed from the DS (ADR-004) — the `origam` package now
974
+ ships only the `light` and `dark` base token sets. Consumer themes
975
+ (`sobre`, `geek`, `glass`, `cartoon`, `apple`, `ecom`, `editorial`,
976
+ `material`) are now authored in semantic JSON and installed via
977
+ `createOrigam({ themes: [...] })` rather than bundled into the lib.
978
+ The DS stays unopinionated; themes travel with the consumer
979
+ (commits `fbea1e3f`, `5f1bf51c`, `e0a47050`, `edc30e80`, `f7c56f06`,
980
+ `3b9f8510`).
981
+
982
+ ### Fixed
983
+
984
+ - `OrigamImg` — `markBooted` `requestAnimationFrame` callback was called
985
+ during SSR, crashing server-render (`fix(ds): guard OrigamImg markBooted
986
+ rAF behind IN_BROWSER` — commit `6b09c5df`).
987
+ - Six SSR/theming gaps surfaced by the marketing site refactor: theme cookie
988
+ injection race, `data-mode` missing on first SSR render, component-level
989
+ token var scope, active-theme guard in `useTheme`, Menu/Tooltip token
990
+ resolution under sub-tree providers, Nuxt hydration mismatch on theme
991
+ toggle (commit `b9fe3219`).
992
+ - `OrigamSelect` — the chevron icon opened then instantly re-closed the menu
993
+ on the very first click (field not yet focused): the opening `mousedown`
994
+ bubbled to the control handler and double-toggled. The toggle now only
995
+ runs when the field is already focused; otherwise the event bubbles to the
996
+ single control handler that opens + focuses in one pass (PR #70).
997
+ - Sass `mixed-decls` deprecation warnings eliminated in `OrigamBtn` and
998
+ `OrigamSwitchTrack` — declarations that followed a nested rule (`@media` /
999
+ `&:focus-visible`) are moved above it. Pure reorder, identical compiled CSS
1000
+ (PR #73).
1001
+ - Layout offset counted twice in SSR / production builds (invisible in dev):
1002
+ Vue's hydration-mismatch recovery could abandon a layout item mid-`setup()`
1003
+ after it registered, leaving an orphan in `registered` so a 240 px drawer
1004
+ reserved 480 px. `useLayoutItem`'s `register()` now evicts stale entries
1005
+ occupying the same `(order, position)` slot (PR #76).
1006
+
1007
+ ---
1008
+
1009
+ ## [2.6.0] — 2026-06-11
1010
+
1011
+ > **The bracket + a11y + theming release.** `OrigamBracket` ships as a
1012
+ > production-ready e-sport tournament component (double-elimination, Grand
1013
+ > Final, statuses, full cross-cutting prop surface). `v-contrast` becomes
1014
+ > the DS-wide WCAG text-legibility guard applied to every colour-bearing
1015
+ > component. A two-axis theming engine (`data-theme` × `data-mode`) replaces
1016
+ > the previous single-axis approach and enables brand themes to be installed
1017
+ > at runtime via `createOrigam()`. Multiple composables and layout components
1018
+ > also land fixes. 170 commits total in this range (DS-relevant subset
1019
+ > documented below).
1020
+
1021
+ ### Added
1022
+
1023
+ - `v-contrast` directive — runtime WCAG 2.1 AA text-legibility guard
1024
+ (`feat(ds): v-contrast directive` — commit `8a80787a`). Applied
1025
+ automatically to every colour-bearing component in the same cycle
1026
+ (`feat(ds/a11y): apply v-contrast to all colour-bearing components` —
1027
+ commit `4a4d4473`). Two correctness fixes landed before release: correct
1028
+ WCAG luminance maths for composite translucent backgrounds (commit
1029
+ `96ff45c4`), and `color(srgb …)` token format support + guard against
1030
+ clobbering an explicit `color` prop (commits `34786ed5`, `12a3e378`).
1031
+ - `OrigamBracket` — **major enrichment** on top of the 2.3.0 initial
1032
+ component. Full double-elimination layout with two independent trees +
1033
+ a Grand Final match (`feat(ds/bracket): real two-tree double-elimination
1034
+ layout` — commit `dee1c23d`; Grand Final — commit `560eaf5b`).
1035
+ Full cross-cutting prop surface on both `OrigamBracketMatch`
1036
+ (`rounded` / `elevation` / `border` / `color` / `bgColor` — commit
1037
+ `8998f0e3`) and `OrigamBracketCompetitor` (commit `3088ad31`).
1038
+ Distinct status indicators (live, forfeit) + live-link prop (commit
1039
+ `4d38c65c`). Connector trait inherits match `border-width` / `style` /
1040
+ `color` (commit `94242ab3`). State variant (hover / active) wired
1041
+ (commits `0f4d5924`, `5edeca52`, `92edcd1b`). Auto-contrast on match
1042
+ text and seed numbers via `v-contrast` (commit `4cd5bc44`).
1043
+ `OrigamDivider` used internally for the match divider (commit `c472bb71`).
1044
+ - `OrigamBlockquote` — **major enrichment** from the initial Wave 4 stub.
1045
+ Two-axis colour model: `color` (foreground / accent) and `bgColor`
1046
+ (surface / left-bar background) applied on all 5 variants
1047
+ (`feat(ds/blockquote): two-axis colour model` — commit `ccfe8c82`).
1048
+ `bgColor` accent on every variant including `elegant` and `minimal`
1049
+ (commit `b2f5c1f3`). `color` also drives the source label (commit
1050
+ `d80e88ca`). Accent pseudo-element decoupled from the `border` prop
1051
+ (commit `c5843ee1`, revert + re-land in same release).
1052
+ - `OrigamBottomNav` — three additions: dimension props (`width`, `height`,
1053
+ etc.) now apply (fix — commit `21d44f63`); `position` prop
1054
+ (`start | center | end`) controls item distribution (commit `395a797e`);
1055
+ `active` state exposed and diffused to the child buttons (commit
1056
+ `5bdb3e85`).
1057
+ - `OrigamAvatarGroup` — three additions: `rounded`, `elevation`, `border`
1058
+ propagated to all child avatars (commit `725303bd`); `hover` / `active`
1059
+ state fan-out made reactive (commit `e163cfba`); click-outside collapses
1060
+ an expanded group (commit `c364aae3`).
1061
+ - `OrigamAppbar` — `scroll-behavior="active"` engages the active surface
1062
+ state on scroll (commit `07feb981`). `hide` and `inverted` scroll
1063
+ behaviours repaired; `scrollBehavior` now accepts combined tokens
1064
+ (commits `01912f4c`, `b625ea1e`). Removed dead props: `absolute`
1065
+ (commit `50c5ea32`), `floating` (commit `3d707ef5`), `width` /
1066
+ `minWidth` / `maxWidth` (commit `c5e7a6b9` — layout owns the
1067
+ cross-axis).
1068
+ - Two-axis theming engine — `data-theme` (brand identity) × `data-mode`
1069
+ (light/dark) replace the previous single `data-theme` approach. Shadow
1070
+ tokens gain per-theme variants (commit `f7c56f06`). `createOrigam()`
1071
+ gains a `theme` option to install one or multiple presets at init time,
1072
+ SSR no-flash (commits `edc30e80`, `5f1bf51c`, `3b9f8510`). Token
1073
+ authoring via semantic JSON (colors, radius, typography, shadow, spacing,
1074
+ animation groups) — `feat(ds): semantic JSON theme authoring` (commit
1075
+ `e0a47050`).
1076
+ - Field validation surface extended to 5 additional field components
1077
+ (`InlineEdit`, `OtpInputField`, `Clipboard`, `NumberField`,
1078
+ `SliderField`) via unified `useValidation` wiring (commit `27977831`).
1079
+ - `OrigamStatus` — forces its intent onto `color` / `bgColor` (non-
1080
+ overridable) so status badges are always correctly painted regardless of
1081
+ surrounding theme (commit `8a72740a`).
1082
+
1083
+ ### Changed
1084
+
1085
+ - `OrigamAudio` — dimension props (`width`, `height`) now apply (commit
1086
+ `adaf4395`); content fills the available height with controls pushed
1087
+ to the bottom (commit `97207e26`); progress row grows when the bar is
1088
+ taller (commit `8f8f1c1f`); `rounded="none"` and shaped variants added
1089
+ (commit `073d0513`). Compact disc rendering fixed — oversized with
1090
+ floating grooves (commit `bef36e20`). Reactive `autoplay` (commit
1091
+ `0dc90fea`). Play-button focus tint lingering + playlist active item
1092
+ blinking fixed (commit `6e6042bb`).
1093
+ - `OrigamToolbar` — `hover` / `active` now drive the surface colour
1094
+ (state-aware) (commit `c966e9c6`).
1095
+ - `OrigamAlert` — removed redundant `prominent` prop (commit `f32af0a8`).
1096
+ - `OrigamAvatar` — `color` prop now applies; `v-contrast` was previously
1097
+ overriding an explicit `color` (commit `369e0afa`). Dead `start` / `end`
1098
+ props removed (commit `84f324fe`).
1099
+ - `OrigamApp` — now exposes only `color` / `bgColor` and forwards them to
1100
+ the layout (commit `111f6596`).
1101
+ - DS-wide: `useStateEffect` repaired — runtime prop changes for
1102
+ `color` / `bgColor` and similar were silently ignored after initial
1103
+ mount (commit `a1a66120`). Reactive-update loops in `AvatarGroup` and
1104
+ `RadioGroup` killed (commit `4c5eb18d`).
1105
+ - Monorepo migration — `packages/ds`, `packages/marketing`,
1106
+ `packages/stories`, `packages/docs`, `packages/tests`,
1107
+ `packages/figma-plugin` are now first-class pnpm workspace packages
1108
+ (commits `4d53558b`, `70f1819f`, `fa0ed997`).
1109
+
1110
+ ### Fixed
1111
+
1112
+ - `OrigamBtn` — `border-color` and `border-style` are now customisable
1113
+ props (commit `b44add69`).
1114
+ - `OrigamField` — themed `border-radius` now resolves via the real
1115
+ component token (commit `851b2a2f`).
1116
+ - `OrigamExpansionPanels` — `useElevation` import missing, causing
1117
+ `ReferenceError` at mount (commit `2eef9288`).
1118
+ - `OrigamDatePicker` — crash guard for empty `daysInMonth` when reading
1119
+ `date` prop (commit `896b9eec`).
1120
+ - `OrigamBorder` — numeric `border-width` values were invisible (missing
1121
+ `border-style` and `border-color` defaults) (commit `e9a151ec`).
1122
+ - `rounded="none"` — did not actually remove the border radius; affected
1123
+ `OrigamAudio` and other consumers (commit `073d0513`).
1124
+ - `fix(ds): /* @vite-ignore */ on shiki dynamic import` — defensive guard
1125
+ to silence the Vite bundler warning on the `OrigamCode` lazy highlighter
1126
+ import (commit `f6bbc494`).
1127
+ - DS/marketing integration — MDI class prefix resolution, search hotkey
1128
+ double-fire, Menu anchor positioning (commit `d113a245`); lib dist
1129
+ missing CSS in first production deploy (commit `62667fbc`).
1130
+
1131
+ ---
1132
+
1133
+ ## [2.5.1] — 2026-05-27
1134
+
1135
+ > **Patch.** Two housekeeping commits with no public API impact.
1136
+
1137
+ ### Changed
1138
+
1139
+ - Repository structure cleaned up: unit specs moved to `tests/TU/`,
1140
+ the dev playground dropped (commit `52f7dc70`).
1141
+ - `.gitignore` updated to exclude `tests/a11y/.report` and
1142
+ `tests/a11y/.results` artefacts (commit `6c2ef153`).
1143
+
1144
+ ---
1145
+
1146
+ ## [2.5.0] — 2026-05-24
1147
+
1148
+ > **The accessibility release.** WCAG 2.1 AA pass across the entire
1149
+ > component catalogue, critical backlog items (Select combobox pattern,
1150
+ > DataTable caption, ColorPicker keyboard), and a VitePress sidebar
1151
+ > reorganised by UI taxonomy.
1152
+
1153
+ ### Fixed
1154
+
1155
+ - WCAG 2.1 AA pass — 35 targeted fixes across 36 components covering:
1156
+ missing `aria-label` on icon-only buttons, incorrect `role` assignments,
1157
+ focus management gaps, colour-contrast warnings on default token values,
1158
+ and keyboard-navigation holes in Slider / Rating / Switch / Radio
1159
+ (commit `1ad0aeaf`).
1160
+ - Critical a11y backlog:
1161
+ - `OrigamSelect` — full combobox ARIA pattern (`role="combobox"` +
1162
+ `aria-expanded` + `aria-activedescendant` + `aria-autocomplete`).
1163
+ - `OrigamDataTable` — `<caption>` element added so screen readers
1164
+ announce the table purpose.
1165
+ - `OrigamColorPicker` — keyboard navigation restored; tab order and
1166
+ focus ring corrected.
1167
+ - A11y test infrastructure wired (commit `b3dd8552`).
1168
+ - VitePress docs — SSR crash in the build resolved; chart legend guard
1169
+ against undefined series data (commit `6cfb0029`).
1170
+
1171
+ ### Changed
1172
+
1173
+ - VitePress component sidebar reorganised by UI taxonomy (layout /
1174
+ navigation / data-display / forms / feedback / utility) instead of
1175
+ flat alphabetical order (commit `0bd5e2f2`).
1176
+
1177
+ ---
1178
+
1179
+ ## [2.4.0] — 2026-05-23
1180
+
1181
+ > **The chart engine + media kit + Wave 4 release.** In-house chart
1182
+ > engine (27 primitives, 19 families, pure SVG, zero external dep),
1183
+ > atomic media kit (`OrigamAudio` with waveform / vinyl / stem-tracks /
1184
+ > playlist, `OrigamVideo` YouTube-style player, `OrigamMediaScrubber`
1185
+ > and `OrigamMediaController` primitives), native `SliderField`, six
1186
+ > utility / content components (`OrigamGrid`, `OrigamMasonry`,
1187
+ > `OrigamEmptyState`, `OrigamClipboard`, `OrigamInlineEdit`,
1188
+ > `OrigamNumberFormat`), `OrigamWatermark`, `OrigamQRCode`,
1189
+ > `OrigamCalendar`, `OrigamTextMask`, gradient support on color props,
1190
+ > and a DS-wide reuse-interfaces audit. 149 commits.
1191
+
1192
+ ### Added
1193
+
1194
+ #### Chart engine
1195
+
1196
+ - `OrigamChart` + `useChart` — in-house chart component. 8 base types
1197
+ (`line` / `area` / `bar` / `column` / `pie` / `donut` / `scatter` /
1198
+ `radar`). Pure SVG rendering (no canvas, no `d3`, no `chart.js`, no
1199
+ `echarts`). Tooltip inline-positioned via mouse move; legend as
1200
+ `<ul role="list">` with click-to-toggle series visibility. Animated
1201
+ entrance with `prefers-reduced-motion` respect. Custom slots for
1202
+ `tooltip` / `legend-item` / `title` / `empty`. ARIA `role="img"` with
1203
+ `<title>` + `<desc>`; every data point is `tabindex="0"` +
1204
+ `role="button"` with a descriptive `aria-label`. Pure-function SVG
1205
+ path utilities — SSR-safe. `tokens/component/chart.json` (title,
1206
+ subtitle, axis, grid, tooltip, legend, point, bar, pie, radar,
1207
+ animation groups). (commit `b46541ce`)
1208
+ - Family split into 9 per-type components (`OrigamChartCartesian`,
1209
+ `OrigamChartPolar`, `OrigamChartRadar`, `OrigamChartGauge`, …) sharing
1210
+ the engine via `OrigamChartAxis` and `OrigamChartLegend` sub-components
1211
+ (commits `4da143c9`, `c950c364`, `ef4ab360`).
1212
+ - 15 additional chart families extending the base engine:
1213
+ - **Honeycomb / Treemap / Sankey / Word-cloud** (commit `94902edc`)
1214
+ - **Heatmap / Sunburst / Box-plot / Pictorial** (commit `c1e69d60`)
1215
+ - **Candlestick / Streamgraph** (commit `c5edc9b0`)
1216
+ - **Pyramid / Funnel** family with outside labels + leader lines on
1217
+ narrow bands (commits `445acb05`, `f4af7a27`, `293905bc`)
1218
+ - **Polar-bar / Variwide** (commit `26380205`)
1219
+ - **Pareto / Bullet** (commit `39f141a3`)
1220
+ - **Combination chart** (line + column / area + line) (commit
1221
+ `4406e92d` area, `10d012ad` bar-axis swap)
1222
+ - **Map / Choropleth / Flight-routes / Plot bands** (commit `c728bd94`,
1223
+ `6e72e7f4`)
1224
+ - **Stacked percent / Multi-axis Y** (commit `6e72e7f4`)
1225
+ - **Drilldown** (commit `8f827e7d`)
1226
+ - **Sparkline / Zoom-pan / RangeSelector** (commit `23d97e29`)
1227
+ - **Annotations** (arrows, label callouts, circle highlights, brackets)
1228
+ (commit `e1e41b89`)
1229
+ - Chart composables wired to full DS cross-cutting prop surface
1230
+ (`IDimensionProps` via `useDimension`, `margin`, `padding`, `rounded`,
1231
+ `elevation`, `bgColor`) (commits `7179cd6c`, `e16742e0`).
1232
+ - Scatter chart: `z` dimension drives bubble radius (commit `2eb6c868`).
1233
+ - Pie / donut: multi-series concentric rings; per-slice legend toggle
1234
+ (commit `ba51275b`).
1235
+ - DS intent colours and custom CSS colours resolve to the correct token
1236
+ namespace in charts (commit `8166cd1e`).
1237
+ - 7 canonical transverse emit interfaces (`IChartEmits`, etc.) added
1238
+ (commit `e1c7b890`).
1239
+ - DS-wide typed `defineEmits` migration — 7 batches covering 54+
1240
+ components (commits `01381e41`, `a03627a8`, `ce40e686`, `2ce1ccd2`,
1241
+ `3347bc58`, `279fb2f0`, `e91c9518`).
1242
+
1243
+ #### Media kit
1244
+
1245
+ - `OrigamAudio` + `useAudioPlayer` + `useWaveform` — in-house audio
1246
+ player. Custom UI (play/pause, scrubber, volume, waveform via
1247
+ `OfflineAudioContext`). Media Session API for lock-screen controls +
1248
+ metadata. Cover image. 3 controls modes. Vinyl disc animation. Stem-
1249
+ tracks (multi-channel mute/solo). Playlist with tri-state loop and
1250
+ random shuffle. Zero external dep. ARIA: dynamic `aria-label`,
1251
+ `role="slider"` on scrubber, `role="img"` on waveform. SSR-safe.
1252
+ `tokens/component/sound.json` (commits `293efc06`, `d3781288`,
1253
+ `9c12e283`, `f3d3c797`). Waveform + album ported from `OrigamSound`
1254
+ (commit `d5a0246b`). `useWaveform` moved to Audio namespace (commit
1255
+ `d4822f26`). `<OrigamSound>` namespace retired — superseded by
1256
+ `<OrigamAudio>` (commit `a77911bc`).
1257
+ - `OrigamVideo` + `useVideoPlayer` — in-house video player. YouTube-style
1258
+ restructure: custom UI (play/pause, scrubber, volume, fullscreen, PIP,
1259
+ captions), 3 controls modes, WebVTT captions with language switcher,
1260
+ `aspect-ratio` preset, download prop + quality switcher in cog menu,
1261
+ YouTube-style skip ripple (half-disk + chevrons stagger). Zero external
1262
+ dep. ARIA: `role="slider"` on scrubber, `role="status"` / `role="alert"`
1263
+ on state overlays. SSR-safe. `tokens/component/video.json` (commits
1264
+ `8a37102e`, `97420a99`, `31dcfdae`, `f50ccbdb`, `dcccb4dd`, `0208b2f3`,
1265
+ `9bbbea96`, `75b0779f`, `80e2a9c0`).
1266
+ - `OrigamMediaScrubber` — reusable horizontal + vertical media scrubber
1267
+ primitive (thin track, buffer indicator, hover thumb + tooltip). Used
1268
+ by both `OrigamAudio` and `OrigamVideo` for their respective scrubber
1269
+ and volume controls (commits `9f87c917`, `9e050390`, `15d53094`).
1270
+ - `OrigamMediaController` — shared media-control shell extracted from
1271
+ `useVideoPlayer` (commit `2cbb9618`). `useMediaPlayer` base composable
1272
+ split out (commit `c2a9f282`).
1273
+
1274
+ #### New utility / content components
1275
+
1276
+ - `OrigamGrid` + `OrigamGridItem` — declarative CSS Grid wrapper. Props
1277
+ for `columns` / `rows` / `areas` / `gap` (token or raw CSS) /
1278
+ `autoFlow` / `align*` / `justify*` and item-level `column` / `row` /
1279
+ `area` / `*Self` shorthands. Object syntax (`{ start, end }`) or raw
1280
+ CSS strings (`'1 / 5'`, `'span 2'`) for span control. Token group
1281
+ `--origam-grid---gap-{xs,sm,md,lg,xl}` (commit `0ebb1cd4`).
1282
+ - `OrigamMasonry` + `useMasonry` — Pinterest-style masonry layout.
1283
+ CSS-first via `grid-template-rows: masonry` (detected via `useCssSupport`
1284
+ — new `masonry` flag). JS bucket-fill fallback with `ResizeObserver`.
1285
+ Responsive columns via container-query breakpoints. `animated` prop.
1286
+ `tokens/component/masonry.json` (`animation-duration`,
1287
+ `animation-easing`) (commit `2691c4e5`).
1288
+ - `OrigamEmptyState` — placeholder for absent data. 5 presets
1289
+ (`no-data` / `no-results` / `error` / `offline` / `locked`) with auto
1290
+ icon + intent mapping. Three sizes, two alignments. ARIA `role="status"`
1291
+ + `aria-live="polite"`. `tokens/component/empty-state.json` (commit
1292
+ `abaca302`).
1293
+ - `OrigamClipboard` + `useClipboard` — copy-to-clipboard helper.
1294
+ `navigator.clipboard.writeText` + `execCommand` fallback. Scoped
1295
+ `#default` slot exposes `{ copy, copied, error }`. Auto-resetting
1296
+ feedback state. ARIA `aria-live="polite"`. SSR-safe.
1297
+ `tokens/component/clipboard.json` (commit `d93c2957`).
1298
+ - `OrigamInlineEdit` + `useInlineEdit` — edit-in-place pattern. Click
1299
+ display → input prefilled → Enter confirms / Escape cancels. Async
1300
+ `validate` callback. Multiline textarea mode. Custom slots
1301
+ `#display` / `#edit` / `#actions`. ARIA `aria-invalid` +
1302
+ `aria-describedby`. `tokens/component/inline-edit.json` (commit
1303
+ `d4a95557`).
1304
+ - `OrigamNumberFormat` + `useNumberFormat` — i18n number formatting via
1305
+ `Intl.NumberFormat`. 7 formats, full locale support with auto-resolution
1306
+ chain, LRU-cached `Intl` instances. Scoped `#default` slot exposes
1307
+ `{ formatted, parts, value }`. ARIA `aria-label` expansion for compact
1308
+ notation (commit `dd8bfe3c`).
1309
+ - `OrigamWatermark` + `useWatermark` — diagonal repeating overlay. SVG
1310
+ data-URL (no canvas). Text or image mode. Anti-tamper MutationObserver.
1311
+ `pointer-events: none`. `tokens/component/watermark.json`. SSR-safe
1312
+ (commit `cc715c38`).
1313
+ - `OrigamQRCode` + `useQRCode` — SVG QR code rendering via
1314
+ `qrcode-generator` (~5 kB). 4 ECC levels. Optional logo overlay.
1315
+ Rounded modules. LRU cache. ARIA `role="img"`. Renamed from
1316
+ `OrigamQRCode` → `OrigamQrCode` (Vue style guide — commits `9f5a4eea`,
1317
+ `eabb92a3`, `1ee2acf6`, `da34f20d`, multiple QrCode fixes).
1318
+ - `OrigamCalendar` + `useCalendar` — full calendar. 4 views
1319
+ (month / week / day / agenda), navigation, events with
1320
+ start / end / color / category, range select with drag-to-create,
1321
+ RRULE recurring events (`DAILY | WEEKLY | MONTHLY` + `INTERVAL` /
1322
+ `COUNT` / `UNTIL` / `BYDAY`). Toolbar with view switcher. Locale-aware
1323
+ via `Intl.DateTimeFormat`. Zero external dep. ARIA
1324
+ `application / gridcell / toolbar` + keyboard navigation. SSR-safe
1325
+ (commit `8b5d7f0a`).
1326
+ - `OrigamTextMask` + gradient color props — text reveals an animated
1327
+ background via `background-clip: text`. 4 animation types
1328
+ (`pan` / `rotate` / `pulse` / `zoom`) with `prefers-reduced-motion`.
1329
+ Zero JS (pure CSS keyframes). SSR-safe (commit `31f67d37`).
1330
+ - Gradient support for `color` / `bgColor` / `textColor` props. Three
1331
+ input formats: raw CSS gradient string, `IGradient` structured object
1332
+ (`{ from, to, via?, direction?, type? }` or `{ stops: [...] }`), or
1333
+ preset name (`color="gradient-sunset"`). 5 built-in semantic presets
1334
+ (`sunset`, `ocean`, `forest`, `fire`, `midnight`) with light + dark
1335
+ variants. 100 % backward-compatible (commit `c922fbff`).
1336
+ - `SliderField` — native HTML `<input type="range">` implementation
1337
+ (commit `d3781288`).
1338
+
1339
+ ### Changed
1340
+
1341
+ - DS-wide reuse-interfaces audit — 9 batches (commits `d0afd731`,
1342
+ `2c6e3aab`, `325c4df1`, `5f1abaf8`, and earlier in the cycle).
1343
+ Every component that declared `height` / `width` / `margin` / etc.
1344
+ inline now `extends IDimensionProps` / `IMarginProps` / etc. and
1345
+ consumes the matching composable. Eliminates half-implemented
1346
+ surfaces and drift from the standard `convertToUnit` helper.
1347
+ - All ~28 inline composable constants extracted to `src/consts/`
1348
+ (commits `7cd6ff81`, `de48f7da`).
1349
+ - Story Playground variants universally renamed to `Default` across all
1350
+ 177 stories (commits `cfeaa682`, `ace53342`, `f2e89284`).
1351
+
1352
+ ### Fixed
1353
+
1354
+ - `OrigamChart` — area fill previously overridden by the SCSS
1355
+ `fill: none` cascade (commit `ed1c4610`); scatter points overflowing
1356
+ the plot zone (commit `ba51275b`); horizontal bar — category/value
1357
+ axis swapped (commit `f8ec65db`); legend click filtered wrong series
1358
+ index (commit `d25eae00`); `legendPosition` not moving the legend
1359
+ (commit `082d053b`); column/bar slots overlapping value axis on first
1360
+ index (commit `4406e92d`); stepped-line tail cut off (commit
1361
+ `ee45357a`); pie/donut legend showing wrong labels (commit `4406e92d`).
1362
+ - `OrigamVideo` — double-click skip on desktop (`f50ccbdb`); controls bar
1363
+ clicks restored after overlay removal (`4fd5f375`); skip ripple sizing
1364
+ cascade (`80e2a9c0`, `9bbbea96`, `75b0779f`); cross-origin download
1365
+ via blob (`73c2e2a4`, `d84fa9bc`).
1366
+ - `OrigamCalendar` — Calendar loading shape + active toolbar button
1367
+ contrast (`55e58b3d`).
1368
+ - `OrigamQrCode` — intent-to-paint mapping, SVG host sizing, title
1369
+ centring (commits `c1823057`, `dceb4cfe`).
1370
+ - Link composable — `ComputedRef` for `tag` and `href` unwrapped in
1371
+ templates; previously caused a `[object Object]` render (commit
1372
+ `9822c47b`, reverted and re-landed as `3f79d0c0`).
1373
+ - `withDefaults()` inline literal rule enforced — `Grid`, `Masonry`,
1374
+ `Blockquote` were referencing `XXX_DEFAULTS` objects statically
1375
+ unresolvable by the SFC compiler, causing `undefined` prop crashes
1376
+ (commit `276fff1e`).
1377
+ - `OrigamSnackbar` — `roundedClasses` `ReferenceError` at mount (commit
1378
+ `aa86f93c`).
1379
+
1380
+ ---
1381
+
1382
+ ## [2.3.0] — 2026-05-15
1383
+
1384
+ > **The features release.** Four new components, four major
1385
+ > enrichments, an official Nuxt module, and a comprehensive SSR
1386
+ > safety audit. All additions are backward-compatible — drop-in
1387
+ > upgrade from 2.2.x. 378 unit tests (+158 vs 2.2.1), 0 lint errors.
1388
+
1389
+ ### Added
1390
+
1391
+ - `origam/nuxt` sub-export — official Nuxt 3 / Nuxt 4 module. Auto-imports
1392
+ components and composables. SSR-safe theme resolution via cookie +
1393
+ `Sec-CH-Prefers-Color-Scheme` header (no FOUC, no hydration mismatch).
1394
+ Auto-injects token CSS files (primitive + selected themes + utilities).
1395
+ Configurable via `origam: {}` in `nuxt.config.ts`. Resolves through
1396
+ `modules: ['origam/nuxt']`. New `IOrigamNuxtModuleOptions` and
1397
+ `IOrigamNuxtRuntimeConfig` interfaces; new theme constants
1398
+ (`ORIGAM_THEME_AUTO`, `ORIGAM_THEME_LIGHT`, `ORIGAM_THEME_DARK`,
1399
+ `ORIGAM_THEME_ATTR`, `ORIGAM_THEME_STORAGE_KEY`). Reference
1400
+ documentation at `docs/integrations/nuxt.md`.
1401
+
1402
+ ### Changed
1403
+
1404
+ - `OrigamTextField` — new `mask` prop with built-in patterns
1405
+ (`phone:fr`, `phone:us`, `phone:international`, `iban`, `siret`,
1406
+ `creditcard`, `date:iso`, `date:fr`, `date:us`, `time`, `time:12h`,
1407
+ `postcode:fr`, `postcode:us`) plus a custom pattern syntax
1408
+ (`#` = digit, `A` = letter, `*` = any, anything else is a literal).
1409
+ In-house mask engine — zero external dependency (no `imask.js` /
1410
+ `cleave.js` / `vue-the-mask`). Reactive validation pipeline with
1411
+ built-in `luhn` (credit card), `iban` (mod-97) and date parsers
1412
+ (`date:iso` / `date:fr` / `date:us`); custom validators accepted as
1413
+ `(unmasked) => boolean`. New emits `@valid(boolean)` and
1414
+ `@complete({ complete, unmasked })` fire on every value change. The
1415
+ v-model exposes the **unmasked** value while the DOM input displays
1416
+ the formatted (masked) one — paste handling strips literals and
1417
+ reformats, `aria-invalid` toggles on touched fields, and the engine
1418
+ auto-promotes phone-shaped patterns to `type="tel"` for mobile
1419
+ keyboard hints. New `useMask` composable, `applyMask` / `unmaskValue`
1420
+ / `resolveMaskConfig` / `validatePattern` utils, new `IMaskOptions`
1421
+ interface and `TMask` / `TBuiltInPattern` / `TPatternValidator`
1422
+ types.
1423
+ - `OrigamTextareaField` — new `mode="rich"` enabling a lightweight
1424
+ HTML / Markdown editor based on `contenteditable`. Built in-house
1425
+ with zero external dependencies (no TipTap, ProseMirror, Quill).
1426
+ 9 toolbar commands (`bold`, `italic`, `underline`, `link`,
1427
+ `list-bullet`, `list-ordered`, `heading`, `code-inline`,
1428
+ `clear-format`), customisable via the new `toolbar` prop (or
1429
+ `toolbar: false` to hide). Keyboard shortcuts (Cmd/Ctrl+B/I/U/K/E
1430
+ and Cmd/Ctrl+Shift+7/8 for the two list modes). New `output` prop
1431
+ switches the v-model serialisation between `'html'` (sanitised) and
1432
+ `'markdown'` (CommonMark-flavoured subset). New `toolbarPosition`
1433
+ prop (`'top' | 'bottom' | 'floating'`). New `format` emit fired on
1434
+ every toolbar click or keyboard shortcut with the command id (and
1435
+ the URL for link insertion). New slots `#toolbar` and
1436
+ `#toolbar-item` allow replacing the default UI entirely. Internal
1437
+ sub-component `OrigamRichToolbar.vue` + composable
1438
+ `useTextareaRich` own the contenteditable contract. In-house HTML
1439
+ sanitiser with allowlist on tags (`p`, `br`, `strong`, `b`, `em`,
1440
+ `i`, `u`, `a`, `ul`, `ol`, `li`, `h1`, `h2`, `h3`, `code`),
1441
+ attributes (`href` restricted to `http:` / `https:` / `mailto:` /
1442
+ `tel:` plus relative URLs; `class` restricted to the
1443
+ `origam-rich--*` prefix), and stripping every `on*` event handler
1444
+ before per-tag filtering. External links are auto-hardened with
1445
+ `rel="noopener noreferrer nofollow" target="_blank"`. ARIA:
1446
+ `role="toolbar" + aria-label` on the toolbar, real `<button>` per
1447
+ command with `aria-label` + `aria-pressed`, the editing surface
1448
+ carries `role="textbox" + aria-multiline="true"`. New tokens under
1449
+ `component/textarea-field/rich-*` (toolbar surface, button states,
1450
+ content padding, inline-code colors, link color, heading sizes).
1451
+ The plain-mode (`mode="plain"`, default) API is fully
1452
+ backward-compatible — passing nothing keeps the previous
1453
+ `<textarea>` behaviour.
1454
+
1455
+ - `OrigamCode` — major enrichment via shiki integration. Syntax
1456
+ highlighting for 13 languages (vue, ts, js, tsx, jsx, scss, css, json,
1457
+ bash, html, xml, yaml, md) plus `plaintext`. New props: `lineNumbers`,
1458
+ `highlightLines` (accepts both `number[]` and the range syntax
1459
+ `'2,5-7'`), `copyable`, `maxHeight`, `theme` (`'auto' | 'light' | 'dark'`),
1460
+ `wrap`, `filename`. Copy button with `navigator.clipboard` and an
1461
+ `execCommand('copy')` fallback for legacy WebViews. Lazy-init shiki
1462
+ highlighter cached as a module-level singleton across instances; an
1463
+ LRU (max 64 entries) caches highlighted HTML by `(code, lang, theme)`
1464
+ so re-renders never re-tokenise. Theme defaults to `auto` and tracks
1465
+ the host `<html data-theme>` attribute. New `useCode` composable
1466
+ exposing `{ highlight, prime, isReady, resetCacheForTesting }`. New
1467
+ utility `parseHighlightLines()` shared with stories/tests. Pure-CSS
1468
+ line-numbers gutter (CSS counter, no JS layout) and line-highlight
1469
+ swap (class toggle on already-rendered rows, no re-tokenisation).
1470
+ ARIA: `role="region"` on the surface, `aria-live="polite"` on the
1471
+ copy feedback, hidden line numbers. `shiki` (`^3.8.1`) promoted from
1472
+ `devDependencies` to `dependencies` (it's a runtime dep now); the
1473
+ bundle adds ~3 MB to installed `node_modules` for the curated subset.
1474
+ New tokens under `component/code` (35 vars: surface, header, filename,
1475
+ copy button, line-number gutter, line-highlight, scrollbar). The v2.x
1476
+ plain-text `<pre>` API is fully backward-compatible — passing just
1477
+ `code` and `lang` keeps the previous behaviour.
1478
+
1479
+ - `OrigamParallax` — major enrichment. Multi-layer support via new
1480
+ `OrigamParallaxLayer` subcomponent (`speed`, `offsetX`, `offsetY`,
1481
+ `zIndex` props; layers register themselves into the host runtime and
1482
+ receive direct DOM mutations of `transform` outside Vue reactivity).
1483
+ Host gains `direction` (`'vertical' | 'horizontal' | 'both'`),
1484
+ `easing` (`'linear' | 'ease-out' | 'spring'`, in addition to the
1485
+ legacy raw CSS timing-function string), `disabled`, `speed`,
1486
+ `threshold` props. New emits: `@enter`, `@leave`,
1487
+ `@scroll-progress(0→1)` driven by `IntersectionObserver` +
1488
+ `requestAnimationFrame`. `prefers-reduced-motion: reduce` is honoured
1489
+ natively — layers stay at `translate3d(offsetX, offsetY, 0)` and the
1490
+ rAF loop short-circuits. CSS-first scroll-driven animations
1491
+ (`animation-timeline: view()`) when `view-timeline` is supported
1492
+ (Chrome 115+, Edge 115+) AND `easing === 'linear'`; JS fallback
1493
+ otherwise. Spring easing implemented as a damped lerp in the JS path.
1494
+ Existing single-layer / `<OrigamParallaxElement>` API preserved (the
1495
+ two layer kinds use independent injection contexts and can coexist
1496
+ inside the same host). New tokens: `parallax.transition-duration-spring`,
1497
+ `parallax.transition-easing-default` / `-spring`,
1498
+ `parallax.layer.will-change` / `.transform-origin`.
1499
+
1500
+ ### Added
1501
+
1502
+ - `OrigamCommandPalette` — ⌘K command launcher. Built on a teleported
1503
+ dialog with focus trap + focus restoration. Custom subsequence-based
1504
+ fuzzy-match algorithm (no external dep) ranks results by
1505
+ consecutive-run + label-prefix + first-position bonuses. Composable
1506
+ `useCommand` exposes a process-wide command registry — entries
1507
+ registered inside a Vue effect scope auto-unregister on dispose.
1508
+ Reuses `OrigamKbd` for inline shortcut display. Hotkey listener
1509
+ built on `useHotkey` (default `⌘+K` on macOS, `Ctrl+K` on
1510
+ Windows / Linux, configurable per combination or disabled with
1511
+ `:hotkey="null"`). ARIA combobox pattern (`role="combobox"` on the
1512
+ input, `role="listbox"` on the result list, `role="option"` per
1513
+ item, `aria-activedescendant` tracking the keyboard cursor) +
1514
+ `role="dialog"` + `aria-modal="true"` on the surface. Tokens exposed
1515
+ under `tokens/component/command-palette.json`
1516
+ (`--origam-command-palette---*`,
1517
+ `--origam-command-palette__input---*`,
1518
+ `--origam-command-palette__item---*`,
1519
+ `--origam-command-palette__group-title---*`,
1520
+ `--origam-command-palette__empty---*`,
1521
+ `--origam-command-palette__footer---*`,
1522
+ `--origam-command-palette--backdrop---*`).
1523
+
1524
+ - `OrigamBracket` — e-sport tournament tree. Supports single-elimination,
1525
+ double-elimination and round-robin variants. SVG connectors
1526
+ auto-computed from `nextMatchId` (with positional fallback). Slots for
1527
+ custom match / competitor / round-title / connector rendering. ARIA
1528
+ `role="region"` with per-round `role="group"` + `aria-labelledby`
1529
+ pointing at title headings. Tokens exposed under
1530
+ `tokens/component/bracket.json` (`--origam-bracket---*`,
1531
+ `--origam-bracket-match---*`, `--origam-bracket-competitor---*`,
1532
+ `--origam-bracket-connector---*`, `--origam-bracket-round-robin---*`).
1533
+
1534
+ - `OrigamSnackbarStack` — multi-toast notification system. Reuses
1535
+ `OrigamSnackbar` styling vocabulary for rendering. Composable
1536
+ `useSnackbarStack({ id })` exposes `notify` / `dismiss` /
1537
+ `dismissAll`. 8 anchor locations, max stack size with FIFO eviction,
1538
+ per-item auto-dismiss (or `0` for sticky), action buttons with
1539
+ optional `keepOpen`, ARIA `role="region"` on the stack root +
1540
+ `role="status"` / `"alert"` per intent on each item with matching
1541
+ `aria-live`. Slide-in / slide-out transitions degrade to a fade under
1542
+ `prefers-reduced-motion`. Tokens exposed under
1543
+ `tokens/component/snackbar-stack.json`
1544
+ (`--origam-snackbar-stack---*`, `--origam-snackbar-stack__item---*`).
1545
+
1546
+ - `OrigamTabs` / `OrigamTab` / `OrigamTabPanels` / `OrigamTabPanel` —
1547
+ full tab system with horizontal / vertical orientation, three visual
1548
+ variants (`default` / `pills` / `underline`), ARIA `role="tablist"` +
1549
+ `role="tab"` + `role="tabpanel"` wiring, full keyboard navigation
1550
+ (`←`/`→`/`↑`/`↓`/`Home`/`End`/`Enter`/`Space`), lazy or eager panel
1551
+ mounting, optional touch-swipeable panels. Reuses the shared
1552
+ `useGroup` orchestration so the selection state, mandatory behaviour
1553
+ and disabled-tab semantics align with the rest of the system
1554
+ (`OrigamBtnToggle`, `OrigamCarousel`, `OrigamBottomNav`). Tokens
1555
+ exposed under `tokens/component/tabs.json` (item color, indicator
1556
+ color, panel padding, transition duration).
1557
+
1558
+ - `OrigamClientOnly` — SSR helper component that renders its default
1559
+ slot only after `onMounted`. Optional `#fallback` slot (or
1560
+ `placeholder-tag` / `placeholder-class` props) reserves layout space
1561
+ during SSR to avoid CLS. Use to wrap fragments whose markup truly must
1562
+ differ between server and client (audio, deviceorientation,
1563
+ IntersectionObserver-driven features, …) without triggering hydration
1564
+ mismatches.
1565
+
1566
+ - `useCssSupportClient(feature, { defaultValue })` — hydration-safe
1567
+ feature-gate helper. Returns a `Ref<boolean>` that starts at
1568
+ `defaultValue` (default `false`) on both SSR and first client render,
1569
+ then flips to the real `CSS.supports()` result inside `onMounted`.
1570
+ Use to gate **markup** branches (template `v-if`) when the regular
1571
+ `useCssSupport().css.value.X` would produce a hydration mismatch
1572
+ (class-only branches keep using the existing API — Vue 3 reconciles
1573
+ class diffs fine).
1574
+
1575
+ ### Fixed
1576
+
1577
+ - SSR safety — comprehensive audit of all composables and components
1578
+ that previously could crash on server render (`window is not defined`,
1579
+ `document is not defined`). `useCssSupport` already returned all-false
1580
+ flags during SSR; the rest of the surface (`useTheme`, `useCommand`,
1581
+ `useSnackbarStack`, `useCode`, `useMask`, `useTouch`, `useHotkey`,
1582
+ `useSticky`, `useSheetSwipe`, `useScroll`, `useParallax`, `useStyleTag`,
1583
+ `useTeleport`, `useLocationStrategies`, `useScrollStrategies`,
1584
+ `useDisplay`) was confirmed safe via the audit and patched where a
1585
+ composable's public method or a `computed` could be evaluated during
1586
+ SSR. Specifically: `useAspectRatio` no longer dereferences
1587
+ `window.innerWidth/Height` when no explicit `aspectRatio` prop is
1588
+ given; `useVirtual`'s `viewportHeight` computed guards
1589
+ `document.documentElement`; `useSnackbarStack.dismiss()` guards
1590
+ `window.clearTimeout`. Overlay components (Dialog, Drawer, Menu,
1591
+ Tooltip, Snackbar, ContextualMenu, SnackbarStack, CommandPalette)
1592
+ confirmed SSR-safe via `<Teleport>` (Vue defers the mount until the
1593
+ client). New guide `docs/guide/ssr.md`. New `src/__tests__/ssr-smoke.spec.ts`
1594
+ exercises every refactored composable in a simulated SSR environment
1595
+ (window/document/CSS stripped) **and** through `@vue/server-renderer`'s
1596
+ real `renderToString()`.
1597
+
1598
+ ---
1599
+
1600
+ ## [2.2.1] — 2026-05-14
1601
+
1602
+ ### Fixed
1603
+
1604
+ - Expose `./package.json` explicitly in the `exports` map. The catch-all
1605
+ `"./*": "./dist/src/*"` previously intercepted `import 'origam/package.json'`
1606
+ and re-routed it to a non-existent file inside `dist/`. Standard
1607
+ consumer pattern (reading the version from the package metadata) was
1608
+ broken — fixed by adding `"./package.json": "./package.json"` ahead
1609
+ of the catch-all so it matches first.
1610
+ - 2.2.0 was tagged but never published to npm. 2.2.1 is the first
1611
+ version that lands on the registry.
1612
+
1613
+ ---
1614
+
1615
+ ## [2.2.0] — 2026-05-14
1616
+
1617
+ > **The "ready for npm" release.** Package metadata, peer dependencies,
1618
+ > tree-shaking signals and a real README are all in. Tarball preview
1619
+ > shrunk from 5.6 MB to **867.9 kB** (2 343 files, zero suspect entry).
1620
+ > No public API change vs 2.1.0 — every consumer upgrade is a
1621
+ > drop-in.
1622
+
1623
+ ### Added — stories, slots, emits coverage
1624
+
1625
+ - 525+ missing `Slot` / `Emit` Variants across 113 component stories
1626
+ (`feat(stories)`). Every documented slot now has a dedicated Variant
1627
+ with a custom-content example, and every emit a Variant that logs
1628
+ to a live counter.
1629
+ - `OrigamSwitchTrack` extracted as a standalone primitive (track-only)
1630
+ so consumers can build switch-like compounds without rewriting the
1631
+ thumb logic.
1632
+ - Tooling: `useStateEffect` now reacts to every per-axis composable
1633
+ (`useHover` / `useActive` / `useFocus`) in a single call — collapses
1634
+ the 14 components that historically wired them axis by axis.
1635
+
1636
+ ### Fixed
1637
+
1638
+ - `useStyle` now instrumented across the 124 components that
1639
+ previously inlined `*Styles` arrays — single source of truth for the
1640
+ inline-style escape hatch.
1641
+ - Carousel — `progress` prop renders a real-time progress bar wired to
1642
+ the cycle timer (was rendering a static "step / total" bar).
1643
+ - Carousel — `hideDelimiterBackground` finally has a background to
1644
+ hide (default `__controls` bg = `rgba(0, 0, 0, 0.4)`).
1645
+ - Histoire sandbox/panel sash now resizes the iframe in **both
1646
+ directions** (was capped at the 720 px responsive-preset width when
1647
+ dragging right).
1648
+ - `useRounded` — utility variants (`xs|sm|md|lg|xl|none|full`) now
1649
+ emit BOTH the `.origam--rounded-*` class AND an inline
1650
+ `border-radius` declaration as a specificity escape hatch (Strategy
1651
+ A documented in `CLAUDE.md`).
1652
+ - 130 unused-vars cleared from `src/`, 39 ESLint warnings fixed.
1653
+
1654
+ ### Changed — package for npm
1655
+
1656
+ - `peerDependencies` introduced: `vue ^3.5`, `vue-i18n ^11`,
1657
+ `vue-router ^4.5` (last two `optional` via `peerDependenciesMeta`).
1658
+ They are no longer auto-installed as `dependencies` — consumers
1659
+ bring their own versions.
1660
+ - `sideEffects: ["**/*.css", "**/*.scss", "**/*.vue"]` for downstream
1661
+ tree-shaking.
1662
+ - `engines.node: ">=22"` (matches `.nvmrc`).
1663
+ - `prepublishOnly` script chains `tokens:build` → `server:build` →
1664
+ unit tests. Every `npm publish` rebuilds from scratch with green
1665
+ tests.
1666
+ - `files` whitelist tightened to `dist/src/`, `dist/tokens/`, LICENSE,
1667
+ README, CHANGELOG. The Histoire bundle (`dist/stories/`, ~13 MB) is
1668
+ no longer shipped to consumers.
1669
+ - `build.config.ts` `externals` extended to `['vue', 'vue-i18n',
1670
+ 'vue-router', '@mdi/font']` so peer deps don't get re-bundled.
1671
+ - mkdist exclusion patterns expanded: `.spec.ts`, `__tests__/**`,
1672
+ `.cy.ts`, `.story.vue` are stripped from `dist/`.
1673
+ - ESLint config ignores `docs/.vitepress/cache/**` and
1674
+ `figma-plugin/**` (these aren't part of the published library).
1675
+
1676
+ ### Documentation
1677
+
1678
+ - Full `README.md` written from scratch (253 lines) — install,
1679
+ peer deps, plugin registration, theming (`data-theme` +
1680
+ `useTheme()` + `<OrigamThemeProvider>`), token tiers, component
1681
+ families, composable table, CSS-first principle.
1682
+
1683
+ ### Internal
1684
+
1685
+ - ESLint rule `no-restricted-imports` blocks `@origam` /
1686
+ `@stories` / `@docs` / `@cypress` aliases inside `src/` — once
1687
+ published, those aliases can't be resolved by consumers.
1688
+ - 124 components instrumented with `useStyle` following the
1689
+ canonical pattern.
1690
+ - `.tsbuildinfo` and other transient artefacts removed from the tree.
1691
+
1692
+ ---
1693
+
1694
+ ## [2.1.0] — 2026-05-07
1695
+
1696
+ > **The classes-first release.** The 13 transversal composables
1697
+ > (`useColor`, `useBackgroundColor`, `useTextColor`, `useColorEffect`,
1698
+ > `useElevation`, `useRounded`, `useBorder`, `useMargin`, `usePadding`,
1699
+ > `useSize`) now emit utility classes (e.g. `.origam--bg-primary`,
1700
+ > `.origam--shadow-md`) when the consumer passes a tokenised value. The
1701
+ > existing `*Styles` outputs stay populated only when the value is
1702
+ > non-tokenisable (legacy hex, custom dimensions) — the inline style
1703
+ > stack on a typical button drops from ~12 to ~6 declarations.
1704
+ >
1705
+ > **Non-breaking.** Every composable keeps its previous return shape;
1706
+ > the new `*Classes` keys are additive. Components have been migrated
1707
+ > to read both shapes in parallel (transition strategy A).
1708
+
1709
+ ### Migrating from v2.0 to v2.1
1710
+
1711
+ Nothing is required. The release is additive; existing consumers keep
1712
+ working unchanged. To start using the new path:
1713
+
1714
+ ```vue
1715
+ <!-- v2.0 — still works, still emits inline style -->
1716
+ <OrigamBtn color="primary">Click</OrigamBtn>
1717
+
1718
+ <!-- v2.1 — same code, but the emitted DOM now also carries the
1719
+ `.origam--bg-primary` utility class instead of inline
1720
+ `background-color: var(...)`. Inspecting the DOM is much cleaner. -->
1721
+ ```
1722
+
1723
+ If you write your own components on top of `useColor*` / `useElevation`
1724
+ / `useRounded` / etc., you can now destructure the new `*Classes`
1725
+ return:
1726
+
1727
+ ```ts
1728
+ const { colorClasses, colorStyles } = useColorEffect(props, isHover, isActive, isDisabled)
1729
+ // colorClasses: ['origam--bg-primary'] when intent
1730
+ // colorStyles : [] when intent
1731
+ // colorClasses: [] when '#7c3aed'
1732
+ // colorStyles : ['background-color: ...']when '#7c3aed'
1733
+ ```
1734
+
1735
+ Bind both — `:class="[..., colorClasses]"` AND `:style="[..., colorStyles]"`.
1736
+ The hover/active/disabled state still flows through `colorStyles` (utility
1737
+ classes are static, by design).
1738
+
1739
+ ### Added
1740
+
1741
+ - **66 utility classes** generated by Style Dictionary, exposed at
1742
+ `origam/tokens/css/utilities` and forwarded by `origam/styles`:
1743
+ - `.origam--color-{primary|secondary|success|warning|danger|info|neutral}`
1744
+ - `.origam--bg-{primary|secondary|success|warning|danger|info|neutral}`
1745
+ - `.origam--shadow-{none|xs|sm|md|lg|xl}`
1746
+ - `.origam--rounded-{none|xs|sm|md|lg|xl|full}`
1747
+ - `.origam--border-{none|thin|thick}`
1748
+ - `.origam--p-{0..12}`, `.origam--m-{0..12}`, `.origam--gap-{0..12}`
1749
+ - `.origam--text-{xs|sm|md|lg|xl|2xl}`
1750
+ - New return keys on transversal composables:
1751
+ `colorClasses`, `backgroundColorClasses`, `textColorClasses`,
1752
+ `elevationClasses` (extended), `roundedClasses` (extended),
1753
+ `borderClasses` (extended), `marginClasses`, `paddingClasses`,
1754
+ `sizeClasses`.
1755
+ - `tests/e2e/utilities.spec.ts` — 66 Playwright tests, one per
1756
+ utility class, asserting both class presence and `getComputedStyle`
1757
+ resolution against the CSS var pipeline.
1758
+ - `tests/e2e/prop-audit-phase4.spec.ts` — DOM audit on representative
1759
+ components confirming the inline-style pile reduction.
1760
+ - 79 new Vitest unit specs in `src/composables/Commons/__tests__/`
1761
+ covering the classes-first branch of every refactored composable.
1762
+
1763
+ ### Changed
1764
+
1765
+ - ~54 components migrated to consume `*Classes` in parallel with
1766
+ `*Styles`. Notable surface-bearing children: Menu/Tooltip/Picker/
1767
+ Snackbar/Badge — utility class lands on the `__content` / `__pill` /
1768
+ `__wrapper` BEM child, never on the teleport root.
1769
+ - `OrigamSelectionControl.__wrapper` now carries
1770
+ `useTextColor(color)`'s class + style on its root element. This
1771
+ re-instates the Switch thumb tinting (`currentColor`) for tokenised
1772
+ intents, which had silently broken when `OrigamSwitchTrack` was
1773
+ extracted earlier in the cycle.
1774
+ - `OrigamSwitch` SCSS — the legacy `[style*="color:"]` selector is
1775
+ joined by a class-driven set
1776
+ (`.origam-selection-control__wrapper.origam--color-{intent} &__thumb`)
1777
+ so both the new tokenised path and the legacy hex path tint the thumb
1778
+ via `currentColor`.
1779
+
1780
+ ### Deprecated
1781
+
1782
+ - `*Styles` returns on the 10 refactored composables — prefer the
1783
+ matching `*Classes` for tokenised values. Both are kept for one
1784
+ major cycle; removal scheduled for v3.0.0.
1785
+
1786
+ ### Fixed
1787
+
1788
+ - `OrigamSwitch` thumb stayed white when `color="primary"` (or any
1789
+ tokenised intent) — regression from the `OrigamSwitchTrack`
1790
+ extraction. Re-introduced via classes-first.
1791
+ - `OrigamSnackbar` `__wrapper` no longer duplicates the
1792
+ `roundedClasses`/`borderClasses` already applied at the root.
1793
+
1794
+ ### Known limits — planned for v2.2 (Phase 1.5)
1795
+
1796
+ - No utility for margin / padding axes (`mx`, `my`, `mt`, `mb`, `ml`,
1797
+ `mr`, `px`, `py`, `pt`, `pb`, `pl`, `pr`). Components passing
1798
+ axis-specific values fall back to inline styles for now.
1799
+ - No `2xl` / `3xl` shadow utilities (the rungs exist as primitives but
1800
+ are off the manifest).
1801
+ - Legacy `ROUNDED` enum (`x-small | small | default | medium | large
1802
+ | x-large | shaped | shaped-invert`) does not bridge to the
1803
+ `none|xs|sm|md|lg|xl|full` utility taxonomy — components passing a
1804
+ legacy value get their existing component-scoped BEM modifier and an
1805
+ inline style fallback.
1806
+ - `useColorEffect` does NOT emit a `*Classes` value when `isHover` /
1807
+ `isActive` / `isDisabled` is true (states are inline-only by design).
1808
+ Components that bind `useActive(props, 'modelValue')` (Alert, Badge,
1809
+ BottomNav) therefore never carry a utility class while visible — the
1810
+ inline style still paints them correctly. This is intentional; do not
1811
+ rely on the utility class for state-dependent styling.
1812
+
1813
+ ---
1814
+
1815
+ ## [2.0.0] — 2026-04-26
1816
+
1817
+ > **The design tokens release.** Every component now resolves its colors,
1818
+ > spacing, typography, and motion through a centralised, theme-aware
1819
+ > token pipeline (Style Dictionary v4 + Tokens Studio for Figma). 50+
1820
+ > components migrated; 18 of them also picked up real bug fixes from the
1821
+ > companion `optimus-design-system` codebase. Multi-theme (light / dark)
1822
+ > works out of the box; `brand-X` themes are an additive layer.
1823
+
1824
+ ### Migrating from v0.x to v2.0.0
1825
+
1826
+ This is a major bump because three things change:
1827
+
1828
+ 1. **The CSS variables that components read are now generated.** The old
1829
+ `<style>:root{}` blocks that each component shipped have been removed.
1830
+ Consumers MUST load the generated token CSS — either:
1831
+
1832
+ ```ts
1833
+ import 'origam/styles' // primitive + light + dark + helpers
1834
+ ```
1835
+ or pick the layers separately:
1836
+ ```ts
1837
+ import 'origam/tokens/css/primitive'
1838
+ import 'origam/tokens/css/light'
1839
+ import 'origam/tokens/css/dark' // applied via [data-theme="dark"]
1840
+ ```
1841
+
1842
+ If you were overriding origam vars at the document root (e.g.
1843
+ `:root { --origam-btn---background-color: red }`), that still works —
1844
+ the generated CSS is just a default. But if you depended on the
1845
+ per-component `:root{}` block being injected when the component
1846
+ mounted, that's gone. The vars are now set once globally.
1847
+
1848
+ 2. **`useColorEffect` is intent-aware.** The `color` / `bgColor` props on
1849
+ `<OrigamBtn>`, `<OrigamChip>`, `<OrigamAvatar>`, etc. now expect a
1850
+ semantic intent (`'primary'`, `'success'`, `'danger'`, …) rather than
1851
+ a raw hex. Raw hex still works but emits a one-shot `console.warn`
1852
+ per `(prop, value)` pair (full removal in v3.0.0).
1853
+
1854
+ ```vue
1855
+ <!-- v0.x -->
1856
+ <OrigamBtn color="#7c3aed">Click</OrigamBtn>
1857
+
1858
+ <!-- v2.0 -->
1859
+ <OrigamBtn color="primary">Click</OrigamBtn>
1860
+
1861
+ <!-- One-off custom color -->
1862
+ <OrigamBtn :style="{ '--origam-btn---background-color': '#7c3aed' }">…</OrigamBtn>
1863
+ ```
1864
+
1865
+ 3. **`useElevation` no longer computes shadows in JS.** The `bgColor`
1866
+ parameter is accepted for signature compat but ignored. The composable
1867
+ now emits `box-shadow: var(--origam-shadow-{rung})` and the rung is
1868
+ themed at the token level (different shadow recipes for light vs dark).
1869
+
1870
+ The legacy `formatElevationStyle(level, bgColor)` utility is still
1871
+ exported but `@deprecated` — it will be removed in v3.0.0.
1872
+
1873
+ ### Added
1874
+
1875
+ - **Multi-tier design tokens** (`tokens/`):
1876
+ - Primitive: 12 color ramps, 12 spacing steps, full font / radius /
1877
+ shadow / motion / zIndex / opacity / border ladders.
1878
+ - Semantic: `surface`, `text`, `border`, `action.{primary,secondary,
1879
+ ghost}`, `feedback.{success,warning,danger,info}`, `overlay`. Per
1880
+ theme (light + dark), with hooks for brand-X.
1881
+ - Component: ~60 files, one per component, in DTCG / Tokens Studio
1882
+ format — editable from the Figma plugin.
1883
+ - **Pipeline**: Style Dictionary v4 + sd-transforms. Outputs CSS,
1884
+ SCSS partials, and TypeScript union types. `npm run tokens:build`,
1885
+ `tokens:watch`, `tokens:lint`.
1886
+ - **GitHub Action** (`.github/workflows/tokens-sync.yml`): consumes
1887
+ Figma → Tokens Studio → `tokens-sync` branch, rebuilds artifacts,
1888
+ opens a PR to `develop` automatically.
1889
+ - **Multi-theme runtime**:
1890
+ - `useTheme()` composable — singleton ref, persistence, prefers-
1891
+ color-scheme fallback, toggle helper.
1892
+ - `<OrigamThemeProvider theme="dark">` for sub-tree overrides.
1893
+ - **CSS-first / JS-fallback principle**:
1894
+ - `useCssSupport()` composable — feature detection layer wrapping
1895
+ `CSS.supports()` with caching for 20 modern features (subgrid,
1896
+ container queries, `:has()`, aspect-ratio, color-mix, anchor
1897
+ positioning, view transitions, …). Components now branch via
1898
+ this composable instead of calling `CSS.supports()` directly.
1899
+ - **Defaults system** (ported from optimus):
1900
+ - `useDefaults()` composable + `<OrigamDefaultsProvider>` for
1901
+ cascading default props (mirrors Vuetify's `<v-defaults-provider>`).
1902
+ - `IDefault`, `IDefaultProviderProps`, `IDefaultProviderSlots`.
1903
+ - **`<OrigamConfirmWrapper>`** — type-it-twice form helper with
1904
+ auto-injected validation, two render modes (slot-based / `field=`
1905
+ shortcut), and bidirectional defaults forwarding.
1906
+ - **`SCSS helpers`** (`src/assets/scss/_helpers.scss`): `ds-intent`,
1907
+ `ds-elevation`, `ds-text-style`, `ds-space`, `ds-focus-ring`,
1908
+ `ds-visually-hidden`.
1909
+ - **VARIANT / VARIANT_INPUT enums** + `TVariant` / `TVariantInput`
1910
+ types.
1911
+ - Common emit / slot interfaces: `IFieldEmits`, `IFieldSlots`,
1912
+ `IFieldDefaultSlotProps`, `IInputEmits`, `IInputSlots`,
1913
+ `IAdjacentEmits`, `IAdjacentSlots`, `IAdjacentInnerEmits`,
1914
+ `IAdjacentInnerSlots`, `IActiveEmits`, `IFocusEmits`,
1915
+ `ICommonsComponentEmits`, `ICommonsComponentSlots`.
1916
+
1917
+ ### Changed
1918
+
1919
+ - **Every component** with chrome (~50) now consumes design tokens
1920
+ rather than hardcoded values. The per-component `<style>:root{}`
1921
+ blocks are gone.
1922
+ - **`OrigamPasswordField`** rebuilt from the optimus version (629
1923
+ vs 399 lines). New features: strength-requirements popup
1924
+ (`requirements` + `need*` flags + `minLength`), auto-injected
1925
+ validation rules, show/hide toggle, intersect-driven autofocus,
1926
+ click:control / mousedown:control emits.
1927
+ - **`OrigamFileField`** split into 3 files (FileField + DragNDropItem
1928
+ + ListItem) for clearer responsibility boundaries; drag-and-drop
1929
+ mode reworked, `maxFileSize` validation added, typed emits.
1930
+ - **`OrigamForm`** grew form-level validation, `scrollToError`,
1931
+ global `messages` rendering through OrigamMessages.
1932
+ - **`OrigamField`** now exposes the `outline__notch` BEM child for
1933
+ the floating-label rendering, slot-based prefix/suffix detection,
1934
+ and proper focus↔active synchronisation.
1935
+ - **Snackbar z-index** normalised from `10000` to `1060` via
1936
+ `{zIndex.toast}`. Consumers stacking custom overlays on top must
1937
+ verify their stacking context.
1938
+ - **`color.action.primary.bgSubtle / fgSubtle`** added to the
1939
+ semantic layer (was missing — Select selected-item background and
1940
+ Slider thumb focus ring needed it).
1941
+
1942
+ ### Fixed
1943
+
1944
+ - **OrigamMessages**: `:id` was bound to the entire messages array
1945
+ instead of the current message. Fixed by using a kebab-cased
1946
+ per-message key.
1947
+ - **OrigamNumberField**: `watchEffect` → `watch(props.modelValue)` —
1948
+ the eager mode was causing reactive write loops while the user
1949
+ typed. Increment / decrement handlers split.
1950
+ - **OrigamOtpInputField**: undefined-current crash guard;
1951
+ `isValidNumber` → `isInvalidValue` (semantic was inverted);
1952
+ double `update:focused` emit prevented; handleClear added.
1953
+ - **OrigamFileField**: `@clik:append` and `@lick:append-inner` typo
1954
+ fixes; drag&drop append mode no longer overwrites existing files;
1955
+ locale arg shape `t(key, [arg])` corrected.
1956
+ - **OrigamMenu**: stray `console.log()` removed.
1957
+ - **OrigamOverlayScrim**: `<style scoped>` block was missing
1958
+ entirely — scrim wasn't rendering.
1959
+ - **OrigamProgressCircular**: `backgroundColorClasses` /
1960
+ `loaderColorClasses` were missing on the SVG `<circle>` elements.
1961
+ - **OrigamProgressLinear**: `useBackgroundColor` → `useTextColor`
1962
+ (the bar's color was being applied as background, washing it out).
1963
+ - **OrigamListSubheader**: vuetify-specific
1964
+ `rgba(var(--v-theme-on-surface), …)` references replaced with
1965
+ origam-native tokens. Style block converted from `lang="css"` to
1966
+ `lang="scss"`.
1967
+ - **OrigamExpansionPanel**: vuetify shadow var leak
1968
+ (`var(--v-shadow-key-umbra-opacity, …)`) removed; selector
1969
+ `:not(.v-expansion-panel-title--static)` matched a vuetify class
1970
+ that never renders in origam — corrected to the origam class.
1971
+ - **OrigamSlideGroup**: `<style scoped>` block was missing entirely.
1972
+ - **OrigamCheckbox / OrigamCheckboxBtn / OrigamSelectionControl**:
1973
+ `handleClickLabel` argument typed `MouseEvent` (was `Event` —
1974
+ caused TS strict failures).
1975
+ - **OrigamSelectionControlGroup**: `item` slot now exposes
1976
+ `{ item, index }` (was `{ item }` only).
1977
+
1978
+ ### Deprecated
1979
+
1980
+ - `formatElevationStyle(level, bgColor)` — replaced by the
1981
+ `--origam-shadow-{rung}` token ladder. Kept exported for one
1982
+ major version; full removal in v3.0.0.
1983
+ - Raw hex / rgb passed to `useColorEffect` color props — pass a
1984
+ `TIntent` value or use `:style` for one-off custom colors. Removal
1985
+ in v3.0.0.
1986
+
1987
+ ### Outstanding (deferred to v2.x or v3.0.0)
1988
+
1989
+ - Several form components still carry hardcoded hex inside their
1990
+ scoped styles (Switch 11, Select 8, DatePickerField 5,
1991
+ ColorPickerField 2, Img 2, Highlight 3). The token JSONs are in
1992
+ place; the `var(--origam-…)` references need to be wired in a
1993
+ follow-up. Fallback-only behaviour ensures consumers see the
1994
+ intended design today.
1995
+ - Outstanding token-naming gaps: `color.surface.chrome` (SystemBar),
1996
+ `color.overlay.backdrop` (Drawer / Overlay scrim), `color.surface
1997
+ .inverse` (Tooltip), `opacity.20` / `opacity.30`, `radius.xs2: 6px`,
1998
+ `font.size.6xl: 45px`, `font.letterSpacing.widish`. Promote at
1999
+ next ui-designer review.
2000
+ - `OrigamMain` uses `--origam-main--{prop}` (double-tiret) instead
2001
+ of the canonical `---` convention. Cosmetic, breaks consumer
2002
+ overrides. To be aligned at v3.
2003
+
2004
+ ---
2005
+
2006
+ ## Pre-2.0 history
2007
+
2008
+ The `[WIP]` commits before this release were the foundation work
2009
+ (audit, Figma integration spike, package layout). The design-token
2010
+ migration started at commit `2124ab8` (Lot 0) and shipped over
2011
+ ten incremental commits visible in `git log --oneline main..HEAD`.
2012
+