origam 2.18.8 → 2.18.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,8 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.18.8] - 2026-09-23
22
+
21
23
  ### Fixed — #901 `OrigamBtn` — `calc(auto + 0px)` ramenait `min-width` à `0` sur tous les boutons
22
24
 
23
25
  `OrigamBtn.vue`, second bloc `<style scoped>`, déclarait sans condition sur
@@ -69,6 +71,117 @@ capture ne bouge**. Les 7 libellés de la matrice (`Text` … `Outlined`, 4 à
69
71
  padding `12px`×2 ajouté, donc le nouveau plancher ne change la largeur
70
72
  d'aucun bouton de ce pilote. Aucune baseline à régénérer dans cette PR.
71
73
 
74
+ ### Fixed — #411 : `OrigamChartMap` faisait **disparaître** contours et pays sous un thème de marque
75
+
76
+ Ce n'est pas « la couleur ne suit pas le thème ». `borderColor` **était** une
77
+ prop déclarée, donc le résolveur d'ADR-005 l'atteignait déjà : un thème
78
+ nommant `components['origam-chart-map'].borderColor` posait bien sa valeur.
79
+ Le défaut est en aval — la valeur partait **verbatim dans le `stroke` inline
80
+ du SVG, sans passer par `resolveColor()`**, contrairement à sa voisine
81
+ `lineColor` qui y passait déjà.
82
+
83
+ Or un intent comme `'success'` **n'est pas du CSS valide pour `stroke`** : le
84
+ navigateur ne repeint pas en gris, il **jette la déclaration entière**. Une
85
+ marque nommant cette prop ne recevait donc pas un contour mal thémé — elle
86
+ n'avait **plus de contours de pays du tout**.
87
+
88
+ ```
89
+ - :style="{ fill: country.fill, stroke: borderColor }"
90
+ + :style="{ fill: country.fill, stroke: resolveColor(borderColor) }"
91
+ ```
92
+
93
+ **Le jumeau, trouvé en réparant et livré dans la même PR** :
94
+ `defaultCountryFill` portait le défaut identique une ligne plus bas
95
+ (`fill = props.defaultCountryFill` sans `resolveColor()`, typé `string` nu
96
+ face au `TIntent | string` de `lineColor`) — donc les pays **absents du jeu
97
+ de données** disparaissaient de la même façon. Vérifié dans le code avant de
98
+ le traiter, pas déduit par symétrie.
99
+
100
+ Les deux props sont retypées `TIntent | string` et passent par
101
+ `resolveColor()`, qui laisse traverser les littéraux CSS.
102
+
103
+ ⚠️ **Le défaut de `defaultCountryFill` reste le littéral
104
+ `rgba(0,0,0,0.08)`, délibérément.** `resolveColor()` ne rend qu'une couleur
105
+ **opaque fixe**, là où ce littéral est un **voile semi-transparent composité
106
+ sur le `bgColor` que le graphique peint lui-même** : il s'adapte donc au
107
+ clair, au sombre et à un `bgColor` personnalisé. Aucun intent ne reproduit
108
+ ça — le retyper « pour la symétrie » aurait échangé un comportement
109
+ adaptatif contre un comportement figé. Consigné dans le JSDoc de
110
+ `IChartMapProps` et dans la table de la doc. Le défaut de `borderColor`,
111
+ lui, passe de `rgba(0,0,0,0.2)` à l'intent `'neutral'`.
112
+
113
+ ⚠️ **Le ticket était périmé aux deux tiers**, revérifié avant d'écrire une
114
+ ligne : des trois composants qu'il nomme, `OrigamSelect` était déjà réparé
115
+ par `331d9b62` (#456) et `OrigamDatePickerField` par `374858be5` — lequel
116
+ citait déjà #411 sans le fermer. Son périmètre à 3 était en revanche exact :
117
+ grep des littéraux cités, aucune 4ᵉ occurrence dans le DS.
118
+
119
+ A/B contre le commit parent, en Vitest (chaîne `style` littérale) **et** en
120
+ Chromium réel (`getComputedStyle`) : `borderColor` stable **10/10** ;
121
+ `defaultCountryFill` **2/4 rouge avant, 4/4 vert après** en unitaire, rouge
122
+ puis vert en navigateur, stable 10/10 sous `--repeat-each=5`. Story, doc,
123
+ 2 specs TU et 2 specs e2e (ajoutées à `GREEN_SPECS`).
124
+
125
+ ### Fixed — #535 : le contraste syntaxique de `OrigamCode` était sous AA, deux fois plus bas que le ticket ne le disait
126
+
127
+ ⚠️ **Le `4,27:1` du ticket ne se reproduit plus** — `HomeHero` a migré vers
128
+ un extrait `bash` qui ne touche que 2 des 8 catégories shiki, toutes deux
129
+ déjà conformes. Le défaut sous-jacent, lui, est réel et bien plus large.
130
+
131
+ Remesuré sur **8 catégories shiki** (mots-clés, ponctuation, chaînes,
132
+ commentaires, nombres/variables, fonctions, paramètres, balises) × **8 fonds**
133
+ (DS par défaut + 7 thèmes de marque) × **2 modes**, depuis les valeurs hex
134
+ littérales réelles (shiki 4.3.1, `*.theme.ts`) :
135
+
136
+ | | avant | thème shiki seul | **livré** |
137
+ |---|---|---|---|
138
+ | clair, pire cas | **2,93:1** *(paramètre, `geek`)* | 4,23:1 | **4,77:1** *(commentaire, `glass`)* |
139
+ | sombre, pire cas | **2,55:1** *(commentaire, `material`)* | 5,79:1 | **6,77:1** *(mot-clé, `material`)* |
140
+
141
+ **Le pire cas réel était à 2,55:1 : le ticket sous-estimait son propre défaut
142
+ d'un facteur deux.**
143
+
144
+ **D'où vient la couleur, établi et non supposé.** Shiki peint chaque token via
145
+ `--shiki-light` / `--shiki-dark` **inline sur chaque `<span>`**, depuis deux
146
+ thèmes figés déclarés dans `consts/Code/code.const.ts`. **Aucun token
147
+ `--origam-code---syntax-*` par catégorie n'existe** — déjà établi par #399 /
148
+ #661, reconfirmé en relisant le code. La fausse piste signalée dans le ticket
149
+ tient toujours : `--origam-color__feedback--success---fgSubtle` (touché par
150
+ #524) alimente bien `--origam-code__syntax---string`, mais **ce n'est pas ce
151
+ que shiki peint**.
152
+
153
+ **Aucun token créé, et l'arbitrage a été évité plutôt que contourné.** La
154
+ question « faut-il des tokens de syntaxe par catégorie ? » était posée comme
155
+ point d'arrêt ; le calcul a montré qu'aucune décision d'architecture n'était
156
+ nécessaire :
157
+
158
+ 1. `CODE_LIGHT_THEME` / `CODE_DARK_THEME` → `github-light-high-contrast` /
159
+ `github-dark-high-contrast` — **mêmes paquets shiki, coût de bundle
160
+ inchangé**.
161
+ 2. `--origam-code---background-color` : `surface---sunken` →
162
+ `surface---raised`, **un token sémantique qui existe déjà par thème**.
163
+
164
+ Le thème shiki seul ne suffisait pas (4,23:1 en clair) : **il fallait les
165
+ deux**.
166
+
167
+ Nouveau `describe` `Syntax contrast (#535)` dans `code.spec.ts`,
168
+ `getComputedStyle` réel sur chaque couleur shiki distincte, clair + sombre —
169
+ **3/3 vert**.
170
+
171
+ **Non vérifié** : les 7 thèmes de marque **via Playwright contre le site
172
+ marketing lui-même** — calcul déterministe sur les valeurs source à la place,
173
+ le mécanisme CSS étant, lui, vérifié en navigateur réel. VitePress a son
174
+ propre pipeline shiki indépendant, hors périmètre. Le seed marketing
175
+ (`server/db/seed/const.json`) garde une copie figée des anciens noms de
176
+ thème : signalé, **non resynchronisé**, ça demande un re-sync de base séparé.
177
+
178
+ **Trouvaille annexe** : le `#29` cité dans les commentaires « bugs DS
179
+ connus » de `code.spec.ts` ne correspond à aucun ticket — `gh issue view 29`
180
+ est une PR dependabot sans rapport. Corrigé dans le commentaire. Même motif
181
+ que le `#46` qui a circulé des mois dans la doc de ce dépôt.
182
+
183
+ ## [2.18.7] - 2026-09-23
184
+
72
185
  ### Fixed — #607 / #569 : 19 canaux de thème confisqués par le bloc du composant
73
186
 
74
187
  Un composant qui redéclare un token que les feuilles déclarent déjà rend ce
@@ -207,6 +320,8 @@ sont **compositées** sur le `default` de leur propre thème, avec l'opération
207
320
  décimales (composite 0.9757 contre pixel rendu 0.9754). Vérifiée ROUGE sur les
208
321
  valeurs du commit parent, une fois par marque : `geek/light` puis `glass/light`.
209
322
 
323
+ ## [2.18.6] - 2026-09-23
324
+
210
325
  ### Fixed — #818 baseline shrink : les 8 `color-contrast` du DS (8 → 0)
211
326
 
212
327
  ⚠️ **Ce lot porte sur le DS lui-même, pas sur une palette marketing** : il
@@ -345,76 +460,7 @@ d'intention) rejouée après le lot : **226 passed, 0 failed**. Suite unitaire
345
460
  complète : **7149 passed**. `pnpm -F origam guards` 28/28, `guards:self`
346
461
  15/15, `type-check` et `pnpm audit` propres.
347
462
 
348
- ### Fixed — #818 baseline shrink : `aria-allowed-attr`/`aria-prohibited-attr`/`aria-valid-attr-value` (12 des 25 entrées)
349
-
350
- Recompté sur `a11y-violations.baseline.json` (pas repris d'un chiffre cité
351
- ailleurs) : la baseline #818 portait **25 clés / 33 violations** sur 24
352
- composants, dont **12 `aria-allowed-attr`** concentrées sur la famille
353
- `OrigamDataTable*`. Cause commune, pas 12 défauts distincts :
354
- `OrigamTextField` ne posait pas `inheritAttrs: false` alors qu'il
355
- redistribue déjà explicitement chaque attr de fall-through
356
- (`filterInputAttrs` → `rootAttrs` sur `<origam-input>`, `inputAttrs` sur le
357
- vrai `<input>`). Sans ce flag, Vue appliquait EN PLUS le `$attrs` brut sur
358
- la racine `<origam-input>`, qui ne s'y soustrayait pas non plus — doublant
359
- `aria-haspopup`/`aria-expanded`/`aria-controls`/`aria-valuenow`/… sur le
360
- `<div>` wrapper, qui ne porte aucun rôle les autorisant. `role` (prop
361
- déclarée sur `OrigamTextField`) n'atteignait par ailleurs que
362
- `<origam-field>`, jamais le vrai `<input>` — c'est ce défaut exact que le
363
- commentaire `KNOWN_FAILURES` documentait déjà pour `OrigamSelect` (test
364
- `fixme`).
365
-
366
- **Correctif à la source** (`packages/ds/src/components/TextField/OrigamTextField.vue`) :
367
- `defineOptions({ inheritAttrs: false })` + `role` désormais aussi lié sur le
368
- `<input>` réel. Élimine, mesuré axe-core en navigateur réel (Playwright +
369
- Histoire statique) :
370
- - `OrigamSelect` — sort de `KNOWN_FAILURES` (`components.spec.ts`), test
371
- `fixme` → vert.
372
- - `OrigamDataTable`, `OrigamDataTableFooter`, `OrigamDataTableHeaderCell`,
373
- `OrigamDataTableHeaders`, `OrigamDataTableHeadersCell`,
374
- `OrigamDataTableRow` — `aria-allowed-attr` disparaît, entrée de baseline
375
- retirée entièrement.
376
- - `OrigamDataTableGroupHeaderRow`, `OrigamDataTableRows`, `OrigamNumberField`
377
- — `aria-allowed-attr` disparaît, l'entrée reste pour leur AUTRE violation
378
- (`button-name` / `color-contrast`), non touchée par ce lot.
379
- - `OrigamDataTableHeadersCellMobile` — `aria-allowed-attr` ET
380
- `aria-prohibited-attr` disparaissent (même fuite, deux règles axe
381
- différentes), entrée retirée entièrement.
382
-
383
- **Deuxième cause, distincte, deux composants** —
384
- `packages/ds/src/components/ColorPickerField/OrigamColorPickerField.vue` et
385
- `.../DatePickerField/OrigamDatePickerField.vue` posaient un
386
- `aria-haspopup` avec une valeur inventée (`'colorpickerbox'` /
387
- `'datepickerbox'`, jamais un token ARIA valide) sur l'élément
388
- `.origam-field` (via `activator="parent"` d'`OrigamMenu`), sans `role` le
389
- justifiant. Remplacé par `aria-haspopup: 'dialog'` (motif WAI-ARIA « Date
390
- Picker Dialog ») + `role: 'combobox'` sur le même élément — même principe
391
- que `comboboxAriaAttrs` d'`OrigamSelect`. `aria-allowed-attr` ET
392
- `aria-valid-attr-value` disparaissent pour les deux ; `OrigamColorPickerField`
393
- est intégralement retiré de la baseline, `OrigamDatePickerField` y reste
394
- pour son `color-contrast` (non touché).
395
-
396
- **Bilan mesuré** : baseline #818 25 clés/33 violations → **17 clés/18
397
- violations**. Suite `pnpm -F @origam/tests test:a11y` complète (226 tests,
398
- catalogue 218/218) : **226 passed**, aucune régression sur les 36
399
- composants à baseline vide. Contrôle positif exécuté : réintroduire l'ancien
400
- comportement fait échouer `a11y — OrigamSelect Default Variant` avec
401
- `aria-allowed-attr`/`aria-prohibited-attr` NON baselinés — la porte capte
402
- la régression, pas seulement l'édition du JSON.
403
-
404
- **Laissé de côté, délibérément** (17 clés / 18 violations restent dans
405
- `a11y-violations.baseline.json`, cf. #818) : `color-contrast` (8 instances —
406
- plusieurs agents dédiés au contraste travaillent déjà sur ce périmètre),
407
- `button-name` (3, boutons icône sans libellé — famille distincte),
408
- `scrollable-region-focusable` (3), `aria-required-parent`/`aria-required-children`/
409
- `listitem`/`aria-prohibited-attr` (`OrigamChartMap`, sans lien avec ce
410
- correctif) — un défaut chacun, cause non partagée avec ce lot, non
411
- instruit ici.
412
-
413
- Récolte de deux dépréciations posées "pour la prochaine majeure" avant que le
414
- `CLAUDE.md` n'acte que `3.0.0` est réservé à la séparation en modules et que
415
- les ruptures, elles, ne le sont pas — voir "Work priorities and versioning"
416
- du `CLAUDE.md`. `origam` n'a aucun consommateur : les deux ruptures
417
- ci-dessous partent en **mineure**, sans shim ni période de grâce.
463
+ ## [2.18.5] - 2026-09-23
418
464
 
419
465
  ### Fixed — `button-name` et `scrollable-region-focusable` (6 des 18 violations laissées de côté par le lot précédent)
420
466
 
@@ -493,172 +539,109 @@ au vert. `pnpm -F origam guards` (28/28), `guards:self` (15/15),
493
539
  d'autres agents), `aria-required-parent`/`aria-required-children`/`listitem`/
494
540
  `aria-prohibited-attr` (un défaut chacun, cause non partagée avec ce lot).
495
541
 
496
- ### ⚠️ BREAKING — `click:prepend` / `click:append` retirés d'`IBtnEmits` (#443, #577)
542
+ ## [2.18.4] - 2026-09-23
497
543
 
498
- `<OrigamBtn>` n'émet plus `click:prepend` ni `click:append` — ni au niveau du
499
- type, ni au niveau de l'exécution. `IBtnEmits` n'`extends` plus
500
- `IAdjacentEmits`, et les `<span>` `origam-btn__prepend` / `__append` ne
501
- portent plus de `@click` du tout : un clic souris dessus ne déclenche plus
502
- rien.
544
+ ### Fixed — #818 baseline shrink : `aria-allowed-attr`/`aria-prohibited-attr`/`aria-valid-attr-value` (12 des 25 entrées)
503
545
 
504
- **Pourquoi cette paire précisément, et pourquoi une suppression plutôt qu'un
505
- correctif.** Les deux émissions n'ont jamais été atteignables au clavier :
506
- l'événement partait d'un `<span>` descendant, alors qu'une activation clavier
507
- synthétise son clic sur la RACINE du composant — l'écouteur posé sur le
508
- descendant ne le voit jamais. Le correctif générique appliqué aux dix autres
509
- consommateurs d'`useAdjacent` (promouvoir la zone en `role="button"` + arrêt
510
- de tabulation) est **illégal** ici : `<OrigamBtn>` rend un `<button>` ou un
511
- `<a>`, et le modèle de contenu HTML interdit à tous deux un descendant de
512
- contenu interactif *et* tout descendant portant `tabindex`. Un
513
- `<button type="button">` imbriqué y est tout aussi invalide. La forme était
514
- fausse, pas seulement le balisage : un contrôle qui possède déjà une action
515
- ne peut pas en héberger une seconde — deux actions sont deux boutons,
516
- composés via `<origam-btn-group>`.
546
+ Recompté sur `a11y-violations.baseline.json` (pas repris d'un chiffre cité
547
+ ailleurs) : la baseline #818 portait **25 clés / 33 violations** sur 24
548
+ composants, dont **12 `aria-allowed-attr`** concentrées sur la famille
549
+ `OrigamDataTable*`. Cause commune, pas 12 défauts distincts :
550
+ `OrigamTextField` ne posait pas `inheritAttrs: false` alors qu'il
551
+ redistribue déjà explicitement chaque attr de fall-through
552
+ (`filterInputAttrs` → `rootAttrs` sur `<origam-input>`, `inputAttrs` sur le
553
+ vrai `<input>`). Sans ce flag, Vue appliquait EN PLUS le `$attrs` brut sur
554
+ la racine `<origam-input>`, qui ne s'y soustrayait pas non plus — doublant
555
+ `aria-haspopup`/`aria-expanded`/`aria-controls`/`aria-valuenow`/… sur le
556
+ `<div>` wrapper, qui ne porte aucun rôle les autorisant. `role` (prop
557
+ déclarée sur `OrigamTextField`) n'atteignait par ailleurs que
558
+ `<origam-field>`, jamais le vrai `<input>` — c'est ce défaut exact que le
559
+ commentaire `KNOWN_FAILURES` documentait déjà pour `OrigamSelect` (test
560
+ `fixme`).
517
561
 
518
- **Ce qui NE bouge PAS** : les slots `prepend` / `append` (`IBtnSlots extends
519
- IAdjacentSlots`) restent intacts, ainsi que les props `prependIcon` /
520
- `appendIcon` / `prependAvatar` / `appendAvatar` — un contenu décoratif ou
521
- informationnel en prepend/append reste parfaitement légitime, seule
522
- l'émission d'un clic disparaît.
562
+ **Correctif à la source** (`packages/ds/src/components/TextField/OrigamTextField.vue`) :
563
+ `defineOptions({ inheritAttrs: false })` + `role` désormais aussi lié sur le
564
+ `<input>` réel. Élimine, mesuré axe-core en navigateur réel (Playwright +
565
+ Histoire statique) :
566
+ - `OrigamSelect` — sort de `KNOWN_FAILURES` (`components.spec.ts`), test
567
+ `fixme` → vert.
568
+ - `OrigamDataTable`, `OrigamDataTableFooter`, `OrigamDataTableHeaderCell`,
569
+ `OrigamDataTableHeaders`, `OrigamDataTableHeadersCell`,
570
+ `OrigamDataTableRow` — `aria-allowed-attr` disparaît, entrée de baseline
571
+ retirée entièrement.
572
+ - `OrigamDataTableGroupHeaderRow`, `OrigamDataTableRows`, `OrigamNumberField`
573
+ — `aria-allowed-attr` disparaît, l'entrée reste pour leur AUTRE violation
574
+ (`button-name` / `color-contrast`), non touchée par ce lot.
575
+ - `OrigamDataTableHeadersCellMobile` — `aria-allowed-attr` ET
576
+ `aria-prohibited-attr` disparaissent (même fuite, deux règles axe
577
+ différentes), entrée retirée entièrement.
523
578
 
524
- **Preuve du comportement retiré** — A/B contre le commit parent, fonctionnel
525
- et pas seulement typé : `packages/tests/TU/components/Btn/OrigamBtn.adjacent-emit-removed.spec.ts`
526
- monte `<OrigamBtn>` avec un écouteur `onClick:prepend` / `onClick:append` et
527
- déclenche un vrai `click` DOM sur la zone. Sur le commit parent, ce même test
528
- appellerait l'écouteur (l'émission déclarée par `IAdjacentEmits` partait
529
- encore) ; sur ce commit, l'écouteur n'est jamais invoqué — vérifié en
530
- rejouant le fichier de spec avant/après le changement, pas seulement en le
531
- lisant une fois vert.
579
+ **Deuxième cause, distincte, deux composants** —
580
+ `packages/ds/src/components/ColorPickerField/OrigamColorPickerField.vue` et
581
+ `.../DatePickerField/OrigamDatePickerField.vue` posaient un
582
+ `aria-haspopup` avec une valeur inventée (`'colorpickerbox'` /
583
+ `'datepickerbox'`, jamais un token ARIA valide) sur l'élément
584
+ `.origam-field` (via `activator="parent"` d'`OrigamMenu`), sans `role` le
585
+ justifiant. Remplacé par `aria-haspopup: 'dialog'` (motif WAI-ARIA « Date
586
+ Picker Dialog ») + `role: 'combobox'` sur le même élément — même principe
587
+ que `comboboxAriaAttrs` d'`OrigamSelect`. `aria-allowed-attr` ET
588
+ `aria-valid-attr-value` disparaissent pour les deux ; `OrigamColorPickerField`
589
+ est intégralement retiré de la baseline, `OrigamDatePickerField` y reste
590
+ pour son `color-contrast` (non touché).
532
591
 
533
- Nettoyage induit : le helper `warnDeprecatedEmit` (`color.util.ts`) et la
534
- constante `ADJACENT_EMIT_REPLACEMENT` (`consts/Btn/btn.const.ts`) sont
535
- retirés — `<OrigamBtn>` était leur seul appelant, et le fichier `btn.const.ts`
536
- devenu vide est supprimé.
592
+ **Bilan mesuré** : baseline #818 25 clés/33 violations → **17 clés/18
593
+ violations**. Suite `pnpm -F @origam/tests test:a11y` complète (226 tests,
594
+ catalogue 218/218) : **226 passed**, aucune régression sur les 36
595
+ composants à baseline vide. Contrôle positif exécuté : réintroduire l'ancien
596
+ comportement fait échouer `a11y — OrigamSelect Default Variant` avec
597
+ `aria-allowed-attr`/`aria-prohibited-attr` NON baselinés — la porte capte
598
+ la régression, pas seulement l'édition du JSON.
537
599
 
538
- **Migration.** Aucun consommateur interne (DS, marketing, stories, docs,
539
- tests) n'utilisait `click:prepend` / `click:append` sur `<OrigamBtn>` —
540
- balayage exhaustif, zéro résultat. Un consommateur externe qui les écoutait
541
- doit remplacer l'action posée sur la zone prepend/append par un second
542
- `<origam-btn>` dans un `<origam-btn-group>`.
600
+ **Laissé de côté, délibérément** (17 clés / 18 violations restent dans
601
+ `a11y-violations.baseline.json`, cf. #818) : `color-contrast` (8 instances —
602
+ plusieurs agents dédiés au contraste travaillent déjà sur ce périmètre),
603
+ `button-name` (3, boutons icône sans libellé — famille distincte),
604
+ `scrollable-region-focusable` (3), `aria-required-parent`/`aria-required-children`/
605
+ `listitem`/`aria-prohibited-attr` (`OrigamChartMap`, sans lien avec ce
606
+ correctif) — un défaut chacun, cause non partagée avec ce lot, non
607
+ instruit ici.
543
608
 
544
- ### ⚠️ BREAKING — `createOrigam()` nu n'installe plus le thème par défaut (#360)
609
+ ### Fixed
545
610
 
546
- `createOrigam()` préfixait inconditionnellement chaque install avec le thème
547
- neutre interne `origamTheme` (`[...origamTheme, ...suppliedThemes]`) : même un
548
- appel nu, ou un `themes: []` explicite, recevait quand même les variables CSS
549
- et — surtout — les **props par défaut par composant** (ADR-005,
550
- `components: { 'origam-avatar': { rounded: 'full' }, … }`) de ce thème. Une
551
- application qui n'en voulait pas ne pouvait pas s'en défaire. Ce préfixage est
552
- retiré : `createOrigam()` installe désormais exactement ce qu'on lui passe,
553
- rien de plus.
611
+ - **#871 (clôture) — les 7 dernières violations AA étaient UN défaut de
612
+ palette répété, pas sept défauts de composant.** Mesuré avec
613
+ `packages/tests/audit/dark-contrast.audit.mjs` : **7 / 1 664 → 0 / 1 664**,
614
+ les 8 identités × 2 modes × 2 portées, quatre contrôles verts des deux
615
+ côtés. Exactement **7 lignes changent, aucune ne se dégrade**, et le ratio
616
+ minimum de toute la surface passe de 3.65 à **4.70**.
554
617
 
555
- **Ce qui change concrètement.** Sans `themes`/`theme` explicite,
556
- `createOrigam()` n'injecte plus aucune variable `--origam-*` et le résolveur
557
- de props par défaut (`installThemePropsResolver`, ADR-005) n'a plus rien à
558
- résoudre — chaque composant retombe uniquement sur ses propres valeurs
559
- `withDefaults()`. Concrètement : un `<origam-avatar>` sans prop `rounded`
560
- explicite n'est plus automatiquement circulaire (`rounded="full"` venait du
561
- thème, pas du composant).
618
+ Les 7 se répartissaient en deux causes, toutes deux dans
619
+ `packages/marketing/src/themes` — **aucune dans le DS**, qui était déjà à
620
+ zéro :
562
621
 
563
- **Le thème par défaut reste disponible, à la demande** — `origamTheme`,
564
- exporté par `origam/themes` (déjà public avant cette rupture, aucun nouvel
565
- export nécessaire) :
622
+ | cause | marque · mode | couple | avant | après |
623
+ |---|---|---|---|---|
624
+ | encre héritée du mode clair | `apple` sombre | `#ffffff` → `#000000` sur `#0a84ff` | 3.65 | **5.76** |
625
+ | encre héritée du mode clair | `ecom` sombre | `#ffffff` → `#1a0f0a` sur `#f43f5e` | 3.67 | **5.12** |
626
+ | jeton de rôle inversé | `ecom` clair | `action.primary.bg` → `.fgSubtle` sur la crème | 4.43 | **5.93** |
566
627
 
567
- ```ts
568
- // AVANT (2.x) — enregistrait implicitement le thème origam par défaut
569
- import { createOrigam } from 'origam'
570
- app.use(createOrigam())
628
+ **La cause commune.** Le mode sombre ÉCLAIRCIT l'accent de marque — c'est
629
+ la bonne pratique pour une grande surface (`#0071e3` → `#0a84ff` chez
630
+ `apple`, rose-600 → rose-500 chez `ecom`) — mais les deux palettes gardaient
631
+ l'encre `#ffffff` de leur jumeau clair. Or chacune applique **déjà** la règle
632
+ inverse à ses intentions `feedback` : les 4 d'`ecom` et 3 des 4 d'`apple`
633
+ encrent leur accent éclairci avec le `text.inverse` de la palette.
634
+ `action.primary` était la dernière exception. Le badge n'était pas en cause :
635
+ `--origam-badge__badge---{background-color,color}` lit exactement ce couple —
636
+ tout comme `--origam-btn--primary---*`, qui n'est pas dans la sonde et était
637
+ donc en défaut sans être mesuré.
571
638
 
572
- // APRÈS — le thème par défaut est un choix explicite
573
- import { createOrigam } from 'origam'
574
- import { origamTheme } from 'origam/themes'
575
- app.use(createOrigam({ themes: origamTheme }))
576
- ```
577
-
578
- **Rayon de souffle mesuré, corrigé dans le même lot** — quatre consommateurs
579
- directs de `createOrigam()` nu trouvés par balayage exhaustif (DS, marketing,
580
- stories, docs, tests) :
581
- - `packages/ds/src/nuxt/module.ts` — le module Nuxt officiel. **Non cassé** :
582
- son propre `DEFAULT_THEMES` (utilisé quand l'option `origam.themes` du
583
- consommateur est omise) passe désormais `origamTheme` explicitement à
584
- `createOrigam()`, préservant à l'identique le comportement de tout
585
- consommateur Nuxt existant (marketing compris) qui ne configurait rien.
586
- Seul un `createOrigam()` direct, hors module Nuxt, doit s'adapter.
587
- - `packages/marketing/nuxt.config.ts` — configure `origam.themes`
588
- explicitement (7 thèmes de marque + un thème `origam` renommé pour le
589
- playground `/theming`), ce qui **contourne** le fallback du module. Le vrai
590
- thème neutre non-nommé (celui que `activeDefaultsFor` fusionne toujours en
591
- premier, avant la marque active) était fourni jusqu'ici par le préfixage
592
- implicite de `createOrigam()` — invisible dans la config marketing. Il est
593
- désormais listé explicitement, en premier, dans le tableau `themes`.
594
- - `packages/stories/histoire.setup.ts` — Histoire (utilisé par les ~208
595
- stories de composants) appelait `createOrigam()` nu ; sans correction,
596
- chaque story aurait silencieusement perdu les props par défaut du thème
597
- (avatars carrés au lieu de circulaires, etc.). Passe désormais
598
- `{ themes: origamTheme }`.
599
- - `packages/docs/.vitepress/theme/index.ts` — les démos de composants live de
600
- la doc VitePress appelaient `createOrigam()` nu ; même correction.
601
-
602
- Les échantillons de code montrés au consommateur (`installation.const.ts` /
603
- `installation.vue` sur le marketing, `guide/usage.md` côté docs) sont mis à
604
- jour pour montrer la forme correcte — un utilisateur copiant l'ancien
605
- exemple aurait obtenu des composants non stylés selon le thème.
606
-
607
- **Tests adaptés** (comptés avant correction, comme demandé) — 3 fichiers /
608
- 5 assertions reposaient sur l'enregistrement implicite et ont été corrigés
609
- pour installer `origamTheme` explicitement là où le test vérifie précisément
610
- une valeur par défaut issue du thème, ou réécrits pour pinner le NOUVEAU
611
- contrat plutôt que l'ancien :
612
- - `theme-props-resolver.spec.ts` (2 assertions — `origam-radio`/`origam-text-field`
613
- vs `origam-input` density) ;
614
- - `installed-themes.composable.spec.ts` (describe block entier renommé
615
- `origam baseline — explicit opt-in only (#360)`, 2 assertions corrigées et
616
- 2 nouvelles ajoutées pour couvrir explicitement le nouveau contrat) ;
617
- - `OrigamChip.spec.ts` (1 assertion — `components['origam-chip'].size`).
618
-
619
- **Preuve fonctionnelle** (pas seulement structurelle) —
620
- `packages/tests/TU/origam/createOrigam-bare-no-theme-360.spec.ts` monte un
621
- vrai `<OrigamAvatar>` : sans thème, la classe `origam--rounded-full` est
622
- absente ; avec `origamTheme` passé explicitement, elle est présente — la
623
- même monteuse prouve donc que le harnais peut actionner la prop avant de
624
- conclure qu'elle ne l'est plus par défaut.
625
-
626
- ### Fixed
627
-
628
- - **#871 (clôture) — les 7 dernières violations AA étaient UN défaut de
629
- palette répété, pas sept défauts de composant.** Mesuré avec
630
- `packages/tests/audit/dark-contrast.audit.mjs` : **7 / 1 664 → 0 / 1 664**,
631
- les 8 identités × 2 modes × 2 portées, quatre contrôles verts des deux
632
- côtés. Exactement **7 lignes changent, aucune ne se dégrade**, et le ratio
633
- minimum de toute la surface passe de 3.65 à **4.70**.
634
-
635
- Les 7 se répartissaient en deux causes, toutes deux dans
636
- `packages/marketing/src/themes` — **aucune dans le DS**, qui était déjà à
637
- zéro :
638
-
639
- | cause | marque · mode | couple | avant | après |
640
- |---|---|---|---|---|
641
- | encre héritée du mode clair | `apple` sombre | `#ffffff` → `#000000` sur `#0a84ff` | 3.65 | **5.76** |
642
- | encre héritée du mode clair | `ecom` sombre | `#ffffff` → `#1a0f0a` sur `#f43f5e` | 3.67 | **5.12** |
643
- | jeton de rôle inversé | `ecom` clair | `action.primary.bg` → `.fgSubtle` sur la crème | 4.43 | **5.93** |
644
-
645
- **La cause commune.** Le mode sombre ÉCLAIRCIT l'accent de marque — c'est
646
- la bonne pratique pour une grande surface (`#0071e3` → `#0a84ff` chez
647
- `apple`, rose-600 → rose-500 chez `ecom`) — mais les deux palettes gardaient
648
- l'encre `#ffffff` de leur jumeau clair. Or chacune applique **déjà** la règle
649
- inverse à ses intentions `feedback` : les 4 d'`ecom` et 3 des 4 d'`apple`
650
- encrent leur accent éclairci avec le `text.inverse` de la palette.
651
- `action.primary` était la dernière exception. Le badge n'était pas en cause :
652
- `--origam-badge__badge---{background-color,color}` lit exactement ce couple —
653
- tout comme `--origam-btn--primary---*`, qui n'est pas dans la sonde et était
654
- donc en défaut sans être mesuré.
655
-
656
- ⚠️ **On n'a PAS foncé l'accent**, bien que ce soit l'autre levier : chez
657
- `apple`, `action.primary.bg` sert aussi d'ENCRE (le bloc `components` passe
658
- ce `var()` en `color` sur `origam-breadcrumb-item`). Le ramener à `#0071e3`
659
- donnait 4.70 sous du blanc mais faisait tomber le fil d'Ariane à **4.47** sur
660
- le fond noir — un défaut échangé contre un autre. Vérifié par calcul avant
661
- d'écrire la moindre ligne.
639
+ ⚠️ **On n'a PAS foncé l'accent**, bien que ce soit l'autre levier : chez
640
+ `apple`, `action.primary.bg` sert aussi d'ENCRE (le bloc `components` passe
641
+ ce `var()` en `color` sur `origam-breadcrumb-item`). Le ramener à `#0071e3`
642
+ donnait 4.70 sous du blanc mais faisait tomber le fil d'Ariane à **4.47** sur
643
+ le fond noir — un défaut échangé contre un autre. Vérifié par calcul avant
644
+ d'écrire la moindre ligne.
662
645
 
