fold-ng 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,239 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.13.0] - 2026-08-23
12
+
13
+ ### Changed
14
+
15
+ - **BREAKING — `brand` / `on-brand` entrent au catalogue (48 rôles).** La
16
+ marque IDENTIFIE, donc contrairement à `primary` elle ne doit pas basculer de
17
+ polarité : une marque teal sur la page et bleu pâle sur le rail a cessé
18
+ d'être la marque. `primary` faisait deux métiers contradictoires — porter la
19
+ marque ET les affordances de premier plan, dont la seconde doit s'éclaircir
20
+ sur un chrome sombre. Un sous-bloc scopé ne peut **jamais** les redéclarer, et
21
+ un test le vérifie.
22
+
23
+ - **BREAKING — `fold-data-table` : `narrowLayout` / `cardsAt` /
24
+ `rowCardChrome`, et `mobileLayout` déprécié** (retiré avant la 1.0). La
25
+ bascule se décide sur la largeur du **conteneur** (600px par défaut), pas sur
26
+ la fenêtre : une table dans un panneau de 480px sur un écran de 1920 restait
27
+ une table illisible. `custom` était une redondance — projeter un
28
+ `foldRowCard` disait déjà tout.
29
+
30
+ - **BREAKING — `surface-band` entre au catalogue (46 rôles).** Une bande ne
31
+ monte pas, elle **s'écarte**, et la direction dépend de la polarité du thème.
32
+ `fold-card` la codait en dur (`surface-hover`, et `surface-card` en sunken) —
33
+ une hypothèse de polarité posée dans un composant. Sur navi la bande valait
34
+ la couleur de la page sur une carte blanche (1,09:1) ; sur le chrome navi
35
+ elle valait **exactement** le corps de la carte (1,00:1). Les quatre thèmes
36
+ non modifiés reprennent la valeur qu'ils avaient déjà : le rendu ne bouge
37
+ pas, seul le rôle devient explicite. Un thème maison doit le déclarer.
38
+
39
+ - **Les bordures de navi descendent d'un cran** (`paper-300` / `paper-200`).
40
+ Sur un fond clair la profondeur vient du **contour**, pas du remplissage : il
41
+ reste moins de 10% de luminance entre le blanc et une page claire, et trois
42
+ niveaux n'y tiennent pas. `surface-sunken` ne bouge pas.
43
+
44
+ - **BREAKING — `justify` sépare la répartition de la densité.** `size` se
45
+ documentait « pure padding/typography » et décidait en douce du modèle de
46
+ répartition : `compact` justifiait les onglets, `comfortable` les calait à
47
+ gauche. Une barre `compact` épouse maintenant son contenu ;
48
+ `justify="stretch"` restaure l'ancien comportement.
49
+
50
+ - **BREAKING — `FoldNavLayoutContext` gagne `barCollapsed`.** Le contexte ne
51
+ parlait que dans un sens : la barre savait tout du layout, le layout rien de
52
+ la barre. `collapsed` appartient à la barre et la largeur de piste au layout,
53
+ et le JSDoc disait d'aller poser `--fold-nav-layout-rail-width` à la main, à
54
+ chaque usage. Une implémentation maison du token doit exposer `barCollapsed`.
55
+
56
+ - **BREAKING — quatre rôles s'ajoutent au catalogue de couleurs** :
57
+ `on-info` · `on-warning` · `on-alert` · `on-success` (41 → 45). Un thème
58
+ maison doit les déclarer, sinon le test de parité échoue.
59
+
60
+ Ils ferment un **P0** indépendant de navi : `emphasis="solid"` +
61
+ `intent="warning"` peignait `on-primary` — du blanc — sur de l'ambre, à
62
+ **2,4:1 sur umbra et 3,3:1 sur les quatre autres**. Même défaut sur la bulle
63
+ compteur de `fold-nav-tile`. La combinaison est exprimable depuis que
64
+ emphasis et intent sont deux axes indépendants ; rien ne la testait, parce
65
+ que le catalogue n'avait pas d'encre pour un remplissage de statut.
66
+
67
+ - **BREAKING — les primitives `--fold-ref-navy-*` (9) et `--fold-ref-ivory-50`
68
+ sont supprimées**, remplacées par les familles `graphite` / `paper` /
69
+ `signal` / `navyink`. Une app qui référençait une primitive navi directement
70
+ doit migrer.
71
+
72
+ - **BREAKING — `--fold-shadow-*` peut être redéclarée par un thème.**
73
+ L'invariant disait « seul le rayon varie » et rangeait l'ombre avec les
74
+ mesures. La vraie ligne : un thème peut changer ce à quoi une surface
75
+ **ressemble**, jamais où elle **se trouve**. Une ombre ne déplace pas une
76
+ boîte. Un thème maison qui héritait des ombres de base les garde.
77
+
78
+ - **navi 2 — « Graphite & Signal ».** navi avait deux polarités et un seul jeu
79
+ de rôles : son sous-bloc chrome en déclarait **17 sur 45** et héritait les 28
80
+ autres de la page. Le givre blanc de la page posé sur le rail sombre rendait
81
+ une infobulle à **1,9:1**. Huit échecs WCAG mesurés sont fermés ; `card`,
82
+ `sunken` et `page` valaient blanc, blanc et ivoire — une table imbriquée dans
83
+ une carte n'avait aucun bord — et font maintenant trois marches distinctes.
84
+ Le rayon passe de 1/2/3/4px à 2/4/6/8px : à 1px un coin est un artefact de
85
+ rendu, pas une intention.
86
+
87
+ - **BREAKING — `--fold-text-md` vaut 13px ; le corps de texte s'appelle
88
+ `--fold-text-base`.** Un consommateur non migré rétrécit d'un cran **en
89
+ silence** : c'est un rechercher/remplacer, `--fold-text-md` →
90
+ `--fold-text-base`, à faire avant de monter de version.
91
+
92
+ - **Le bouton `lg` cesse d'être un `md` déguisé.** Les deux préréglages
93
+ nommaient littéralement la même taille — l'échelle n'avait aucune marche
94
+ entre le corps et 20px à saisir. `lg` rend maintenant en `--fold-text-lg`.
95
+
96
+ ### Added
97
+
98
+ - **Un système typographique, au contrat.** La typo était la moitié manquante
99
+ du design system : cinq tailles, et graisse, interligne, interlettrage et
100
+ famille écrits en dur composant par composant — 74, 25, 22 déclarations,
101
+ pour 4, 9 et 11 valeurs distinctes. Personne n'avait décidé qu'il y aurait
102
+ onze interlettrages ; ils se sont accumulés.
103
+
104
+ Quatre échelles nouvelles, toutes dans le test de contrat au même titre que
105
+ la couleur, donc une marche ne peut plus naître ou disparaître sans que le
106
+ catalogue le dise :
107
+
108
+ - `--fold-font-sans` · `--fold-font-mono` — deux fontes, pas de `display` :
109
+ rien n'en rend une. Elles restent **hors de l'axe de thème** : changer de
110
+ fonte re-flow une page, et seul le radius peut varier par thème.
111
+ - `--fold-text-2xs … 2xl` — l'échelle s'étend vers le bas et vers
112
+ l'intérieur (10 · 11 · 12 · 13 · 14 · 16 · 20 · 24), là où le code avait
113
+ des littéraux. Elle s'arrête à 24px parce que rien, ni dans la librairie
114
+ ni dans la galerie, ne rend plus gros.
115
+ - `--fold-weight-regular|medium|semibold|bold|extrabold`
116
+ - `--fold-leading-none|tight|snug|normal|relaxed`
117
+ - `--fold-tracking-tightest|tighter|tight|normal|wide|caps`
118
+
119
+ `extrabold` et `tightest` viennent de la galerie : c'est elle, pas la
120
+ librairie, qui rend des titres hero — 800 sur treize sites, -0.04em sur
121
+ cinq, toujours ensemble avec une taille fluide. Un registre d'affichage
122
+ cohérent que personne n'avait nommé.
123
+
124
+ - **Un rôle sémantique que rien ne peint est un rôle mort.** Le contrat
125
+ vérifiait l'orphelinat dans un seul sens — une primitive que rien ne pointe
126
+ échoue depuis longtemps, mais un rôle que les cinq thèmes déclarent et
127
+ qu'aucun composant ne peint ne regardait personne. Sur 45 rôles, il y en
128
+ avait exactement un.
129
+
130
+ - **`depth-contrast.spec.ts`** — 4,5:1 entre deux **surfaces** ne veut rien
131
+ dire : personne ne lit un contour. Deux planchers, par le travail que fait la
132
+ séparation — **structure** (contour de carte, cadre de panneau) ≥ 1,25:1 et
133
+ **subdivision** (bande, séparateur) ≥ 1,15:1. `lumen` est exempté avec sa
134
+ raison, et sa valeur épinglée plutôt que passée sous silence.
135
+
136
+ - **`chrome-contrast.spec.ts`** — douze paires de contraste, chacune un échec
137
+ mesuré avant la refonte, plus deux invariants structurels. Les alphas sont
138
+ compositées sur le fond qu'elles rencontrent vraiment : mesurer un
139
+ `color-mix(…, transparent)` contre rien, c'est comment une surface
140
+ translucide passe un test qu'elle devrait échouer. Rejoué sur l'ancien
141
+ navi : 10 échecs sur 14.
142
+
143
+ - **`status-ink-contrast.spec.ts`** — chaque encre de statut sur son propre
144
+ fond, à 4,5:1, sur les cinq thèmes. Mesurer plutôt que décréter a payé :
145
+ `bubbly` type `info` en azure et non en violet, donc le blanc y tombe à
146
+ 2,8:1 et il prend l'encre sombre là où les quatre autres prennent le blanc.
147
+
148
+ - **Un sous-bloc de thème scopé doit être CLOS.** Le test de parité
149
+ dédupliquait les sélecteurs `[data-theme]` et ne gardait que le bloc de
150
+ tête — le sous-bloc chrome de navi n'était vérifié par rien, et son
151
+ commentaire l'assumait. Une famille se redéclare désormais entière : prendre
152
+ `primary` sans `on-primary`, c'est poser sur le nouveau remplissage l'encre
153
+ prévue pour l'autre polarité.
154
+
155
+ - **`pnpm run lint:typography`** — jumeau de `lint:spacing`, branché en
156
+ pre-push et dans les deux workflows. Aucun `font-size`, `font-weight`,
157
+ `line-height` ou `letter-spacing` littéral sous `src/components`. Porte dure
158
+ d'emblée : le solde est à zéro, **galerie comprise** — elle en portait 276
159
+ à elle seule, plus que la librairie entière — et le garde-fou lit aussi les
160
+ styles inline des templates.
161
+
162
+ Trois exceptions, chacune pour une raison : les huit tailles qui restent des
163
+ littéraux sont de la **géométrie de composant**, pas du texte (les initiales
164
+ d'un avatar suivent son diamètre) ; `clamp()` est du **texte fluide**, une
165
+ expression qui balaie l'échelle plutôt qu'une marche dessus ; et un
166
+ commentaire n'est pas du code.
167
+
168
+ Les indirections Sass sont **suivies, pas crues** : `_field-box.scss` route
169
+ ses tailles par une map, donc `font-size: map.get($s, font)` ne porte aucun
170
+ littéral — il vit à l'entrée de map, qu'aucun motif `font-size:` n'aurait
171
+ jamais vue. Chaque `map.get(…, clé)` et chaque `$variable` est résolu contre
172
+ les liaisons du fichier et vérifié là-bas, et une indirection qui ne résout
173
+ nulle part est elle-même un signalement : une valeur qu'on ne peut pas
174
+ suivre, c'est exactement le trou.
175
+
176
+ ### Fixed
177
+
178
+ - **Un clic hors tuile ferme le `fold-nav-launcher`.** Le scrim portait bien
179
+ `(click)="close()"` et ne pouvait jamais se déclencher : `.nl-dialog` est
180
+ `position: fixed; inset: 0` par-dessus lui. Le test qui le couvrait cliquait
181
+ le scrim directement et passait au vert depuis toujours — jsdom n'a pas de
182
+ mise en page, il n'a jamais vu le recouvrement.
183
+
184
+ - **Le mode cartes de `fold-data-table` rend une vraie liste.** `auto-cards`
185
+ était une réécriture CSS du tableau (`display: block` sur le `<tr>`, `flex`
186
+ sur les cellules) — ce qui **retire le rôle implicite** des éléments de
187
+ tableau : la table cessait d'être une table sans devenir une liste, et le
188
+ `<thead>` restait en en-têtes orphelins. Une `<ul>/<li>` maintenant, avec une
189
+ coquille commune (contour, ton, sélection) et un gabarit par défaut en `<dl>`.
190
+
191
+ - **Le zébrage se voit sur navi** — 1,07 → 1,15:1. Et les diviseurs de ligne
192
+ disparaissent dès que le zébrage est actif : la rayure porte déjà la
193
+ séparation.
194
+
195
+ - **Une barre d'onglets repliée défile, et son libellé actif n'est plus rogné.**
196
+ Le repli et le défilement s'excluaient : passé une douzaine d'items, le
197
+ premier écrasé était l'item actif, le seul qui garde son libellé. L'infobulle
198
+ d'un item icône passe dans le **top layer** (`popover`), donc elle échappe au
199
+ débordement du scroller sans qu'il faille lui réserver de la place.
200
+
201
+ - **La typographie de la barre répond à `size` seul.** L'orientation pilotait
202
+ taille ET graisse, donc franchir `foldAt` avec `direction="auto"` recomposait
203
+ les libellés au lieu de déplacer la barre.
204
+
205
+ - **`background="surface"` + `activeStyle="fill"` : la pilule active n'est plus
206
+ rognée** contre l'arête basse du bandeau.
207
+
208
+ - **Le gap d'un `fold-nav-layout` suit sa propre largeur, pas la fenêtre.** Il
209
+ tenait sur `@media (max-width: 640px)` pendant que le pli tenait sur la
210
+ largeur du conteneur : un layout étroit sur grand écran gardait 16px alors
211
+ qu'il était replié.
212
+
213
+ - **Le header et les rails du `fold-app-shell` peignent enfin leur fond.**
214
+ `--fold-color-bg-header` était déclaré par les cinq thèmes et consommé par
215
+ **zéro composant** : le header rendait transparent sur le `:host` du shell,
216
+ qui peint `bg-page` — résolu **hors** de la région chrome. Sous navi, du
217
+ texte clair de chrome sur un fond paper ; idem pour toute bande de rail que
218
+ le `fold-menu` ne recouvre pas. Antérieur à navi 2 — invisible tant que la
219
+ page et le chrome partageaient la même polarité.
220
+
221
+ - **`--fold-font-mono` existe.** Neuf déclarations de la galerie le lisaient,
222
+ aucune ne le déclarait : il ne rendait que par son fallback. Quatorze autres
223
+ recopiaient la pile à la main.
224
+
225
+ ## [0.12.1] - 2026-08-22
226
+
227
+ ### Fixed
228
+
229
+ - **A panel body no longer crushes a child that clips its own overflow.**
230
+ `fold-panel-body` laid its children out as a flex column, where every child
231
+ may shrink and a child whose `overflow` is not `visible` has an automatic
232
+ minimum size of **zero**. `fold-danger-zone` clips its overflow to keep its
233
+ dividers flush with its rounded corners, so in a full panel it was measured
234
+ at **2px** — its two borders — with the destructive action, its heading and
235
+ its explanation all erased. The box reported barely any overflow, so there
236
+ was nothing to scroll to either: the panel looked finished and was not.
237
+
238
+ The body is now a single-column grid with `grid-auto-rows: max-content`:
239
+ rows are sized by their content and the box overflows, which is what a
240
+ scrolling column is for. Every child is protected, not only the ones we
241
+ thought of. `fold-danger-zone` also asks for its content-based minimum back
242
+ explicitly, so it survives a hand-rolled flex column too.
243
+
11
244
  ## [0.12.0] - 2026-08-22
12
245
 
13
246
  ### Added
@@ -1616,7 +1849,9 @@ design-token stylesheet.
1616
1849
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
1617
1850
  strings localise via inputs / providers (`provideFoldPanelLabels`).
1618
1851
 
1619
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.12.0...HEAD
1852
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.13.0...HEAD
1853
+ [0.13.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.13.0
1854
+ [0.12.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.12.1
1620
1855
  [0.12.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.12.0
1621
1856
  [0.11.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.11.1
1622
1857
  [0.11.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.11.0
package/README.md CHANGED
@@ -99,11 +99,14 @@ is the umbra or lumen block with its primitive families re-pointed. Adding a
99
99
  sixth is a new `[data-theme]` block in `semantic.css` plus the primitives it
100
100
  names.
101
101
 
102
- `bubbly` and `navi` also change their corners **radius is the one scale a theme may
103
- re-declare**, because corner softness is a brand axis (friendly vs
104
- institutional) and it is the only scale that changes nothing about where a box
105
- sits. Type, space, motion and elevation stay theme-invariant: retheming must
106
- never re-flow a page. The contract test enforces that split.
102
+ `bubbly` and `navi` also change their corners, and `navi` its depth **radius
103
+ and elevation are the two scales a theme may re-declare**. The line is not that
104
+ one scale is special: a theme may change what a surface **looks like**, never
105
+ where it **sits**. Corner softness is a brand axis (friendly vs institutional);
106
+ depth is calibrated against a ground, and a shadow tuned for near-black ink is
107
+ a grey smear on a light page. Neither moves a box by one pixel. Type, space and
108
+ motion stay theme-invariant — those re-flow or re-time a page. The contract
109
+ test enforces that split.
107
110
 
108
111
  `navi` is the interesting one: mixing chrome and page means one theme needs the
109
112
  `text`/`border`/`surface` roles to differ **per region**, which a single set of