origam 2.18.17 → 2.18.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +981 -0
  2. package/dist/src/assets/css/main.css +1 -1
  3. package/dist/src/assets/css/tokens/dark.css +212 -6
  4. package/dist/src/assets/css/tokens/light.css +106 -3
  5. package/dist/src/assets/scss/tokens/_dark.scss +212 -6
  6. package/dist/src/assets/scss/tokens/_light.scss +106 -3
  7. package/dist/src/components/Audio/OrigamAudio.vue +66 -2
  8. package/dist/src/components/Chip/OrigamChip.vue +34 -0
  9. package/dist/src/components/Field/OrigamField.vue +76 -3
  10. package/dist/src/components/List/OrigamList.vue +90 -3
  11. package/dist/src/components/Main/OrigamMain.vue +2 -2
  12. package/dist/src/components/Media/OrigamMediaScrubber.vue +11 -1
  13. package/dist/src/components/Pagination/OrigamPagination.vue +64 -2
  14. package/dist/src/components/SliderField/OrigamSliderField.vue +138 -3
  15. package/dist/src/components/SliderField/OrigamSliderFieldTrack.vue +12 -1
  16. package/dist/src/components/Snackbar/OrigamSnackbar.vue +2 -2
  17. package/dist/src/components/Switch/OrigamSwitch.vue +32 -0
  18. package/dist/src/components/TextField/OrigamTextField.vue +3 -1
  19. package/dist/src/components/Toolbar/OrigamToolbar.vue +58 -0
  20. package/dist/src/components/Video/OrigamVideo.vue +11 -1
  21. package/dist/src/composables/Commons/layout.composable.cjs +2 -2
  22. package/dist/src/composables/Commons/layout.composable.d.ts +16 -2
  23. package/dist/src/composables/Commons/layout.composable.js +1 -1
  24. package/dist/src/composables/Commons/theme-props-resolver.composable.cjs +44 -9
  25. package/dist/src/composables/Commons/theme-props-resolver.composable.d.ts +33 -1
  26. package/dist/src/composables/Commons/theme-props-resolver.composable.js +42 -10
  27. package/dist/src/consts/Commons/variant-preset.const.cjs +8 -0
  28. package/dist/src/consts/Commons/variant-preset.const.d.ts +60 -0
  29. package/dist/src/consts/Commons/variant-preset.const.js +2 -0
  30. package/dist/src/consts/index.cjs +11 -0
  31. package/dist/src/consts/index.d.ts +1 -0
  32. package/dist/src/consts/index.js +1 -0
  33. package/dist/src/interfaces/Commons/theme.interface.d.ts +33 -0
  34. package/dist/src/interfaces/Masonry/masonry.interface.d.ts +2 -2
  35. package/dist/src/origam.cjs +2 -1
  36. package/dist/src/origam.js +8 -2
  37. package/dist/src/types/Commons/variant-preset.type.cjs +1 -0
  38. package/dist/src/types/Commons/variant-preset.type.d.ts +73 -0
  39. package/dist/src/types/Commons/variant-preset.type.js +0 -0
  40. package/dist/src/types/index.cjs +11 -0
  41. package/dist/src/types/index.d.ts +1 -0
  42. package/dist/src/types/index.js +1 -0
  43. package/dist/src/types/tokens.type.d.ts +1 -1
  44. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -18,6 +18,987 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.18.19] - 2026-09-30