663
646
  La 3ᵉ cause est une erreur de RÔLE : `bg` est un jeton de surface, `fgSubtle`
664
647
  la teinte prévue pour être LUE sur une surface neutre. `ecomDarkTheme` posait
@@ -724,40 +707,89 @@ conclure qu'elle ne l'est plus par défaut.
724
707
  du filtre. **Vérifiée ROUGE sur `origin/develop`** : 3 échecs / 14 succès.
725
708
  C'est elle qui a trouvé la 8ᵉ violation ci-dessus.
726
709
 
727
- - **#871 — le mode sombre ne peignait qu'à moitié dans un sous-arbre thémé.**
728
- Les feuilles de tokens portent ~1 761 déclarations **dérivées**
729
- (`--origam-title---color: var(--origam-color__text---primary)`). Une custom
730
- property est substituée **sur l'élément qui la déclare** ; ce qu'un
731
- descendant hérite est la valeur DÉJÀ substituée. Le bloc sombre étant ancré
732
- à la racine (`:root:not([data-theme])[data-mode="dark"]`), un sous-arbre
733
- `<OrigamThemeProvider mode="dark">` basculait bien les ~60 tokens
734
- sémantiques — le bloc runtime, lui, émet `[data-mode="dark"]` — mais laissait
735
- les 1 761 dérivés **gelés** sur les valeurs claires de la racine. Motif
736
- dominant mesuré : `rgb(10,10,10)` sur `rgb(10,10,10)`, texte et fond
737
- identiques.
710
+ ## [2.18.3] - 2026-09-23
738
711
 
