@madojs/mado 0.5.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.
Files changed (162) hide show
  1. package/AGENTS.md +291 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +371 -0
  5. package/ROADMAP.md +52 -0
  6. package/dist/src/component.d.ts +48 -0
  7. package/dist/src/component.js +140 -0
  8. package/dist/src/component.js.map +1 -0
  9. package/dist/src/context.d.ts +40 -0
  10. package/dist/src/context.js +67 -0
  11. package/dist/src/context.js.map +1 -0
  12. package/dist/src/css.d.ts +54 -0
  13. package/dist/src/css.js +137 -0
  14. package/dist/src/css.js.map +1 -0
  15. package/dist/src/devtools.d.ts +22 -0
  16. package/dist/src/devtools.js +63 -0
  17. package/dist/src/devtools.js.map +1 -0
  18. package/dist/src/diagnostics.d.ts +11 -0
  19. package/dist/src/diagnostics.js +28 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/each.d.ts +39 -0
  22. package/dist/src/each.js +35 -0
  23. package/dist/src/each.js.map +1 -0
  24. package/dist/src/forms.d.ts +71 -0
  25. package/dist/src/forms.js +161 -0
  26. package/dist/src/forms.js.map +1 -0
  27. package/dist/src/head.d.ts +19 -0
  28. package/dist/src/head.js +97 -0
  29. package/dist/src/head.js.map +1 -0
  30. package/dist/src/html/bindings.d.ts +78 -0
  31. package/dist/src/html/bindings.js +304 -0
  32. package/dist/src/html/bindings.js.map +1 -0
  33. package/dist/src/html/parser.d.ts +64 -0
  34. package/dist/src/html/parser.js +521 -0
  35. package/dist/src/html/parser.js.map +1 -0
  36. package/dist/src/html/template-types.d.ts +27 -0
  37. package/dist/src/html/template-types.js +8 -0
  38. package/dist/src/html/template-types.js.map +1 -0
  39. package/dist/src/html/template.d.ts +45 -0
  40. package/dist/src/html/template.js +119 -0
  41. package/dist/src/html/template.js.map +1 -0
  42. package/dist/src/html.d.ts +16 -0
  43. package/dist/src/html.js +16 -0
  44. package/dist/src/html.js.map +1 -0
  45. package/dist/src/index.d.ts +35 -0
  46. package/dist/src/index.js +39 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/lazy.d.ts +38 -0
  49. package/dist/src/lazy.js +73 -0
  50. package/dist/src/lazy.js.map +1 -0
  51. package/dist/src/lifecycle.d.ts +45 -0
  52. package/dist/src/lifecycle.js +66 -0
  53. package/dist/src/lifecycle.js.map +1 -0
  54. package/dist/src/page.d.ts +161 -0
  55. package/dist/src/page.js +38 -0
  56. package/dist/src/page.js.map +1 -0
  57. package/dist/src/persisted.d.ts +47 -0
  58. package/dist/src/persisted.js +119 -0
  59. package/dist/src/persisted.js.map +1 -0
  60. package/dist/src/resource.d.ts +120 -0
  61. package/dist/src/resource.js +275 -0
  62. package/dist/src/resource.js.map +1 -0
  63. package/dist/src/router/manifest.d.ts +56 -0
  64. package/dist/src/router/manifest.js +302 -0
  65. package/dist/src/router/manifest.js.map +1 -0
  66. package/dist/src/router/match.d.ts +62 -0
  67. package/dist/src/router/match.js +117 -0
  68. package/dist/src/router/match.js.map +1 -0
  69. package/dist/src/router/navigation.d.ts +89 -0
  70. package/dist/src/router/navigation.js +263 -0
  71. package/dist/src/router/navigation.js.map +1 -0
  72. package/dist/src/router.d.ts +13 -0
  73. package/dist/src/router.js +13 -0
  74. package/dist/src/router.js.map +1 -0
  75. package/dist/src/signal.d.ts +67 -0
  76. package/dist/src/signal.js +238 -0
  77. package/dist/src/signal.js.map +1 -0
  78. package/docs/README.md +12 -0
  79. package/docs/en/00-the-mado-way.md +106 -0
  80. package/docs/en/01-routing.md +204 -0
  81. package/docs/en/02-project-layout.md +58 -0
  82. package/docs/en/03-static-bake.md +251 -0
  83. package/docs/en/04-ide-setup.md +162 -0
  84. package/docs/en/05-why-mado.md +193 -0
  85. package/docs/en/06-for-backenders.md +422 -0
  86. package/docs/en/07-llm-pitfalls.md +486 -0
  87. package/docs/en/08-llm-zero-history-test.md +56 -0
  88. package/docs/en/09-shadow-vs-light-dom.md +122 -0
  89. package/docs/en/README.md +16 -0
  90. package/docs/fr/00-the-mado-way.md +108 -0
  91. package/docs/fr/01-routing.md +202 -0
  92. package/docs/fr/02-project-layout.md +58 -0
  93. package/docs/fr/03-static-bake.md +290 -0
  94. package/docs/fr/04-ide-setup.md +162 -0
  95. package/docs/fr/05-why-mado.md +193 -0
  96. package/docs/fr/06-for-backenders.md +432 -0
  97. package/docs/fr/07-llm-pitfalls.md +487 -0
  98. package/docs/fr/08-llm-zero-history-test.md +60 -0
  99. package/docs/fr/09-shadow-vs-light-dom.md +121 -0
  100. package/docs/fr/README.md +16 -0
  101. package/docs/ru/00-the-mado-way.md +93 -0
  102. package/docs/ru/01-routing.md +194 -0
  103. package/docs/ru/02-project-layout.md +57 -0
  104. package/docs/ru/03-static-bake.md +251 -0
  105. package/docs/ru/04-ide-setup.md +144 -0
  106. package/docs/ru/05-why-mado.md +193 -0
  107. package/docs/ru/06-for-backenders.md +422 -0
  108. package/docs/ru/07-llm-pitfalls.md +485 -0
  109. package/docs/ru/08-llm-zero-history-test.md +56 -0
  110. package/docs/ru/09-shadow-vs-light-dom.md +122 -0
  111. package/docs/ru/README.md +14 -0
  112. package/docs/uk/00-the-mado-way.md +54 -0
  113. package/docs/uk/01-routing.md +82 -0
  114. package/docs/uk/02-project-layout.md +46 -0
  115. package/docs/uk/03-static-bake.md +49 -0
  116. package/docs/uk/04-ide-setup.md +26 -0
  117. package/docs/uk/05-why-mado.md +34 -0
  118. package/docs/uk/06-for-backenders.md +50 -0
  119. package/docs/uk/07-llm-pitfalls.md +82 -0
  120. package/docs/uk/08-llm-zero-history-test.md +31 -0
  121. package/docs/uk/09-shadow-vs-light-dom.md +40 -0
  122. package/docs/uk/README.md +16 -0
  123. package/llms.txt +155 -0
  124. package/package.json +81 -0
  125. package/scripts/bake.mjs +406 -0
  126. package/scripts/bundle.mjs +146 -0
  127. package/scripts/cli.mjs +382 -0
  128. package/scripts/new.mjs +80 -0
  129. package/scripts/preview.mjs +176 -0
  130. package/scripts/release-notes.mjs +66 -0
  131. package/scripts/showcase-regression.mjs +392 -0
  132. package/server/serve.mjs +292 -0
  133. package/starters/crud/README.md +21 -0
  134. package/starters/crud/index.html +20 -0
  135. package/starters/crud/package.json +17 -0
  136. package/starters/crud/src/components/app-shell.ts +51 -0
  137. package/starters/crud/src/components/ticket-detail.ts +33 -0
  138. package/starters/crud/src/components/ticket-form.ts +69 -0
  139. package/starters/crud/src/components/ticket-list.ts +66 -0
  140. package/starters/crud/src/lib/api.ts +76 -0
  141. package/starters/crud/src/main.ts +12 -0
  142. package/starters/crud/src/pages/home.ts +18 -0
  143. package/starters/crud/src/pages/not-found.ts +12 -0
  144. package/starters/crud/src/pages/ticket-detail.ts +6 -0
  145. package/starters/crud/src/pages/ticket-new.ts +6 -0
  146. package/starters/crud/src/pages/tickets.ts +6 -0
  147. package/starters/crud/src/routes.ts +9 -0
  148. package/starters/crud/src/styles/global.ts +155 -0
  149. package/starters/crud/tsconfig.json +15 -0
  150. package/starters/minimal/README.md +19 -0
  151. package/starters/minimal/index.html +20 -0
  152. package/starters/minimal/package.json +17 -0
  153. package/starters/minimal/src/components/app-counter.ts +31 -0
  154. package/starters/minimal/src/main.ts +9 -0
  155. package/starters/minimal/src/pages/home.ts +18 -0
  156. package/starters/minimal/src/pages/not-found.ts +14 -0
  157. package/starters/minimal/src/routes.ts +6 -0
  158. package/starters/minimal/src/styles/global.ts +60 -0
  159. package/starters/minimal/tsconfig.json +15 -0
  160. package/templates/page-detail.ts +63 -0
  161. package/templates/page-form.ts +94 -0
  162. package/templates/page-list.ts +79 -0
