@madojs/mado 0.11.1 → 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.
Files changed (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
@@ -1,625 +0,0 @@
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
-
8
- 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.
9
- 2. **Les humains** qui ont reçu du code d'une IA avec ces erreurs et ne comprennent pas ce qui ne va pas.
10
-
11
- ---
12
-
13
- ## Piège #1 : `${signal()}` au lieu de `${() => signal()}`
14
-
15
- **Symptôme :** la valeur dans le template s'affiche mais ne se met pas à jour quand le signal change.
16
-
17
- ```ts
18
- const count = signal(0);
19
-
20
- // ❌ L'IA génère souvent ceci
21
- html`<div>Compte : ${count() * 2}</div>`;
22
- // → Affichera "Compte : 0" et ne se mettra plus jamais à jour.
23
- // count() est lu une seule fois quand le TemplateResult est créé.
24
-
25
- // ✅ Correct — fonction getter
26
- html`<div>Compte : ${() => count() * 2}</div>`;
27
- // → Mado créera un effect() pour cette fonction et re-rendra quand count change.
28
-
29
- // ✅ Aussi correct — le signal lui-même est une fonction
30
- html`<div>Compte : ${count}</div>`;
31
- ```
32
-
33
- **Règle :**
34
-
35
- - Si `${...}` contient une **expression** (quelque chose est fait avec le signal) — enveloppez dans `() => ...`.
36
- - Si `${...}` contient **le signal lui-même** — il peut être utilisé tel quel.
37
-
38
- Ceci s'applique aux **bindings enfants** (texte à l'intérieur des tags) et aux **attributs de valeur** (`@click`, `.prop`, `?attr`, attributs ordinaires).
39
-
40
- ---
41
-
42
- ## Piège #2 : `<button disabled=${loading}>` au lieu de `?disabled`
43
-
44
- **Symptôme :** le bouton n'est pas désactivé, ou est toujours désactivé.
45
-
46
- ```ts
47
- const loading = signal(false);
48
-
49
- // ❌ C'est setAttribute("disabled", "false") — le DOM traite ça comme disabled
50
- html`<button disabled=${loading()}>Enregistrer</button>`;
51
-
52
- // ✅ Correct — binding booléen (basculer l'attribut)
53
- html`<button ?disabled=${loading}>Enregistrer</button>`;
54
- ```
55
-
56
- **Règles pour les attributs :**
57
- | Préfixe | Ce qu'il fait | Quand utiliser |
58
- |---|---|---|
59
- | `attr=` | `setAttribute("attr", value)` | strings, nombres, URLs |
60
- | `.attr=` | `el.attr = value` (propriété DOM) | objets, tableaux, `.value` d'input |
61
- | `?attr=` | basculer l'attribut (par vérité) | `disabled`, `hidden`, `checked`, etc. |
62
- | `@evt=` | `addEventListener("evt", fn)` | gestionnaires d'événements |
63
-
64
- ---
65
-
66
- ## Piège #3 : style useState / useEffect
67
-
68
- **Symptôme :** l'IA génère du code de style React qui ne fonctionne pas dans Mado.
69
-
70
- ```ts
71
- // ❌ L'IA écrit souvent ceci
72
- function Counter() {
73
- const [count, setCount] = useState(0);
74
- useEffect(() => { console.log(count); }, [count]);
75
- return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
76
- }
77
-
78
- // ✅ Correct dans Mado
79
- import { component, signal, effect, html } from "@madojs/mado";
80
-
81
- component("x-counter", () => {
82
- const count = signal(0);
83
- effect(() => console.log(count())); // auto-abonnement, disposé automatiquement
84
- return () => html`
85
- <button @click=${() => count.update(c => c + 1)}>${count}</button>
86
- `;
87
- });
88
- ```
89
-
90
- **Différences clés :**
91
-
92
- - Pas de hooks, pas de règles de hooks.
93
- - `signal()` peut être créé n'importe où — dans le setup, dans un effect, dans un handler.
94
- - `effect()` voit ce qu'il a lu de lui-même — pas besoin de tableau de dépendances.
95
- - Un composant est `component("x-name", setup)`, pas une fonction JSX.
96
-
97
- ---
98
-
99
- ## Piège #4 : `useEffect(() => { ... return cleanup })`
100
-
101
- **Symptôme :** l'IA écrit `return cleanup` à l'intérieur d'un effect, attendant que ça fonctionne comme dans React.
102
-
103
- ```ts
104
- // ❌ L'IA essaie d'écrire ceci
105
- component("x-timer", () => {
106
- effect(() => {
107
- const id = setInterval(..., 1000);
108
- return () => clearInterval(id); // NE fonctionnera PAS, utilisez ctx.onDispose à la place
109
- });
110
- return () => html`...`;
111
- });
112
-
113
- // ✅ Correct : nettoyage via ctx.onDispose
114
- component("x-timer", (ctx) => {
115
- const id = setInterval(..., 1000);
116
- ctx.onDispose(() => clearInterval(id));
117
- return () => html`...`;
118
- });
119
- ```
120
-
121
- **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`.
122
-
123
- ---
124
-
125
- ## Piège #5 : Composant comme classe ou avec un décorateur
126
-
127
- **Symptôme :** l'IA génère une classe de style Lit ou vanilla WebComponent.
128
-
129
- ```ts
130
- // ❌ IA : "faisons comme Lit"
131
- import { LitElement, html } from "lit";
132
- import { customElement, property } from "lit/decorators.js";
133
-
134
- @customElement('x-counter')
135
- class XCounter extends LitElement { ... }
136
-
137
- // ❌ IA : "faisons en style vanilla"
138
- class XCounter extends HTMLElement {
139
- connectedCallback() { ... }
140
- }
141
- customElements.define("x-counter", XCounter);
142
-
143
- // ✅ Correct : component() fonctionnel
144
- import { component, html, signal } from "@madojs/mado";
145
-
146
- component("x-counter", () => {
147
- const count = signal(0);
148
- return () => html`<button @click=${() => count.update(n => n + 1)}>${count}</button>`;
149
- });
150
- ```
151
-
152
- ---
153
-
154
- ## Piège #6 : imports sans l'extension `.js`
155
-
156
- **Symptôme :** TypeScript compile, mais le navigateur obtient une 404.
157
-
158
- ```ts
159
- // ❌ L'IA omet souvent l'extension
160
- import { foo } from "./bar";
161
- import { Home } from "./pages/home";
162
-
163
- // ✅ Correct : les modules ES dans le navigateur nécessitent l'extension
164
- import { foo } from "./bar.js";
165
- import { Home } from "./pages/home.js";
166
- ```
167
-
168
- **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.
169
-
170
- ---
171
-
172
- ## Piège #7 : listes via `.map()` sans clés
173
-
174
- **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.
175
-
176
- ```ts
177
- // ❌ Fonctionne, mais sans clé : recrée le DOM à chaque changement
178
- html`<ul>
179
- ${() => items().map((t) => html`<li>${t.name}</li>`)}
180
- </ul>`;
181
-
182
- // ✅ Correct : each() avec une fonction de clé
183
- import { each } from "@madojs/mado";
184
- html`<ul>
185
- ${() =>
186
- each(
187
- items(),
188
- (t) => t.id,
189
- (t) => html`<li>${t.name}</li>`,
190
- )}
191
- </ul>`;
192
- ```
193
-
194
- **Règle :** utilisez toujours `each()` pour les listes de tableaux avec des IDs stables. Réservez `.map()` uniquement pour les listes statiques.
195
-
196
- ---
197
-
198
- ## Piège #8 : `signal.value` ou `count.get()`
199
-
200
- **Symptôme :** l'IA écrit une API de style Vue ou Solid pre-v1.
201
-
202
- ```ts
203
- const count = signal(0);
204
-
205
- // ❌ Pas une telle API
206
- count.value;
207
- count.value = 5;
208
- count.get();
209
-
210
- // ✅ Correct
211
- count(); // lecture
212
- count.set(5); // écriture
213
- count.update((n) => n + 1);
214
- count.peek(); // lecture sans abonnement
215
- ```
216
-
217
- ---
218
-
219
- ## Piège #9 : `provide(ApiCtx, value)` sans host
220
-
221
- **Symptôme :** TypeError lors de la tentative de fournir le context.
222
-
223
- ```ts
224
- // ❌ L'IA oublie host
225
- provide(ApiCtx, myApi);
226
- inject(ApiCtx);
227
-
228
- // ✅ Correct : le premier argument est host (le composant courant)
229
- component("x-app", ({ host }) => {
230
- provide(host, ApiCtx, myApi);
231
- return () => html`...`;
232
- });
233
-
234
- component("x-child", ({ host }) => {
235
- const api = inject(host, ApiCtx); // signal<valeur>
236
- return () => html`...`;
237
- });
238
- ```
239
-
240
- ---
241
-
242
- ## Piège #10 : attendre du SSR
243
-
244
- **Symptôme :** l'IA écrit du code en supposant que la page est pré-rendue côté serveur.
245
-
246
- ```ts
247
- // ❌ Ceci ne fonctionne que dans le navigateur
248
- const userId = location.pathname.split("/")[2];
249
-
250
- // ❌ Ceci aussi ne fonctionne que dans le navigateur
251
- if (typeof window !== "undefined") { ... } // dans Mado, window est TOUJOURS disponible
252
- ```
253
-
254
- 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.
255
-
256
- Cela signifie :
257
-
258
- - ✅ `window`, `document`, `location`, `fetch` — disponibles sans vérifications.
259
- - ❌ N'écrivez pas de code qui essaie de "fonctionner universellement sur serveur et client".
260
- - ❌ N'utilisez pas les patterns Next.js (`getServerSideProps`, `headers()`).
261
-
262
- ---
263
-
264
- ## Piège #11 : `useForm()` avec un résolveur zod/yup
265
-
266
- **Symptôme :** l'IA veut brancher un validateur.
267
-
268
- ```ts
269
- // ❌ Pas une telle API
270
- const f = useForm({ resolver: zodResolver(schema) });
271
-
272
- // ✅ Correct : validation proche du HTML via le schéma useForm
273
- const f = useForm({
274
- email: { required: true, type: "email" },
275
- age: { required: true, type: "number", min: 18 },
276
- });
277
-
278
- // ✅ Ou une fonction personnalisée si HTML5 ne suffit pas
279
- const f = useForm(
280
- { name: { required: true } },
281
- {
282
- validate: (values) => {
283
- const errors: Record<string, string> = {};
284
- if (values.name && /\d/.test(values.name as string)) {
285
- errors.name = "Le nom ne doit pas contenir de chiffres";
286
- }
287
- return Object.keys(errors).length ? errors : null;
288
- },
289
- },
290
- );
291
- ```
292
-
293
- ---
294
-
295
- ## Piège #12 : Tailwind / styled-components / CSS Modules
296
-
297
- **Symptôme :** l'IA suggère des solutions CSS React standard.
298
-
299
- Mado utilise **Shadow DOM + `css\`\`` + variables CSS**. Les frameworks UI globaux (Tailwind,
300
- Bootstrap-via-classes) **ne fonctionnent qu'en light DOM** (`shadow: false`) :
301
-
302
- ```ts
303
- // Composant page/écran Light-DOM, les classes Tailwind fonctionnent
304
- component(
305
- "x-admin-page",
306
- () => () => html`
307
- <section class="bg-white shadow-lg rounded-lg p-4">...</section>
308
- `,
309
- { shadow: false },
310
- );
311
-
312
- // Composant Shadow-DOM (par défaut) — Tailwind ne fonctionne PAS.
313
- // Utilisez css`` ou ::part() pour le stylage externe.
314
- component("x-button", () => () => html`<button><slot></slot></button>`, {
315
- styles: css`
316
- button {
317
- background: var(--button-bg, #2563eb);
318
- color: white;
319
- padding: 0.5rem 1rem;
320
- border-radius: 6px;
321
- }
322
- `,
323
- });
324
- ```
325
-
326
- **Thèmes et personnalisation — via des variables CSS**, pas des classes.
327
-
328
- ---
329
-
330
- ## Piège #13 : `import * as Mado from "@madojs/mado"`
331
-
332
- **Symptôme :** l'IA veut un import de namespace.
333
-
334
- Cela fonctionne, mais duplique les noms et se tree-shake mal. Les imports nommés sont préférés :
335
-
336
- ```ts
337
- // ✅ Canonique
338
- import { signal, html, component, css, page } from "@madojs/mado";
339
-
340
- // ⚠️ Fonctionne, mais excessif
341
- import * as Mado from "@madojs/mado";
342
- Mado.signal(0);
343
- ```
344
-
345
- ---
346
-
347
- ## Piège #14 : tentative d'ajouter une dépendance runtime
348
-
349
- **Symptôme :** l'IA suggère `npm install lodash` / `npm install date-fns` / etc.
350
-
351
- Mado est **zéro dépendances runtime** par conception. Si l'IA veut ajouter :
352
-
353
- - **lodash** → utilisez du JS natif (`Object.entries`, `Array.prototype`, `structuredClone`) ;
354
- - **date-fns** → utilisez `Intl.DateTimeFormat` et `Intl.RelativeTimeFormat` ;
355
- - **uuid** → `crypto.randomUUID()` ;
356
- - **axios** → `fetch` natif + `jsonFetcher()` de Mado ;
357
- - **classnames** → template literal natif ou une map d'objet.
358
-
359
- 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.
360
-
361
- ---
362
-
363
- ## Piège #15 : `<style>` inline dans les templates de page
364
-
365
- **Symptôme :** l'IA met un grand `<style>` directement dans une page `html\`\``.
366
-
367
- ```ts
368
- // ❌ Fonctionne, mais se met à l'échelle difficilement et complique le nettoyage
369
- page({
370
- view: () => html`
371
- <style>
372
- .panel {
373
- padding: 1rem;
374
- }
375
- </style>
376
- <section class="panel">...</section>
377
- `,
378
- });
379
-
380
- // ✅ Correct : styles de composant via css``
381
- component(
382
- "x-admin-panel",
383
- () => () => html` <section class="panel">...</section> `,
384
- {
385
- styles: css`
386
- .panel {
387
- padding: 1rem;
388
- }
389
- `,
390
- },
391
- );
392
- ```
393
-
394
- Pour les écrans route/page d'admin backend, il est souvent approprié d'utiliser `shadow: false`,
395
- afin que les utilitaires globaux de layout/form/table fonctionnent comme un panneau d'admin
396
- ordinaire. Mais si le layout utilise `<slot>` pour projeter la page dans le shell, gardez le
397
- layout en Shadow DOM et gardez les styles du shell dans `styles: css\`\``.
398
-
399
- ---
400
-
401
- ## Piège #16 : liens Shadow DOM sans `data-link`
402
-
403
- **Symptôme :** un lien à l'intérieur d'un Web Component provoque un rechargement complet de
404
- la page ou n'est pas préchargé.
405
-
406
- ```ts
407
- // ❌ Lien ordinaire : le navigateur effectuera un rechargement complet
408
- html`<a href="/tickets/42">Ouvrir</a>`;
409
-
410
- // ✅ Navigation SPA : router() interceptera le clic même à travers Shadow DOM
411
- html`<a href="/tickets/42" data-link>Ouvrir</a>`;
412
- ```
413
-
414
- Mado trouve le lien via `event.composedPath()`, donc `data-link` fonctionne aussi à l'intérieur
415
- de Shadow DOM. Le hover-prefetch utilise le même chemin ; `data-no-prefetch` désactive le
416
- prefetch pour un lien spécifique.
417
-
418
- ---
419
-
420
- ## Piège #17 : `resource()` en dehors du setup du composant
421
-
422
- **Symptôme :** l'IA crée une resource dans le scope du module pour "réutiliser" des données
423
- entre les pages.
424
-
425
- ```ts
426
- // ❌ Pas de nettoyage du lifecycle, générera un avertissement dev
427
- const tickets = resource(
428
- () => "tickets",
429
- () => api.listTickets(),
430
- );
431
-
432
- component("x-tickets", () => {
433
- return () => html`${() => tickets.data()?.length ?? 0}`;
434
- });
435
-
436
- // ✅ Créer la resource à l'intérieur du setup du composant
437
- component("x-tickets", () => {
438
- const tickets = resource(
439
- () => "tickets",
440
- () => api.listTickets(),
441
- );
442
- return () => html`${() => tickets.data()?.length ?? 0}`;
443
- });
444
- ```
445
-
446
- Ainsi, les abonnements d'invalidation, les abort controllers et les effects seront
447
- nettoyés quand le composant se déconnecte.
448
-
449
- ---
450
-
451
- ## Piège #18 : supposer que les templates imbriqués ne nécessitent pas de nettoyage
452
-
453
- **Symptôme :** l'IA assemble un outlet de route ou une UI conditionnelle à partir de
454
- `TemplateResult`s imbriqués, et les anciens éléments continuent de vivre sous la nouvelle page.
455
-
456
- ```ts
457
- const view = signal(html`<x-home></x-home>`);
458
-
459
- // ✅ Pattern normal : un TemplateResult imbriqué peut être retourné depuis un binding enfant
460
- html`${view}`;
461
- ```
462
-
463
- À partir de v0.3, ceci est garanti par des tests de régression : quand un binding enfant est
464
- remplacé, Mado dispose récursivement les instances/effects de template imbriqués. Si vous
465
- voyez des pages s'accumuler dans `#app`, c'est un bug core, pas quelque chose que vous devez
466
- nettoyer manuellement.
467
-
468
- ---
469
-
470
- ## Piège #19 : utilitaires CSS globaux dans Shadow DOM
471
-
472
- **Symptôme :** la page a l'air "sans style" : `.page-head`, `.btn`, `.form-grid`,
473
- `.metric-grid` ne sont pas appliqués.
474
-
475
- ```ts
476
- // ❌ .page-head est déclaré globalement, mais x-dashboard utilise Shadow DOM par défaut
477
- component(
478
- "x-dashboard",
479
- () => () => html`
480
- <header class="page-head">...</header>
481
- <div class="metric-grid">...</div>
482
- `,
483
- );
484
-
485
- // ✅ Les composants page/layout/admin-shell doivent souvent être Light DOM
486
- component(
487
- "x-dashboard",
488
- () => () => html`
489
- <header class="page-head">...</header>
490
- <div class="metric-grid">...</div>
491
- `,
492
- { shadow: false },
493
- );
494
- ```
495
-
496
- Règle : Shadow DOM — pour les widgets feuilles et les layouts basés sur slot, Light DOM — pour
497
- les composants route/page/admin-screen qui utilisent intentionnellement des utilitaires partagés
498
- de layout/form/table. Rappel : `<slot>` ne projette les enfants qu'en Shadow DOM ; avec
499
- `shadow: false` c'est un élément ordinaire.
500
- Plus de détails : [`09-shadow-vs-light-dom.md`](./09-shadow-vs-light-dom.md).
501
-
502
- ---
503
-
504
- ## Piège #20 : `host.getAttribute()` dans render = pas réactif
505
-
506
- **Symptôme :** l'apparence du composant ne se met pas à jour quand le parent change un attribut.
507
-
508
- ```ts
509
- // ❌ host.getAttribute() dans la fonction render est lu une seule fois.
510
- // Le render ne se relance que quand ses propres signaux changent.
511
- component("x-badge", ({ host }) => () => {
512
- const variant = host.getAttribute("variant") ?? "default";
513
- return html`<span class=${variant}>...</span>`;
514
- });
515
-
516
- // ✅ Correct : ctx.attr() — retourne un Signal<string> réactif
517
- component("x-badge", ({ attr }) => {
518
- const variant = attr("variant", "default");
519
- return () => html`<span class=${() => `badge-${variant()}`}>...</span>`;
520
- });
521
- ```
522
-
523
- **Règle :** n'utilisez jamais `host.getAttribute()` ou `host.hasAttribute()` dans la
524
- fonction render pour des valeurs qui peuvent changer de l'extérieur. Utilisez `ctx.attr()` —
525
- il retourne un Signal qui se met à jour via `attributeChangedCallback`.
526
-
527
- ---
528
-
529
- ## Piège #21 : `<button>` Shadow DOM ne soumet pas les formulaires
530
-
531
- **Symptôme :** cliquer sur `<x-button type="submit">` dans un `<form>` ne fait rien.
532
-
533
- Un `<button>` dans le Shadow DOM ne participe pas à l'algorithme form-owner pour
534
- `<form>` dans le Light DOM — c'est une limitation de la spécification.
535
-
536
- ```ts
537
- // ❌ Le <button type="submit"> interne ne peut pas déclencher le <form> parent
538
- component("x-button", ({ host }) => {
539
- return () => html`<button type="submit"><slot></slot></button>`;
540
- });
541
-
542
- // ✅ Pont via requestSubmit()
543
- component("x-button", ({ host, attr }) => {
544
- const disabled = attr("disabled");
545
-
546
- const handleClick = () => {
547
- const typeAttr = host.getAttribute("type");
548
- if (typeAttr === "button" || typeAttr === "reset") return;
549
- const form = host.closest("form");
550
- if (form && !host.hasAttribute("disabled")) form.requestSubmit();
551
- };
552
-
553
- return () => html`
554
- <button ?disabled=${() => disabled() !== ""} @click=${handleClick}>
555
- <slot></slot>
556
- </button>
557
- `;
558
- });
559
- ```
560
-
561
- Plus de détails : [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md).
562
-
563
- ---
564
-
565
- ## Piège #22 : `useForm()` avec des inputs Shadow DOM personnalisés
566
-
567
- **Symptôme :** `form.onInput` reçoit `undefined` pour name/value de `<x-input>`.
568
-
569
- Quand un input Shadow DOM dispatche un événement `input`, le navigateur retarget
570
- `e.target` du `<input>` interne vers le host `<x-input>`. Mais `<x-input>`
571
- (HTMLElement) n'a pas `.name` ni `.value` — donc `useForm` ne reçoit rien.
572
-
573
- ```ts
574
- // ❌ Pas de propriétés proxy — useForm ignore silencieusement les événements
575
- component("x-input", ({ host, attr }) => {
576
- const name = attr("name", "");
577
- return () => html`<input name=${name} />`;
578
- });
579
-
580
- // ✅ Ajouter des propriétés proxy pour la compatibilité useForm
581
- component("x-input", ({ host, attr }) => {
582
- const name = attr("name", "");
583
-
584
- Object.defineProperty(host, "name", {
585
- get: () => host.getAttribute("name") ?? "",
586
- configurable: true,
587
- });
588
- Object.defineProperty(host, "value", {
589
- get: () => host.shadowRoot?.querySelector("input")?.value ?? "",
590
- configurable: true,
591
- });
592
-
593
- return () => html`<input name=${name} />`;
594
- });
595
- ```
596
-
597
- Plus de détails : [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md).
598
-
599
- ---
600
-
601
- ## Aide-mémoire pour l'IA
602
-
603
- | Si vous voulez faire… | Correct dans Mado |
604
- | ------------------------------------- | ------------------------------------------- |
605
- | `useState(0)` | `signal(0)` |
606
- | `useEffect(() => {...}, [a, b])` | `effect(() => {...})` (auto-dépendances) |
607
- | `useEffect(() => return cleanup, [])` | `ctx.onDispose(cleanup)` |
608
- | `useMemo(() => x, [a])` | `computed(() => x)` |
609
- | `useCallback(fn, [])` | fonction ordinaire |
610
- | `useContext(Ctx)` | `inject(host, Ctx)` |
611
- | `useQuery(['key'], fn)` | `resource(() => 'key', fn)` |
612
- | `useMutation(fn)` | `mutation(fn, { invalidates: [...] })` |
613
- | `useRouter().push('/')` | `navigate('/')` |
614
- | `useRouter().query.q` | `queryParam('q')` |
615
- | `<input value={v} onChange={...}>` | `<input .value=${v} @input=${...}>` |
616
- | `{items.map(x => ...)}` | `${() => each(items, x => x.id, x => ...)}` |
617
- | `useForm({ resolver: zodResolver })` | `useForm({...}, { validate: (v) => ... })` |
618
- | `class extends HTMLElement` | `component('x-name', setup)` |
619
- | `@customElement('x')` | `component('x-name', setup)` |
620
- | `host.getAttribute('x')` dans render | `ctx.attr('x', default)` (réactif) |
621
- | `jsonFetcher()` avec auth | `apiFetcher()` (attache le Bearer token) |
622
- | `setInterval` dans page view | `onDispose(() => clearInterval(id))` |
623
- | lecture signal dans async init view() | `untracked(() => cursor())` |
624
-
625
- Si quelque chose ne rentre pas dans cette liste — ouvrez `src/` et **lisez 500 lignes**. Sérieusement. Mado est intentionnellement petit pour être lisible.
@@ -1,38 +0,0 @@
1
- # Test LLM sans historique
2
-
3
- Ce document définit un test manuel pour vérifier qu'un LLM fraîchement
4
- initialisé écrit du Mado idiomatique au lieu de reproduire React dans des
5
- tagged templates.
6
-
7
- ## Contexte autorisé
8
-
9
- - `AGENTS.md`
10
- - `README.md`
11
- - `docs/fr/07-llm-pitfalls.md` ou la version anglaise
12
- - fichiers de l'espace externe `madojs-examples` seulement si l'agent demande
13
- un pattern d'application plus large
14
-
15
- ## Tâche
16
-
17
- Construire une petite SPA ticket-admin :
18
-
19
- - routes : `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*` ;
20
- - mock API en mémoire avec délais async réalistes ;
21
- - liste avec `resource()`, `queryParam()`, `computed()` et `each()` keyed ;
22
- - create/edit avec `useForm()` + `mutation()` + `invalidates` ;
23
- - état UI local avec `signal()`.
24
-
25
- ## Checklist d'échec
26
-
27
- - JSX, `useState`, `useEffect`, `ref`, `$state`, classes custom elements ;
28
- - `${signal()}` là où un child thunk réactif est nécessaire ;
29
- - `disabled=${...}` au lieu de `?disabled=${...}` ;
30
- - `.map()` non-keyed pour des listes dynamiques ;
31
- - `resource()` créé hors contexte lifecycle-aware ;
32
- - nouvelles dépendances runtime ou nouvelles API publiques.
33
-
34
- ## Notes
35
-
36
- L'implémentation historique tickets vit dans l'espace externe d'exemples. Le
37
- core repository ne livre plus cet artefact ; utilisez cette page comme script
38
- d'évaluation manuel quand vous mettez à jour les règles LLM.