739
- Les deux feuilles s'accrochent désormais aussi à l'axe `data-mode` :
712
+ #### `v-contrast` était silencieusement inerte sur fond opaque (#869)
740
713
 
741
- ```css
742
- :root, [data-theme="light"], [data-mode="light"] { /* clair */ }
743
- [data-theme="dark"], [data-mode="dark"]:not([data-theme="light"]) { /* sombre */ }
744
- ```
714
+ La directive `v-contrast`, câblée sur 30 composants, n'émettait **jamais**
715
+ rien — ni classe, ni `color: !important`, ni `console.warn` — dès que fond
716
+ **et** texte étaient tous deux OPAQUES, c'est-à-dire la configuration par
717
+ défaut du DS. Deux ruptures indépendantes dans le code partagé, corrigeant
718
+ l'une ne suffisait pas sans l'autre :
745
719
 
746
- Rejoué sur 30 composants × 8 identités × 2 modes × 2 portées (racine ET
747
- sous-arbre), soit **1 664 instances**, Chromium, contrôles positif et négatif
748
- verts : **185 → 7 violations AA**, dont sombre **182 → 4** et clair
749
- **3 → 3** (aucune régression). Les 7 restantes appartiennent toutes aux
750
- palettes de marque de `packages/marketing`, aucune au DS, et aucune ne
751
- descend sous 2:1. Harnais : `pnpm -F @origam/tests audit:dark-contrast`.
720
+ 1. **`toRgb()`** faisait **toujours** passer la couleur par un aller-retour
721
+ `canvas.fillStyle`, même quand `getComputedStyle` avait déjà rendu une
722
+ chaîne `rgb()/rgba()` exploitable. Le getter `fillStyle` de Chromium
723
+ sérialise toute couleur OPAQUE en hexadécimal `#rrggbb` (seul l'alpha < 1
724
+ ressort en `rgba(…)`), ce qui transformait une valeur déjà utilisable en
725
+ une valeur morte pour `rgbaParts`/`channelsOf`.
726
+ 2. **`channelsOf()`** extrayait les canaux via `match(/[\d.]+/g)`, taillé
727
+ pour `rgb(r, g, b)` — une suite de chiffres hexadécimaux SANS séparateur
728
+ (ex. `#777777`, six chiffres) s'agrège en UN seul grand nombre au lieu de
729
+ trois, donc `channelsOf` rend `null` sur exactement la forme hex que la
730
+ rupture 1 pouvait encore produire (couleur nommée, `hsl()`, …
731
+ nécessitant légitimement le détour par le canvas).
752
732
 