@@ -0,0 +1,487 @@
1
+ # Mado · Pièges LLM
2
+
3
+ > Erreurs typiques que les assistants IA (Copilot, Claude, ChatGPT, Cursor)
4
+ > commettent lors de la génération de code Mado. Et comment les corriger.
5
+
6
+ Ce document s'adresse à **deux publics** :
7
+ 1. **Les agents IA dans l'IDE** qui lisent `AGENTS.md` / `.cursorrules` / `.github/copilot-instructions.md`. Plus de détails sur les pièges typiques sont fournis ici.
8
+ 2. **Les humains** qui ont reçu du code d'une IA avec ces erreurs et ne comprennent pas ce qui ne va pas.
9
+
10
+ ---
11
+
12
+ ## Piège #1 : `${signal()}` au lieu de `${() => signal()}`
13
+
14
+ **Symptôme :** la valeur dans le template s'affiche mais ne se met pas à jour quand le signal change.
15
+
16
+ ```ts
17
+ const count = signal(0);
18
+
19
+ // ❌ L'IA génère souvent ceci
20
+ html`<div>Compte : ${count() * 2}</div>`
21
+ // → Affichera "Compte : 0" et ne se mettra plus jamais à jour.
22
+ // count() est lu une seule fois quand le TemplateResult est créé.
23
+
24
+ // ✅ Correct — fonction getter
25
+ html`<div>Compte : ${() => count() * 2}</div>`
26
+ // → Mado créera un effect() pour cette fonction et re-rendra quand count change.
27
+
28
+ // ✅ Aussi correct — le signal lui-même est une fonction
29
+ html`<div>Compte : ${count}</div>`
30
+ ```
31
+
32
+ **Règle :**
33
+ - Si `${...}` contient une **expression** (quelque chose est fait avec le signal) — enveloppez dans `() => ...`.
34
+ - Si `${...}` contient **le signal lui-même** — il peut être utilisé tel quel.
35
+
36
+ Ceci s'applique aux **bindings enfants** (texte à l'intérieur des tags) et aux **attributs de valeur** (`@click`, `.prop`, `?attr`, attributs ordinaires).
37
+
38
+ ---
39
+
40
+ ## Piège #2 : `<button disabled=${loading}>` au lieu de `?disabled`
41
+
42
+ **Symptôme :** le bouton n'est pas désactivé, ou est toujours désactivé.
43
+
44
+ ```ts
45
+ const loading = signal(false);
46
+
47
+ // ❌ C'est setAttribute("disabled", "false") — le DOM traite ça comme disabled
48
+ html`<button disabled=${loading()}>Enregistrer</button>`
49
+
50
+ // ✅ Correct — binding booléen (basculer l'attribut)
51
+ html`<button ?disabled=${loading}>Enregistrer</button>`
52
+ ```
53
+
54
+ **Règles pour les attributs :**
55
+ | Préfixe | Ce qu'il fait | Quand utiliser |
56
+ |---|---|---|
57
+ | `attr=` | `setAttribute("attr", value)` | strings, nombres, URLs |
58
+ | `.attr=` | `el.attr = value` (propriété DOM) | objets, tableaux, `.value` d'input |
59
+ | `?attr=` | basculer l'attribut (par vérité) | `disabled`, `hidden`, `checked`, etc. |
60
+ | `@evt=` | `addEventListener("evt", fn)` | gestionnaires d'événements |
61
+
62
+ ---
63
+
64
+ ## Piège #3 : style useState / useEffect
65
+
66
+ **Symptôme :** l'IA génère du code de style React qui ne fonctionne pas dans Mado.
67
+
68
+ ```ts
69
+ // ❌ L'IA écrit souvent ceci
70
+ function Counter() {
71
+ const [count, setCount] = useState(0);
72
+ useEffect(() => { console.log(count); }, [count]);
73
+ return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
74
+ }
75
+
76
+ // ✅ Correct dans Mado
77
+ import { component, signal, effect, html } from "@madojs/mado";
78
+
79
+ component("x-counter", () => {
80
+ const count = signal(0);
81
+ effect(() => console.log(count())); // auto-abonnement, disposé automatiquement
82
+ return () => html`
83
+ <button @click=${() => count.update(c => c + 1)}>${count}</button>
84
+ `;
85
+ });
86
+ ```
87
+
88
+ **Différences clés :**
89
+ - Pas de hooks, pas de règles de hooks.
90
+ - `signal()` peut être créé n'importe où — dans le setup, dans un effect, dans un handler.
91
+ - `effect()` voit ce qu'il a lu de lui-même — pas besoin de tableau de dépendances.
92
+ - Un composant est `component("x-name", setup)`, pas une fonction JSX.
93
+
94
+ ---
95
+
96
+ ## Piège #4 : `useEffect(() => { ... return cleanup })`
97
+
98
+ **Symptôme :** l'IA écrit `return cleanup` à l'intérieur d'un effect, attendant que ça fonctionne comme dans React.
99
+
100
+ ```ts
101
+ // ❌ L'IA essaie d'écrire ceci
102
+ component("x-timer", () => {
103
+ effect(() => {
104
+ const id = setInterval(..., 1000);
105
+ return () => clearInterval(id); // NE fonctionnera PAS, utilisez ctx.onDispose à la place
106
+ });
107
+ return () => html`...`;
108
+ });
109
+
110
+ // ✅ Correct : nettoyage via ctx.onDispose
111
+ component("x-timer", (ctx) => {
112
+ const id = setInterval(..., 1000);
113
+ ctx.onDispose(() => clearInterval(id));
114
+ return () => html`...`;
115
+ });
116
+ ```
117
+
118
+ **Note :** `effect()` supporte bien `return cleanup`, mais c'est un **nettoyage par exécution** (s'exécute avant la prochaine exécution de l'effect), pas un nettoyage au démontage. Pour le nettoyage au démontage, utilisez `ctx.onDispose`.
119
+
120
+ ---
121
+
122
+ ## Piège #5 : Composant comme classe ou avec un décorateur
123
+
124
+ **Symptôme :** l'IA génère une classe de style Lit ou vanilla WebComponent.
125
+
126
+ ```ts
127
+ // ❌ IA : "faisons comme Lit"
128
+ import { LitElement, html } from "lit";
129
+ import { customElement, property } from "lit/decorators.js";
130
+
131
+ @customElement('x-counter')
132
+ class XCounter extends LitElement { ... }
133
+
134
+ // ❌ IA : "faisons en style vanilla"
135
+ class XCounter extends HTMLElement {
136
+ connectedCallback() { ... }
137
+ }
138
+ customElements.define("x-counter", XCounter);
139
+
140
+ // ✅ Correct : component() fonctionnel
141
+ import { component, html, signal } from "@madojs/mado";
142
+
143
+ component("x-counter", () => {
144
+ const count = signal(0);
145
+ return () => html`<button @click=${() => count.update(n => n + 1)}>${count}</button>`;
146
+ });
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Piège #6 : imports sans l'extension `.js`
152
+
153
+ **Symptôme :** TypeScript compile, mais le navigateur obtient une 404.
154
+
155
+ ```ts
156
+ // ❌ L'IA omet souvent l'extension
157
+ import { foo } from "./bar";
158
+ import { Home } from "./pages/home";
159
+
160
+ // ✅ Correct : les modules ES dans le navigateur nécessitent l'extension
161
+ import { foo } from "./bar.js";
162
+ import { Home } from "./pages/home.js";
163
+ ```
164
+
165
+ **Pourquoi `.js` et pas `.ts` :** le navigateur reçoit du JS déjà compilé. TypeScript est assez intelligent pour comprendre `./bar.js` comme une référence à `./bar.ts` à la compilation.
166
+
167
+ ---
168
+
169
+ ## Piège #7 : listes via `.map()` sans clés
170
+
171
+ **Symptôme :** lors du réordonnancement des éléments, le focus d'input est perdu / les animations CSS se cassent / les performances souffrent sur les grandes listes.
172
+
173
+ ```ts
174
+ // ❌ Fonctionne, mais sans clé : recrée le DOM à chaque changement
175
+ html`<ul>${() => items().map(t => html`<li>${t.name}</li>`)}</ul>`
176
+
177
+ // ✅ Correct : each() avec une fonction de clé
178
+ import { each } from "@madojs/mado";
179
+ html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.name}</li>`)}</ul>`
180
+ ```
181
+
182
+ **Règle :** utilisez toujours `each()` pour les listes de tableaux avec des IDs stables. Réservez `.map()` uniquement pour les listes statiques.
183
+
184
+ ---
185
+
186
+ ## Piège #8 : `signal.value` ou `count.get()`
187
+
188
+ **Symptôme :** l'IA écrit une API de style Vue ou Solid pre-v1.
189
+
190
+ ```ts
191
+ const count = signal(0);
192
+
193
+ // ❌ Pas une telle API
194
+ count.value
195
+ count.value = 5
196
+ count.get()
197
+
198
+ // ✅ Correct
199
+ count() // lecture
200
+ count.set(5) // écriture
201
+ count.update(n => n + 1)
202
+ count.peek() // lecture sans abonnement
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Piège #9 : `provide(ApiCtx, value)` sans host
208
+
209
+ **Symptôme :** TypeError lors de la tentative de fournir le context.
210
+
211
+ ```ts
212
+ // ❌ L'IA oublie host
213
+ provide(ApiCtx, myApi);
214
+ inject(ApiCtx);
215
+
216
+ // ✅ Correct : le premier argument est host (le composant courant)
217
+ component("x-app", ({ host }) => {
218
+ provide(host, ApiCtx, myApi);
219
+ return () => html`...`;
220
+ });
221
+
222
+ component("x-child", ({ host }) => {
223
+ const api = inject(host, ApiCtx); // signal<valeur>
224
+ return () => html`...`;
225
+ });
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Piège #10 : attendre du SSR
231
+
232
+ **Symptôme :** l'IA écrit du code en supposant que la page est pré-rendue côté serveur.
233
+
234
+ ```ts
235
+ // ❌ Ceci ne fonctionne que dans le navigateur
236
+ const userId = location.pathname.split("/")[2];
237
+
238
+ // ❌ Ceci aussi ne fonctionne que dans le navigateur
239
+ if (typeof window !== "undefined") { ... } // dans Mado, window est TOUJOURS disponible
240
+ ```
241
+
242
+ Mado **ne fait pas de SSR avec hydratation**. Le code ne s'exécute pas sur le serveur — il y a uniquement `bake` (prérendu statique au moment du build) et edge-prerender. Les deux remplacent le code utilisateur par un environnement linkedom, mais c'est **uniquement** pour générer du HTML avec des meta tags, pas pour exécuter la logique de page.
243
+
244
+ Cela signifie :
245
+ - ✅ `window`, `document`, `location`, `fetch` — disponibles sans vérifications.
246
+ - ❌ N'écrivez pas de code qui essaie de "fonctionner universellement sur serveur et client".
247
+ - ❌ N'utilisez pas les patterns Next.js (`getServerSideProps`, `headers()`).
248
+
249
+ ---
250
+
251
+ ## Piège #11 : `useForm()` avec un résolveur zod/yup
252
+
253
+ **Symptôme :** l'IA veut brancher un validateur.
254
+
255
+ ```ts
256
+ // ❌ Pas une telle API
257
+ const f = useForm({ resolver: zodResolver(schema) });
258
+
259
+ // ✅ Correct : validation HTML5 via attributs
260
+ const f = useForm({
261
+ email: { required: true, type: "email" },
262
+ age: { required: true, type: "number", min: 18 },
263
+ });
264
+
265
+ // ✅ Ou une fonction personnalisée si HTML5 ne suffit pas
266
+ const f = useForm(
267
+ { name: { required: true } },
268
+ {
269
+ validate: (values) => {
270
+ const errors: Record<string, string> = {};
271
+ if (values.name && /\d/.test(values.name as string)) {
272
+ errors.name = "Le nom ne doit pas contenir de chiffres";
273
+ }
274
+ return Object.keys(errors).length ? errors : null;
275
+ },
276
+ },
277
+ );
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Piège #12 : Tailwind / styled-components / CSS Modules
283
+
284
+ **Symptôme :** l'IA suggère des solutions CSS React standard.
285
+
286
+ Mado utilise **Shadow DOM + `css\`\`` + variables CSS**. Les frameworks UI globaux (Tailwind,
287
+ Bootstrap-via-classes) **ne fonctionnent qu'en light DOM** (`shadow: false`) :
288
+
289
+ ```ts
290
+ // Composant page/écran Light-DOM, les classes Tailwind fonctionnent
291
+ component("x-admin-page", () => () => html`
292
+ <section class="bg-white shadow-lg rounded-lg p-4">
293
+ ...
294
+ </section>
295
+ `, { shadow: false });
296
+
297
+ // Composant Shadow-DOM (par défaut) — Tailwind ne fonctionne PAS.
298
+ // Utilisez css`` ou ::part() pour le stylage externe.
299
+ component("x-button", () => () => html`<button><slot></slot></button>`, {
300
+ styles: css`
301
+ button {
302
+ background: var(--button-bg, #2563eb);
303
+ color: white;
304
+ padding: .5rem 1rem;
305
+ border-radius: 6px;
306
+ }
307
+ `,
308
+ });
309
+ ```
310
+
311
+ **Thèmes et personnalisation — via des variables CSS**, pas des classes.
312
+
313
+ ---
314
+
315
+ ## Piège #13 : `import * as Mado from "@madojs/mado"`
316
+
317
+ **Symptôme :** l'IA veut un import de namespace.
318
+
319
+ Cela fonctionne, mais duplique les noms et se tree-shake mal. Les imports nommés sont préférés :
320
+
321
+ ```ts
322
+ // ✅ Canonique
323
+ import { signal, html, component, css, page } from "@madojs/mado";
324
+
325
+ // ⚠️ Fonctionne, mais excessif
326
+ import * as Mado from "@madojs/mado";
327
+ Mado.signal(0);
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Piège #14 : tentative d'ajouter une dépendance runtime
333
+
334
+ **Symptôme :** l'IA suggère `npm install lodash` / `npm install date-fns` / etc.
335
+
336
+ Mado est **zéro dépendances runtime** par conception. Si l'IA veut ajouter :
337
+ - **lodash** → utilisez du JS natif (`Object.entries`, `Array.prototype`, `structuredClone`) ;
338
+ - **date-fns** → utilisez `Intl.DateTimeFormat` et `Intl.RelativeTimeFormat` ;
339
+ - **uuid** → `crypto.randomUUID()` ;
340
+ - **axios** → `fetch` natif + `jsonFetcher()` de Mado ;
341
+ - **classnames** → template literal natif ou une map d'objet.
342
+
343
+ Toute dépendance runtime est une **violation des principes du framework**. Si vous ne pouvez vraiment pas l'éviter — ajoutez-la au projet utilisateur, pas au cœur de Mado.
344
+
345
+ ---
346
+
347
+ ## Piège #15 : `<style>` inline dans les templates de page
348
+
349
+ **Symptôme :** l'IA met un grand `<style>` directement dans une page `html\`\``.
350
+
351
+ ```ts
352
+ // ❌ Fonctionne, mais se met à l'échelle difficilement et complique le nettoyage
353
+ page({
354
+ view: () => html`
355
+ <style>.panel { padding: 1rem; }</style>
356
+ <section class="panel">...</section>
357
+ `,
358
+ });
359
+
360
+ // ✅ Correct : styles de composant via css``
361
+ component("x-admin-panel", () => () => html`
362
+ <section class="panel">...</section>
363
+ `, {
364
+ styles: css`
365
+ .panel { padding: 1rem; }
366
+ `,
367
+ });
368
+ ```
369
+
370
+ Pour les écrans route/page d'admin backend, il est souvent approprié d'utiliser `shadow: false`,
371
+ afin que les utilitaires globaux de layout/form/table fonctionnent comme un panneau d'admin
372
+ ordinaire. Mais si le layout utilise `<slot>` pour projeter la page dans le shell, gardez le
373
+ layout en Shadow DOM et gardez les styles du shell dans `styles: css\`\``.
374
+
375
+ ---
376
+
377
+ ## Piège #16 : liens Shadow DOM sans `data-link`
378
+
379
+ **Symptôme :** un lien à l'intérieur d'un Web Component provoque un rechargement complet de
380
+ la page ou n'est pas préchargé.
381
+
382
+ ```ts
383
+ // ❌ Lien ordinaire : le navigateur effectuera un rechargement complet
384
+ html`<a href="/tickets/42">Ouvrir</a>`
385
+
386
+ // ✅ Navigation SPA : router() interceptera le clic même à travers Shadow DOM
387
+ html`<a href="/tickets/42" data-link>Ouvrir</a>`
388
+ ```
389
+
390
+ Mado trouve le lien via `event.composedPath()`, donc `data-link` fonctionne aussi à l'intérieur
391
+ de Shadow DOM. Le hover-prefetch utilise le même chemin ; `data-no-prefetch` désactive le
392
+ prefetch pour un lien spécifique.
393
+
394
+ ---
395
+
396
+ ## Piège #17 : `resource()` en dehors du setup du composant
397
+
398
+ **Symptôme :** l'IA crée une resource dans le scope du module pour "réutiliser" des données
399
+ entre les pages.
400
+
401
+ ```ts
402
+ // ❌ Pas de nettoyage du lifecycle, générera un avertissement dev
403
+ const tickets = resource(() => "tickets", () => api.listTickets());
404
+
405
+ component("x-tickets", () => {
406
+ return () => html`${() => tickets.data()?.length ?? 0}`;
407
+ });
408
+
409
+ // ✅ Créer la resource à l'intérieur du setup du composant
410
+ component("x-tickets", () => {
411
+ const tickets = resource(() => "tickets", () => api.listTickets());
412
+ return () => html`${() => tickets.data()?.length ?? 0}`;
413
+ });
414
+ ```
415
+
416
+ Ainsi, les abonnements d'invalidation, les abort controllers et les effects seront
417
+ nettoyés quand le composant se déconnecte.
418
+
419
+ ---
420
+
421
+ ## Piège #18 : supposer que les templates imbriqués ne nécessitent pas de nettoyage
422
+
423
+ **Symptôme :** l'IA assemble un outlet de route ou une UI conditionnelle à partir de
424
+ `TemplateResult`s imbriqués, et les anciens éléments continuent de vivre sous la nouvelle page.
425
+
426
+ ```ts
427
+ const view = signal(html`<x-home></x-home>`);
428
+
429
+ // ✅ Pattern normal : un TemplateResult imbriqué peut être retourné depuis un binding enfant
430
+ html`${view}`
431
+ ```
432
+
433
+ À partir de v0.3, ceci est garanti par des tests de régression : quand un binding enfant est
434
+ remplacé, Mado dispose récursivement les instances/effects de template imbriqués. Si vous
435
+ voyez des pages s'accumuler dans `#app`, c'est un bug core, pas quelque chose que vous devez
436
+ nettoyer manuellement.
437
+
438
+ ---
439
+
440
+ ## Piège #19 : utilitaires CSS globaux dans Shadow DOM
441
+
442
+ **Symptôme :** la page a l'air "sans style" : `.page-head`, `.btn`, `.form-grid`,
443
+ `.metric-grid` ne sont pas appliqués.
444
+
445
+ ```ts
446
+ // ❌ .page-head est déclaré globalement, mais x-dashboard utilise Shadow DOM par défaut
447
+ component("x-dashboard", () => () => html`
448
+ <header class="page-head">...</header>
449
+ <div class="metric-grid">...</div>
450
+ `);
451
+
452
+ // ✅ Les composants page/layout/admin-shell doivent souvent être Light DOM
453
+ component("x-dashboard", () => () => html`
454
+ <header class="page-head">...</header>
455
+ <div class="metric-grid">...</div>
456
+ `, { shadow: false });
457
+ ```
458
+
459
+ Règle : Shadow DOM — pour les widgets feuilles et les layouts basés sur slot, Light DOM — pour
460
+ les composants route/page/admin-screen qui utilisent intentionnellement des utilitaires partagés
461
+ de layout/form/table. Rappel : `<slot>` ne projette les enfants qu'en Shadow DOM ; avec
462
+ `shadow: false` c'est un élément ordinaire.
463
+ Plus de détails : [`09-shadow-vs-light-dom.md`](./09-shadow-vs-light-dom.md).
464
+
465
+ ---
466
+
467
+ ## Aide-mémoire pour l'IA
468
+
469
+ | Si vous voulez faire… | Correct dans Mado |
470
+ |---|---|
471
+ | `useState(0)` | `signal(0)` |
472
+ | `useEffect(() => {...}, [a, b])` | `effect(() => {...})` (auto-dépendances) |
473
+ | `useEffect(() => return cleanup, [])` | `ctx.onDispose(cleanup)` |
474
+ | `useMemo(() => x, [a])` | `computed(() => x)` |
475
+ | `useCallback(fn, [])` | fonction ordinaire |
476
+ | `useContext(Ctx)` | `inject(host, Ctx)` |
477
+ | `useQuery(['key'], fn)` | `resource(() => 'key', fn)` |
478
+ | `useMutation(fn)` | `mutation(fn, { invalidates: [...] })` |
479
+ | `useRouter().push('/')` | `navigate('/')` |
480
+ | `useRouter().query.q` | `queryParam('q')` |
481
+ | `<input value={v} onChange={...}>` | `<input .value=${v} @input=${...}>` |
482
+ | `{items.map(x => ...)}` | `${() => each(items, x => x.id, x => ...)}` |
483
+ | `useForm({ resolver: zodResolver })` | `useForm({...}, { validate: (v) => ... })` |
484
+ | `class extends HTMLElement` | `component('x-name', setup)` |
485
+ | `@customElement('x')` | `component('x-name', setup)` |
486
+
487
+ Si quelque chose ne rentre pas dans cette liste — ouvrez `src/` et **lisez 500 lignes**. Sérieusement. Mado est intentionnellement petit pour être lisible.
@@ -0,0 +1,60 @@
1
+ # Test LLM sans historique
2
+
3
+ Ce document définit un test de validation pratique pour Mado.
4
+
5
+ La question n'est pas "un LLM peut-il générer du code frontend ?" Il le peut. La question est :
6
+ un LLM fraîchement initialisé peut-il écrire du Mado idiomatique sans retomber dans du code
7
+ de forme React ?
8
+
9
+ ## Contexte autorisé
10
+
11
+ Pour le premier passage, donnez à l'agent uniquement :
12
+
13
+ - `AGENTS.md`
14
+ - `README.md`
15
+ - `docs/ru/07-llm-pitfalls.md`
16
+ - `examples/basic/README.md` si un tour minimal de l'API est nécessaire
17
+ - des fichiers `examples/showcase/**` spécifiques uniquement quand l'agent demande un pattern
18
+ d'application plus grande
19
+
20
+ L'agent peut rechercher des API ciblées dans `src/` quand il est bloqué, mais ne doit pas
21
+ charger tout le framework dans le context.
22
+
23
+ ## Tâche
24
+
25
+ Construire `examples/tickets` : une petite SPA d'administration de tickets pour un développeur
26
+ solo/backend.
27
+
28
+ Comportement requis :
29
+
30
+ - routes : `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*` ;
31
+ - API mock en mémoire avec des délais async réalistes ;
32
+ - page de liste avec `resource()`, `queryParam()` filtres de recherche/statut, `computed()`,
33
+ et des lignes `each()` avec clés ;
34
+ - flux de création et d'édition avec `useForm()` + `mutation()` + `invalidates` ;
35
+ - état UI local avec `signal()` ;
36
+ - composants shell, metric et badge avec slot pour une UI admin plus réaliste ;
37
+ - test de smoke important le build de l'exemple.
38
+
39
+ ## Liste de contrôle des échecs
40
+
41
+ Cherchez ces éléments après l'implémentation :
42
+
43
+ - JSX, `useState`, `useEffect`, `ref`, `$state`, ou composants de style classe ;
44
+ - `${signal()}` ou `${signal() + 1}` là où un thunk enfant réactif est requis ;
45
+ - `disabled=${...}` au lieu de `?disabled=${...}` ;
46
+ - listes dynamiques rendues avec un mapping de tableau sans clé au lieu de `each()` ;
47
+ - imports ESM navigateur sans `.js` ;
48
+ - `resource()` créé en dehors du setup du composant ;
49
+ - nouvelles dépendances runtime ou nouvelles API publiques du framework.
50
+
51
+ ## Notes de résultats
52
+
53
+ L'implémentation actuelle de `examples/tickets` n'a pas nécessité de nouvelles API publiques ni
54
+ de dépendances runtime.
55
+
56
+ Le principal point de pression dans la documentation reste le lifecycle : les anciens exemples
57
+ peuvent donner l'impression qu'il est acceptable de créer `resource()` directement dans
58
+ `page.view()`. L'exemple tickets utilise plutôt des composants wrapper au niveau page, de sorte
59
+ que les resources sont enregistrées à l'intérieur du setup du composant et se nettoient avec
60
+ le composant.
@@ -0,0 +1,121 @@
1
+ # Shadow DOM vs Light DOM
2
+
3
+ Les composants Mado utilisent Shadow DOM par défaut. C'est un bon défaut pour les widgets
4
+ autonomes, mais ce n'est pas le bon défaut pour chaque composant dans une application.
5
+
6
+ ## Règle générale
7
+
8
+ Utilisez **Shadow DOM** pour les widgets feuilles :
9
+
10
+ - boutons, badges, cartes, métriques ;
11
+ - modals, toasts, petits composants visuels ;
12
+ - widgets d'intégration qui ne devraient pas hériter du CSS de l'app accidentellement ;
13
+ - composants dont le stylage doit appartenir au composant lui-même.
14
+
15
+ Utilisez **Light DOM** (`{ shadow: false }`) pour la structure de l'app qui veut partager
16
+ les utilitaires CSS globaux :
17
+
18
+ - composants route/page ;
19
+ - écrans admin avec des layouts denses de tableau/formulaire ;
20
+ - écrans riches en données avec des tableaux et des formulaires ;
21
+ - composants qui partagent intentionnellement les utilitaires globaux de layout, formulaire et
22
+ tableau ;
23
+ - endroits où les enfants doivent simplement rester dans le DOM normal du document.
24
+
25
+ ## Le piège
26
+
27
+ Le CSS global ne franchit pas une frontière Shadow DOM.
28
+
29
+ ```ts
30
+ // global.ts
31
+ export const globalStyles = css`
32
+ .page-head { display: flex; justify-content: space-between; }
33
+ .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
34
+ `;
35
+
36
+ // ❌ .page-head et .metric-grid ne s'appliqueront pas à l'intérieur du shadowRoot de x-dashboard
37
+ component("x-dashboard", () => () => html`
38
+ <header class="page-head">...</header>
39
+ <div class="metric-grid">...</div>
40
+ `);
41
+ ```
42
+
43
+ Corrigez en rendant le composant route/page Light DOM :
44
+
45
+ ```ts
46
+ component("x-dashboard", () => () => html`
47
+ <header class="page-head">...</header>
48
+ <div class="metric-grid">...</div>
49
+ `, {
50
+ shadow: false,
51
+ styles: css`
52
+ x-dashboard { display: block; }
53
+ x-dashboard .panel { padding: 1rem; }
54
+ `,
55
+ });
56
+ ```
57
+
58
+ Maintenant les utilitaires globaux et les styles locaux scopés fonctionnent tous les deux.
59
+
60
+ ## Comment les styles se comportent
61
+
62
+ - `styles: css\`\`` en Shadow DOM est adopté dans le shadowRoot du composant.
63
+ - `styles: css\`\`` avec `shadow: false` est scopé au nom du tag et adopté globalement.
64
+ - Les propriétés CSS personnalisées (`--accent`, `--bg`, etc.) traversent les frontières Shadow DOM.
65
+ - Les sélecteurs de classe comme `.btn`, `.form-grid`, `.page-head` ne traversent **pas** les
66
+ frontières Shadow DOM.
67
+ - Les enfants slottés conservent leurs propres styles de document ; le composant shadow ne peut
68
+ les cibler qu'à travers `::slotted(...)`.
69
+ - `<slot>` projette les enfants uniquement en Shadow DOM. Dans un composant `shadow: false`,
70
+ c'est juste un élément `<slot>` normal et ne déplacera pas les enfants à cet endroit dans
71
+ votre layout.
72
+
73
+ ## Forme recommandée de l'application
74
+
75
+ ```ts
76
+ // racine et pages : Light DOM
77
+ component("x-app", setup, { shadow: false });
78
+ component("x-users-page", setup, { shadow: false });
79
+
80
+ // layout basé sur slot : Shadow DOM par défaut, car il possède la grille du shell
81
+ component("x-app-layout", setup);
82
+
83
+ // widgets feuilles : Shadow DOM par défaut
84
+ component("x-status-badge", setup);
85
+ component("x-stat-card", setup);
86
+ component("x-toast-stack", setup);
87
+ ```
88
+
89
+ Cela donne aux écrans d'admin backend un CSS prévisible tout en préservant l'encapsulation
90
+ pour les widgets réutilisables et les shells basés sur slot.
91
+
92
+ Si un layout n'a pas besoin de projection slot et doit être entièrement stylé par du CSS
93
+ global, `shadow: false` peut rester un bon choix. S'il contient `<slot>`, gardez Shadow DOM
94
+ et mettez les styles du shell dans `styles: css\`\``.
95
+
96
+ ## Routage et liens
97
+
98
+ `data-link` fonctionne à l'intérieur de Shadow DOM. Le router utilise `event.composedPath()`,
99
+ donc l'interception de clic et le hover-prefetch peuvent voir les liens depuis les shadow roots
100
+ ouverts.
101
+
102
+ ```ts
103
+ component("x-card-link", () => () => html`
104
+ <a href="/app/accounts" data-link>Comptes</a>
105
+ `);
106
+ ```
107
+
108
+ Le lien peut être en Shadow DOM ; la navigation reste SPA.
109
+
110
+ ## Leçon du Showcase
111
+
112
+ `examples/showcase` utilise cette séparation délibérément :
113
+
114
+ - `x-app` et les pages de route CRM sont Light DOM ;
115
+ - `x-app-layout` garde Shadow DOM car il possède un shell sidebar/contenu basé sur slot ;
116
+ - les utilitaires de tableau/formulaire/page vivent dans `styles/global.ts` ;
117
+ - les composants feuilles comme `x-stat-card`, `x-status-badge`, `x-modal` et `x-toast-stack`
118
+ gardent Shadow DOM.
119
+
120
+ Si une page a soudainement l'air sans style, vérifiez si elle utilise des classes globales
121
+ à l'intérieur d'un composant Shadow DOM. C'est généralement le problème.
@@ -0,0 +1,16 @@
1
+ # Mado docs — Français
2
+
3
+ Documentation française.
4
+
5
+ | Section | Source |
6
+ |---|---|
7
+ | The Mado way | [00-the-mado-way.md](./00-the-mado-way.md) |
8
+ | Routing | [01-routing.md](./01-routing.md) |
9
+ | Project layout | [02-project-layout.md](./02-project-layout.md) |
10
+ | Static bake & SEO | [03-static-bake.md](./03-static-bake.md) |
11
+ | IDE setup | [04-ide-setup.md](./04-ide-setup.md) |
12
+ | Why Mado | [05-why-mado.md](./05-why-mado.md) |
13
+ | For backenders | [06-for-backenders.md](./06-for-backenders.md) |
14
+ | LLM pitfalls | [07-llm-pitfalls.md](./07-llm-pitfalls.md) |
15
+ | LLM zero-history test | [08-llm-zero-history-test.md](./08-llm-zero-history-test.md) |
16
+ | Shadow DOM vs Light DOM | [09-shadow-vs-light-dom.md](./09-shadow-vs-light-dom.md) |