22
+
23
+ ### Changed — ⚠️ RUPTURE : `useLayout` renommé `useLayoutMain`
24
+
25
+ Le DS exportait `useLayout`, **le même nom que l'auto-import natif de Nuxt**
26
+ (`#app/composables/layout`). Chez tout consommateur Nuxt, c'est le nôtre qui
27
+ gagnait et celui du framework qui était ignoré — relevé dans le journal d'un
28
+ serveur de dev, pas par un test :
29
+
30
+ ```
31
+ WARN [NUXT_B6002] useLayout is already auto-imported by Nuxt as a built-in
32
+ WARN Duplicated imports "useLayout", the one from "#app/composables/layout"
33
+ has been ignored
34
+ ```
35
+
36
+ ⛔ Ce n'était pas cosmétique : notre version **lève une exception** sans provider
37
+ Origam (`throw new Error('[Origam] Could not find injected layout')`). Tout code
38
+ Nuxt appelant `useLayout` hors d'un `OrigamLayout` plantait au lieu de recevoir
39
+ le composable du framework.
40
+
41
+ Le nouveau nom dit ce qu'il fait — il expose la zone **main** (`mainRect`,
42
+ `mainStyles`, `mainId`) — et s'aligne sur `useLayoutItem` et `useCreateLayout`,
43
+ avec lesquels il partage le contrat `ORIGAM_LAYOUT_KEY`. Rupture d'API publique
44
+ assumée, livrée en mineure conformément à la politique du dépôt.
45
+
46
+ ### Added — ADR-005 lot 1 : le variant devient un preset de props
47
+
48
+ Le mécanisme que l'ADR décrivait depuis des mois **n'avait jamais été
49
+ construit** : ni `variant-preset.type.ts`, ni table de presets, ni code
50
+ d'application. Seule la garde `no-variant-css` existait, et elle ne faisait que
51
+ **geler** 36 violations sans jamais en retirer une.
52
+
53
+ Ce lot pose le mécanisme, pas les conversions : le type, le registre, le **rang
54
+ dans le resolver** (site d'appel > thème > preset > `withDefaults`, l'ordre
55
+ arbitré en round 2 de l'ADR), la clé `IOrigamTheme.variants` et sa fusion à
56
+ l'installation. 19 specs, dont deux qui épinglent des **no-op silencieux** que le
57
+ lot risquait : l'early-out qui ne patchait rien sans thème enregistré — le cas
58
+ par défaut depuis #360 — et l'union des clés limitée au variant actif, qui gelait
59
+ un basculement à l'exécution.
60
+
61
+ La preuve de non-régression est une VRT : les 7 variants d'`OrigamBtn` restent
62
+ **pixel-identiques** à leurs références.
63
+
64
+ Mesure conservée dans l'en-tête de la garde : chaque `!important` d'un bloc
65
+ variant n'existait que pour battre la déclaration **inline**, jamais l'utilitaire
66
+ tokenisé que la règle scopée battait déjà sur la spécificité. Une fois le variant
67
+ résolu au rang des props, la bataille de cascade **n'a plus lieu**.
68
+
69
+ ### Fixed — le menu de navigation était disproportionné sur les 8 identités
70
+
71
+ Signalé par le propriétaire. Mesuré sur la section « Features » (10 items), en
72
+ mesurant la **pastille** et non le texte :
73
+
74
+ | | avant | après |
75
+ |---|---|---|
76
+ | rangée | 48px | **36px** |
77
+ | côtés gauche/droite | 12-15px | **8-11px** |
78
+ | haut/bas | 5-7px | **8-11px** |
79
+ | écart entre pastilles | **0px** sur 7 identités sur 8 | **2px** |
80
+ | largeur du menu | **180px fixes** pour 114px de contenu | **128-140px** |
81
+ | hauteur du menu | ~590px | **394px** |
82
+
83
+ Quatre causes distinctes :
84
+
85
+ - `--origam-list---padding-block-*` valait **0 sur sept identités** — la matrice
86
+ runtime l'émet à zéro pour chaque marque, et `origam` n'y échappait que par le
87
+ reset généré qu'elle traîne encore (#609) ;
88
+ - l'indent `-8px` du mode nav ne s'applique qu'au côté *start*, d'où une
89
+ asymétrie gauche/droite sur cinq identités ;
90
+ - aucun écart entre items, et la liste étant un conteneur **bloc**, `gap` n'était
91
+ pas disponible sans la passer en flex — ce qui toucherait ~200 consommateurs ;
92
+ - `layouts/default.vue` posait `min-width: 180px` **et** `width: 180px`, une
93
+ largeur figée pour 114px de contenu utile.
94
+
95
+ ⚠️ La rangée à 36px passe **sous la cible tactile 44×44** de WCAG 2.5.5.
96
+ Compromis assumé par le propriétaire pour un menu d'en-tête pointé à la souris,
97
+ et confiné à `&--nav` : une liste de contenu garde ses 48px. Les trois valeurs
98
+ sont des **tokens déclarés**, pas des littéraux.
99
+
100
+ ### Fixed — le catalogue marketing vendait un pipeline supprimé (#960)
101
+
102
+ Le tableau de tokens de **chaque** page composant affichait une légende
103
+ promettant `--origam-<composant>---<propriété>` au-dessus d'un bouton **Copier**
104
+ qui livrait `alert.background-color` pour `{color.surface.disabled}`.
105
+
106
+ 523 des 603 lignes re-dérivées ; **80 abandonnées sans qu'aucune soit inventée**
107
+ — repointer vers un token voisin aurait remplacé un nom qui ne résout rien par un
108
+ nom qui résout *la mauvaise chose*. Vérifié par un second analyseur de feuilles
109
+ écrit indépendamment : 3204 déclarations des deux côtés, 0 divergence.
110
+ `pipelineNote` supprimé partout, baseline 230 → 0.
111
+
112
+ ### Fixed — trois attributs ARIA morts sur tout `OrigamSelect` (#938)
113
+
114
+ `OrigamTextField` appelait `filterInputAttrs(attrs)` **une seule fois dans le
115
+ corps de `setup()`**, ce qui **copie** les clés hors du proxy `$attrs` et fige la
116
+ distribution au montage. `aria-expanded` restait donc à `"false"` listbox ouvert,
117
+ et `aria-controls` / `aria-activedescendant` n'étaient **jamais posés** : un
118
+ lecteur d'écran annonçait le select comme toujours replié, et la navigation
119
+ clavier était muette de bout en bout.
120
+
121
+ C'est la règle « un prop lu *eagerly* dans `setup()` ne voit jamais le thème »
122
+ appliquée aux `$attrs`. Le spec qui gardait ce contrat expirait à **45,7 s** sans
123
+ jamais atteindre son assertion ; il passe en **796 ms** et tient 25/25 en
124
+ `--repeat-each=5`.
125
+
126
+ ⛔ Ce spec **ne tournait pas en CI** : il figurait parmi les 174 enregistrées
127
+ comme non exécutées. La garantie n'existait donc pas. Il entre dans `GREEN_SPECS`
128
+ (81 → 82 specs réellement exécutées).
129
+
130
+ ### Fixed — #597, #596 : 58 canaux de thème morts résorbés
131
+
132
+ Btn, DataTable, Grid, List (#597) puis Tabs, Toolbar, Pagination (#596).
133
+ **Aucune** variante ni état de `Tabs` n'était déclaré — 17 canaux, donc toute
134
+ édition de thème sur eux était un **no-op silencieux**.
135
+
136
+ 13 alias de nommage DataTable **renommés au lieu d'être dupliqués** : net nul au
137
+ lieu de +13 tokens, confirmé par l'ensemble émetteur passant de 3142 à 3129.
138
+
139
+ ## [2.18.18] - 2026-09-30
140
+
141
+ ### Fixed — #596 : Tabs, Toolbar et Pagination (26 canaux morts)
142
+
143
+ 299 → 273 canaux de thème morts. Le constat qui justifie le tag à lui seul :
144
+ **aucune** des variantes ni des états de `Tabs` n'était déclarée (17 canaux), donc
145
+ toute édition de thème sur eux était un no-op silencieux.
146
+
147
+ Zéro changement de rendu, **mesuré par canal** : Playwright/Chromium contre le
148
+ Histoire statique, light et dark, A (livré) vs B (token neutralisé à `initial`,
149
+ donc le repli reconstitue le rendu d'avant) vs C (sonde) — 52 lignes, A==B
150
+ partout, C!=A partout.
151
+
152
+ ### Fixed — six versions publiées sans entrée de changelog
153
+
154
+ `v2.18.12` à `v2.18.17` étaient **taguées et publiées sur npm** mais absentes de
155
+ ce document — et donc de la page publique `/changelog`, qui annonçait 2.18.11
156
+ quand npm servait 2.18.16.
157
+
158
+ ⚠️ Le job « Changelog drift check » ne pouvait pas le voir : il vérifie que la
159
+ constante marketing colle au document, **jamais que le document couvre les
160
+ tags**. Angle mot documenté dans l'en-tête de `generate-changelog.mjs`.
161
+
162
+
163
+ ## [2.18.17] - 2026-09-29
164
+
165
+ Dix-huit commits, dont la réparation qui débloquait `Type-check (vue-tsc)` sur
166
+ `develop` et bloquait donc toute PR. Deux des quatre PR du lot ne sont **pas**
167
+ dans le tag, faute de toucher le paquet publié : #986 (barrière e2e du catalogue)
168
+ et #989 (commentaire seul).
169
+
170
+ ### Fixed — #597 : 32 canaux de thème morts résorbés sur Btn, DataTable et Grid, zéro changement de rendu
171
+
172
+ Le périmètre annoncé par le ticket — « 55 tokens morts » — n'a pas résisté à la
173
+ mesure : il en restait **48**. List avait déjà été traitée en PR #586 (il n'en
174
+ reste que les 3 laissées délibérément) et Btn en portait 17, pas 16.
175
+
176
+ Et ces 48 n'avaient pas une cause mais **trois**, établies avant d'écrire une
177
+ ligne — ce qui interdisait le traitement uniforme que la campagne #550 suggère :
178
+
179
+ | famille | entrées | cause réelle |
180
+ |---|---|---|
181
+ | DataTable | 16 | moitié manquante de la migration #503 : le 1er échelon nomme la grammaire par sous-composant, que seules 3 familles déclaraient |
182
+ | Grid | 12 | les vars sont écrites **en style inline par instance**, repli mot-clé CSS constant — personne ne les déclarait, simplement |
183
+ | Btn | 17 | aucun jumeau ; 7 replis littéraux, les 10 autres relatifs à l'élément (`currentColor`, `calc()` de densité) donc indéclarables sans changer le rendu |
184
+
185
+ **31 noms déclarés** à la valeur exacte du repli déjà rendu, dans les 4 feuilles
186
+ et dans `TTokenName` ; **16 entrées laissées en baseline**, chacune avec sa raison
187
+ écrite **à son site de lecture**. Deux lectures sont renommées au passage parce
188
+ que leur nom ne désignait aucun composant réel
189
+ (`--origam-data-table-sortable---cursor` → `-header-cell--sortable---cursor`), et
190
+ un échelon intermédiaire inatteignable est retiré du SCSS plutôt que déclaré.
191
+
192
+ Compteur de la garde, **recompté sur la baseline elle-même** et non repris d'un
193
+ message : `token-var-channels` dead **299 → 267**, dormant **460 → 460**, le diff
194
+ de la baseline ne contenant que des suppressions.
195
+
196
+ ⚠️ Le corps de `e83a1fbf1` annonce « dead 438 → 406 ». C'est une mesure
197
+ **pré-rebase**, prise sur une branche qui portait Bracket + Card mais pas encore
198
+ Chart ; elle ne décrit aucun état de l'historique final. Les chiffres retenus
199
+ ci-dessus sont ceux des fichiers de baseline aux tags concernés.
200
+
201
+ Preuve de non-régression, en trois instruments plutôt qu'un :
202
+ **1 927 584 lectures** de longhands calculés (20 stories, tous leurs Variants,
203
+ 2 modes, `::before` / `::after` inclus) rejouées avant/après — hors les couples
204
+ `(élément, propriété)` d'animations en vol, **1 926 702 lectures comparées,
205
+ `diff` exit 0, zéro ligne**. Le contrôle négatif est ce qui donne sa valeur au
206
+ résultat : deux passes sur le **même** bundle produisent 148 lignes de diff sur
207
+ exactement le même ensemble de couples — ce bruit est l'instant
208
+ d'échantillonnage, pas un changement de style. S'y ajoutent une sonde
209
+ d'équivalence de 83 expressions `var()` × 2 modes (une seule différence, `0` →
210
+ `0px`, valeur calculée identique) et 28 captures dont 27 byte-identiques.
211
+
212
+ ### Changed — #597 : les 13 alias DataTable sont renommés, pas dupliqués
213
+
214
+ Correction portée sur la première passe du lot lui-même. Elle déclarait 13
215
+ tokens DataTable pour éteindre la garde **en laissant en place leur jumeau
216
+ historique** lu en repli : 13 doublons introduits par la PR, et un second
217
+ échelon que la chaîne ne consultait plus jamais.
218
+
219
+ Deux mesures établissent qu'il s'agit de purs alias : chaque nom historique a
220
+ **exactement 1 site de lecture** — la position de repli de sa chaîne, aucun autre
221
+ consommateur — et les deux échelons portent la **même valeur** en clair comme en
222
+ sombre (26 comparaisons, 0 divergence). Les chaînes sont donc effondrées
223
+ (`var(NEUF, var(HISTORIQUE, T))` → `var(NEUF, T)`) et les déclarations
224
+ historiques retirées des 4 feuilles : **net nul**, 13 ajoutés et 13 retirés, au
225
+ lieu de +13. Ensemble émetteur 3142 → 3129, soit exactement −13.
226
+
227
+ Le principe du lot, jusque-là nulle part écrit, est consigné : on déclare
228
+ l'échelon 1 quand l'échelon 2 est un token **statique** de feuille ; on refuse et
229
+ on documente quand l'échelon 2 est écrit **par instance** (Grid en style inline,
230
+ List par le parent) ou relatif à `currentColor` (Btn ghost), parce qu'une
231
+ déclaration `:root` y figerait le rendu.
232
+
233
+ ### Fixed — `develop` était rouge : un import cassé par la scission des interfaces
234
+
235
+ `Type-check (vue-tsc)` échouait sur `develop`, donc bloquait toute PR :
236
+ `theme.interface.ts(3,15) TS2305`, `IThemeVars` introuvable. Le commit
237
+ `4fc7fcd92` avait déplacé le symbole vers
238
+ `interfaces/Commons/semantic-tree.interface.ts` sans mettre à jour ce seul import
239
+ resté en arrière — `apply-theme.util.ts` pointait déjà au bon endroit.
240
+ `type-check` passe de `exit 2` à **exit 0**.
241
+
242
+ Même cause pour la garde `comment-format`, qui rougissait à **30/31** : les blocs
243
+ de commentaire ont suivi les symboles, et trois chemins neufs se sont retrouvés
244
+ sans allocation. La baseline est re-dérivée, conservation vérifiée à l'unité sur
245
+ les trois paires concernées, et les quatre autres lignes ne font que **baisser**
246
+ (−14, exactement le delta du total) : la baseline sort plus **stricte**, jamais
247
+ plus laxiste.
248
+
249
+ ### Fixed — #953 : le catalogue de composants entre dans la barrière CI, et l'assertion qui l'en empêchait était fausse
250
+
251
+ `components.spec.ts` existait depuis longtemps sans être exécuté par **aucun
252
+ job**. `/components` — la vitrine du DS — pouvait donc rendre une page **vide**
253
+ sans une seule ligne rouge : `useReferenceCatalog` déclare `default: () => []`,
254
+ et « 0 famille sur 0 catégorie » passe pour un succès.
255
+
256
+ ⛔ Le dernier défaut qui bloquait la promotion était **dans le test**, pas dans le
257
+ produit. L'assertion de la section « Design tokens » comptait des `tbody tr` — or
258
+ cette section n'a **jamais** contenu de `<table>` : c'est un `<dl class="prop-list">`
259
+ dont chaque ligne est un `<div class="prop-list__item">`. Elle rendait donc
260
+ toujours 0, quelle que soit la donnée, et masquait une fonctionnalité qui
261
+ marchait. Les assertions voisines du même fichier utilisaient déjà le bon
262
+ sélecteur ; seule celle-ci divergeait.
263
+
264
+ Mesure sur un seed frais (2733 entrées) : `btn` rend 8 lignes de tokens, et
265
+ **127 des 218** composants en ont au moins une. Barrière complète
266
+ `MARKETING_GREEN_ONLY` : **109 passed** contre 80 avant promotion.
267
+
268
+ ⚠️ #960 n'est pas fermé par là : il porte sur le **contenu** du seed (notation
269
+ DTCG à accolades, chemins pointés de l'ancien format), pas sur la forme du DOM.
270
+
271
+ ### Changed — #544 : la conclusion du ticket est inversée, `useDefaults` reste
272
+
273
+ #544 classait `useDefaults` « doublon du résolveur ADR-005, plus aucun appelant,
274
+ à retirer ». **Sa prémisse factuelle est juste, sa conclusion est fausse** — et
275
+ l'entrée est ici parce que le composable a été à deux doigts d'être supprimé.
276
+
277
+ Juste : zéro appelant interne, les 40 derniers partis sous #363. Faux : ce n'est
278
+ pas un doublon. `installThemePropsResolver` a une limitation **structurelle**,
279
+ déjà documentée dans son propre fichier et qui n'est pas un oubli mais le
280
+ séquencement de Vue — le hook écrit dans `beforeCreate`, qui s'exécute **après**
281
+ le retour de `setup()`. Un prop lu une fois, synchroniquement, au top level de
282
+ `setup()` capture donc la valeur d'**avant** le patch : le thème ne l'atteint
283
+ jamais et rien n'avertit. `useDefaults` n'a pas cette lacune, le getter de son
284
+ `computed` s'évaluant à l'accès `.value`. C'est le **seul échappatoire** pour un
285
+ prop qui doit être lu tôt.
286
+
287
+ La preuve est exécutable et **existait avant le ticket** — une paire appariée
288
+ dans `TU/origam/setup-level-prop-reads.spec.ts` dont les deux titres disent
289
+ exactement la différence. Deux faits que le ticket demandait de vérifier et que
290
+ personne n'avait mesurés : la surface publique est **réelle** (page de doc dédiée
291
+ `docs/composables/useDefaults.md`, plus deux citations), et **35 fichiers** sous
292
+ `packages/tests/TU` le référencent — pas « 3 specs, 23 tests », chiffre de
293
+ mémoire qu'une note de session avançait.
294
+
295
+ ### Documentation
296
+
297
+ - `CLAUDE.md` présentait **#964 et #966 comme vivants** alors que les deux sont
298
+ fermés, et l'un contredisait la note écrite deux lignes plus haut. Le
299
+ paragraphe #964 était de surcroît **charcuté** — une note française insérée au
300
+ milieu d'une phrase anglaise, ressemant l'affirmation fausse dans la moitié
301
+ restée en anglais. Recousu, et la leçon qui survit au correctif est conservée :
302
+ une garde qui énumère le disque mesure la machine, pas le dépôt.
303
+ - Compteurs de gardes remis à jour, **sixième fois** : `guards` 31/31,
304
+ `guards:self` 25/25. Le 17 → 25 n'est **pas** huit tests neufs mais un
305
+ sous-comptage corrigé — un glob sur `lib/` seul en rate six.
306
+ - Deux justifications C2 fausses **en prémisse** corrigées, conclusions
307
+ inchangées : les jumeaux `ghost` de Btn ne portent pas « une couleur
308
+ d'intention opaque » (`action--ghost---bg` vaut `rgba(0,0,0,0)`), et l'exception
309
+ Grid ne tient pas à « un repli mot-clé constant » mais à **qui le repli
310
+ désigne** — `column-gap` / `row-gap` lisent le token du voisin, les déclarer
311
+ ferait cesser d'agir la prop `gap`.
312
+
313
+ ### Tooling
314
+
315
+ - La transition `min-height` du subheader de liste est sanctionnée comme
316
+ **exception assumée** et non comme défaut : `transform` ne fait pas refluer les
317
+ frères, et `grid-template-rows` suppose un conteneur grid quand le subheader est
318
+ en `display: flex`. 6 composants du DS animent une propriété de layout, dont 2
319
+ exactement `min-height` — le second emploie la construction **identique**.
320
+ L'ignore est porté au seul fichier, et la config dit explicitement que c'est un
321
+ jugement d'agent, non une décision validée.
322
+
323
+
324
+ ## [2.18.16] - 2026-09-27
325
+
326
+ ⚠️ Ce tag couvre **deux merges**, pas un — dérogation assumée à la règle « un
327
+ ticket = un merge = un tag ». Les PR #983 (#592 Bracket, #593 Card) et #984
328
+ (#591 Chart) sont le même lot de campagne, mergées à une heure d'intervalle, et
329
+ #984 a dû être rebasée sur #983 pour un conflit sur un artefact généré.
330
+
331
+ ### Fixed — #591, #592, #593 : 221 canaux de thème rendus au DS, zéro changement de rendu
332
+
333
+ `token-var-channels` (direction *dead*) **520 → 299**, direction *dormant*
334
+ **460 → 460** — inchangée dans les deux sens. Chiffres recomptés sur les fichiers
335
+ de baseline aux tags `v2.18.15` et `v2.18.16`, pas repris des messages de commit.
336
+
337
+ | famille | avant | après | cause réelle |
338
+ |---|---|---|---|
339
+ | Chart | 147 | 8 | le SCSS lisait **97 noms que personne ne déclarait** |
340
+ | Bracket | 65 | 24 | direction dormante à 0 : aucun jumeau à renommer, les lectures nommaient des tokens jamais écrits nulle part |
341
+ | Card | 42 | 1 | second échelon d'une chaîne de replis dont le premier est déclaré — l'alias plat est **inatteignable** |
342
+
343
+ ⛔ **Rien n'a été supprimé**, et les trois familles avaient **trois causes
344
+ différentes** dont aucune n'était celle qu'annonce la campagne #550 (le désaccord
345
+ de grammaire). C'est le point de l'entrée : traiter les trois de la même façon
346
+ aurait cassé quelque chose à chaque fois.
347
+
348
+ Côté Card, 33 des 42 entrées sont le second échelon d'une chaîne à deux niveaux
349
+ dont le premier est bien déclaré. Mesure en navigateur : **29 des 33** tokens
350
+ externes résolvent une valeur non vide, leur échelon interne ne peut donc jamais
351
+ être consulté ; les 4 qui lisent vide sont exactement ceux déclarés `inherit`.
352
+ Ces 33 échelons sont donc du **code mort retiré du SCSS** plutôt que déclaré —
353
+ déclarer l'alias plat aurait créé un second nom public perpétuellement masqué
354
+ par le nom BEM canonique.
355
+
356
+ Les tokens déclarés le sont **à la valeur exacte du repli déjà rendu**, littéral
357
+ pour un littéral et référence de token pour une référence, afin que le mode
358
+ sombre continue de suivre. C'est ce qui garantit l'absence de changement visuel.
359
+
360
+ ⚠️ Trois pièges mesurés plutôt que supposés, et chacun aurait été une régression :
361
+
362
+ - **Déclarer un échelon d'une cascade de replis détruit le bouton général** — une
363
+ custom property est substituée sur l'élément qui la déclare, donc une
364
+ déclaration `:root` figerait les quatre côtés à la valeur racine. Vérifié :
365
+ régler le seul bouton général agit toujours sur les 4 côtés.
366
+ - **`--origam-chart-path---length` n'est pas un token** : c'est la longueur
367
+ *mesurée* de chaque tracé, écrite en inline par instance. La déclarer aurait
368
+ été inerte **et** aurait cassé l'animation de dessin partout.
369
+ - **`inherit` en repli ne peut pas être gravé tel quel** dans la feuille : un
370
+ mot-clé CSS-wide rend la custom property *guaranteed-invalid*, donc le token
371
+ serait non déclaré **tout en ayant l'air corrigé**. Les deux tokens concernés
372
+ passent à `currentColor`, et l'équivalence sur la propriété `color` est mesurée
373
+ en navigateur, pas supposée.
374
+
375
+ Non-régression : Bracket + Card, **13 272 lectures** de propriétés calculées
376
+ comparées avant/après (2 modes, 12 Variants, pseudo-éléments inclus) → **0
377
+ différence**, 14 paires de captures dont 12 byte-identiques. Chart, **31 mesures
378
+ absolues** sur 7 variantes → **31/31 identiques**, 7 captures byte-identiques, et
379
+ l'assertion « les tokens réconciliés sont déclarés par la feuille » vérifiée
380
+ **rouge sur le commit parent**.
381
+
382
+ 8 entrées Chart restent en baseline délibérément, dont
383
+ `--origam-chart__axis-label---font-size` (7 fichiers) parce que les replis **ne
384
+ s'accordent pas** — `0.75rem` sur cinq fichiers, `0.6875rem` sur trois : une
385
+ déclaration unique changerait le rendu d'un des deux camps. C'est un choix de
386
+ design, remonté plutôt que tranché en passant.
387
+
388
+ ### Fixed — #855 : l'état « introuvable » des 8 pages de détail était structurellement mort
389
+
390
+ `useReferenceDoc` levait un `createError({ fatal: true })` sur **toute** erreur,
391
+ 404 comprise. Les huit pages de détail portent pourtant chacune un bloc
392
+ `v-if="!catalogEntry"` avec son `data-cy` dédié — et ce bloc ne pouvait jamais
393
+ s'afficher, le composable levant avant que la page rende quoi que ce soit.
394
+
395
+ Sur un slug inconnu : `/components/<inconnu>` et `/types/<inconnu>` rendaient
396
+ **HTTP 500**. Après, les **huit** familles — components, types, enums,
397
+ interfaces, consts, composables, utils, directives — rendent **HTTP 200** avec
398
+ leur état not-found, **8/8**.
399
+
400
+ ⛔ Les autres statuts continuent de lever. Un 500 ou un 503 (base absente) n'est
401
+ pas « cette entrée n'existe pas », c'est une panne — la masquer derrière un joli
402
+ « introuvable » mentirait au visiteur comme au développeur. C'est d'ailleurs ce
403
+ 503 qui donnait un 500 au lieu d'un 404 sans base.
404
+
405
+ Vérifié sur un **build de production** et non en mode dev : le worker vite-node
406
+ du serveur de dev mourait en boucle sur `IPC connection closed` (famille #853),
407
+ et trois tentatives de mesure ont été perdues avant le changement d'outil.
408
+
409
+ ### Fixed — contraste WCAG du libellé des métadonnées de tokens (4,24:1 → conforme sur 16/16)
410
+
411
+ axe le levait sur `/components/btn` : `.component-tokens__meta-label`, `#7e5fb0`
412
+ sur `#efe8fc`, **4,24:1** en 12 px **gras**. Douze pixels gras restent du « texte
413
+ normal » au sens de WCAG 1.4.3 — le seuil est 4,5:1, pas 3:1.
414
+
415
+ ⚠️ Le token n'était **pas** mort, contrairement au premier diagnostic :
416
+ `geek.theme.ts` déclare bien `tertiary: '#7e5fb0'`, émis à l'exécution par la
417
+ matrice de thème. C'est sa **valeur** qui ne passe pas, pas son nom.
418
+
419
+ Mesure des deux encres candidates contre le fond **réel** du panneau
420
+ (`surface---sunken` **composite** sur `surface---default`), 8 identités × 2 modes :
421
+ `tertiary` échoue **6 fois sur 16** (pire cas apple sombre à 2,75) ; `secondary`
422
+ **0 fois sur 16**, minimum 5,42.
423
+
424
+ ⛔ La composition n'est pas un détail : sans elle, `glass` sortait 1,58 et 1,30 —
425
+ des chiffres absurdes, son `sunken` étant à 3 % d'alpha. Composite, il donne 8,91
426
+ et 14,56. La conclusion inverse — « `secondary` est pire » — a failli être tirée.
427
+
428
+ ⚠️ `--origam-color__text---tertiary` reste lu à ~72 endroits du marketing et
429
+ échoue sur 6 configurations partout où il sert de texte lisible. Hors périmètre
430
+ ici, noté dans le code.
431
+
432
+ ### Tooling
433
+
434
+ - Le job `test-e2e-marketing` **n'avait pas de base** (#953) : `/api/reference/*`
435
+ répondait 503 en CI, et toute page lisant le catalogue rendait du **vide** sans
436
+ qu'aucun check ne rougisse. Trois étapes copiées de `docs-fixtures.yml` où
437
+ elles tournent déjà — service `postgres:16`, `db:migrate`, `docs:seed`. Recette
438
+ vérifiée en local avant d'être écrite : 2733 entrées semées,
439
+ `/api/reference/component` = 218, `/components/btn` = 200 contre 500 sans base.
440
+ La seconde moitié de #953 — l'assertion qui rougit — arrive en 2.18.17.
441
+
442
+
443
+ ## [2.18.15] - 2026-09-26
444
+
445
+ Un correctif de DS et deux de marketing. Le correctif DS est la moitié
446
+ **invisible** d'un défaut signalé sur capture : la puce se voyait, l'indentation
447
+ de 40px qui l'accompagnait non.
448
+
449
+ ### Fixed — `OrigamGrid` neutralise la puce et l'indentation que le navigateur pose sur un `ul`
450
+
451
+ Signalé sur capture : un petit cercle apparaissait à gauche de chaque élément de
452
+ grille. La cause est **dans le DS**, pas dans la page : le thème de base
453
+ (`themes/origam.theme.ts`) impose `tag: 'ul'` aux grilles et `tag: 'li'` aux
454
+ items — choix sémantique volontaire — mais une grille n'est pas une liste à
455
+ puces, et le navigateur appliquait quand même ses styles de `ul`.
456
+
457
+ | | avant | après |
458
+ |---|---|---|
459
+ | `list-style-type` | `circle` sur **9** items visibles | **0** |
460
+ | `padding-inline-start` | **40px** sur 3 des 7 grilles de la page | **0** |
461
+
462
+ ⚠️ L'indentation de 40px **n'était pas dans le signalement** — trouvée en
463
+ mesurant. C'est la moitié invisible du même défaut. Et les autres pages du site
464
+ ne montraient rien parce qu'elles remettaient `list-style: none` **à la main,
465
+ page par page** : exactement le signal que le manque était dans le DS.
466
+
467
+ ⛔ `:where()` n'est pas décoratif, il met la spécificité à **zéro**. Une règle
468
+ scopée ordinaire vaudrait (0,2,0) et **confisquerait le canal `padding`**, les
469
+ classes utilitaires émises par `usePadding` valant (0,1,0) — c'est le défaut de
470
+ #950, corrigé sur 21 composants dans la 2.18.14 juste avant. Le réintroduire
471
+ dans la version suivante aurait été absurde.
472
+
473
+ Vérifié sur la CSS **réellement servie**, pas sur l'intention :
474
+ `:where(.origam-grid[data-v-711d86d8]) { list-style: none; padding-inline-start: 0 }`.
475
+ La règle bat l'origine navigateur et perd contre tout ce qu'un consommateur écrit.
476
+
477
+ ⚠️ Note de méthode conservée parce qu'elle a failli produire un faux défaut : une
478
+ première sonde ajoutant `.origam--p-6` à la grille rendue lisait `0px` et
479
+ suggérait une confiscation. Faux — un `div` neuf avec la même classe reçoit bien
480
+ 24px, et l'énumération de règles utilisée renvoyait **0 règle au total**, les
481
+ feuilles cross-origin étant avalées par un `try/catch`. L'artefact était dans la
482
+ mesure, pas dans le code.
483
+
484
+ ### Fixed — #955 : le menu du site lit enfin son canal de thème (et le retrait seul aurait tout cassé)
485
+
486
+ `padding: 4px` en dur sur `.appbar-menu .origam-menu__content` écrasait
487
+ `--origam-menu__content---padding`, que sept thèmes déclarent : quatre en
488
+ demandaient 6px et obtenaient 4px.
489
+
490
+ ⛔ Mais le simple **retrait** du littéral — ce que le ticket prescrivait — ne rend
491
+ pas la main au thème. Mesure, 8 identités × 2 modes : le panneau tombe à **0px
492
+ partout**. La régression a failli être livrée.
493
+
494
+ | | résultat |
495
+ |---|---|
496
+ | avant | 4px partout — 4 thèmes sur 7 ignorés |
497
+ | retrait seul | **0px partout** — régression |
498
+ | après | **16/16 conformes**, zéro débordement |
499
+
500
+ La cause, trouvée en énumérant les règles qui matchent l'élément : **aucune** ne
501
+ pose de padding sur `.origam-menu__content`. Le DS applique
502
+ `var(--origam-menu__content---padding, 4px)` sur `.origam-menu__list`, un élément
503
+ rendu **uniquement dans le repli du slot par défaut**. Dès qu'un consommateur
504
+ fournit son contenu — ce que fait toute la navigation du site — l'élément
505
+ n'existe pas et le panneau ne reçoit aucun padding du DS. **Le token porte donc
506
+ le nom d'un élément et peint sur un autre, qui n'est presque jamais rendu.**
507
+
508
+ Le vrai défaut reste côté DS et n'est pas corrigé ici : le corriger changerait la
509
+ géométrie de tous les consommateurs d'`origam-menu`, ce qui n'est pas une PR
510
+ marketing. Consigné dans le commentaire, à côté du code.
511
+
512
+ ### Fixed — wireframe pricing : les CTA alignés en bas quelle que soit la longueur du contenu
513
+
514
+ Deux causes distinctes, et la seconde ne s'est vue **qu'en regardant le rendu**.
515
+
516
+ 1. La chaîne d'étirement était rompue au milieu. La grille étire bien les trois
517
+ cartes à la même hauteur (184px chacune), mais `.origam-card__content` est en
518
+ `display: block` et ne remplit pas la carte (159px dans une carte de 184) : le
519
+ `block-size: 100%` de l'inner résolvait donc sur 159. Corrigé en chaînant
520
+ `flex: 1` + `min-block-size: 0` sur toute la profondeur, au lieu de demander
521
+ « 100% » à un parent qui ne fait pas 100%.
522
+ 2. ⛔ Le premier correctif a produit des **chiffres parfaits et un rendu faux**.
523
+ Écart entre les bas de boutons : 0. Et pourtant les CTA des cartes courtes
524
+ faisaient **51px** de haut contre **28px** pour celui du milieu — ils
525
+ s'étiraient. Cause : `origam-btn` avec la prop `block` pose `flex-grow: 1`,
526
+ donc en colonne flex le bouton absorbe l'espace libre.
527
+
528
+ ⚠️ Deux manques du DS notés au passage, non corrigés : une carte à hauteur
529
+ imposée dont le contenu ne s'étire pas, et `block` qui implique `flex-grow: 1` —
530
+ un comportement qui surprendra quiconque pose un `origam-btn block` dans une
531
+ colonne flex.
532
+
533
+
534
+ ## [2.18.14] - 2026-09-26
535
+
536
+ Un seul correctif, sur deux props que **21 composants** ignoraient en silence
537
+ depuis toujours — la classe était émise, et rien ne bougeait.
538
+
539
+ ### Fixed — #950 : `:where(&)` sur les défauts `padding` / `margin`, 21 composants, 93 déclarations
540
+
541
+ La forme d'échelle de `padding` / `margin` (`padding="6"`) n'appliquait **rien**
542
+ sur 21 composants : la classe utilitaire était bien émise, et le longhand calculé
543
+ ne bougeait pas d'un pixel.
544
+
545
+ **La cause n'est pas celle que le ticket nommait d'abord.** La spécificité et
546
+ « longhand > raccourci » sont vraies mais ne discriminent rien — elles valent
547
+ pour toutes les familles d'utilitaires. Le filtre décisif est l'absence de
548
+ *companion inline* sur le chemin tokenisé : `usePadding` / `useMargin` sautent le
549
+ rung inline, quand `rounded`, `elevation`, `border`, `bg` et `fg` en émettent un,
550
+ et l'inline bat toujours une règle scopée. Ces deux familles n'avaient donc plus
551
+ aucun canal capable de gagner la cascade.
552
+
553
+ **93 déclarations** passent sous `:where(&)`, soit 31 couples (composant × prop)
554
+ sur **21 composants**. Les deux chiffres sont vérifiés sur le diff : 21 fichiers
555
+ de composant touchés, et 85 déclarations *longhand* plus 8 *raccourcis* = 93,
556
+ avec autant de lignes retirées qu'ajoutées — un déplacement pur, ce qui est
557
+ cohérent avec l'absence de changement de rendu.
558
+
559
+ Vérifié via `@vue/compiler-sfc` : le compilateur scopé insère l'attribut **à
560
+ l'intérieur** de la pseudo-classe, `:where(.origam-sheet[data-v-abc123])` =
561
+ (0,0,0).
562
+
563
+ ⛔ **Seuls les défauts descendent.** Les 495 déclarations modificatrices ou d'état
564
+ (`:hover` à (0,3,0), `--variant-flat` à (0,2,0), `+ .origam-row` à (0,3,0)) sur
565
+ 154 composants gardent leur poids — c'est précisément ce qui a fait écarter
566
+ `@layer`, et trois contrôles au navigateur le confirment (`--compact` 8px,
567
+ `--inset` 16px, `--variant-minimal` 0px peignent toujours). La réserve de la
568
+ décision — « une règle interne comptant sur l'ordre source peut changer de
569
+ gagnant » — est levée et vérifiée au compilateur : aucun gagnant ne change nulle
570
+ part.
571
+
572
+ ⚠️ **Trois composants restent morts, sciemment** : Btn, Chip et Stepper. Chez eux
573
+ un modificateur **toujours présent** (`--size-default`, `--density-default`)
574
+ déclare la propriété à (0,2,0) ; il bat l'utilitaire quoi qu'on fasse au défaut,
575
+ et Chip n'a même aucune déclaration racine à abaisser. Les ressusciter exigerait
576
+ d'abaisser une règle modificatrice, ce que la décision interdit. Mais la prop
577
+ n'est pas morte pour autant — seule sa forme d'**échelle en chaîne** l'est :
578
+ `:padding="6"` (nombre), `padding="6px 8px"` (custom) et les formes
579
+ directionnelles peignent.
580
+
581
+ Partiels et nommés dans le code : Blockquote (`--variant-default` garde
582
+ `padding-inline-start`) et Row (la gouttière `+ .origam-row` garde
583
+ `margin-block-start`).
584
+
585
+ Épinglé par `utility-cascade-padding-margin-950.spec.ts` — 34 tests, dont 3 de
586
+ non-régression qui vérifient qu'un modificateur continue de battre l'utilitaire.
587
+ A/B fait : **34 failed à `HEAD~1`, 34 passed à `HEAD`**.
588
+
589
+ Au passage, story Sheet/Design : `style="padding: 16px"` remplacé par
590
+ `padding: '4'`. Ce hatch rendait le contrôle « Padding » de la variante
591
+ définitivement inerte — `OrigamSheet` reverse son binding `:style` dans une règle
592
+ générée portant l'**id** de l'élément, soit (1,0,0), qui battait l'utilitaire, la
593
+ règle scopée, et le retrait de l'attribut `style` lui-même.
594
+
595
+
596
+ ## [2.18.13] - 2026-09-26
597
+
598
+ Deux correctifs, dont la cause racine du journal de 5,5 Go de #853. Dans les deux
599
+ cas la **mesure a corrigé le périmètre du ticket** — en le réduisant de 15 à 12
600
+ composants pour l'un, en l'étendant d'une surface à quatre pour l'autre.
601
+
602
+ ### Fixed — #916 : `inheritAttrs` sur 12 racines fragment/teleport — pas 15, et 4 auraient été cassés
603
+
604
+ Vue ne peut pas fusionner les attributs de *fallthrough* sur une racine qui n'est
605
+ ni élément ni composant — fragment, texte, ou `<teleport>`. En dev il logue alors
606
+ « Extraneous non-props attributes », et la trace de ce warning **sérialise les
607
+ props de chaque ancêtre**, dont le `vnode` de `RouteProvider` : **~4,4 Mo par
608
+ occurrence**. C'est la cause racine de #853, le journal de 5,5 Go qui finissait en
609
+ worker mort et 500 sur toutes les routes.
610
+
611
+ ⛔ **Le ticket annonçait 15 composants. Il y en avait 12, et 4 de ses entrées
612
+ étaient fausses** — `OrigamChart`, `OrigamNumberField`, `OrigamSliderField`,
613
+ `OrigamSwitch`, mesurés à **0 avertissement**. Les « réparer » aurait fabriqué le
614
+ défaut de #492 quatre fois : sur une racine à élément unique,
615
+ `inheritAttrs: false` avale **en silence** tous les attributs du consommateur.
616
+
617
+ La différence vient de la méthode, et c'est l'enseignement du lot. Le relevé du
618
+ ticket comptait les **balises de profondeur 0** ; ici la question est posée au
619
+ **compilateur Vue** (forme du `codegenNode` racine), puis confirmée au runtime en
620
+ montant chaque composant avec un attribut non déclaré et un écouteur non émis :
621
+
622
+ | méthode | verdict |
623
+ |---|---|
624
+ | comptage à profondeur 0 | 15 candidats, **4 faux, 2 manques** |
625
+ | `codegenNode` du compilateur | **12**, tous reproduits au runtime |
626
+
627
+ Une paire `<template v-if>` / `<template v-else>` compte pour deux nœuds et en
628
+ rend **un** : le fallthrough marche, et il n'y a pas de défaut.
629
+
630
+ Effet mesuré : **34 avertissements → 0**, sur 27 montages, chacun dans la
631
+ configuration qui **atteint** sa branche fragment — un montage par défaut aurait
632
+ donné un faux vert sur les 4 racines mixtes.
633
+
634
+ ⚠️ Aucun changement d'API. Les attributs atterrissent exactement où ils
635
+ atterrissaient : table identique ligne pour ligne sur les 8 branches, et chacun
636
+ des 7 `v-bind` retiré un par un produit un rouge, donc chacun est porteur. Où ils
637
+ vont a été décidé composant par composant — forward explicite sur la racine
638
+ visible (5), drapeau seul là où le forward existait déjà (2), et **nulle part,
639
+ délibérément**, pour les 5 dont toutes les branches sont des fragments.
640
+
641
+ ⚠️ Non touché volontairement : le `<tr>` `--loading` de `DataTableRows`. Il
642
+ ressemble à son jumeau `--no-data` mais n'est **pas** une racine — le compilateur
643
+ donne `COND{ FRAGMENT | COND{ element(tr) | FRAGMENT } }` — donc y lier `$attrs`
644
+ aurait introduit un comportement nouveau sous couvert d'un correctif de warning.
645
+ L'asymétrie est préexistante et désormais mesurée.
646
+
647
+ Une nouvelle garde ferme la récidive, parce que **le symptôme est un fichier de
648
+ journal, pas un test rouge** : aucune autre vérification du dépôt ne le voit, et
649
+ #853 a vécu des mois. Ses 26 fixtures couvrent le rappel (les 12 formes réelles)
650
+ et la précision — les 4 faux positifs sont des **témoins négatifs**, les poser en
651
+ violation enverrait quelqu'un « réparer » quatre composants sains. Un contrôle de
652
+ **cécité** exige qu'un template que le compilateur refuse nomme sa raison au lieu
653
+ de se lire « propre ». Sa portée est **statique** et le dit : elle prouve que
654
+ l'option est déclarée, jamais que les attributs atteignent encore le bon élément —
655
+ c'est la moitié dangereuse, et elle vit dans le spec TU.
656
+
657
+ ⚠️ `v-bind="{ ...$attrs }"` et non `v-bind="$attrs"` sur les 5 forwards ajoutés,
658
+ et ce n'est pas cosmétique : la garde `id-forwarding` crédite un `v-bind="$attrs"`
659
+ littéral comme preuve que la prop `id` atteint le DOM. L'inférence est fausse dès
660
+ que `id` est une prop **déclarée**, puisque Vue la retire de `$attrs`. Avec la
661
+ forme nue, la garde annonçait `SnackbarGroup:no-id-reach` comme « déjà corrigée »
662
+ et demandait sa suppression, sur un composant où l'`id` du consommateur n'atteint
663
+ toujours aucun nœud — supprimer cette entrée aurait masqué un défaut vivant.
664
+
665
+ Un test de #550 est renversé en l'assumant : il affirmait que « Vue **avertit** au
666
+ lieu d'avaler » et présentait cet avertissement comme « tout le gain ». Le
667
+ raisonnement tenait tant qu'on ignorait son coût. Il assert désormais les **deux**
668
+ moitiés — plus d'avertissement (#916) **et** toujours pas de classe sur ces lignes
669
+ (#550, inchangé).
670
+
671
+ ### Fixed — #924 : sous `cartoon` clair, l'anneau de focus était invisible sur toutes ses surfaces claires
672
+
673
+ `cartoon` clair déclarait `border.focus: '#ff8fa3'` — le rose de
674
+ `action.primary.bg`, qui ne se détache d'**aucune** de ses surfaces claires. Le
675
+ ticket en nommait une ; la mesure en trouve **quatre**, toutes sous le seuil de
676
+ 3:1 de WCAG 2.1 SC 1.4.11 :
677
+
678
+ | surface | contraste |
679
+ |---|---|
680
+ | page | 2,07:1 |
681
+ | `surface.raised` | 2,16:1 |
682
+ | `overlay` / `sunken` | 1,96:1 |
683
+ | `feedback.danger.bgSubtle` | **1,53:1** |
684
+
685
+ Le token est **global** : 38 des 96 familles de composants peignent leur anneau
686
+ avec — 10 le lisent directement, 28 y accèdent via Btn / Field /
687
+ SelectionControl. **L'identité entière était inutilisable au clavier.**
688
+
689
+ Correctif : `border.focus` → `#c0174a`, qui est `action.primary.fgSubtle` de
690
+ cette même palette — la réponse que cartoon donne déjà à « le rose, à une valeur
691
+ qui se lit sur un fond clair ». **Valeur dérivée, pas choisie à l'œil.** L'anneau
692
+ passe à **4,30–6,06:1** sur ces quatre surfaces, dans la bande des 7 autres
693
+ identités (3,82–12,56).
694
+
695
+ La couche retenue est la **sémantique** (`vars.color.border.focus`) et non les
696
+ props : vérifié, aucun composant du DS ne déclare de prop exprimant la couleur de
697
+ l'anneau — elle n'est lue que comme `var(--origam-color__border---focus)` depuis
698
+ le SCSS. Les props ne peuvent pas l'exprimer ; les `cssVars` seraient un
699
+ contournement d'un tier qui nomme déjà la valeur.
700
+
701
+ ⚠️ Le bloc **sombre** de cartoon garde `#ff8fa3` **à dessein** : mesuré conforme
702
+ (6,09–8,05:1). Corriger les deux par symétrie est l'erreur de #829.
703
+
704
+ Un anneau se mesure contre **ce qu'il entoure** : `outline-offset` est positif sur
705
+ Btn et SelectionControl, donc la couleur adjacente est le fond du **conteneur** —
706
+ une carte, un menu, une alerte — pas « le fond de la page ». L'outil est élargi en
707
+ conséquence, de un composant sur un fond à **8 identités × 2 modes × 6 fonds
708
+ sémantiques × 5 familles focusables**, soit 448 lignes. A/B du spec, vrai `$?`
709
+ hors pipe : sans le correctif **1 échec / 17 verts**, l'échec étant
710
+ `cartoon/light … = 2.07:1` ; avec, **18/18**.
711
+
712
+ ### Tooling
713
+
714
+ - **La garde 30 balayait le disque, pas l'index git** (#966), donc
715
+ `packages/marketing/public/stories/` — 35 Mo de bundle Histoire, **zéro fichier
716
+ suivi par git**. La pollution allait dans les **deux sens**, ce qui rend le
717
+ symptôme dangereux : le bundle *déclare* des `--origam-*`, donc il entrait aussi
718
+ dans l'ensemble émetteur et des lectures réellement mortes apparaissaient
719
+ corrigées. Une seule déclaration dans un faux fichier de trois lignes faisait
720
+ passer **8 entrées de baseline** en « déjà corrigée » : un mainteneur suivant le
721
+ message de la garde aurait supprimé 8 lignes décrivant de **vrais défauts**.
722
+ L'énumération passe par `git ls-files --cached --others --exclude-standard` — la
723
+ source de vérité connaît déjà les artefacts, là où une liste de noms ne peut pas
724
+ contenir le nom du **prochain** artefact. ⛔ La variante minimale du ticket
725
+ (exclure `public/`) aurait été fausse : ce répertoire porte 25 fichiers suivis.
726
+ - **`guards:self` ne découvrait qu'un niveau sur trois** (#964) : cinq self-tests
727
+ vivent au niveau `guards/` et n'étaient invoqués par **rien** — ni le runner, ni
728
+ la CI, ni un script npm. Leurs seules « références » étaient les lignes
729
+ `Run: node …` de leurs propres en-têtes, dont celui de `token-var-channels`, la
730
+ garde la plus sollicitée du dépôt. **Lancés à froid avant tout câblage, les cinq
731
+ passent** : aucune garde muette en production. Un **sixième orphelin** que le
732
+ ticket n'avait pas recensé a été trouvé par le contrôle anti-récidive
733
+ lui-même. Compteur **17/17 → 24/24**, et la récidive est fermée par un balayage
734
+ qui fait échouer le runner **avant exécution** sur tout `*.selftest.mjs` que la
735
+ découverte raterait.
736
+ - **#902 est invalidé par la mesure, dans l'autre sens.** Le ticket annonçait
737
+ 5 faux positifs de la garde 29 ; la sonde montre qu'elle ne les **signale** pas,
738
+ elle les **rate** — aucun n'est dans la baseline. Et ils doivent continuer à
739
+ être ratés, car les 5 sont **porteurs** : le `!important` protège la barre de
740
+ transport contre un thème posant `density: 'comfortable'` sur chaque bouton, et
741
+ les 4 `calc(24px / 2)` sont **dérivés** et non dupliqués — l'égalité avec `12px`
742
+ n'est vraie qu'à l'échelon comfortable, et retirer les lignes casserait
743
+ `gutter="none"`. La correction prescrite par le ticket, appliquée telle
744
+ qu'écrite, produit **2 FAIL** sur les deux fixtures étiquetées « faux positif ».
745
+ Les deux exclusions deviennent **explicites et testées** au lieu d'être
746
+ accidentelles — aujourd'hui `'0px !important' !== '0px'` sauve la garde par
747
+ hasard, et un accident n'est pas une décision.
748
+
749
+ ### Documentation
750
+
751
+ - Le commentaire de `_shared.css` justifiait un override **vivant** par une
752
+ prémisse fausse (#961). ⚠️ Et le ticket se trompait aussi : il affirmait que
753
+ `--origam-radius---btn` n'était « déclaré nulle part », conclusion tirée d'un
754
+ grep sur `light.css` seul. Il est déclaré dans `_shared.css`. **Une seule des
755
+ deux prémisses était fausse, pas deux** ; le code reste, le commentaire dit
756
+ désormais la vraie raison.
757
+
758
+
759
+ ## [2.18.12] - 2026-09-25
760
+
761
+ Un seul des 19 commits touche `packages/ds/src`, donc le **code publié** : le
762
+ reste est marketing, outillage et documentation. Deux autres touchent bien
763
+ `packages/ds/` mais sous `scripts/`, que le tarball n'embarque pas. Les
764
+ compteurs de gardes cités dans les corps de commit de ce lot divergent (29/29
765
+ puis 30/30) : la garde 30 est **arrivée au milieu du lot**.
766
+
767
+ ### Fixed — #957 : `OrigamChip` n'ouvrait aucune gouttière pour `prepend` / `append`
768
+
769
+ `__close` possédait ses deux canaux de marge depuis toujours ; `__prepend` et
770
+ `__append` n'en avaient **aucun**. Une puce avec icône rendait donc son icône
771
+ collée au texte — mesuré **0 px**, `margin-inline-end: 0px`, `gap: normal`, sur
772
+ les 8 identités — et **aucun thème ne pouvait corriger l'espacement puisque le
773
+ canal n'avait jamais été ouvert**.
774
+
775
+ La valeur par défaut, 6 px, n'est pas un nouveau chiffre de design : c'est celle
776
+ de `--origam-chip__close---margin-inline-start`, le précédent du composant
777
+ lui-même.
778
+
779
+ ⛔ La gouttière est **conditionnelle au libellé non vide**, et c'est le cœur du
780
+ correctif. `__content` n'a pas de `v-if` : une puce icône-seule rend quand même
781
+ un `<div class="…__content">` portant le nœud texte vide que Vue émet pour
782
+ `{{ text }}`. Une marge inconditionnelle y accrocherait un **fantôme de 6 px**.
783
+ Mesuré en Chromium, ce qui fait de `:empty` le bon discriminant :
784
+
785
+ | contenu de `__content` | `:empty` | gouttière |
786
+ |---|---|---|
787
+ | `"texte"` | false | 6px |
788
+ | `<div></div>` | true | 0px |
789
+ | nœud texte vide (le cas Vue) | true | **0px** |
790
+ | `" "` (une espace) | false | 6px |
791
+
792
+ `__prepend` dépend de son frère **suivant**, d'où `:has(+ …)` ; `__append` de son
793
+ frère **précédent**, d'où un simple `+`. Les deux gardent la déclaration sur
794
+ l'élément que le token **nomme**. Là où `:has()` manque, la gouttière prepend est
795
+ simplement absente — le rendu d'aujourd'hui, pas une régression.
796
+
797
+ ⚠️ **Effet visuel assumé** : toute puce portant un libellé **et** un
798
+ `prepend-icon` / `append-icon` s'élargit de 6 px (mesuré 105 → 111 px). Les puces
799
+ icône-seule et sans affixe ne bougent pas.
800
+
801
+ ⚠️ Le ticket — et le corps du commit après lui — chiffrent l'exposition à
802
+ **« 217 emplacements »**. Ce nombre n'a pas pu être reproduit : huit méthodes de
803
+ comptage sur l'arbre au tag `v2.18.12` donnent 145, 233, 241, 272, 373 ou 413
804
+ selon qu'on compte les lignes ou les occurrences et selon le périmètre de
805
+ fichiers, jamais 217. L'ordre de grandeur — plusieurs centaines de sites d'appel
806
+ — est juste ; le chiffre exact reste **non établi**, et il n'entre dans aucune
807
+ décision du correctif.
808
+
809
+ Les deux nouveaux noms sont déclarés dans `light.css` / `dark.css` (bloc
810
+ explicite **et** bloc `prefers-color-scheme`) et leurs jumeaux SCSS, et ajoutés à
811
+ `tokens.type.ts`. Aucun changement d'API — ni prop, ni emit, ni slot.
812
+
813
+ ### Fixed — #958 : 151 lectures de deux tokens qui n'existent pas
814
+
815
+ Le site lisait `--origam-font-family---mono` (87×) et `--origam-font-size---base`
816
+ (64×) — **151 lectures dans 28 fichiers**, sur deux noms qu'aucune feuille ne
817
+ déclare. La grammaire du dépôt sépare le bloc BEM par un **double tiret bas** ; le
818
+ marketing écrivait un tiret simple. Chaque lecture portait un repli littéral, donc
819
+ **rien ne cassait à l'écran** : le repli peignait toujours, et le canal de thème
820
+ était mort sans qu'aucun test, aucun lint, aucune console ne le dise.
821
+
822
+ ⛔ **L'échelon retenu n'est pas celui que le ticket proposait, et c'est une mesure
823
+ qui l'a décidé.** Le ticket suggérait `--origam-font__size---md`. Or
824
+ `primitive.css` pose `md: 0.875rem`, tandis que le repli effectivement rendu par
825
+ les 64 lectures était `1rem`, c'est-à-dire l'échelon `lg`. Substituer `md` aurait
826
+ **rétréci le texte de 12,5 %** dans 27 fichiers en croyant faire un renommage.
827
+
828
+ 151 lignes ajoutées, 151 retirées, toutes des déclarations CSS dans un bloc
829
+ `<style>` : aucun `.ts`, aucun template, aucune clé i18n touchés.
830
+
831
+ **La vraie demande du ticket était la garde.** `token-var-channels` pose exactement
832
+ la bonne question depuis #435, mais ne balayait que `packages/ds/`. Le site
833
+ marketing, plus gros consommateur de tokens du dépôt et sa vitrine, était regardé
834
+ par **personne**. La nouvelle garde délègue son **verdict** à l'analyse existante
835
+ — aucun doublon de la logique de décision — et seule la **collecte** est neuve,
836
+ parce que trois différences interdisaient d'élargir simplement le périmètre :
837
+ l'ensemble émetteur est **double** (le marketing déclare légitimement ses propres
838
+ `--origam-…`), la portée de ces déclarations est **globale** et non locale au
839
+ fichier, et les consommateurs ne sont pas que des `.vue`.
840
+
841
+ La direction « dormant » n'est **pas** reprise, délibérément : « ce token du DS
842
+ n'est lu par personne » est un défaut dans une bibliothèque publiée et le cas
843
+ **normal** chez un consommateur. Reporter les ~2 900 autres serait du bruit, et
844
+ une garde bruyante se fait désactiver.
845
+
846
+ ⚠️ **L'extension révèle 50 autres noms morts**, laissés en baseline et **non
847
+ corrigés** — recensement remis pour arbitrage plutôt que corrigé en silence :
848
+ 841 lectures mortes / 52 noms distincts / 292 paires, dont **44 sans aucun
849
+ repli** — la déclaration entière est alors jetée par le navigateur, donc rendu
850
+ réellement cassé et pas seulement canal mort.
851
+
852
+ ### Fixed — #922 : les résidus du pipeline supprimé que la première passe avait laissés
853
+
854
+ Complément à une première correction qui avait traité les 4 valeurs de locale
855
+ nommées par le ticket mais laissé trois résidus. Les appels `t(clé, fallback)`
856
+ portaient encore le texte d'origine mot pour mot — invisibles à l'écran
857
+ puisque la clé existe, mais lisibles dans un dépôt public, **ce qui est
858
+ précisément l'argument du ticket**.
859
+
860
+ Un **chiffre gravé dans une locale** est retiré : `218 components` figurait dans
861
+ deux valeurs. Recompté à 218 le jour même, mais un nombre dans une locale se
862
+ périme tout seul — reformulé sans chiffre, sur la propriété vérifiée qui porte
863
+ vraiment l'argument (« no per-component wiring », ADR-005).
864
+
865
+ Deux commentaires citaient des chemins **supprimés le 2026-08-31**. Pour l'un,
866
+ l'affirmation restait **vraie** et seule sa source était morte : les 10 barreaux
867
+ ont été re-vérifiés un à un contre `primitive.css`, tous identiques, commentaire
868
+ repointé.
869
+
870
+ ⚠️ La ligne « Pipeline » du détail de composant est **masquée, pas corrigée** —
871
+ décision du propriétaire en attendant #960. La donnée vit **en base** : 129
872
+ entrées, dont **125 vendent encore** « Built with Style Dictionary v4 ». Rendue,
873
+ cette ligne contredisait le paragraphe juste au-dessus, sur **chaque** composant
874
+ du catalogue. Restaurer la ligne fait partie de #960, et le commentaire laissé
875
+ dans le template le dit.
876
+
877
+ Non touché volontairement, et c'est ce qui rend le lot honnête : les entrées de
878
+ changelog des versions qui ont **réellement** livré avec Style Dictionary, et la
879
+ note sur la conformité DTCG de PrimeVue, qui est vraie.
880
+
881
+ ### Fixed — #951, #944, #946, #294 : quatre défauts de menu mesurés sur les 8 identités
882
+
883
+ - **`/why-origam` lisait `---fg` comme une teinte.** Trois déclarations peignaient
884
+ une surface **neutre** avec `--origam-color__feedback--{success,warning}---fg`.
885
+ `fg` n'est pas la teinte de l'intention : c'est le premier plan posé **sur** son
886
+ aplat, et hors de cet aplat il rend un neutre. Avant, `fg` ne rendait une teinte
887
+ **nulle part** sur les 16 configurations ; 9 sur 16 passaient sous 1,6:1, et
888
+ `glass` sombre à **1,00** — exactement la couleur de son fond. Après : **0
889
+ configuration sous 3:1**, les 16 entre 4,38:1 et 13,22:1. Le point 3 du ticket
890
+ (la nomenclature invite à l'erreur, une garde vaudrait mieux qu'un quatrième
891
+ correctif) **n'est pas traité** et #951 reste ouvert dessus.
892
+ - **Le menu déroulant portait la forme de tiroir M3** — 28px de rayon sur un
893
+ conteneur et des lignes de 48px de haut, donc une pilule. Vérifié **dans les
894
+ sources de tokens Material**, pas de mémoire : un conteneur de menu déroulant
895
+ est `corner-extra-small` = 4px, un item de liste `corner-none` = 0, et
896
+ `corner-extra-large` = 28px est la forme d'une destination de **tiroir**.
897
+ ⛔ Au passage, un fait qui circulait est corrigé : **il n'y a pas de rognage**.
898
+ La ligne ne déborde jamais de son conteneur (`scrollWidth === clientWidth`), et
899
+ forcer `overflow: visible` ne change pas un pixel — deux captures du coin à
900
+ `deviceScaleFactor` 8 sont identiques. Deux arcs de **même rayon** laissaient un
901
+ croissant très fin qui se lit comme une coupe à l'échelle 1×.
902
+ - **Conteneurs de `glass` et `cartoon` alignés sur leur rung `sm`**, et la valeur
903
+ **vient du thème** et non de l'œil : ces deux identités déclarent `sm`, `md`,
904
+ `lg` mais **pas** `xs`, donc `rounded: 'xs'` retomberait sur le primitif DS à
905
+ 2px, une valeur qu'aucune des deux n'a choisie. Après : **0 vignette au-delà de
906
+ 18px contre 6 avant**.
907
+ - **Le dropdown d'`origam-select` ne suivait pas**, et le mécanisme est correct :
908
+ `rounded` sur `origam-menu` est un **défaut de composant** que trois surfaces
909
+ héritent, tandis que `menuProps` est un binding **explicite** qui bat le défaut
910
+ de thème. Canal séparé, donc correction séparée — il fallait régler la valeur là
911
+ où elle est écrite, pas retirer le canal. Recensement complet fait pour ne pas y
912
+ revenir une troisième fois : **quatre** thèmes portent un `menuProps`, dont deux
913
+ déjà alignés qui servent de **témoins négatifs**. Le **champ** garde
914
+ `rounded: 'lg'` : c'est un contrôle de formulaire, pas un menu.
915
+ - **La taille du texte des menus suit enfin celle de l'activateur.** Les items
916
+ rendaient 16px quand le lien qui les ouvre en rend 13px, sur **7 identités sur
917
+ 8**. `geek` était la seule alignée, et pour une raison qui n'avait rien à voir :
918
+ une règle posée pour sa police monospace fixait aussi `font-size: 0.8125rem`.
919
+ L'exception cachée devient la règle générale, portée par **une seule**
920
+ déclaration que l'activateur et les items lisent tous deux. ⛔ Déclarée sur
921
+ `:root` et non sur la barre : le contenu d'`origam-menu` est **téléporté** hors
922
+ d'elle, une variable posée sur la barre ne l'atteindrait jamais.
923
+ - **La liste du menu `material` avait 8px de `padding-inline` en trop.** ⛔ Corrigé
924
+ par la **prop**, pas la cssVar, et ce n'est pas un goût : sur un
925
+ `origam-list nav`, `OrigamList` **re-déclare** le token sur l'élément lui-même
926
+ depuis sa règle scopée, et une cssVar de thème posée à la racine est **héritée**
927
+ — elle ne peut pas battre une déclaration locale.
928
+
929
+ ### Changed — /roadmap refondue sur wireframe validé, et 8 affirmations fausses corrigées
930
+
931
+ Page ramenée de **11 892 à 6 868 px** en desktop 1440 (**−42,2 %**) et de 20 058 à
932
+ 11 279 px en mobile 390 (**−43,8 %**), mesuré sur la même machine et un serveur
933
+ isolé. La section des phases, qui pesait 4 203 px à elle seule, tombe à 1 326 px
934
+ (**−68,5 %**) **sans qu'aucun item soit coupé ni raccourci** : les 38 chantiers
935
+ gardent titre et description complets, seulement repliés.
936
+
937
+ Chaque chiffre de la page a été **re-mesuré, jamais recopié** : npm 2.18.8 →
938
+ **2.18.11**, tests 7 187/563 → **7 192/564**, specs e2e 254 → **256**, monorepo
939
+ « 6 paquets » → **5**. La barrière CI était annoncée par deux textes qui **se
940
+ contredisaient** — « 81 de 254 » d'un côté, « 58 de 229 » de l'autre, tous deux
941
+ faux ; un seul survit, **81 de 256**, compté en interrogeant Playwright lui-même
942
+ plutôt qu'en recomptant les littéraux de la liste blanche. Un composant annoncé
943
+ sous le nom `OrigamSound` **n'a jamais été un export** : la page décrivait un
944
+ import qui échouerait.
945
+
946
+ ⛔ Et la page annonçait « CHANGELOG à jour ». **Faux**, passé à `done: false` — le
947
+ document s'arrêtait alors à 2.18.8 quand npm servait 2.18.11. C'est le même trou
948
+ que la présente entrée comble, et il avait déjà été identifié ici.
949
+
950
+ La section « livré » annonçait « 15 components & features » au-dessus de 15 tuiles
951
+ écrites à la main : un lecteur en déduisait que le DS ne livre que 15 composants,
952
+ alors que **tout** le catalogue est publié. Elle rend désormais le catalogue
953
+ complet **lu en direct** sur les mêmes endpoints que `/components`. ⛔ **Aucun
954
+ compte n'est gravé**, nulle part — ni titre, ni fallback, ni commentaire présenté
955
+ comme un fait : la taille du catalogue **dépend de l'environnement**, mesuré à
956
+ 218 entrées en local contre **194** sur l'env déployé. Un littéral corrigé aurait
957
+ donc déjà été faux sur l'un des deux.
958
+
959
+ Cinq constats **internes** que la page publiait sont retirés, dont « la CI ne
960
+ barre que 81 des 256 specs » et « pas encore de communauté » — la comptabilité
961
+ interne d'un dépôt n'a pas à être publiée, et l'un des cinq était déjà devenu
962
+ faux.
963
+
964
+ ⚠️ Trois défauts du DS trouvés **en implémentant**, signalés et non corrigés là :
965
+ `<origam-sheet padding="6">` est une **prop morte** (cause de #950, corrigé en
966
+ 2.18.14), `feedback--success---fg` vaut `#000000` sous `geek`, et les liens
967
+ d'ancrage atterrissaient **sous** l'app bar fixe.
968
+
969
+ ### Tooling
970
+
971
+ - `marketing-primary-nav.spec.ts` existait de longue date **sans être exécuté par
972
+ aucun job** : il était inscrit dans la baseline `spec-coverage` comme non
973
+ exécuté, donc la garde restait verte et rien ne le signalait. Avant de le
974
+ brancher, la stabilité du **fichier entier** a été mesurée et pas seulement
975
+ celle des cas ajoutés — brancher un fichier qui contient des cas instables
976
+ rendrait la CI rouge en permanence, ce qui est **pire** que pas de garde :
977
+ 110/110 en `--repeat-each=5 --retries=0`, machine au repos, serveur isolé.
978
+ - Quatre captures d'écran committées par accident à la racine sont retirées. Elles
979
+ étaient arrivées dans un commit qui n'avait rien à voir, balayées par un
980
+ `git add`, et **déjà supprimées du disque sans que la suppression soit
981
+ enregistrée** — l'arbre restait donc sale et un `git add -A` distrait les aurait
982
+ re-committées. `/*.png` rejoint `.gitignore` : une image ne se relit pas dans
983
+ une revue, elle passe.
984
+
985
+ ### Documentation
986
+
987
+ - La règle « pas de tag pour un lot qui ne touche pas `packages/ds/` » est
988
+ consignée, avec sa raison **mécanique** : `release.yml` publie depuis
989
+ `packages/ds/` exclusivement, donc un lot hors de ce dossier produit un tarball
990
+ **byte-identique**. ⚠️ Le paragraphe disait auparavant l'**inverse** — « when in
991
+ doubt, tag: a redundant patch costs nothing ». C'était faux : un patch redondant
992
+ coûte un numéro de version qui ne veut rien dire, sur un registre où une version
993
+ publiée ne se retire jamais.
994
+ - Deux pièges de mesure ajoutés, tous deux payés sur le terrain : **le serveur
995
+ Nuxt ne recharge pas les thèmes** (imports de `nuxt.config.ts`, hors du graphe
996
+ HMR), donc une remesure après édition rend les **anciennes** valeurs et
997
+ ressemble à une prop morte ; et **une sonde qui clique en aveugle** dans la barre
998
+ d'actions tombe sur la bascule de mode et mesure le mode **opposé** — chiffres
999
+ inversés, cohérents entre eux, parfaitement crédibles.
1000
+
1001
+
21
1002
  ## [2.18.11] - 2026-09-24
22
1003
 
23
1004
  ### Fixed — #935 : l'anneau `focus-visible` du Switch débordait de la piste, peint sur un frère non clippé