753
- ⚠️ **Ces bornes ont d'abord été publiées à `189 → 11`, et les deux étaient
754
- fausses de 4.** Le parseur de couleurs du harnais ne connaissait pas
755
- `color(srgb …)`, que la palette `apple` émet : le fond translucide du
756
- tooltip était pris pour « pas de fond », la remontée d'ancêtres sautait
757
- jusqu'à la surface opaque et rapportait du noir sur noir à 1.00 — quatre
758
- violations **fabriquées**, présentes des DEUX côtés de la mesure. Le
759
- **delta de 178 n'a jamais été faux** ; seules les bornes l'étaient. Détail
760
- et garde de non-récidive plus bas.
733
+ **Correctif** : `toRgb()` court-circuite désormais le canvas quand l'entrée
734
+ est déjà `rgb()/rgba()` (le cas de la quasi-totalité des appels, puisqu'ils
735
+ lisent `getComputedStyle`), et le chemin canvas restant convertit son
736
+ résultat hexadécimal en `rgb()/rgba()` via un nouveau `hexToRgb()` avant de
737
+ le rendre — `toRgb()` ne rend plus jamais de hex. La mécanique de correction
738
+ elle-même (forcer noir/blanc, poser `data-origam-contrast-fixed`, journaliser
739
+ le ratio) n'est pas modifiée : elle fonctionnait déjà, elle n'était
740
+ simplement jamais atteinte.
741
+
742
+ **Preuve** — `packages/tests/e2e/contrast-directive.spec.ts` (Playwright,
743
+ Chromium réel — jsdom n'a pas de `canvas.getContext('2d')` fonctionnel dans
744
+ ce dépôt, donc ne peut ni reproduire ni vérifier ce bug) : contrôle positif
745
+ (`#777777` sur blanc, 4.48:1, opaque des deux côtés) déclenche désormais la
746
+ correction + le `console.warn` ; contrôle négatif (noir sur blanc, 21:1) ne
747
+ déclenche rien ; non-régression du chemin translucide (fond composé via
748
+ `resolvePaintedBackground`) inchangée. **A/B contre le commit parent** : 4
749
+ des 5 tests rougissent sur le code d'avant (le contrôle négatif reste vert
750
+ des deux côtés, comme attendu).
751
+
752
+ ⚠️ **Rayon mesuré séparément, pas hérité de #871 — et le chiffre publié par
753
+ #871 contenait un artefact, corrigé depuis par #883.** Le chiffrage initial
754
+ du ticket (346 violations en mode sombre) datait d'avant #876. #876 avait
755
+ d'abord publié **189 → 11** ; en rejouant `audit/dark-contrast.audit.mjs`
756
+ avec la VRAIE directive (redirect du stub désactivé) au lieu de sa
757
+ reformulation mathématique, seuls **7 `console.warn` réels** se
758
+ déclenchaient sur les 32 configurations (30 composants × 8 identités × 2
759
+ modes, portées racine + sous-arbre) — écart signalé à l'auteur de #876, qui
760
+ l'a reproduit, creusé et corrigé sous #883 : **les bornes AVANT et APRÈS
761
+ étaient toutes les deux fausses (185 → 7, pas 189 → 11)**, la même erreur de
762
+ mesure existant déjà dans le comptage "avant". Le delta réel (178) n'a
763
+ jamais changé.
764
+
765
+ L'écart venait d'`origam-tooltip__content` (identité `apple`, root + subtree,
766
+ light + dark) et, découvert par #883, également d'`origam-bottom-nav` sur la
767
+ même identité. Fond réel : `color(srgb 0.898039 0.898039 0.905882 / 0.94)`
768
+ (translucide) ; `toRgb()` le résout correctement en `rgb(215, 215, 217)` une
769
+ fois composé sur son ancêtre opaque, contre du texte noir : **ratio 14.61:1,
770
+ conforme** — vérifié en appelant directement `toRgb()`/
771
+ `resolvePaintedBackground()` de la vraie directive sur cette page. La
772
+ fonction `parse()` de `dark-contrast.audit.mjs` (une réimplémentation
773
+ distincte, pas la directive) ne reconnaissait que `rgba?\(…\)` — elle
774
+ ignorait silencieusement cette couche `color(srgb …)`, sautait jusqu'à
775
+ l'ancêtre opaque suivant et rapportait à tort un faux "noir sur noir"
776
+ (ratio 1.00). **Ce n'était pas un défaut du DS ni de `v-contrast`** — la
777
+ même classe de lacune que ce ticket corrige, mais dans un autre fichier. Les
778
+ 7 restantes (4 `origam-badge__badge`, 3 `origam-breadcrumb-item`) sont
779
+ confirmées identiques entre les deux mesures, et `sous 2:1` passe à **0** une
780
+ fois l'artefact retiré : plus rien de ce qui reste n'est proche d'invisible.
781
+
782
+ Non corrigé ici : `dark-contrast.audit.mjs` appartenait à #871/#876, pas à ce
783
+ ticket — signalé, reproduit et corrigé séparément sous #883 (ne pas citer
784
+ 189 → 11, c'était faux des deux côtés).
785
+
786
+ **Rupture d'API** : aucune — `toRgb`/`channelsOf`/`hexToRgb` restent des
787
+ fonctions privées du module, non exportées ; la surface publique
788
+ (`setContrastConfig`, `v-contrast` par défaut) est inchangée.
789
+
790
+ ## [2.18.2] - 2026-09-23
791
+
792
+ ### Fixed
761
793
 
