origam 2.18.11 → 2.18.13

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,649 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.18.11] - 2026-09-24
22
+
23
+ ### Fixed — #935 : l'anneau `focus-visible` du Switch débordait de la piste, peint sur un frère non clippé
24
+
25
+ Ce n'est pas le pouce qui débordait — mesuré sur **32 configurations**
26
+ (8 identités × 2 modes × coché/décoché) en Chromium réel, sans exception. Ce
27
+ qui débordait est l'**anneau de focus**, peint par le `&--focus-visible`
28
+ d'`OrigamSelectionControl` sur `.origam-selection-control__input` : un carré
29
+ de ~28px, **frère** de `.origam-switch-track`, donc jamais clippé par son
30
+ `overflow: hidden`.
31
+
32
+ Avant correctif, Chromium réel (`/why-origam`, sélecteur d'identité cliqué,
33
+ `Tab` réel), état coché :
34
+
35
+ | identité | débord haut/bas | débord (côté opposé au pouce) |
36
+ |---|---|---|
37
+ | origam, apple, cartoon, ecom, editorial, geek, material | 8px | 8px |
38
+ | glass (inset) | 4px | 4px |
39
+
40
+ Après : **`0px` sur les 16 configurations** (8 identités × 2 modes), pouce et
41
+ anneau tous deux contenus.
42
+
43
+ Le correctif déplace l'anneau sur `.origam-switch-track` — le contrôle
44
+ réellement visible — en réutilisant les deux tokens déjà lus par la règle
45
+ partagée. `OrigamSelectionControl.vue` n'est **pas** modifié : pour Checkbox
46
+ et Radio, le carré EST le contrôle visible, ils ne sont pas concernés.
47
+
48
+ Deux pistes écartées avant celle-là, et pourquoi. Clipper via `overflow` sur
49
+ le wrapper commun : il est déjà plus grand que la piste (formule de densité
50
+ indépendante des tokens de piste), donc ça ne résout rien à la racine et ça
51
+ risque de tronquer l'indicateur de focus selon les combinaisons de props
52
+ (WCAG 2.4.11). Agrandir la piste pour englober le carré de hit-area : ça
53
+ changerait la taille visuelle du switch sur les 8 marques pour un problème de
54
+ **référence** — l'anneau vise le mauvais élément — et non de taille.
55
+
56
+ 3 nouveaux tests e2e (`packages/tests/e2e/switch.spec.ts`, describe
57
+ `Focus-visible ring`), vérifiés **rouges sur le commit parent**.
58
+ `switch.spec.ts` 38/38, `checkbox.spec.ts` 34/34, `radio.spec.ts` +
59
+ accumulation + a11y 57/57, Vitest 7187/7187, `guards` 29/29, `guards:self`
60
+ 16/16, `pnpm audit` propre — `$?` réel hors pipe. PR #936.
61
+
62
+ ### Fixed — #933 : `.origam-btn` ne déclarait **aucun** `display` — un CTA rendu 984 × 15 px
63
+
64
+ Le root retombait sur le défaut UA du tag rendu — `inline` pour le `<a>` que
65
+ `useLink` produit dès qu'on passe `href`. Une boîte inline ignore `height`,
66
+ `min-width` et le padding vertical, et s'étale sur toute sa ligne.
67
+
68
+ | | avant | après |
69
+ |---|---|---|
70
+ | CTA « Build your own identity » (`/why-origam`, dans un `<p>`) | **984 × 15** (`display: inline`) | **199 × 28** (`inline-block`) |
71
+
72
+ Vérifié sur les **8 identités** (984–986 → 199–201 px).
73
+
74
+ **`inline-block` et non `inline-flex`, exprès** : dans un parent flex/grid il
75
+ se blockifie en `block`, c'est-à-dire byte pour byte la valeur calculée
76
+ d'avant — donc aucune instance saine ne bouge. Balayage A/B de **181 instances
77
+ de `.origam-btn`** sur 8 pages marketing (serveur pré-fix contre serveur
78
+ post-fix) : **2 changements de géométrie**, tous deux les instances cassées
79
+ (`/why-origam`, et `themes-cta-builder` sur `/`, 168 × 17 → 167 × 28). 178 des
80
+ 181 ont un parent flex/grid et sont inertes au changement.
81
+ `vertical-align: middle`, déjà déclaré dans la même règle, n'a de sens que sur
82
+ une boîte de niveau inline : c'est la valeur que le reste de la règle supposait
83
+ déjà.
84
+
85
+ ⚠️ **Effet visible à valider** : le CTA était *visuellement* centré sous la
86
+ démo — par accident, la grille interne `__loader` flottant dans une boîte 5×
87
+ trop large. À la bonne taille il s'aligne à gauche, comme le titre et les
88
+ légendes de la même section. Si le centrage était voulu, c'est un
89
+ `text-align: center` sur `.why-demo__outro`, une ligne.
90
+
91
+ ⛔ **La première version de `btn-display.spec.ts` était verte contre le commit
92
+ parent, donc sans valeur.** Elle visait le Variant Design, qui rend un
93
+ `<button>` — dont le défaut UA de Chromium est déjà `inline-block` : le spec
94
+ mesurait le navigateur, pas la règle. Réécrit pour passer par `href`, donc par
95
+ le `<a>` que `useLink` produit — le seul tag qui distingue les deux mondes, et
96
+ exactement la forme du CTA cassé sur le site. PR #939.
97
+
98
+ ### Fixed — #934 : `--*---padding-inline`, un nom que rien ne lit — la gouttière `nav` n'a jamais existé
99
+
100
+ `.origam-list--nav` et `.origam-list-item--nav` déclaraient
101
+ `--origam-*---padding-inline`. Les règles de base consomment les longhands
102
+ `-start` / `-end`, et **une custom property n'est pas un raccourci** : elle ne
103
+ se décompose pas. Même famille que **#440**, sens inverse.
104
+
105
+ | | avant | après |
106
+ |---|---|---|
107
+ | `padding-inline` de la liste du menu (8 identités) | **`0px / 0px`** | **`8px / 8px`** |
108
+ | largeur de ligne dans un menu de 180px | 170–172 | **154–156** |
109
+
110
+ Conséquence corrigée du même coup : sous **material / glass / cartoon**,
111
+ `.origam-menu__content` porte `overflow: auto` et un rayon supérieur à sa
112
+ propre gouttière de 4px — la pastille de survol de la première et de la
113
+ dernière ligne était donc **rognée à plat par le coin arrondi du menu**. Les 4
114
+ menus de la barre (`Introduction`, `Getting started`, `Features`, sélecteur
115
+ d'identité) : **0 titre tronqué, 0 débordement horizontal**, avant comme
116
+ après.
117
+
118
+ `list-nav-padding.spec.ts` vérifié **rouge sur le commit parent** (liste
119
+ `0px`, ligne `16px`). Rayon d'impact e2e (btn × 8, list × 5, menu × 2,
120
+ select) : **193 passed / 1 failed** — l'échec est **#938**, pré-existant,
121
+ reproduit à l'identique sur le commit parent. `guards` 29/29, `guards:self`
122
+ 16/16, `test:unit:run` 7192 passed / 3 expected fail / 78 skipped,
123
+ `pnpm -F origam build` et `pnpm audit` à `$?` = 0. PR #939.
124
+
125
+ ### Fixed — #937 : la bande de messages vide (22px) et le bouton à 12px de la carte de démo — deux zéros côté marketing
126
+
127
+ `origam-input__details` est réservée par défaut, même sans message, et c'est
128
+ **voulu** côté DS (`hasDetails`, `OrigamInput.vue:222`) : réserver la bande
129
+ évite qu'une erreur qui apparaît fasse sauter la mise en page. Mais le switch
130
+ et le champ de la carte de démo ne portent **aucune** `rules`, aucun `error`,
131
+ aucun `messages` — leur bande ne pouvait qu'être vide, pour toujours. Mesuré
132
+ sur les **8 identités** : `min-height: 22px`, `padding-top` 6–8px,
133
+ `textContent` **`""`** partout. C'est elle qui portait le switch à 70px et
134
+ écrasait la rangée de contrôles.
135
+
136
+ Corrigé par **`hide-details="auto"`** sur les deux contrôles — `"auto"` et non
137
+ `true` : la bande **revient** dès qu'un message existe, donc aucun message
138
+ avalé. Épinglé par 5 tests unitaires (`OrigamInput.hide-details.spec.ts`), dont
139
+ « la restitue sur `messages` » et « la restitue sur une erreur de validation ».
140
+
141
+ | | champ | switch | pied de carte | carte |
142
+ |---|---|---|---|---|
143
+ | avant | 58 | 70 | 144 | 297 |
144
+ | après | **36** | **48** | **100** | **253** |
145
+
146
+ ⚠️ **Arbitrage laissé au propriétaire** : replier la bande **par défaut dans
147
+ le DS** changerait le comportement de tous les consommateurs et créerait
148
+ partout le saut de 22px à la première erreur. Non pris ici ; les quatre états
149
+ sont rendus et mesurés dans #937 pour trancher sur pièce.
150
+
151
+ **Le bouton à 12px — le second zéro.** `--origam-btn---min-height` n'est
152
+ déclarée nulle part, `--origam-btn---height` vaut `28px`, et
153
+ `--origam-btn---density` valait `0` **sans unité** : `calc(28px + 0)` est
154
+ invalide, donc `min-height` retombait sur `auto` et le bouton rendait 12px.
155
+ Mutation à une seule variable en navigateur réel — `0` → `0px` — rend
156
+ `min-height` auto → **28px**, `min-width` auto → **50px**, hauteur 12 → **28**.
157
+ Le `0` venait de `origam-reset.generated.ts`, le `cssVars` du thème `origam`.
158
+ **Signature décisive : le défaut ne touchait que l'identité `origam`** ; les 7
159
+ autres rendaient déjà 28px. Si la règle DS était en cause, les 8 le seraient.
160
+
161
+ ⛔ **Ce n'est PAS une régression de #904 / 2.18.8.** `git show 8e2937df7^`
162
+ porte **déjà** `"--origam-btn---density": "0"`, 12 jours plus tôt. Le fichier
163
+ était figé au 2026-09-11 (`630755c35`), la feuille DS corrigée le 2026-09-17
164
+ (#568 / PR #799) — et les deux versions de la règle, avant comme après #904,
165
+ traversent `var(--origam-btn---density)`.
166
+
167
+ **Le générateur (#609).** `generate-origam-reset.mjs` localisait son bloc par
168
+ `indexOf`, qui tombait sur la **citation du sélecteur dans le commentaire
169
+ d'en-tête** de `dark.css` (l. 16) au lieu de la vraie règle (l. 175). Le script
170
+ écrivait `dark: 0 vars` **en sortant 0** : une régénération de bonne foi aurait
171
+ amputé tout le bloc sombre (−3 398 lignes). Même famille que **#903**. Corrigé
172
+ par un sélecteur ancré en début de ligne sur une source dont les commentaires
173
+ sont neutralisés, **plus un garde-fou** qui refuse d'écrire un bloc de moins de
174
+ 500 variables. La régénération resynchronise **13 jours de dérive** : 731 clés
175
+ ajoutées / 671 retirées (des **renommages** de la campagne de grammaire) et 60
176
+ valeurs corrigées, dont les deux familles mortes de #609
177
+ (`--origam-grids__*` 36 → 0, `--origam-inline-edit--error---*` 16 → 0). Elle
178
+ fait au passage suivre le playground sur
179
+ `--origam-switch__track---background-color`, qui passe de
180
+ `var(--origam-color__surface---disabled)` au `color-mix()` de #919 : c'est **la
181
+ valeur que le DS livre réellement**, là où le playground affichait un
182
+ instantané figé sous une légende disant *« Live components from the published
183
+ origam package. Not a screenshot. »* PR #939.
184
+
185
+ ### Fixed — #931 : `border: true` retiré du switch sur 5 identités — la prop ne peignait pas l'élément que le ticket mesurait
186
+
187
+ `cartoon`, `ecom`, `editorial`, `geek` et `glass` déclaraient
188
+ `border: true` sur `'origam-switch'`. Mesuré en Chromium réel sur
189
+ `/why-origam` — sonde qui **clique le vrai sélecteur d'identité** et vérifie
190
+ `track.closest('.origam-theme-provider').getAttribute('data-theme')` avant
191
+ chaque lecture, ce qui écarte par construction le piège du cookie de page —
192
+ la prop **ne peint pas le track**. Elle peint deux autres éléments :
193
+ `.origam-input.origam-switch`, le wrapper racine qui entoure tout le contrôle,
194
+ et `.origam-selection-control__input`, l'anneau autour du pouce. Sur le track
195
+ elle n'ajoute que `.origam--border-thin`, qui déclare
196
+ `border-width: var(--origam-border__width---thin)` — **exactement la valeur du
197
+ défaut DS déjà en place**.
198
+
199
+ | identité (mode clair) | wrapper racine | anneau pouce | track (non touché) |
200
+ |---|---|---|---|
201
+ | origam | 0px → 0px | 0px → 0px | 0px |
202
+ | apple | 0px → 0px | 0px → 0px | 1px |
203
+ | **cartoon** | **1px → 0px** | **1px → 0px** | 1px |
204
+ | **ecom** | **1px → 0px** | **1px → 0px** | 1px |
205
+ | **editorial** | **1px → 0px** | **1px → 0px** | 1px |
206
+ | **geek** | **1px → 0px** | **1px → 0px** | 1px |
207
+ | **glass** | **1px → 0px** | **1px → 0px** | 1px |
208
+ | material | 0px → 0px | 0px → 0px | 1px |
209
+
210
+ Mode sombre remesuré après correctif : les 8 identités à 0px sur le wrapper et
211
+ l'anneau. Diff complet du sous-arbre (`border-*`, `background-color`,
212
+ `box-shadow`, `outline`, style inline, élément par élément) : les seules
213
+ valeurs qui bougent sont `border-width` / `border-style` sur ces deux éléments
214
+ et ces 5 identités. `rounded` et `elevation` ne bougent **nulle part**.
215
+
216
+ L'« anomalie 1 » du ticket — *« deux identités rendent une bordure qu'elles ne
217
+ déclarent pas »* — se dissout : le 1px du track vient de **#727**
218
+ (`b6a0a5fa7`), `--origam-switch__track---border-width:
219
+ var(--origam-border__width---thin)`, peint à spécificité **(0,0,0)** par
220
+ `:where(.origam-switch-track[data-v-…])` dans `OrigamSwitchTrack.vue`. C'est un
221
+ défaut DS partagé par tout le monde — voilà pourquoi `apple` et `material`
222
+ rendaient déjà 1px sans rien déclarer. Le tableau du ticket mesurait
223
+ `border-top-width` du track alors que la prop agit ailleurs.
224
+
225
+ La bordure était délibérée (`3ac20d506`, 2026-07-17, *« les switch devraient
226
+ être plus proches du design des input »*) : à l'époque le track valait
227
+ `surface---disabled`, contraste **1,06:1**, invisible, et la bordure le
228
+ délimitait seule. Depuis **#919** le track est à **3,06:1** et se délimite tout
229
+ seul — les deux faisaient doublon.
230
+
231
+ ⛔ **L'anomalie 2 du ticket est diagnostiquée mais non corrigée** : le track
232
+ d'`origam` rend `rgb(230, 230, 230)` (l'ancien `surface---disabled`) et 0px de
233
+ bordure, parce que le bloc `:root:root[data-theme="origam"], [data-theme="origam"]`
234
+ injecté par `createOrigam()` depuis `origam-reset.generated.ts` bat
235
+ `:root, [data-theme="light"], [data-mode="light"]` (0,1,0) avec des valeurs
236
+ antérieures à #727 **et** à #919. Aucun fichier de `packages/ds` n'est touché
237
+ par ce lot ; le ticket reste ouvert. PR #932.
238
+
239
+ ### Added — `/why-origam` : la refonte validée intégrée dans la vraie page
240
+
241
+ Nouveau hero (badge, titre sur deux lignes, sous-titre, nav d'action, bandeau
242
+ des 8 identités dont chaque point est peint par son propre
243
+ `<OrigamThemeProvider>`), nouvelle démo de thème vivante
244
+ (`WhyOrigamThemeDemo` : un radiogroup marque × mode pilote 6 composants DS
245
+ réels d'un côté et un extrait `components: {…}` **lu depuis l'objet de thème**
246
+ de l'autre, jamais retapé), les 8 forces en un tableau à filets au lieu de 8
247
+ cartes séparées, les faiblesses en liste à filets, et un troisième lien de CTA
248
+ vers le Theme Builder.
249
+
250
+ Le contenu vient **exclusivement** des `en.json` / `fr.json` de `develop` — la
251
+ copie de la maquette n'a jamais servi de source : 81 clés `why_origam.*`
252
+ pré-existantes reprises telles quelles, ~37 clés nouvelles (EN + FR) pour la
253
+ section de démo et les éléments de hero/CTA sans équivalent. `i18n:check` : 0
254
+ chaîne en dur, 0 écart de parité EN/FR, 0 `@` non échappé.
255
+
256
+ Deux références périmées de la maquette, écrite avant que #921 ne renomme
257
+ `IComparison.designTokens` → `themingPropsFirst` et
258
+ `comparison.col_design_tokens` → `comparison.col_theming_props_first`, sont
259
+ corrigées : `lib.designTokens` (champ inexistant) et `col_theming_props` (clé
260
+ sans son suffixe `_first`).
261
+
262
+ Deux correctifs a11y remontés en exécutant réellement les contrôles plutôt
263
+ qu'en les supposant verts : l'`aria-label` d'un avatar de démo était invalide
264
+ sur un `<div>` sans rôle (axe `aria-prohibited-attr`) → `aria-hidden` ; et la
265
+ note par bibliothèque et le disclaimer du tableau comparatif utilisaient
266
+ `--origam-color__text---tertiary`, mesuré par axe entre **4,03 et 4,24:1**
267
+ contre le fond de cette section — **défaut pré-existant vérifié** en
268
+ rechargeant la page de `develop` telle quelle dans le même serveur (1 violation
269
+ avant que cette PR ne touche quoi que ce soit), dont la refonte a fait
270
+ apparaître 6 instances de plus. Passé à
271
+ `--origam-color__text---secondary`, déjà utilisé une section plus haut.
272
+
273
+ `why-origam.spec.ts` mis à jour pour le nouveau DOM : **16/16**, axe-core 0
274
+ critical / 0 serious inclus. PR #930.
275
+
276
+ ### Documentation — les comptes de gardes du `CLAUDE.md` étaient périmés pour la quatrième fois
277
+
278
+ `pnpm -F origam guards` → **29/29** et `pnpm -F origam guards:self` →
279
+ **16/16**, remesurés, `$?` réels hors pipe. Ces deux lignes ont lu `17/17`,
280
+ puis `25/25`, `27/27`, `28/28` : chaque lot qui ajoute une garde les périme
281
+ mécaniquement, ce qui est exactement pourquoi *« recount, never quote »* est
282
+ dans la même phrase que le chiffre. L'historique des valeurs précédentes est
283
+ conservé dans le fichier — il montre la vitesse de péremption mieux qu'un
284
+ avertissement. Remonté spontanément par un agent qui avait recompté au lieu de
285
+ citer, hors de son périmètre et hors de son brief. PR #940.
286
+
287
+ ## [2.18.10] - 2026-09-24
288
+
289
+ ### Fixed — #919 : le track et le pouce d'`OrigamSwitch` étaient illisibles contre la page — les 16 couples identité × mode
290
+
291
+ `--origam-switch__track---background-color` valait
292
+ `var(--origam-color__surface---disabled)`, une teinte trop proche de la page
293
+ dans les **8 identités**. Mesuré en Playwright / Chromium réel, switch par
294
+ défaut (aucune prop `color`) :
295
+
296
+ - **track contre page** : 16/16 sous le seuil AA 3:1 (WCAG 1.4.11), pire cas
297
+ `glass|light` à **1,06:1** ;
298
+ - **pouce contre track (décoché)** : 14/16 sous le seuil, pire cas
299
+ `glass|light` à **1,11:1**.
300
+
301
+ Le défaut se reproduisait à l'identique sous `origam` seule, sans aucune
302
+ marque : c'est le composant, pas un thème.
303
+
304
+ Correctif :
305
+ `color-mix(in srgb, var(--origam-color__text---primary) 60%, var(--origam-color__surface---default))`
306
+ — contrasté contre la page **par construction** (`text---primary` doit déjà
307
+ être lisible sur la page, sinon tout le texte du DS serait illisible). Le pouce
308
+ par défaut vaut déjà `surface---default`, donc la page : il hérite
309
+ automatiquement du même contraste contre le nouveau track, et **un seul token
310
+ corrigé répare les deux paires**. `glass` et `ecom` (sombre) redéfinissent ce
311
+ token avec un pouce **fixe** plutôt que `surface---default` — dosage
312
+ théma-spécifique de **48 %**, vérifié sur les deux contraintes pour chacun
313
+ (commentaires dans `glass.theme.ts` / `ecom.theme.ts`).
314
+
315
+ Track contre page, Chromium réel :
316
+
317
+ | identité | mode | avant | après |
318
+ |---|---|---|---|
319
+ | origam | clair | 1.25 | **5.25** |
320
+ | origam | sombre | 1.31 | **7.04** |
321
+ | apple | clair | 1.51 | **4.45** |
322
+ | apple | sombre | 1.79 | **6.84** |
323
+ | cartoon | clair | 1.20 | **3.90** |
324
+ | cartoon | sombre | 1.68 | **6.88** |
325
+ | ecom | clair | 1.28 | **4.48** |
326
+ | ecom | sombre | 1.32 | **4.77** |
327
+ | editorial | clair | 1.24 | **4.57** |
328
+ | editorial | sombre | 1.31 | **7.30** |
329
+ | geek | clair | 1.14 | **4.64** |
330
+ | geek | sombre | 1.31 | **6.20** |
331
+ | glass | clair | **1.06** | **3.06** |
332
+ | glass | sombre | 1.23 | **4.96** |
333
+ | material | clair | 1.23 | **4.43** |
334
+ | material | sombre | 1.83 | **5.56** |
335
+
336
+ Pouce contre track (décoché) suit la même paire de valeurs, le pouce valant la
337
+ page dans ce mode — sauf `ecom` et `glass`, qui ont leur pouce fixe :
338
+ `ecom|dark` 2,75 → **3,95**, `glass|light` **1,11 → 3,59**, `glass|dark`
339
+ 12,6 → **3,13**.
340
+
341
+ ⛔ **Trouvé en mesurant les états que la QA ne couvrait pas : l'état `error`
342
+ posait le pouce et le rail sur le MÊME token**
343
+ (`--origam-color__feedback--danger---bg`) — **1,00:1 sur les 16
344
+ configurations, pouce littéralement invisible**.
345
+ `--origam-switch__thumb---background-color-error` ramené à
346
+ `surface---default`, le même token que le pouce non coloré : remesuré, 16/16
347
+ ≥ **3,55:1**.
348
+
349
+ Les autres états, mesurés et non supposés. `disabled` : WCAG 1.4.11 exempte
350
+ les contrôles inactifs, donc pas un défaut, mais mesuré quand même en
351
+ corrigeant pour l'opacité 0.32 — la valeur brute lue par `getComputedStyle`
352
+ n'est **pas** la couleur réellement peinte — et il reste sous 3:1
353
+ (**1,37–1,84:1**). `indeterminate` : aucun changement de couleur, hérite du
354
+ correctif de base. **Anneau de focus** : mesuré sur les 16 configurations via
355
+ un vrai `Tab` clavier (nouveau
356
+ `packages/tests/audit/focus-ring.audit.mjs`, alors nommé
357
+ `switch-focus-ring.audit.mjs` et élargi depuis par #924), 15/16 ≥ **4,43:1** ;
358
+ `cartoon|light` échoue à **2,07:1**, mais la cause est le token de thème global
359
+ `border.focus` (`#ff8fa3`, un rose pastel) qui pilote l'anneau de **tous** les
360
+ composants focusables sous ce thème — hors périmètre, à traiter par un ticket
361
+ dédié plutôt qu'en aveugle sur un seul composant.
362
+
363
+ ⛔ **Régression connue, assumée, non corrigée ici.** Le cas *coché + couleur
364
+ explicite* (`color="primary"`) passe de « majoritairement bon » à **FAIL sur
365
+ 12/16 configurations**. Vérifié mathématiquement qu'**aucun** pourcentage de
366
+ mélange ne satisfait simultanément track-contre-page et pouce-accent-contre-track
367
+ pour les 8 thèmes : le meilleur compromis plafonne à ~2,1:1 de marge minimale,
368
+ jamais 3:1 — l'accent d'`ecom` notamment a une luminance trop proche du point
369
+ milieu entre page et texte. Le vrai correctif est de teinter le **rail** avec
370
+ la couleur d'intention quand il est coché (pattern iOS / Material / Ant) et de
371
+ découpler le pouce, ce qui déplace des pixels sur tous les switches
372
+ cochés-colorés du DS : un lot à part entière, pas une retouche.
373
+
374
+ ⚠️ Ce changement déplace des pixels sur **tous** les switches non colorés
375
+ (track plus foncé ou plus clair selon le mode) — assumé, cf. la règle des
376
+ ruptures pré-1.0. ⛔ Note technique : Chromium sérialise le résultat d'un
377
+ `color-mix()` en `color(srgb …)` et non en `rgb()` legacy, même quand chaque
378
+ canal reste du sRGB plat — une assertion e2e figée sur
379
+ `rgb(230, 230, 230)` a dû être mise à jour en conséquence.
380
+
381
+ Périmètre : `packages/ds/src/assets/css/tokens/{light,dark}.css` et leurs
382
+ jumelles SCSS (`token-twins` vérifié, diff vide après chaque édition),
383
+ `packages/marketing/src/themes/{glass,ecom}.theme.ts`,
384
+ `packages/tests/e2e/switch.spec.ts`, et le harnais de mesure de
385
+ `packages/tests/audit/` — committé pour la première fois, étendu à
386
+ `disabled` / `error` / `indeterminate` / anneau de focus. `guards` 29/29,
387
+ `guards:self` 16/16, `test:unit:run` **7187 passed**, les 3 spécs Switch
388
+ **43/43** en Playwright réel contre Histoire statique, `pnpm audit` à `$?` = 0
389
+ hors pipe. PR #923.
390
+
391
+ ### Fixed — #922 : le site vendait encore le pipeline Style Dictionary / Tokens Studio, supprimé le 2026-08-31
392
+
393
+ Les feuilles de tokens sont des fichiers CSS/SCSS tenus à la main depuis le
394
+ 2026-08-31, sans étape de build. Le marketing continuait à annoncer le
395
+ contraire sur quatre écrans — `/why-origam` traité par PR #921, le reste ici,
396
+ dans `en.json` et `fr.json` au même commit :
397
+
398
+ | clé | avant | après |
399
+ |---|---|---|
400
+ | `home.features.tokens.description` | « Multi-theme via Tokens Studio… » | multi-thème via un objet de thème props-first, résolu par `createOrigam()` sur les 218 composants |
401
+ | `home.themes.subtitle` | « DTCG-compliant design tokens… zero flicker » | conformité DTCG retirée (fausse pour nous) et « zero flicker » retiré (invérifiable) |
402
+ | `home.themes.style_dictionary` | « → Style Dictionary v4 » | « → hand-maintained CSS/SCSS, no build step » |
403
+ | `home.themes.tokens_studio` | « → tokens.studio compatible » | « → props-first theming (218 components) » |
404
+ | `components.detail.tokens.desc` | « DTCG token excerpt… Built with Style Dictionary v4. » | décrit ce que la section affiche réellement : les `--origam-<component>---<property>` de la feuille tenue à la main du composant |
405
+
406
+ **« zero remount » est conservé** dans les deux premières : c'est vrai et
407
+ vérifié (`theme.composable.ts:129`). `home.themes.tokens_studio` n'était pas
408
+ listée dans le ticket, mais c'est le **pill jumeau** de
409
+ `home.themes.style_dictionary` dans le même tableau `THEMES_TOOLING_TEXT` et
410
+ elle portait exactement la même affirmation désormais fausse — laisser l'une
411
+ juste à côté de l'autre corrigée aurait été incohérent.
412
+
413
+ Non touché volontairement : `changelog.versions.v210.h1` / `v200.h1`
414
+ (légitimes, ces versions ont réellement livré ça), les ADR
415
+ `packages/docs/internal/adr-00{1,2,3,4}-*.md` (historique de décision), et
416
+ `packages/docs/integrations/theming-authoring.md`, qui mentionne l'export
417
+ Tokens Studio comme **format d'entrée possible** du champ `tokens` — une
418
+ capacité d'`IOrigamTheme`, pas notre pipeline de build.
419
+
420
+ ⚠️ **Signalé, plus massif, non traité** :
421
+ `packages/marketing/server/db/seed/component.json` porte **129 occurrences** de
422
+ `pipelineNote` mentionnant « Style Dictionary v4 + @tokens-studio/sd-transforms »
423
+ — un champ affiché en direct dans la ligne « Pipeline » de la page de détail de
424
+ **chaque** composant. Une correction de données, un lot à part. `i18n:check`
425
+ PASS : 0 violation bloquante, 0 écart de parité EN/FR, 0 `@` non échappé.
426
+ Le ticket reste ouvert. PR #926.
427
+
428
+ ### Fixed — `/why-origam` affirmait six choses fausses ou invérifiables
429
+
430
+ Audit factuel de la page, corrigé dans les locales, `why-origam.const.ts`,
431
+ `why-origam.interface.ts` et la page elle-même — aucun design, layout ni CSS
432
+ touché.
433
+
434
+ - **« Design tokens DTCG / Style Dictionary v4 »** — pipeline supprimé le
435
+ 2026-08-31. Remplacé par le mécanisme réel : theming props-first
436
+ (`IOrigamTheme.components`), résolu pour les 218 composants par un unique
437
+ installeur sans opt-in (ADR-005). L'affirmation vérifiée « 8 thèmes
438
+ commutent à l'exécution sans remount » est conservée. Clé
439
+ `why_origam.strengths.tokens` renommée → `.theming` : changement de sens
440
+ complet, pas de formulation.
441
+ - **« < 50 kb tree-shakable »** — mesuré **faux** : Divider ≈ 7 ko, Btn
442
+ ≈ 35 ko, DataTable ≈ 172 ko gzip, builds Vite réels. Remplacé par « pay for
443
+ what it does », avec les chiffres et la raison (icônes / transitions /
444
+ loader sur Btn ; tri / pagination / regroupement sur DataTable).
445
+ - **« Certified / WCAG 2.1 AA verified »** — **aucun audit tiers, aucun
446
+ VPAT**. Remplacé par l'affirmation vérifiable : 218/218 composants balayés
447
+ par axe-core + Playwright (variante par défaut + 7 variantes d'intention),
448
+ baseline à zéro violation depuis le 2026-09-23 — avec son périmètre énoncé
449
+ honnêtement (la variante par défaut, pas chaque état ; moderate/minor ne
450
+ bloquent pas la porte).
451
+ - **« Every component exposes its logic as a standalone composable »** —
452
+ faux : **122 des 218** n'ont aucun composable dédié. Réécrit sans
453
+ l'absolu.
454
+ - **Carte CSS-first** : elle citait `:has()`, `color-mix()`, les container
455
+ queries et `view-transition-name` comme utilisés en interne ; seuls les deux
456
+ premiers le sont. Les deux autres sont détectés via `useCssSupport()` et
457
+ exposés aux consommateurs, pas appliqués dans le CSS du DS — la description
458
+ le dit désormais.
459
+ - **Tableau comparatif** : colonne « DTCG tokens » renommée « Theming
460
+ props-first » (`col_design_tokens` → `col_theming_props_first`,
461
+ `IComparison.designTokens` → `themingPropsFirst`) — origam oui, les cinq
462
+ concurrents listés non, aucun ne livre de résolveur de thème props-first. La
463
+ conformité DTCG réelle de PrimeVue (PrimeOne 3.0) est préservée dans sa note,
464
+ puisque le tableau ne suit plus cette dimension.
465
+ - **Cas d'usage « design-token-driven team » supprimé** (`token_team`) : il
466
+ décrivait une intégration qui n'existe plus.
467
+ - **Faiblesses** : « Smaller community » sous-entendait qu'une communauté
468
+ externe existe. Remplacé par l'état honnête — aucune communauté externe à
469
+ ce jour, aucun consommateur externe.
470
+
471
+ `i18n-check.mjs` PASS (0 chaîne en dur bloquante, 0 écart de parité EN/FR),
472
+ eslint propre sur les 5 fichiers, grep dépôt entier sur `designTokens`,
473
+ `col_design_tokens`, `why_origam.strengths.tokens` et
474
+ `why_origam.use_cases.token_team` : aucune référence périmée restante. Diff
475
+ chirurgical, 46 insertions / 56 suppressions sur exactement 5 fichiers. PR
476
+ #921.
477
+
478
+ ## [2.18.9] - 2026-09-23
479
+
480
+ ### Fixed — #853 : `OrigamOverlay` sans `inheritAttrs: false` — un warning Vue qui sérialisait le `vnode` de `RouteProvider`, ~4,4 Mo par occurrence
481
+
482
+ `OrigamOverlay` a un template **multi-racine** (le slot `activator` est frère
483
+ du bloc `<template v-if>` qui enveloppe le `<Teleport>`) et forwarde déjà
484
+ `$attrs` à la main sur le `<div>` téléporté
485
+ (`v-bind="{ ...scopeId, ...$attrs }"`). Sans `inheritAttrs: false`, Vue tente
486
+ quand même sa vérification de fallthrough automatique en dev, la trouve
487
+ impossible sur une racine fragment/teleport, et émet un
488
+ **« Extraneous non-props attributes »** à chaque rendu portant un attribut non
489
+ déclaré (`role`, `data-cy`, l'attribut de scope CSS).
490
+
491
+ La trace de composant de ce warning sérialise les props de chaque ancêtre — y
492
+ compris `RouteProvider` (Vue Router), dont le prop `vnode` est **tout le graphe
493
+ réactif de la page**, ~4,4 Mo par occurrence, dominé par des `"[Circular]"`
494
+ répétés. C'est ce qui gonflait le journal du serveur dev de 1,1 à 5,5 Go,
495
+ mesuré pendant #835.
496
+
497
+ `OrigamOverlay` sert de base à **tout** composant flottant ou téléporté — Menu,
498
+ Dialog, Tooltip, Snackbar, Drawer, OtpInputField… — donc un seul
499
+ `defineOptions({ inheritAttrs: false })` élimine le mécanisme à sa source
500
+ plutôt que chez chaque consommateur. Même famille que #818, où un seul
501
+ `inheritAttrs` manquant produisait 12 violations `aria-allowed-attr`.
502
+
503
+ Serveur `pnpm -F @origam/marketing dev` sans Postgres (le cas le plus
504
+ facilement reproductible du ticket), navigation Playwright réelle vers
505
+ `/theming` puis 3 clics réels (menu langue, menu thème, bouton Import) :
506
+
507
+ | | avant | après |
508
+ |---|---|---|
509
+ | occurrences « Extraneous non-props » | **32** | **1** |
510
+ | octets de journal attribuables | **45 967** | **2 111** |
511
+ | dumps `RouteProvider` / `vnode` | **7** | **0** |
512
+
513
+ **Contrôle positif tenu** : le code d'avant a été rejoué avec exactement la
514
+ même méthode — même séquence de clics, même serveur, même port — et reproduit
515
+ bien le défaut (32 occurrences, 7 dumps), donc la disparition après correctif
516
+ n'est pas un artefact de mesure. L'unique occurrence résiduelle ne porte **pas**
517
+ sur `RouteProvider` : c'est un warning `data-cy` sans rapport sur
518
+ `OrigamCommandPalette`, déjà présent avant.
519
+
520
+ ⚠️ **L'hypothèse initiale du ticket pointait `OrigamSelect` — établie fausse**
521
+ en reproduisant en réel : `OrigamSelect` a une racine unique
522
+ (`<origam-text-field>`), donc n'est jamais concerné par ce warning. Le
523
+ composant fautif est `OrigamOverlay`, atteint ici par les menus langue/thème du
524
+ header et le dialogue « Import a theme » de `/theming`.
525
+
526
+ Non re-mesuré, et dit comme tel : le run « avec base Postgres » (5,5 Go),
527
+ coûteux à rejouer — le ticket note déjà que la version sans Postgres est la
528
+ plus reproductible et porte le même mécanisme (94,6 % du volume).
529
+ `guards` 29/29, `guards:self` 16/16, `vue-tsc --noEmit` sur `packages/ds` 0
530
+ erreur, tests unitaires des 10 consommateurs d'`OrigamOverlay` 237/238 (1 skip
531
+ pré-existant). Aucune prop, slot ni emit public modifié — donc pas de story ni
532
+ de doc à synchroniser. Le ticket reste ouvert. PR #915.
533
+
534
+ ### Fixed — #913 : `/roadmap` affichait 2.17.1 à côté de sa propre version vivante, `/changelog` s'arrêtait à 2.18.0, et le détecteur de dérive était rouge et branché nulle part
535
+
536
+ Trois retards d'un seul ticket.
537
+
538
+ **`CHANGELOG.md` — huit sections publiées manquaient.** `[Unreleased]`
539
+ empilait ~930 lignes couvrant `2.18.1` → `2.18.8`, toutes déjà sur npm.
540
+ Découpées en huit sections datées des tags réels. **L'attribution ne vient pas
541
+ du texte** : elle vient de `git diff <tagA>..<tagB> -- CHANGELOG.md` pour
542
+ chaque plage, puis d'un `git blame` ligne à ligne du bloc entier — chaque ligne
543
+ rattachée au commit qui l'a écrite, chaque commit à sa version. Aucune entrée
544
+ reformulée, uniquement déplacée. Comptage, lignes non vides triées et diffées :
545
+ **4 012 → 4 020**, le delta expliqué exactement par les 8 en-têtes ajoutés,
546
+ un `### Fixed` ajouté pour le lot #871 de la 2.18.4, moins un `---` orphelin.
547
+ **Zéro ligne de contenu perdue, zéro ligne réécrite.** Deux défauts de
548
+ structure corrigés au passage : le paragraphe « Récolte de deux déprécations »
549
+ était séparé de ses deux sections BREAKING par **77 lignes appartenant à un
550
+ autre lot** — le défaut « un titre à 60 lignes de son corps » déjà vu ici.
551
+
552
+ **#411 et #535, publiés dans la 2.18.8, documentés nulle part.** Trouvés en
553
+ découpant `[Unreleased]` : les deux lots ont mergé dans la 2.18.8
554
+ (`eda18226e` / PR #908, `94e1574c6` / PR #909) sans jamais écrire une ligne de
555
+ changelog — `grep -cE '#411|#535' CHANGELOG.md` rendait **0** sur `develop`.
556
+ Les deux touchent `packages/ds` : un consommateur les reçoit dans le paquet npm
557
+ sans qu'aucune note ne les annonce. Contrairement au reste du ticket, ces deux
558
+ entrées sont **rédigées** — depuis les commentaires de fermeture, les PR, et
559
+ une relecture du code livré pour chaque affirmation.
560
+
561
+ **`changelog:generate:check` branché en CI.** Rouge et appelé par aucun
562
+ workflow ni hook — **troisième détecteur de dérive non câblé trouvé le même
563
+ jour**, après `presets:generate:check` (#903) et le Tier 1 de
564
+ `theming-feedback-tokens`. Nouveau job `changelog-check` dans `ci.yml`, son
565
+ **propre** job plutôt qu'une étape d'`i18n-check` : celui-là garde la surface
566
+ de traduction, celui-ci une constante générée face à un document racine. Il
567
+ peut réellement tourner là : le générateur n'importe que `node:fs`,
568
+ `node:path` et `node:url` — ni DB, ni build, ni réseau.
569
+
570
+ **`/roadmap` consomme `useVersion()`.** Le titre rendait la chaîne littérale
571
+ « Where 2.17.1 » quinze lignes sous un badge qui lisait déjà `useVersion()` :
572
+ **c'est #743 qui recommence**, ce ticket avait corrigé le badge et laissé le
573
+ titre. Remplacer le numéro aurait redémarré la même dérive — le titre et la
574
+ ligne npm interpolent désormais `{version}`. La valeur `fr` a été remodelée
575
+ pour que le placeholder tombe sur la **même ligne** dans les deux langues, la
576
+ parité EN/FR d'`i18n:check` restant à **0 écart** (elle passait à 2 avec la
577
+ découpe naïve). La **taille de l'archive quitte la phrase où la version est
578
+ vivante** : apparier une valeur live et une valeur gelée dans une même phrase
579
+ est exactement le défaut de ce ticket.
580
+
581
+ **Les chiffres de `roadmap.const.ts`, tous datés du 2026-09-15, remesurés** le
582
+ 2026-09-23, vrais `$?` hors pipe :
583
+
584
+ | | fichier | mesure |
585
+ |---|---|---|
586
+ | `guards` | 21/21 | **29/29** |
587
+ | `guards:self` | absent | **16/16** |
588
+ | tests unitaires | 6 953 / 532 fichiers | **7 187 / 563** |
589
+ | couverture | 78 % / 79.9 % | **77.81 % instr. / 79.86 % lignes** |
590
+ | spécs e2e | 229 | **254** |
591
+ | spécs barrées en CI | 58 | **81** |
592
+ | npm | 2.17.1, 1 733 668 o | **2.18.8, 1 858 354 o** |
593
+ | composables | 138 | **139** |
594
+ | jobs CI | 17 | **20** |
595
+
596
+ Les `done: false` ont été revérifiées une par une. Une seule ne nommait plus un
597
+ manque réel : le balayage a11y par composant **tourne en CI depuis #765** et
598
+ `a11y-violations.baseline.json` est désormais `{}` — l'entrée est recadrée sur
599
+ ce qui manque vraiment, la couverture focus-trap des overlays, **un seul
600
+ composant** (`OrigamDialog`) sur toute la famille. Les cinq autres tiennent,
601
+ vérifiées.
602
+
603
+ Rendu réel, serveur isolé puis tué : `/roadmap` affiche « v2.18.8 — Wave 4
604
+ shipped », « Where 2.18.8 / stands. », « origam 2.18.8 » ; `/fr/roadmap`
605
+ « Où en est la 2.18.8 / aujourd'hui. » ; **zéro occurrence de `2.17.1`** sur les
606
+ deux pages ; `/changelog` part désormais sur 2.18.8 et non 2.18.0. Tous `$?` = 0
607
+ hors pipe : `changelog:generate:check`, `guards` 29/29, `guards:self` 16/16,
608
+ `lint --max-warnings 0`, `i18n:check`, `pnpm audit`, suite unitaire 7 187
609
+ passed. Le ticket reste ouvert. PR #914.
610
+
611
+ ### Internal — #910 : `marketing-no-third-party` sondait `/` PENDANT sa redirection de locale
612
+
613
+ `packages/tests/e2e/marketing-no-third-party.spec.ts:94`, cas `/`, échouait par
614
+ intermittence en CI **sans qu'aucune assertion ne tourne** :
615
+ `net::ERR_ABORTED; maybe frame was detached?` et
616
+ `Execution context was destroyed, most likely because of a navigation`.
617
+ `nuxt.config.ts` déclare `i18n.detectBrowserLanguage.redirectOn: 'root'` — `/`
618
+ n'est pas une page finale, c'est le point d'entrée qui déclenche la détection
619
+ de locale ; une navigation client peut survenir juste après le `load` initial
620
+ pendant que cette détection se termine.
621
+
622
+ `gotoAndWaitForFonts()` capture les deux signatures transitoires **exactes**
623
+ sur `goto` et sur `evaluate`, et attend `page.waitForLoadState('load')` — une
624
+ barrière sur l'état de la frame, valable quel que soit l'URL final — avant de
625
+ rejouer l'opération **une seule fois**. Une deuxième course, ou une erreur
626
+ différente, remonte normalement. ⛔ Aucun `waitForTimeout` ajouté, aucun
627
+ timeout relevé — conformément à la mise en garde du ticket sur #889.
628
+
629
+ Même worktree, même serveur `nuxt dev` isolé, même config que la CI
630
+ (`workers: 1`, `fullyParallel: false`), `uptime` relevé avant et après chaque
631
+ série :
632
+
633
+ | | N | échecs | charge (load avg 1 min) |
634
+ |---|---|---|---|
635
+ | **avant** (3 séries) | 100 | **1** (1 %) | ~5.1 → 7.9 → 6.7 |
636
+ | **après** (2 séries) | 250 | **0** | ~5.5 → 6.0 |
637
+
638
+ **Contrôle positif tenu** : l'unique échec relevé « avant » porte la signature
639
+ exacte du ticket. ⚠️ **Écart assumé avec les taux rapportés ailleurs sur ce
640
+ flake (20 %, 56 %, 96 % selon les séries citées)** : la mesure locale tombe à
641
+ 1 %. Le mécanisme est confirmé identique, l'amplitude non — très probablement
642
+ parce qu'un serveur `nuxt dev` local déjà chaud élargit beaucoup moins la
643
+ fenêtre de course qu'un serveur CI démarré à froid sous charge concurrente.
644
+ Le taux CI réel reste **non mesuré**. `/` est délibérément conservée dans
645
+ `PAGES`, avec un commentaire disant pourquoi : c'est la seule entrée qui
646
+ exécute réellement le chemin de détection de locale, `/fr` étant déjà préfixé.
647
+ PR #917.
648
+
649
+ ### Internal — le faux positif `side-tab` sur `OrigamCode`, acté plutôt que laissé à redécouvrir
650
+
651
+ Le contrôle design signalait `OrigamCode.vue:637` comme « side-tab accent
652
+ border ». `.origam-code__row--highlighted` porte
653
+ `box-shadow: inset 3px 0 0 var(--origam-code__line-highlight---accent-color)`
654
+ — ce n'est pas l'accent latéral d'une carte, c'est le **marqueur de gouttière
655
+ d'une ligne de code surlignée**, la convention de tout afficheur de code
656
+ (GitHub, Shiki, Prism) ; le retirer supprimerait la seule indication visuelle
657
+ qu'une ligne est mise en avant. Deux éléments confirment le déclenchement
658
+ collatéral : la ligne date de `9f5a4eea1` (2026-05-16), et le lot #535 qui a
659
+ fait réagir le contrôle n'a touché **ni ce fichier ni ce bloc**. Ignore le plus
660
+ étroit disponible : `side-tab` sur **ce seul fichier**, via `ignore-value` —
661
+ pas un `ignore-rule`, pas un `ignore-file`, qui demandent l'accord du
662
+ propriétaire. PR #912.
663
+
21
664
  ## [2.18.8] - 2026-09-23
22
665
 
23
666
  ### Fixed — #901 `OrigamBtn` — `calc(auto + 0px)` ramenait `min-width` à `0` sur tous les boutons