@cyrilld/zestds 0.1.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/README.md ADDED
@@ -0,0 +1,578 @@
1
+ # Zest Design System
2
+
3
+ A React 18 component library, published as a single npm package: `@cyrilld/zestds`. All
4
+ components are exported from the package root:
5
+
6
+ ```tsx
7
+ import { Home01 } from "@cyrilld/zestds";
8
+ import "@cyrilld/zestds/dist/index.css";
9
+
10
+ <Home01 width={20} height={20} />
11
+ ```
12
+
13
+ Today the package publishes **the tokens and the 121 icons**. Interface components are phase 2 —
14
+ see "Ce qui reste à faire".
15
+
16
+ **Both imports matter.** The code and the styles travel separately — `dist/index.js` on one
17
+ side, `dist/index.css` on the other. Importing the first without the second gives components
18
+ that render, structurally, with no background, no spacing and no radius. Nothing fails and
19
+ nothing warns.
20
+
21
+ Its Storybook is the design system's documentation, written in French. Seven Foundations pages
22
+ are written — colors, typography, spacing, radius, shadows, the focus ring, icons — and the rest
23
+ of the plan sits alongside them as **announced but empty pages**: the remaining foundations, then
24
+ Base components and Application components.
25
+
26
+ That isn't decoration. A sidebar that only shows what exists gives the illusion of a complete
27
+ system: someone looking for "Badges" and not finding it can't tell whether it doesn't exist, is
28
+ named something else, or lives elsewhere. Showing the rest says where we are, on every visit.
29
+
30
+ ```sh
31
+ yarn storybook
32
+ ```
33
+
34
+ ## Structure
35
+
36
+ ```
37
+ src/
38
+ styles/ theme.css (the tokens), fonts.css, typography.css, base.css
39
+ fonts/ the six Averta .woff2 files — commercial license, see fonts/README.md
40
+ components/
41
+ base/ interface components — empty, phase 2
42
+ foundations/ the icons: catalogue.ts generated from Figma, index.tsx built on it
43
+ docs/ the Storybook documentation — MDX pages and their boards. NOT shipped.
44
+ Introduction.mdx the Storybook landing page
45
+ index.ts the barrel: imports the four sheets, re-exports everything else
46
+ css.d.ts ambient module declaration for CSS imports
47
+ .storybook/ Storybook config, discovers stories and MDX under src/
48
+ tsup.config.ts build config (ESM + CJS + .d.ts + CSS)
49
+ ```
50
+
51
+ The layout mirrors the `zest-design-system` monorepo, so the two can be read side by side.
52
+
53
+ `src/docs/` never ships: `src/index.ts` doesn't import it, so `tsup` never sees it. Its classes
54
+ are all prefixed `zds-doc-`, which keeps them out of what the library exposes — after a change
55
+ to the barrel, `grep -c 'zds-doc-' dist/index.css` must print `0`.
56
+
57
+ Adding a new component means adding a `src/components/base/<Name>/` folder (component, styles,
58
+ tests, stories, local `index.ts`), then re-exporting it from the root `src/index.ts`.
59
+
60
+ ## What the package publishes
61
+
62
+ **Variables, not utility classes.** There is no CSS engine here. A component writes
63
+ `color: var(--zds-color-text-primary)`.
64
+
65
+ Colors are layered, and a component only ever cites the last two:
66
+
67
+ | Layer | Example | Cited by a component? |
68
+ | --- | --- | --- |
69
+ | Ramps | `--zds-color-brand-600` | Never |
70
+ | Utility palettes | `--zds-color-utility-lime-500` | To categorize |
71
+ | Roles | `--zds-color-text-primary` | Yes |
72
+ | Component | `--zds-color-toggle-border` | Last resort |
73
+
74
+ Beyond colors: the type scale (thirteen sizes, each with its line height), the named spacing
75
+ scale, thirteen widths, eleven radii, seven shadows plus the relief, the focus ring, and the
76
+ eight brand gradients in three angles each.
77
+
78
+ ### Three entry points, not one
79
+
80
+ | Import | What | Weight |
81
+ | --- | --- | --- |
82
+ | `@cyrilld/zestds` | tokens, 121 icons, 27 file icons | 197 KB |
83
+ | `@cyrilld/zestds/flags` | 234 country flags | 328 KB |
84
+ | `@cyrilld/zestds/cursors` | 50 cursors | 94 KB |
85
+
86
+ ⚠️ **The two catalogues are deliberately NOT re-exported from the root.** A catalogue is a data
87
+ object queried by key at runtime — that is the whole point of `<FlagIcon code={country} />` — so
88
+ no bundler can prove an entry is unused and nothing gets tree-shaken away. A single `export *`
89
+ added to `src/index.ts` for convenience would take the root from 197 KB to nearly 600 KB without
90
+ breaking anything visible: typecheck passes, build passes, the package silently triples.
91
+
92
+ Flags need no CSS at all — their colours are national facts, not tokens. Cursors need
93
+ `dist/index.css` for exactly one of the fifty.
94
+
95
+ ## Getting started
96
+
97
+ ```sh
98
+ yarn install
99
+ ```
100
+
101
+ ## Using it locally with `yarn link`
102
+
103
+ To try local changes in another project before publishing a new version:
104
+
105
+ 1. In this repo, build once (the `prepare` script also does this on `yarn install`), then
106
+ keep rebuilding on every change:
107
+
108
+ ```sh
109
+ yarn build # one-off build
110
+ yarn dev # or: rebuild dist/ on every save
111
+ ```
112
+
113
+ 2. Register this package globally and link it from the consumer project:
114
+
115
+ ```sh
116
+ # in this repo
117
+ yarn link
118
+
119
+ # in the consumer project
120
+ yarn link "@cyrilld/zestds"
121
+ ```
122
+
123
+ 3. `react` and `react-dom` are peer dependencies, not bundled — if the linked package
124
+ resolves its own copy of React instead of the consumer's, you'll hit "Invalid hook
125
+ call" errors from two React instances existing side by side. Point this repo's React
126
+ back at the consumer's copy to dedupe them:
127
+
128
+ ```sh
129
+ # in the consumer project
130
+ cd node_modules/react && yarn link && cd -
131
+ cd node_modules/react-dom && yarn link && cd -
132
+
133
+ # in this repo
134
+ yarn link react react-dom
135
+ ```
136
+
137
+ 4. Leave `yarn dev` running in this repo while you work — it watches `src/` and rebuilds
138
+ `dist/`, which the linked consumer picks up on its own rebuild/HMR.
139
+
140
+ 5. When you're done testing:
141
+
142
+ ```sh
143
+ # consumer project
144
+ yarn unlink "@cyrilld/zestds"
145
+ yarn install --force # restore the regular installed copy
146
+
147
+ # this repo
148
+ yarn unlink
149
+ ```
150
+
151
+ ## Scripts
152
+
153
+ | Command | Description |
154
+ | ------------------------ | ---------------------------------------------------------|
155
+ | `yarn storybook` | Launch the documentation locally at http://localhost:6006 |
156
+ | `yarn build-storybook` | Build a static Storybook site to `storybook-static/` |
157
+ | `yarn test` | Run all tests once (Vitest + Testing Library) — see Testing |
158
+ | `yarn test:watch` | Run tests in watch mode |
159
+ | `yarn typecheck` | Type-check the package |
160
+ | `yarn lint` | Lint the package |
161
+ | `yarn build` | Build to `dist/` (ESM, CJS, types, CSS) |
162
+ | `yarn verify:render` | Drive Edge over the pages: fonts, boards, red flags, console errors |
163
+ | `yarn verify:labels` | Contraste des étiquettes d'icône de fichier, sans navigateur |
164
+
165
+ `verify:render` needs a dev server running and `storybook-static/index.json` present (it reads
166
+ the story ids from there rather than guessing them). Point it elsewhere with
167
+ `--port`, and add `--capture page.png` for a screenshot.
168
+
169
+ ⚠️ **It refuses to conclude rather than report a false green.** It carries a decoy token and
170
+ stops if the decoy doesn't trip; it stops if Storybook answers "Couldn't find story"; and it
171
+ skips anything with `offsetParent === null`. Both failure paths are tested — breaking
172
+ `--zds-color-brand-600` on purpose makes it abort, and so does a bad story id.
173
+
174
+ ## Versioning & releases
175
+
176
+ The package has a single semver version (`package.json`). To cut a release:
177
+
178
+ ```sh
179
+ npm version patch # or minor / major
180
+ git push --follow-tags
181
+ npm publish
182
+ ```
183
+
184
+ `npm version` bumps `package.json`, commits the change, and creates a matching `v<version>`
185
+ git tag automatically (e.g. `v0.2.0`) — `--follow-tags` pushes both the commit and that tag.
186
+ `prepublishOnly` runs `yarn build` automatically before `npm publish`.
187
+
188
+ ## Testing
189
+
190
+ ⚠️ **There is no test suite right now**, because there is no component to test: removing `Button`
191
+ emptied it. `yarn test` runs with `--passWithNoTests` so it stays green — a red `yarn test` that
192
+ only means "nothing to run" trains people to ignore red. **Drop the flag with the first phase-2
193
+ component.**
194
+
195
+ When components arrive, they are tested with [Vitest](https://vitest.dev) and
196
+ [React Testing Library](https://testing-library.com/react), driving them the way a user would
197
+ (queries by role/text, `@testing-library/user-event` for interactions) rather than testing
198
+ implementation details.
199
+
200
+ One suite is worth writing before that, and it has nothing to do with components: the icon
201
+ catalogue is **generated**, and its invariants are exactly what a bad regeneration breaks — every
202
+ name unique, every component in `index.tsx` resolving to an entry, every `ZEST_ICONS_EN_CONFLIT`
203
+ name also present in `ZEST_ICONS`. Three separate hand-counts of that catalogue came out wrong
204
+ during the port.
205
+
206
+ A green build says nothing about the rendering. Two defects on this project were invisible to
207
+ both `tsc` and `storybook build`, and only a real browser found them:
208
+
209
+ - an **infinite render loop** — an effect depending on an array rebuilt at every render. React
210
+ cut it off with `Maximum update depth exceeded`, and the page displayed anyway, so nothing
211
+ looked wrong;
212
+ - a **probe that concluded on a page it had never loaded**, because the story id was wrong. It
213
+ reported "0 boards" for a page Storybook was refusing to open. An id with an accent is
214
+ URL-encoded: `Foundations/Icônes` is `foundations-ic%C3%B4nes--docs`. Read the ids from
215
+ `storybook-static/index.json` rather than guessing them.
216
+
217
+ ## Ce qui reste à faire
218
+
219
+ - [x] **Phase 1a** — les couleurs : les couches de jetons, et une page de documentation qui lit
220
+ ses valeurs dans le navigateur au lieu de les recopier
221
+ - [x] **Phase 1a bis** — la vraie palette Zest depuis le Figma *❖ Design System V2.0* : six
222
+ rampes, le vert de marque, les huit dégradés, la table de correspondance des paliers
223
+ - [x] **Phase 1b** — typographie, espacements, rayons, ombres, anneau de focus
224
+ - ⚠️ reste ouvert : la collection Figma `Couleurs/Effets/Ombres` n'a pas été lue. Les sept
225
+ ombres portent la géométrie de la référence et des teintes `rgba(0, 0, 0, …)` ; celles de
226
+ Zest sont teintées `#101828` sur deux couches. La **géométrie** est la même de part et
227
+ d'autre, la **teinte** changera.
228
+ - [ ] **Phase 1c** — icônes, file icons, drapeaux, avatars, logo
229
+ Les quatre pages restantes existent déjà dans la barre latérale, annoncées et vides — voir
230
+ `src/docs/a-venir/`.
231
+ - [x] **Icônes** — 121 publiées, les neuf catégories de la page « Icônes » du Figma.
232
+ Navigateur et règles d'usage. Les conflits de nom et les styles à revoir sont sortis de
233
+ la page le 2026-09-08 : voir « Les décisions ouvertes sur les icônes » plus bas.
234
+ - [x] **File icons** — 27 types × 3 habillages, page en ligne. Les dessins viennent
235
+ **du Figma** (`3847:2911`), re-teintés sur les jetons : dix substitutions,
236
+ l'écart mesuré en Lab. ⚠️ Le cadre Figma `3847:2911` est la bibliothèque d'Untitled UI
237
+ collée telle quelle — variables d'origine, collection en anglais, et en retard sur le
238
+ paquet. Il ne décide que la LISTE des 27 types. ⚠️ Les trois règles d'usage des habillages
239
+ (Default / Gray / Solid) sont une **proposition** : ni la page de ressources d'origine ni
240
+ le Figma n'en donnent, elles attendent un arbitrage.
241
+ - [x] **Flag icons** — 234 drapeaux, page en ligne, renommée « Flag icons » à la demande de
242
+ Micka. Générés depuis le Figma (`3853:17761`), pastille ronde, une seule forme. Les
243
+ couleurs ne sont **pas** portées sur les jetons : celles d'un drapeau sont des faits
244
+ nationaux. Les noms français viennent d'`Intl.DisplayNames`, pas d'une liste recopiée.
245
+ ⚠️ Cinq codes du Figma sont faux et corrigés dans le catalogue — voir « En attente ».
246
+ - [x] **Curseurs** — 50 dessins (29 classiques, 21 macOS 26), page en ligne. Entrée nouvelle,
247
+ pas prévue au plan d'origine, demandée le 2026-09-08. Chaque entrée porte le mot-clé CSS
248
+ qu'elle représente, parce que le cas courant n'est PAS de dessiner un curseur.
249
+ - [x] **Avatars** — page en ligne, 282 fichiers rangés en quatre : Défaut, Couleur, Neutre,
250
+ Transparent. Le Figma ne contient que DEUX choses par personne — sa photo avec son fond
251
+ (154) et le même sujet détouré (128) ; Couleur et Neutre sont ce détourage composé sur un
252
+ fond à l'affichage, pas des fichiers. Recherche par nom ou par photographe, et deux actions
253
+ au survol : Copier et Télécharger, qui aplatissent le fond dans un PNG.
254
+ ⚠️ **Aucun composant `Avatar` n'est livré**, et c'est délibéré : le cadre Figma
255
+ `3857:23794` est une BANQUE D'IMAGES, il ne définit ni tailles, ni anneau, ni pastille de
256
+ présence, ni repli. Il est annoncé dans **Base components → Avatars**, avec les trois
257
+ questions à trancher. Les fichiers sont dans `.storybook/public/avatars/`, jamais dans
258
+ `dist/`.
259
+ - [x] **Logo** — page en ligne. `ZestLogo`, `ZestLogomark`, `ZestWordmark`, `ZestAiLogo`,
260
+ livrés depuis la racine (24 Ko). Le blocage est levé : les SVG viennent du Figma
261
+ (`248:1409` pour le logotype, `3218:143` pour Zest AI). Le favicon
262
+ provisoire — un `Z` sur `brand-600` — a été remplacé par le vrai isotype. ⚠️ C'est une
263
+ COPIE du catalogue : Storybook réclame un fichier, pas un composant. Si le logo change,
264
+ régénérer le catalogue **et** recopier le favicon.
265
+ - [ ] **Phase 2** — base components : boutons, badges, champs, sélecteurs, info-bulles
266
+ - ⚠️ **Un `Button` a été retiré le 2026-09-08**, plutôt que corrigé à moitié. Hérité d'avant
267
+ les fondations, il peignait son primaire en `#4f46e5` — un indigo qui n'est pas le bleu de
268
+ Zest — dimensionnait son `sm` à 13 px, taille absente de l'échelle, et ne citait aucun
269
+ jeton : six de ses sept couleurs n'existaient nulle part dans le thème. Un composant faux
270
+ dans un paquet enseigne le faux, et celui-là contredisait les pages qui documentent les
271
+ jetons. Récupérable par `git checkout 69a30fa -- src/Button` si on veut le relire.
272
+ - ⚠️ **Bloquant pour les badges et les boutons pleins** : les deux aplats de statut sous
273
+ l'AA, voir « En attente ».
274
+ - Chaque composant livré retire un morceau du provisoire de `docs.css` — le tableau est
275
+ dans le `CLAUDE.md`.
276
+ - [ ] **Phase 3** — application components : navigation, tableaux, modales
277
+ - [ ] **Phase 4** — application UI examples
278
+
279
+ ## En attente
280
+
281
+ Des décisions qui appartiennent à Micka ou à l'équipe produit, pas au code.
282
+
283
+ - ⚠️ **Les deux aplats de statut ne tiennent pas l'AA — et ils ont maintenant des victimes.**
284
+ `yarn verify:labels` sort en échec sur **dix étiquettes d'icône de fichier** : CSV, XLS et XLSX
285
+ portent du blanc sur `success-600` (3,59:1), PPT et PPTX sur `warning-600` (3,49:1), en Default
286
+ comme en Solid. Ce n'est pas un défaut de code : c'est cette décision-ci, devenue concrète. Le
287
+ contrôle échoue exprès pour qu'elle ne s'oublie pas. `bg-warning-solid` (`#DC6803`) tient
288
+ **3,49:1** et `bg-success-solid` (`#119B57`) **3,59:1** sous du texte blanc, pour un seuil AA
289
+ de 4,5:1. `bg-error-solid` (4,83) et `bg-brand-solid` (4,54) passent, de justesse pour le
290
+ second. Les fonds **doux** vont bien (4,69 à 6,05) : le défaut ne touche que les aplats. Trois
291
+ issues, à choisir un badge et un bouton sous les yeux — descendre les deux aplats sur le palier
292
+ **700** (5,43 et 5,08) ; ne jamais poser de texte blanc dessus, en privilégiant les badges en
293
+ fond doux ; ou assumer, si ces aplats ne portent jamais de libellé.
294
+ - **La ligne de `.prose` fait 103 caractères**, mesurés ici (Averta 16 px sur 720 px, médiane sur
295
+ les lignes pleines de l'article d'exemple). Le confort de lecture se situe entre 45 et 75, et
296
+ 85 est la limite haute usuelle. Les 720 px viennent pourtant du Figma (`paragraph-max-width`)
297
+ et sont en vigueur : la valeur est **sourcée**, elle n'a donc pas été changée unilatéralement.
298
+ Les replis, mesurés eux aussi :
299
+
300
+ | Colonne | Caractères (médiane) | |
301
+ |---|---|---|
302
+ | 720 px — actuel, du Figma | **103** | au-dessus de la limite usuelle |
303
+ | 680 px | 90 | |
304
+ | 640 px | 87 | |
305
+ | 600 px | **79** | dans la fourchette haute du confort |
306
+ | 560 px | 76 | |
307
+
308
+ ⚠️ La livraison de référence annonce 89 pour la même colonne et la même police. L'écart vient
309
+ du **texte** mesuré, pas du réglage : les deux articles d'exemple n'ont ni les mêmes mots ni la
310
+ même densité d'élisions. C'est une raison de plus de ne pas recopier un chiffre d'un dépôt à
311
+ l'autre. À arbitrer, parce que ça touche un jeton du Figma.
312
+ - ⚠️ **Il n'y a pas de composant `Avatar`, et il en faut un.** Le Figma V2.0 n'en contient aucun :
313
+ `3857:23794` est une banque de photos. Trois spécimens existent dans l'ancienne bibliothèque
314
+ *Design System - ZestMeUp* — `Zestie avatar`, `Anonymous avatar`, `Avatars` — dernière mise à
315
+ jour 2024, et ce n'est pas la source déclarée du V2.0. **Faut-il les porter, ou en dessiner un
316
+ dans le V2.0 ?** Rien n'a été codé en attendant : la page montre des règles, pas un composant.
317
+ - **Les avatars à initiales.** La règle Zest les interdit ; la livraison de référence a tranché
318
+ que la règle vaut pour les *produits*, pas pour une bibliothèque — un composant *offre* un
319
+ état, l'écran choisit de le rendre. ⚠️ `quote-avatar.example.tsx` énonce aujourd'hui la règle
320
+ produit comme si c'était la règle de la bibliothèque. La page « Avatars » montre la variante à
321
+ initiales **uniquement pour dire qu'on ne l'emploie pas** ; elle n'existe nulle part ailleurs.
322
+ - **L'échelle de tailles d'avatar** — 24, 32, 40, 48, 64, 96 — est une **proposition**. Le Figma
323
+ n'en donne aucune ; ces paliers viennent de l'échelle d'espacement du thème parce qu'ils
324
+ existent déjà. À arbitrer en même temps que le composant.
325
+ - **L'aire de protection et la taille plancher du logo** sont une **proposition** elles aussi :
326
+ une demi-hauteur d'isotype, et 24 px sous lesquels on passe à l'isotype seul. Le Figma ne dit
327
+ rien des deux. Dessinées dans la page pour être discutées.
328
+ - **Il n'y a pas de logo monochrome.** La variante `sombre` est du blanc plein ; rien ne couvre le
329
+ noir plein, dont une impression une couleur ou une gravure aura besoin.
330
+ - **Le turquoise du dégradé de Zest AI n'a pas de jeton exact.** `#39D7E4` tombe à ΔE 1,8 de
331
+ `--zds-color-utility-turquoise-500` (`#30D2E0`) — invisible à l'œil, mesurable. Écart voulu ou
332
+ dérive ? À vérifier au Figma. Les deux autres couleurs du logo tombent à ΔE 0,0 de leur jeton.
333
+ - **Une étiquette d'icône diverge** : le symbole Figma `refresh-cw-02` est étiqueté
334
+ `refresh-cw-01`. C'est le libellé qui fait foi, donc le catalogue suit — mais c'est peut-être
335
+ une coquille du Figma.
336
+ - **Les cinq conflits de nom et les sept styles « à revoir »** — détaillés plus bas, avec les
337
+ deux tableaux. La correction se fait dans le Figma, puis on régénère.
338
+ - **Les onze autres ombres de Zest**, dans `Couleurs/Effets/Ombres`. Les valeurs existent et
339
+ feront foi.
340
+ - ⚠️ **Cinq codes de drapeau sont faux dans le Figma**, relevés en regardant les dessins et
341
+ corrigés dans le catalogue. `DS` porte le drapeau du **Soudan** (`SD`, absent du jeu, une
342
+ transposition) ; l'un des deux `CD` porte le **Congo-Brazzaville** (`CG`, absent du jeu) ;
343
+ `GB-2` porte l'**Angleterre** (`GB-ENG` en ISO 3166-2). Sans correction, un sélecteur de pays
344
+ n'aurait jamais rendu le Soudan. Chaque entrée garde `codeFigma`, le code d'origine, pour que
345
+ l'écart reste trouvable. **Le Figma reste à corriger** — après quoi il faudra retirer les
346
+ corrections du générateur.
347
+ ⚠️ `BQ` ×3 n'est **pas** une erreur : `BQ` couvre trois îles avec trois drapeaux, distinguées
348
+ en `BQ-BO`, `BQ-SE`, `BQ-SA`. `FLAG_ALIAS` renvoie `BQ` sur Bonaire.
349
+ - ⚠️ **19 codes ISO n'ont pas de drapeau, et cinq sont français** — Guadeloupe, La Réunion,
350
+ Mayotte, Guyane, Saint-Pierre-et-Miquelon. Sur un produit français, c'est le trou qui se
351
+ remarquera en premier. Les 14 autres sont des territoires peu peuplés (Antarctique, Vatican,
352
+ Svalbard, îles diverses). Liste complète dans l'en-tête du catalogue. À relever au Figma.
353
+ - **Deux familles de curseurs de redimensionnement couvrent les mêmes mots-clés CSS**, et
354
+ laquelle employer quand n'est pas tranché. Les huit `Resize single …` (une flèche nue) et les
355
+ quatre `Resize …` (une flèche contre une barre) donnent tous `n-resize`, `e-resize`,
356
+ `s-resize`, `w-resize`. Constat du cadre Figma, pas une règle. La page le dit et ne choisit pas.
357
+
358
+ ## Décidé
359
+
360
+ Le journal des arbitrages. Plusieurs de ces choix paraissent arbitraires et sont le résultat
361
+ d'une mesure ou d'une décision produit — ne pas les « harmoniser » sans lire l'entrée.
362
+
363
+ - **Le logo porte ses couleurs EN DUR, et il est à la racine** (2026-09-08). Elles correspondent
364
+ pourtant exactement à deux jetons — mesuré, `#00D85D` = `--zds-color-mark` et `#21304E` =
365
+ `--zds-color-navy-100`, ΔE 0,0 tous les deux. Les écrire en `var(--zds-…)` serait une faute :
366
+ un jeton peut être redéfini, c'est sa raison d'être, et un logo qui suit le thème n'en est plus
367
+ un. À la racine et non en sous-chemin parce que les cinq dessins font 24 Ko, et qu'un produit
368
+ qui charge le design system affiche presque toujours le logo.
369
+ - **Les 282 fichiers de portraits ne sont pas livrés** (2026-09-08). Ils vivent sous
370
+ `.storybook/public/avatars/`, que `files: ["dist"]` exclut de npm. Ce sont des images de
371
+ MAQUETTE : dans un produit, l'avatar vient du compte de la personne. Le Figma en sert 23,4 Mo
372
+ (du 640×640) ; ramenés à la taille d'affichage ils en pèsent 4,2.
373
+ - ⚠️ **128 détourages ont été détruits, puis réparés** (2026-09-08). Le premier ré-encodage sortait
374
+ TOUT en JPEG, un format sans couche alpha : les 128 photos détourées ont été aplaties sur le noir
375
+ du canevas. Rien n'a échoué — les fichiers étaient valides, la page les affichait, et sur la
376
+ planche de contrôle je les ai lues comme des « fonds studio sombres ». **Un défaut qui produit
377
+ un résultat plausible ne se voit pas.** Il n'a été trouvé qu'en comptant les pixels non opaques
378
+ des sources, en cherchant tout autre chose. Mesuré : 37 à 54 % de transparence par détourage.
379
+ Ils sont maintenant en **WebP** — 2 Mo contre 17,3 en PNG — et le générateur relit chaque fichier
380
+ écrit pour vérifier que l'alpha a survécu. La sortie (copie, téléchargement) reste du PNG : le
381
+ presse-papier n'accepte que lui, et un fichier téléchargé part souvent vers un outil sans WebP.
382
+ - **Les fonds du rangement « Couleur » viennent de la couche utilitaire** (2026-09-08), pas de la
383
+ marque. C'est la couche dont le rôle est de catégoriser ; un fond d'avatar ne dit ni succès, ni
384
+ erreur, ni appartenance. La teinte est tirée du nom, donc stable d'un rendu à l'autre.
385
+ - **Les drapeaux et les curseurs sont derrière leur propre point d'entrée** (2026-09-08). 328 Ko
386
+ et 94 Ko, contre 197 pour tout le reste : dans le tronc commun, ils auraient triplé le paquet
387
+ pour tout le monde, y compris les écrans qui n'en affichent aucun. C'est une modification de la
388
+ **forme publique** du paquet, pas un arbitrage de conception — signalée à Micka avant d'être
389
+ faite. Voir « Three entry points » plus haut, et la garde écrite dans `src/index.ts`.
390
+ - **Les couleurs des drapeaux ne sont PAS portées sur les jetons** (2026-09-08), à l'inverse
391
+ exact de la règle des icônes de type de fichier. Là-bas la couleur catégorise, donc elle doit
392
+ suivre le thème ; ici le bleu du drapeau français est un fait national. Les deux familles se
393
+ ressemblent — un petit dessin polychrome dans une liste — et obéissent à des règles opposées.
394
+ Même raisonnement pour les curseurs : noirs et blancs parce qu'ils doivent rester lisibles sur
395
+ n'importe quel fond, pas par défaut de marque. Deux exceptions mesurées, dans leurs catalogues.
396
+ - **`brand` est le BLEU de Zest, pas son vert** (2026-09-07). Le Figma nomme sa rampe bleue
397
+ « Bleu (Primaire) » et dit qu'elle porte « tous les éléments interactifs » ; sa rampe verte
398
+ s'appelle « Vert (Succès) ». Peindre la marque en vert rendrait un bouton principal et un badge
399
+ « Terminé » indiscernables. Le vert de marque `#00D85D` vit à part, sous `--zds-color-mark`,
400
+ pour le logo et les dégradés — il n'affiche que 1,83:1 sur blanc. ⚠️ **Les quatre prototypes
401
+ Zest ont `brand` en vert** : le design system les contredit volontairement.
402
+ - **`brand-600` vaut le `Bleu/200` du Figma, pas son pivot `Bleu/100`.** Le pivot n'affiche que
403
+ 3,60:1 sous du texte blanc, le 200 tient 4,54:1 — et un bouton plein doit tenir 4,5:1. Le pivot
404
+ atterrit sur le 500, où la couche 3 s'en sert pour `border-brand` et `focus-ring`.
405
+ - **On garde la numérotation 50 → 950**, pas celle de Zest (10 → 600). Un composant transposé
406
+ fonctionne sans être renuméroté. La page « Couleurs » affiche les deux numéros sur chaque
407
+ palier, et c'est le seul endroit où la correspondance est visible.
408
+ - **Le texte de statut est sur le 700, pas le 600** (2026-09-07). Le Figma nomme ces variables
409
+ `text-error-primary (300)`, et Zest/300 est notre 700. Le contraste va dans le même sens : deux
410
+ des trois ne tenaient pas l'AA (3,59 et 3,49), les trois le tiennent maintenant
411
+ (6,57 / 5,08 / 5,43). Les `fg-*` (icônes) et les `bg-*-solid` (aplats) restent sur le 600 :
412
+ l'annotation du Figma ne porte que sur `Couleurs/Texte`, et une icône relève du seuil 3:1.
413
+ - **Les DEUX rampes neutres servent, et c'est la règle** (2026-09-07 : « dans la réalité, on
414
+ utilise les deux »). Ce n'est pas une incohérence à réparer : `navy` porte ce qu'on **lit**,
415
+ `gray` ce **sur quoi** on lit.
416
+
417
+ | | `navy` (Bleu marine) | `gray` (Gris) |
418
+ |---|---|---|
419
+ | Texte | les huit rangs | — |
420
+ | Bordures | les trois neutres | — |
421
+ | Fonds | — | tous |
422
+ | Icônes (`fg-*`) | — | toutes |
423
+
424
+ - **Les trois bordures neutres sont sur `navy`** (2026-09-07). `border-primary = navy-40` est
425
+ donné ; les deux autres sont déduits — `secondary = navy-30`, `tertiary = navy-20`. La
426
+ déduction est **mesurée** : chaque palier du Bleu marine est le jumeau bleuté d'un palier du
427
+ Gris à 4-5 unités par canal, et décaler d'un cran ferait bondir l'écart à 20-22.
428
+ - **Les palettes utilitaires vont jusqu'à 950** (2026-09-07). Elles s'arrêtaient à 700, ce qui
429
+ amputait chaque palette secondaire de ses trois paliers les plus sombres — 21 valeurs
430
+ dessinées, absentes du code. Le Figma annote leur contraste AAA : elles sont prévues pour
431
+ porter du texte. Une seule échelle dans tout le fichier.
432
+ - **Le huitième dégradé s'appelle « Piloter »** (2026-09-07). Le seul des huit dont le nom ne
433
+ vient pas du Figma : ses trois pastilles n'y sont reliées à aucune variable. Il complète la
434
+ famille de verbes du produit — Écouter, Réussir, Partager, Piloter.
435
+ - **Six jetons `_alt` identiques à leur jumeau ont été retirés** (2026-09-07). Ils n'existaient
436
+ que pour qu'un mode sombre puisse les faire diverger ; sans mode sombre, deux noms pour une
437
+ seule couleur. **Trois restent, et ce ne sont pas des doublons** : `border-brand-alt`
438
+ (brand-600 quand `border-brand` est sur le 500), `border-error-subtle`, et surtout
439
+ `border-secondary-alt`, le noir à 10 % — une bordure translucide prend la teinte de ce qu'elle
440
+ recouvre au lieu de la trancher. C'est le seul jeton de couleur sans source dans le tableau de
441
+ la page « Couleurs », qui l'affiche « valeur propre ».
442
+ - **L'échelle typographique porte les noms de Zest** (2026-09-07) : `caption`, `body`, `heading`,
443
+ `display`, parce que le nom dit à quoi une taille **sert** plutôt que sa place dans un
444
+ classement. Treize tailles — les dix du Figma, plus `display-xs`, `display-xl` et
445
+ `display-2xl`, conservées parce qu'un design system a besoin de grands titres même si les
446
+ maquettes actuelles n'en ont pas.
447
+ - **`font-weight-medium` n'existe pas, et n'existera pas** (2026-09-07). Averta n'a pas de
448
+ Medium. Le jeton a existé une demi-journée, aliasé sur 600 ; retiré parce qu'un rang de graisse
449
+ sans dessin propre est un piège. ⚠️ Dans la livraison de référence, le retirer a demandé
450
+ `--font-weight-medium: initial` et pas seulement de cesser de le déclarer, parce que son moteur
451
+ CSS livre le sien à 500. **Ce paquet-ci n'a pas ce problème** : sans moteur, un jeton non
452
+ déclaré n'existe pas — c'est pourquoi l'`initial` n'est pas porté.
453
+ - **Les six faces de la police sont servies**, vérifié par la mesure : les trois italiques ont
454
+ leur propre avance — environ 48 px de moins que le droit sur une même chaîne à 48 px — donc ce
455
+ sont de vrais dessins et non un faux penché par le navigateur.
456
+ - **Pas de `--breakpoint-*` ni de `--animate-*`** (2026-09-08). La bibliothèque de référence en
457
+ déclare, notre thème n'en a aucun, et c'est voulu : aucun composant n'en réclame encore. Ce
458
+ n'est pas un oubli à combler au prochain passage.
459
+ - **Le paquet accepte React 18** ; la livraison de référence déclare `^18.3.1 || ^19.0.0` à la
460
+ demande de l'équipe produit, dont le projet est en `^18.3.1`. Ici les `peerDependencies` sont
461
+ en `^18.0.0`. ⚠️ **Notre `typecheck` ne vérifie donc qu'une version** ; le jour où un composant
462
+ emploiera une API de React 19, rien ne le signalera. À revoir quand la couche de composants
463
+ existera — et la sonde de compilation croisée demande un **témoin** (un fichier important
464
+ `useActionState`, qui n'existe que dans les types de React 19) : sans lui, le premier essai de
465
+ la référence était vert pour la mauvaise raison, le fichier de test étant hors du `include`.
466
+ - **La feuille de style ne garde que ce qu'un dev ne peut pas déduire du code** (2026-09-08).
467
+ Dans la livraison de référence, `theme.css` est passé de 1355 à 630 lignes **sans qu'aucune
468
+ déclaration ne change** — le CSS émis était identique octet pour octet. Ce qui a disparu, c'est
469
+ le récit : les dates, les mesures, les tableaux de contraste, les étiquettes
470
+ `/* Zest Bleu/20 */` répétées ligne à ligne. Tout ça vit dans ce README. **Ne pas re-gonfler
471
+ les commentaires au prochain passage.**
472
+
473
+ ## Findings kept from the port
474
+
475
+ Les constats de fabrication : le pourquoi du code, quand il n'appartient ni à une règle du
476
+ design system ni à un arbitrage.
477
+
478
+ **The Figma numbers ramps 10 → 600, we number 50 → 950.** Zest's pivot is each ramp's 100; ours
479
+ is the 600, because a solid fill has to hold 4.5:1 under white text and the blue pivot only
480
+ reaches 3.60:1. Twelve Zest values for eleven slots: `Zest/10` stays out, being the near-white
481
+ whose absence costs least. The correspondence table lives in `theme.css` **and** on the Colors
482
+ page — the two must stay in agreement.
483
+
484
+ **`fuschia`, not `fuchsia`.** It's the Figma's spelling, confirmed by the product team on
485
+ 2026-09-07. Never "correct" it.
486
+
487
+ **Never infer a value from a name**, even one carrying a number in parentheses. Twice disproved
488
+ on the source files: `text-secondary (700)` is Navy/**70**, and `text-error-primary (300)` is our
489
+ **600**, not our 700. The Figma even carries stale labels in its own swatches. The variable
490
+ paints, the label narrates.
491
+
492
+ **Averta has no Medium, and there will be no `--zds-font-weight-medium`.** The family runs
493
+ Extrathin, Thin, Light, Regular, Semibold, Bold, Extrabold, Black — nothing between Regular and
494
+ Semibold. A weight step with no drawing of its own is a trap: the browser fakes one.
495
+
496
+ **The fallback stack does not contain Inter, on purpose.** A close-looking substitute would mask
497
+ a failed Averta load — the page would look right while being wrong. If Averta doesn't load,
498
+ everything drops to Segoe UI, which is ugly and exactly what we want to see.
499
+
500
+ **Five names in the icon catalogue carry two drawings.** Not duplicates: other drawings, all
501
+ usable, where a filled silhouette meets an outlined stroke. Only one of each is published as a
502
+ component, because a catalogue with duplicate keys has no single source. Seven more icons mix
503
+ solid and hollow and are filed under "trait" by choice, not by measurement. Both lists are shown
504
+ on the Icons page so the decision can be made by looking — the catalogue's own comment promised
505
+ that, and nothing was keeping the promise. **The fix belongs in the Figma, then regenerate.**
506
+
507
+ **`overflow-x: auto` also sets `overflow-y`.** Per spec, when one axis is not `visible` the other
508
+ computes to `auto`. It costs nothing here, but a height constraint added later would clip a
509
+ board's bottom rows with no warning.
510
+
511
+ **Storybook restyles everything inside its docs area, font AND size, and `.sb-unstyled` is the
512
+ way out.** Measured: `.prose` paragraphs rendered at **14px in Nunito Sans** while the sheet asks
513
+ for 16px in Averta — so the Typography page was showing its specimen, its scale and its nine
514
+ examples in neither the font nor the size it documents. Nothing flagged it: `<body>` was in
515
+ Averta and `document.fonts.check("16px Averta")` answered "loaded". **Loaded is not applied.**
516
+
517
+ Two fixes, and they are not interchangeable. `parameters.docs.theme` with `fontBase` sets the
518
+ font of the *chrome* — the guidelines text, which is Storybook's to style and was in an unchosen
519
+ default. `.sb-unstyled`, Storybook's own opt-out class, is what a *board* needs: a board
520
+ demonstrates the design system, so it must render what the design system renders. `Frame` carries
521
+ it; anything dropped straight into an `.mdx` outside a frame has to carry it itself.
522
+
523
+ **A hidden element measures like a broken one.** Three probes on this project concluded on
524
+ something invisible: Storybook's loading skeleton (an argstable stub counted as real rows), its
525
+ "No Preview" screen (two `h1` in the wrong font), and a page that was never loaded at all. Any
526
+ probe should skip `offsetParent === null` and refuse to conclude when Storybook says "Couldn't
527
+ find story".
528
+
529
+ **A number measured in the reference delivery is not a number about this repo.** Its README
530
+ states 89 characters per prose line; the same column and the same font measure **103** here,
531
+ because the example article isn't the same text. Re-measure, don't inherit.
532
+
533
+ ### Les décisions ouvertes sur les icônes
534
+
535
+ Retirées des pages le 2026-09-08 : elles posent une question à trancher, mais le site dit ce qu'il
536
+ faut faire, pas ce qui reste à décider. **Les deux se corrigent dans le Figma, puis on régénère** —
537
+ le catalogue est généré, une retouche locale serait écrasée sans laisser de trace.
538
+
539
+ **Cinq noms portent deux dessins.** Ce ne sont pas des doublons : ce sont d'autres dessins, tous
540
+ utilisables, où une silhouette remplie affronte un tracé au contour. Un seul est publié, l'autre
541
+ vit dans `ZEST_ICONS_EN_CONFLIT`. Le départage actuel n'est pas un choix de dessin — c'est
542
+ l'ordre des catégories du Figma.
543
+
544
+ | Nom | Publié | Écarté | Pourquoi celui-là |
545
+ | --- | --- | --- | --- |
546
+ | `share-01` | contour (Général) | contour (Général) | Général arrive avant |
547
+ | `help-circle-01` | remplissage (Général) | contour (Général) | Général arrive avant |
548
+ | `help-circle-02` | remplissage (Général) | remplissage (Général) | Général arrive avant |
549
+ | `settings-01` | remplissage (Général) | contour (Général) | Général arrive avant |
550
+ | `mail-01` | remplissage (Modules) | contour (Général) | préférence explicite pour Modules |
551
+
552
+ ⚠️ Le commentaire de `ZEST_ICONS_EN_CONFLIT` promet que « la page Icônes les montre à côté de ceux
553
+ qui gagnent ». Ce n'est plus vrai, et ça ne l'a jamais été côté site de référence. **Corriger le
554
+ commentaire à la prochaine génération** — une promesse écrite dans le code que rien ne tient est
555
+ pire que pas de promesse.
556
+
557
+ **Sept icônes ont un style discutable**, rangées en « trait » par choix et non par mesure. Les
558
+ basculer en « plein » les sortirait de l'onglet où on les cherche.
559
+
560
+ | Nom | Ce qui hésite |
561
+ | --- | --- |
562
+ | `feature-action-plan` | le curseur est plein, l'arc est un trait épais |
563
+ | `feature-objective` | des anneaux pleins qui se lisent comme un dessin creux |
564
+ | `feature-idea` | l'ampoule est creuse, son culot est plein |
565
+ | `download-01` | remplie, mais dessinée comme un trait |
566
+ | `upload-01` | remplie, mais dessinée comme un trait |
567
+ | `plus` | une croix n'a ni creux ni plein |
568
+ | `tick` | une coche n'a ni creux ni plein |
569
+
570
+ ### Les ombres livrées ne sont pas celles de la marque
571
+
572
+ Retiré de la page « Ombres » le 2026-09-08, pour la même raison.
573
+
574
+ La seule ombre relevée dans le Figma — `Shadows/shadow-lg` — est teintée `#101828`, un bleu très
575
+ sombre, et elle empile **deux** couches là où la nôtre en met trois. Les sept paliers du thème
576
+ viennent de la feuille de référence, en `rgba(0, 0, 0, …)`. **La géométrie est la même de part et
577
+ d'autre ; la teinte changera** le jour où `Couleurs/Effets/Ombres` sera lu. Un composant écrit
578
+ aujourd'hui n'aura rien à changer — seule la valeur des jetons bougera.
Binary file
Binary file
Binary file