762
794
  - **#871 — le harnais de contraste ne savait pas lire `color(srgb …)`, et
763
795
  FABRIQUAIT donc des violations.** Son `parse()` ne reconnaissait que
@@ -792,46 +824,6 @@ conclure qu'elle ne l'est plus par défaut.
792
824
  propre n'a été ouvert : le défaut est dans l'instrumentation de #871, donc
793
825
  il se règle sous #871.
794
826
 
795
- - **#871 — `system-bar` et `tooltip` illisibles en sombre.** Les deux peignent
796
- une surface sombre dans les DEUX modes (`neutral---700` / `neutral---800`)
797
- et lisaient `text---inverse`, qui vaut blanc en clair et **encre** en
798
- sombre : 1.91:1 et 1.31:1. `dark.css` lit maintenant `text---primary` ;
799
- `light.css` est inchangé.
800
-
801
- - **#871 — `bottom-nav` peignait une dalle claire dans une UI sombre.** Son
802
- fond était épinglé à la primitive `neutral---200` (#e6e6e6) dans les deux
803
- modes, sous un `text---primary` presque blanc (1.03:1). En sombre il passe
804
- au token sémantique `surface---overlay`. Le clair est inchangé.
805
-
806
- ### Changed — ⛔ RUPTURE (documentée, assumée)
807
-
808
- - **Les feuilles de tokens ne sont plus ancrées à `:root`.** Un consommateur
809
- qui avait contourné le gel ci-dessus en re-déclarant lui-même des variables
810
- de composant dans un sous-arbre `[data-mode]` voit désormais la feuille les
811
- déclarer aussi, en (0,1,0) côté clair et (0,2,0) côté sombre. Son bloc de
812
- contournement doit donc être au moins aussi spécifique, ou être retiré.
813
-
814
- Concrètement dans ce dépôt : `packages/marketing` embarque
815
- `ORIGAM_COMPONENT_RESET_LIGHT/DARK` (`themes/origam-reset.generated.ts`),
816
- une re-déclaration GÉNÉRÉE de ~2 700 variables de composant, écrite
817
- exactement pour ce défaut. Elle est maintenant redondante — **elle n'a pas
818
- été retirée ici**, faute de mesure sur le rendu du site.
819
-
820
- Ce qui NE change pas : `data-theme="light" data-mode="dark"` reste clair
821
- (l'axe marque gouverne quand les deux se contredisent, règle #807),
822
- `data-mode="light"` sous un OS sombre reste clair (#794), et une marque
823
- claire continue de gagner sur la feuille par l'ordre source.
824
-
825
- ### Documentation
826
-
827
- - `CLAUDE.md`, section *Multi-theme* : la note qui affirmait que le bloc
828
- auto-mode ne porte que **11** déclarations est **fausse** — recompté en
829
- parsant la feuille le 2026-09-22, les deux blocs en portent **2 731**
830
- chacun. L'affirmation décrivait un état antérieur à #794 que plus rien ne
831
- remesurait.
832
-
833
- ### Fixed
834
-
835
827
  - **#371 (point 3) — le sélecteur « Items per page » du footer
836
828
  DataTable n'avait aucun nom accessible**, déjà corrigé par `3f063d727`
837
829
  (2026-09-10) — ce commit **citait #371 sans le fermer**, sixième
@@ -867,85 +859,218 @@ conclure qu'elle ne l'est plus par défaut.
867
859
  `OrigamDataTableRow.story.vue`) et d'une section « Responsive » +
868
860
  ligne de table dans `OrigamDataTable.md`.
869
861
 
870
- ---
862
+ ## [2.18.1] - 2026-09-22
871
863
 
872
- #### `v-contrast` était silencieusement inerte sur fond opaque (#869)
864
+ Récolte de deux dépréciations posées "pour la prochaine majeure" avant que le
865
+ `CLAUDE.md` n'acte que `3.0.0` est réservé à la séparation en modules et que
866
+ les ruptures, elles, ne le sont pas — voir "Work priorities and versioning"
867
+ du `CLAUDE.md`. `origam` n'a aucun consommateur : les deux ruptures
868
+ ci-dessous partent en **mineure**, sans shim ni période de grâce.
873
869
 
874
- La directive `v-contrast`, câblée sur 30 composants, n'émettait **jamais**
875
- rien — ni classe, ni `color: !important`, ni `console.warn` — dès que fond
876
- **et** texte étaient tous deux OPAQUES, c'est-à-dire la configuration par
877
- défaut du DS. Deux ruptures indépendantes dans le code partagé, corrigeant
878
- l'une ne suffisait pas sans l'autre :
870
+ ### ⚠️ BREAKING — `click:prepend` / `click:append` retirés d'`IBtnEmits` (#443, #577)
879
871
 
880
- 1. **`toRgb()`** faisait **toujours** passer la couleur par un aller-retour
881
- `canvas.fillStyle`, même quand `getComputedStyle` avait déjà rendu une
882
- chaîne `rgb()/rgba()` exploitable. Le getter `fillStyle` de Chromium
883
- sérialise toute couleur OPAQUE en hexadécimal `#rrggbb` (seul l'alpha < 1
884
- ressort en `rgba(…)`), ce qui transformait une valeur déjà utilisable en
885
- une valeur morte pour `rgbaParts`/`channelsOf`.
886
- 2. **`channelsOf()`** extrayait les canaux via `match(/[\d.]+/g)`, taillé
887
- pour `rgb(r, g, b)` — une suite de chiffres hexadécimaux SANS séparateur
888
- (ex. `#777777`, six chiffres) s'agrège en UN seul grand nombre au lieu de
889
- trois, donc `channelsOf` rend `null` sur exactement la forme hex que la
890
- rupture 1 pouvait encore produire (couleur nommée, `hsl()`, …
891
- nécessitant légitimement le détour par le canvas).
872
+ `<OrigamBtn>` n'émet plus `click:prepend` ni `click:append` — ni au niveau du
873
+ type, ni au niveau de l'exécution. `IBtnEmits` n'`extends` plus
874
+ `IAdjacentEmits`, et les `<span>` `origam-btn__prepend` / `__append` ne
875
+ portent plus de `@click` du tout : un clic souris dessus ne déclenche plus
876
+ rien.
892
877
 
893
- **Correctif** : `toRgb()` court-circuite désormais le canvas quand l'entrée
894
- est déjà `rgb()/rgba()` (le cas de la quasi-totalité des appels, puisqu'ils
895
- lisent `getComputedStyle`), et le chemin canvas restant convertit son
896
- résultat hexadécimal en `rgb()/rgba()` via un nouveau `hexToRgb()` avant de
897
- le rendre — `toRgb()` ne rend plus jamais de hex. La mécanique de correction
898
- elle-même (forcer noir/blanc, poser `data-origam-contrast-fixed`, journaliser
899
- le ratio) n'est pas modifiée : elle fonctionnait déjà, elle n'était
900
- simplement jamais atteinte.
878
+ **Pourquoi cette paire précisément, et pourquoi une suppression plutôt qu'un
879
+ correctif.** Les deux émissions n'ont jamais été atteignables au clavier :
880
+ l'événement partait d'un `<span>` descendant, alors qu'une activation clavier
881
+ synthétise son clic sur la RACINE du composant — l'écouteur posé sur le
882
+ descendant ne le voit jamais. Le correctif générique appliqué aux dix autres
883
+ consommateurs d'`useAdjacent` (promouvoir la zone en `role="button"` + arrêt
884
+ de tabulation) est **illégal** ici : `<OrigamBtn>` rend un `<button>` ou un
885
+ `<a>`, et le modèle de contenu HTML interdit à tous deux un descendant de
886
+ contenu interactif *et* tout descendant portant `tabindex`. Un
887
+ `<button type="button">` imbriqué y est tout aussi invalide. La forme était
888
+ fausse, pas seulement le balisage : un contrôle qui possède déjà une action
889
+ ne peut pas en héberger une seconde — deux actions sont deux boutons,
890
+ composés via `<origam-btn-group>`.
901
891
 
902
- **Preuve** — `packages/tests/e2e/contrast-directive.spec.ts` (Playwright,
903
- Chromium réel — jsdom n'a pas de `canvas.getContext('2d')` fonctionnel dans
904
- ce dépôt, donc ne peut ni reproduire ni vérifier ce bug) : contrôle positif
905
- (`#777777` sur blanc, 4.48:1, opaque des deux côtés) déclenche désormais la
906
- correction + le `console.warn` ; contrôle négatif (noir sur blanc, 21:1) ne
907
- déclenche rien ; non-régression du chemin translucide (fond composé via
908
- `resolvePaintedBackground`) inchangée. **A/B contre le commit parent** : 4
909
- des 5 tests rougissent sur le code d'avant (le contrôle négatif reste vert
910
- des deux côtés, comme attendu).
892
+ **Ce qui NE bouge PAS** : les slots `prepend` / `append` (`IBtnSlots extends
893
+ IAdjacentSlots`) restent intacts, ainsi que les props `prependIcon` /
894
+ `appendIcon` / `prependAvatar` / `appendAvatar` — un contenu décoratif ou
895
+ informationnel en prepend/append reste parfaitement légitime, seule
896
+ l'émission d'un clic disparaît.
911
897
 
912
- ⚠️ **Rayon mesuré séparément, pas hérité de #871 — et le chiffre publié par
913
- #871 contenait un artefact, corrigé depuis par #883.** Le chiffrage initial
914
- du ticket (346 violations en mode sombre) datait d'avant #876. #876 avait
915
- d'abord publié **189 → 11** ; en rejouant `audit/dark-contrast.audit.mjs`
916
- avec la VRAIE directive (redirect du stub désactivé) au lieu de sa
917
- reformulation mathématique, seuls **7 `console.warn` réels** se
918
- déclenchaient sur les 32 configurations (30 composants × 8 identités × 2
919
- modes, portées racine + sous-arbre) — écart signalé à l'auteur de #876, qui
920
- l'a reproduit, creusé et corrigé sous #883 : **les bornes AVANT et APRÈS
921
- étaient toutes les deux fausses (185 → 7, pas 189 → 11)**, la même erreur de
922
- mesure existant déjà dans le comptage "avant". Le delta réel (178) n'a
923
- jamais changé.
898
+ **Preuve du comportement retiré** — A/B contre le commit parent, fonctionnel
899
+ et pas seulement typé : `packages/tests/TU/components/Btn/OrigamBtn.adjacent-emit-removed.spec.ts`
900
+ monte `<OrigamBtn>` avec un écouteur `onClick:prepend` / `onClick:append` et
901
+ déclenche un vrai `click` DOM sur la zone. Sur le commit parent, ce même test
902
+ appellerait l'écouteur (l'émission déclarée par `IAdjacentEmits` partait
903
+ encore) ; sur ce commit, l'écouteur n'est jamais invoqué — vérifié en
904
+ rejouant le fichier de spec avant/après le changement, pas seulement en le
905
+ lisant une fois vert.
924
906
 
925
- L'écart venait d'`origam-tooltip__content` (identité `apple`, root + subtree,
926
- light + dark) et, découvert par #883, également d'`origam-bottom-nav` sur la
927
- même identité. Fond réel : `color(srgb 0.898039 0.898039 0.905882 / 0.94)`
928
- (translucide) ; `toRgb()` le résout correctement en `rgb(215, 215, 217)` une
929
- fois composé sur son ancêtre opaque, contre du texte noir : **ratio 14.61:1,
930
- conforme** — vérifié en appelant directement `toRgb()`/
931
- `resolvePaintedBackground()` de la vraie directive sur cette page. La
932
- fonction `parse()` de `dark-contrast.audit.mjs` (une réimplémentation
933
- distincte, pas la directive) ne reconnaissait que `rgba?\(…\)` — elle
934
- ignorait silencieusement cette couche `color(srgb …)`, sautait jusqu'à
935
- l'ancêtre opaque suivant et rapportait à tort un faux "noir sur noir"
936
- (ratio 1.00). **Ce n'était pas un défaut du DS ni de `v-contrast`** — la
937
- même classe de lacune que ce ticket corrige, mais dans un autre fichier. Les
938
- 7 restantes (4 `origam-badge__badge`, 3 `origam-breadcrumb-item`) sont
939
- confirmées identiques entre les deux mesures, et `sous 2:1` passe à **0** une
940
- fois l'artefact retiré : plus rien de ce qui reste n'est proche d'invisible.
907
+ Nettoyage induit : le helper `warnDeprecatedEmit` (`color.util.ts`) et la
908
+ constante `ADJACENT_EMIT_REPLACEMENT` (`consts/Btn/btn.const.ts`) sont
909
+ retirés — `<OrigamBtn>` était leur seul appelant, et le fichier `btn.const.ts`
910
+ devenu vide est supprimé.
941
911
 
942
- Non corrigé ici : `dark-contrast.audit.mjs` appartenait à #871/#876, pas à ce
943
- ticket — signalé, reproduit et corrigé séparément sous #883 (ne pas citer
944
- 189 → 11, c'était faux des deux côtés).
912
+ **Migration.** Aucun consommateur interne (DS, marketing, stories, docs,
913
+ tests) n'utilisait `click:prepend` / `click:append` sur `<OrigamBtn>` —
914
+ balayage exhaustif, zéro résultat. Un consommateur externe qui les écoutait
915
+ doit remplacer l'action posée sur la zone prepend/append par un second
916
+ `<origam-btn>` dans un `<origam-btn-group>`.
945
917
 
946
- **Rupture d'API** : aucune — `toRgb`/`channelsOf`/`hexToRgb` restent des
947
- fonctions privées du module, non exportées ; la surface publique
948
- (`setContrastConfig`, `v-contrast` par défaut) est inchangée.
918
+ ### ⚠️ BREAKING — `createOrigam()` nu n'installe plus le thème par défaut (#360)
919
+
920
+ `createOrigam()` préfixait inconditionnellement chaque install avec le thème
921
+ neutre interne `origamTheme` (`[...origamTheme, ...suppliedThemes]`) : même un
922
+ appel nu, ou un `themes: []` explicite, recevait quand même les variables CSS
923
+ et — surtout — les **props par défaut par composant** (ADR-005,
924
+ `components: { 'origam-avatar': { rounded: 'full' }, … }`) de ce thème. Une
925
+ application qui n'en voulait pas ne pouvait pas s'en défaire. Ce préfixage est
926
+ retiré : `createOrigam()` installe désormais exactement ce qu'on lui passe,
927
+ rien de plus.
928
+
929
+ **Ce qui change concrètement.** Sans `themes`/`theme` explicite,
930
+ `createOrigam()` n'injecte plus aucune variable `--origam-*` et le résolveur
931
+ de props par défaut (`installThemePropsResolver`, ADR-005) n'a plus rien à
932
+ résoudre — chaque composant retombe uniquement sur ses propres valeurs
933
+ `withDefaults()`. Concrètement : un `<origam-avatar>` sans prop `rounded`
934
+ explicite n'est plus automatiquement circulaire (`rounded="full"` venait du
935
+ thème, pas du composant).
936
+
937
+ **Le thème par défaut reste disponible, à la demande** — `origamTheme`,
938
+ exporté par `origam/themes` (déjà public avant cette rupture, aucun nouvel
939
+ export nécessaire) :
940
+
941
+ ```ts
942
+ // AVANT (2.x) — enregistrait implicitement le thème origam par défaut
943
+ import { createOrigam } from 'origam'
944
+ app.use(createOrigam())
945
+
946
+ // APRÈS — le thème par défaut est un choix explicite
947
+ import { createOrigam } from 'origam'
948
+ import { origamTheme } from 'origam/themes'
949
+ app.use(createOrigam({ themes: origamTheme }))
950
+ ```
951
+
952
+ **Rayon de souffle mesuré, corrigé dans le même lot** — quatre consommateurs
953
+ directs de `createOrigam()` nu trouvés par balayage exhaustif (DS, marketing,
954
+ stories, docs, tests) :
955
+ - `packages/ds/src/nuxt/module.ts` — le module Nuxt officiel. **Non cassé** :
956
+ son propre `DEFAULT_THEMES` (utilisé quand l'option `origam.themes` du
957
+ consommateur est omise) passe désormais `origamTheme` explicitement à
958
+ `createOrigam()`, préservant à l'identique le comportement de tout
959
+ consommateur Nuxt existant (marketing compris) qui ne configurait rien.
960
+ Seul un `createOrigam()` direct, hors module Nuxt, doit s'adapter.
961
+ - `packages/marketing/nuxt.config.ts` — configure `origam.themes`
962
+ explicitement (7 thèmes de marque + un thème `origam` renommé pour le
963
+ playground `/theming`), ce qui **contourne** le fallback du module. Le vrai
964
+ thème neutre non-nommé (celui que `activeDefaultsFor` fusionne toujours en
965
+ premier, avant la marque active) était fourni jusqu'ici par le préfixage
966
+ implicite de `createOrigam()` — invisible dans la config marketing. Il est
967
+ désormais listé explicitement, en premier, dans le tableau `themes`.
968
+ - `packages/stories/histoire.setup.ts` — Histoire (utilisé par les ~208
969
+ stories de composants) appelait `createOrigam()` nu ; sans correction,
970
+ chaque story aurait silencieusement perdu les props par défaut du thème
971
+ (avatars carrés au lieu de circulaires, etc.). Passe désormais
972
+ `{ themes: origamTheme }`.
973
+ - `packages/docs/.vitepress/theme/index.ts` — les démos de composants live de
974
+ la doc VitePress appelaient `createOrigam()` nu ; même correction.
975
+
976
+ Les échantillons de code montrés au consommateur (`installation.const.ts` /
977
+ `installation.vue` sur le marketing, `guide/usage.md` côté docs) sont mis à
978
+ jour pour montrer la forme correcte — un utilisateur copiant l'ancien
979
+ exemple aurait obtenu des composants non stylés selon le thème.
980
+
981
+ **Tests adaptés** (comptés avant correction, comme demandé) — 3 fichiers /
982
+ 5 assertions reposaient sur l'enregistrement implicite et ont été corrigés
983
+ pour installer `origamTheme` explicitement là où le test vérifie précisément
984
+ une valeur par défaut issue du thème, ou réécrits pour pinner le NOUVEAU
985
+ contrat plutôt que l'ancien :
986
+ - `theme-props-resolver.spec.ts` (2 assertions — `origam-radio`/`origam-text-field`
987
+ vs `origam-input` density) ;
988
+ - `installed-themes.composable.spec.ts` (describe block entier renommé
989
+ `origam baseline — explicit opt-in only (#360)`, 2 assertions corrigées et
990
+ 2 nouvelles ajoutées pour couvrir explicitement le nouveau contrat) ;
991
+ - `OrigamChip.spec.ts` (1 assertion — `components['origam-chip'].size`).
992
+
993
+ **Preuve fonctionnelle** (pas seulement structurelle) —
994
+ `packages/tests/TU/origam/createOrigam-bare-no-theme-360.spec.ts` monte un
995
+ vrai `<OrigamAvatar>` : sans thème, la classe `origam--rounded-full` est
996
+ absente ; avec `origamTheme` passé explicitement, elle est présente — la
997
+ même monteuse prouve donc que le harnais peut actionner la prop avant de
998
+ conclure qu'elle ne l'est plus par défaut.
999
+
1000
+ ### Fixed
1001
+
1002
+ - **#871 — le mode sombre ne peignait qu'à moitié dans un sous-arbre thémé.**
1003
+ Les feuilles de tokens portent ~1 761 déclarations **dérivées**
1004
+ (`--origam-title---color: var(--origam-color__text---primary)`). Une custom
1005
+ property est substituée **sur l'élément qui la déclare** ; ce qu'un
1006
+ descendant hérite est la valeur DÉJÀ substituée. Le bloc sombre étant ancré
1007
+ à la racine (`:root:not([data-theme])[data-mode="dark"]`), un sous-arbre
1008
+ `<OrigamThemeProvider mode="dark">` basculait bien les ~60 tokens
1009
+ sémantiques — le bloc runtime, lui, émet `[data-mode="dark"]` — mais laissait
1010
+ les 1 761 dérivés **gelés** sur les valeurs claires de la racine. Motif
1011
+ dominant mesuré : `rgb(10,10,10)` sur `rgb(10,10,10)`, texte et fond
1012
+ identiques.
1013
+
1014
+ Les deux feuilles s'accrochent désormais aussi à l'axe `data-mode` :
1015
+
1016
+ ```css
1017
+ :root, [data-theme="light"], [data-mode="light"] { /* clair */ }
1018
+ [data-theme="dark"], [data-mode="dark"]:not([data-theme="light"]) { /* sombre */ }
1019
+ ```
1020
+
1021
+ Rejoué sur 30 composants × 8 identités × 2 modes × 2 portées (racine ET
1022
+ sous-arbre), soit **1 664 instances**, Chromium, contrôles positif et négatif
1023
+ verts : **185 → 7 violations AA**, dont sombre **182 → 4** et clair
1024
+ **3 → 3** (aucune régression). Les 7 restantes appartiennent toutes aux
1025
+ palettes de marque de `packages/marketing`, aucune au DS, et aucune ne
1026
+ descend sous 2:1. Harnais : `pnpm -F @origam/tests audit:dark-contrast`.
1027
+
1028
+ ⚠️ **Ces bornes ont d'abord été publiées à `189 → 11`, et les deux étaient
1029
+ fausses de 4.** Le parseur de couleurs du harnais ne connaissait pas
1030
+ `color(srgb …)`, que la palette `apple` émet : le fond translucide du
1031
+ tooltip était pris pour « pas de fond », la remontée d'ancêtres sautait
1032
+ jusqu'à la surface opaque et rapportait du noir sur noir à 1.00 — quatre
1033
+ violations **fabriquées**, présentes des DEUX côtés de la mesure. Le
1034
+ **delta de 178 n'a jamais été faux** ; seules les bornes l'étaient. Détail
1035
+ et garde de non-récidive plus bas.
1036
+
1037
+ - **#871 — `system-bar` et `tooltip` illisibles en sombre.** Les deux peignent
1038
+ une surface sombre dans les DEUX modes (`neutral---700` / `neutral---800`)
1039
+ et lisaient `text---inverse`, qui vaut blanc en clair et **encre** en
1040
+ sombre : 1.91:1 et 1.31:1. `dark.css` lit maintenant `text---primary` ;
1041
+ `light.css` est inchangé.
1042
+
1043
+ - **#871 — `bottom-nav` peignait une dalle claire dans une UI sombre.** Son
1044
+ fond était épinglé à la primitive `neutral---200` (#e6e6e6) dans les deux
1045
+ modes, sous un `text---primary` presque blanc (1.03:1). En sombre il passe
1046
+ au token sémantique `surface---overlay`. Le clair est inchangé.
1047
+
1048
+ ### Changed — ⛔ RUPTURE (documentée, assumée)
1049
+
1050
+ - **Les feuilles de tokens ne sont plus ancrées à `:root`.** Un consommateur
1051
+ qui avait contourné le gel ci-dessus en re-déclarant lui-même des variables
1052
+ de composant dans un sous-arbre `[data-mode]` voit désormais la feuille les
1053
+ déclarer aussi, en (0,1,0) côté clair et (0,2,0) côté sombre. Son bloc de
1054
+ contournement doit donc être au moins aussi spécifique, ou être retiré.
1055
+
1056
+ Concrètement dans ce dépôt : `packages/marketing` embarque
1057
+ `ORIGAM_COMPONENT_RESET_LIGHT/DARK` (`themes/origam-reset.generated.ts`),
1058
+ une re-déclaration GÉNÉRÉE de ~2 700 variables de composant, écrite
1059
+ exactement pour ce défaut. Elle est maintenant redondante — **elle n'a pas
1060
+ été retirée ici**, faute de mesure sur le rendu du site.
1061
+
1062
+ Ce qui NE change pas : `data-theme="light" data-mode="dark"` reste clair
1063
+ (l'axe marque gouverne quand les deux se contredisent, règle #807),
1064
+ `data-mode="light"` sous un OS sombre reste clair (#794), et une marque
1065
+ claire continue de gagner sur la feuille par l'ordre source.
1066
+
1067
+ ### Documentation
1068
+
1069
+ - `CLAUDE.md`, section *Multi-theme* : la note qui affirmait que le bloc
1070
+ auto-mode ne porte que **11** déclarations est **fausse** — recompté en
1071
+ parsant la feuille le 2026-09-22, les deux blocs en portent **2 731**
1072
+ chacun. L'affirmation décrivait un état antérieur à #794 que plus rien ne
1073
+ remesurait.
949
1074
 
950
1075
  ## [2.18.0] - 2026-09-19
951
1076
 
@@ -80,6 +80,7 @@ import { SCROLL_STRATEGIES } from "../../enums/Commons/scroll.enum";
80
80
  import { animate } from "../../utils/Commons/animation.util";
81
81
  import { convertToUnit } from "../../utils/Commons/commons.util";
82
82
  import { getScrollParent } from "../../utils/Commons/scroll.util";
83
+ defineOptions({ inheritAttrs: false });
83
84
  const props = defineProps({
84
85
  absolute: { type: Boolean, required: false },
85
86
  attach: { type: [Boolean, String], required: false, skipCheck: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.18.8",
3
+ "version": "2.18.9",
4
4
  "type": "module",
5
5
  "description": "origam — a Vue 3 design system with multi-theme design tokens, CSS-first components and TypeScript types.",
6
6
  "keywords": [