@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,486 @@
1
+ # Mado · LLM pitfalls
2
+
3
+ > Typical mistakes that AI assistants (Copilot, Claude, ChatGPT, Cursor)
4
+ > make when generating Mado code. And how to fix them.
5
+
6
+ This document is for **two audiences**:
7
+ 1. **AI agents in the IDE** that read `AGENTS.md` / `.cursorrules` / `.github/copilot-instructions.md`. More detail on typical pitfalls is provided here.
8
+ 2. **Humans** who received code from an AI with these errors and don't understand what's wrong.
9
+
10
+ ---
11
+
12
+ ## Pitfall #1: `${signal()}` instead of `${() => signal()}`
13
+
14
+ **Symptom:** the value in the template is displayed but does not update when the signal changes.
15
+
16
+ ```ts
17
+ const count = signal(0);
18
+
19
+ // ❌ AI often generates this
20
+ html`<div>Count: ${count() * 2}</div>`
21
+ // → Will render "Count: 0" and never update again.
22
+ // count() is read once when the TemplateResult is created.
23
+
24
+ // ✅ Correct — getter function
25
+ html`<div>Count: ${() => count() * 2}</div>`
26
+ // → Mado will create an effect() for this function and re-render when count changes.
27
+
28
+ // ✅ Also correct — the signal itself is a function
29
+ html`<div>Count: ${count}</div>`
30
+ ```
31
+
32
+ **Rule:**
33
+ - If the `${...}` contains an **expression** (something is done with the signal) — wrap it in `() => ...`.
34
+ - If the `${...}` contains **the signal itself** — it can be used as-is.
35
+
36
+ This applies to **child bindings** (text inside tags) and to **value attributes** (`@click`, `.prop`, `?attr`, regular attributes).
37
+
38
+ ---
39
+
40
+ ## Pitfall #2: `<button disabled=${loading}>` instead of `?disabled`
41
+
42
+ **Symptom:** the button is not disabled, or is always disabled.
43
+
44
+ ```ts
45
+ const loading = signal(false);
46
+
47
+ // ❌ This is setAttribute("disabled", "false") — the DOM treats this as disabled
48
+ html`<button disabled=${loading()}>Save</button>`
49
+
50
+ // ✅ Correct — boolean binding (toggle attribute)
51
+ html`<button ?disabled=${loading}>Save</button>`
52
+ ```
53
+
54
+ **Rules for attributes:**
55
+ | Prefix | What it does | When to use |
56
+ |---|---|---|
57
+ | `attr=` | `setAttribute("attr", value)` | strings, numbers, URLs |
58
+ | `.attr=` | `el.attr = value` (DOM property) | objects, arrays, input `.value` |
59
+ | `?attr=` | toggle attribute (by truthiness) | `disabled`, `hidden`, `checked`, etc |
60
+ | `@evt=` | `addEventListener("evt", fn)` | event handlers |
61
+
62
+ ---
63
+
64
+ ## Pitfall #3: useState / useEffect style
65
+
66
+ **Symptom:** AI generates React-like code that doesn't work in Mado.
67
+
68
+ ```ts
69
+ // ❌ AI often writes this
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 in 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-subscribe, disposed automatically
82
+ return () => html`
83
+ <button @click=${() => count.update(c => c + 1)}>${count}</button>
84
+ `;
85
+ });
86
+ ```
87
+
88
+ **Key differences:**
89
+ - No hooks, no hook rules.
90
+ - `signal()` can be created anywhere — in setup, in an effect, in a handler.
91
+ - `effect()` sees what it read on its own — no dependency array needed.
92
+ - A component is `component("x-name", setup)`, not a JSX function.
93
+
94
+ ---
95
+
96
+ ## Pitfall #4: `useEffect(() => { ... return cleanup })`
97
+
98
+ **Symptom:** AI writes `return cleanup` inside an effect, expecting it to work like in React.
99
+
100
+ ```ts
101
+ // ❌ AI tries to write this
102
+ component("x-timer", () => {
103
+ effect(() => {
104
+ const id = setInterval(..., 1000);
105
+ return () => clearInterval(id); // will NOT work, use ctx.onDispose instead
106
+ });
107
+ return () => html`...`;
108
+ });
109
+
110
+ // ✅ Correct: cleanup 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()` does support `return cleanup`, but this is a **per-run cleanup** (runs before the next effect execution), not an unmount cleanup. For unmount cleanup use `ctx.onDispose`.
119
+
120
+ ---
121
+
122
+ ## Pitfall #5: Component as a class or with a decorator
123
+
124
+ **Symptom:** AI generates a Lit-style or vanilla WebComponent class.
125
+
126
+ ```ts
127
+ // ❌ AI: "let's do it like 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
+ // ❌ AI: "let's do it vanilla style"
135
+ class XCounter extends HTMLElement {
136
+ connectedCallback() { ... }
137
+ }
138
+ customElements.define("x-counter", XCounter);
139
+
140
+ // ✅ Correct: functional component()
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
+ ## Pitfall #6: imports without the `.js` extension
152
+
153
+ **Symptom:** TypeScript compiles, but the browser gets a 404.
154
+
155
+ ```ts
156
+ // ❌ AI often omits the extension
157
+ import { foo } from "./bar";
158
+ import { Home } from "./pages/home";
159
+
160
+ // ✅ Correct: ES modules in the browser require the extension
161
+ import { foo } from "./bar.js";
162
+ import { Home } from "./pages/home.js";
163
+ ```
164
+
165
+ **Why `.js` and not `.ts`:** the browser receives already-compiled JS. TypeScript is smart enough to understand `./bar.js` as a reference to `./bar.ts` at compile time.
166
+
167
+ ---
168
+
169
+ ## Pitfall #7: lists via `.map()` without keys
170
+
171
+ **Symptom:** when reordering elements, input focus is lost / CSS animations break / performance suffers on large lists.
172
+
173
+ ```ts
174
+ // ❌ Works, but not keyed: recreates DOM on every change
175
+ html`<ul>${() => items().map(t => html`<li>${t.name}</li>`)}</ul>`
176
+
177
+ // ✅ Correct: each() with a key function
178
+ import { each } from "@madojs/mado";
179
+ html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.name}</li>`)}</ul>`
180
+ ```
181
+
182
+ **Rule:** always use `each()` for lists of arrays with stable IDs. Reserve `.map()` only for static lists.
183
+
184
+ ---
185
+
186
+ ## Pitfall #8: `signal.value` or `count.get()`
187
+
188
+ **Symptom:** AI writes an API in Vue or pre-v1 Solid style.
189
+
190
+ ```ts
191
+ const count = signal(0);
192
+
193
+ // ❌ No such API
194
+ count.value
195
+ count.value = 5
196
+ count.get()
197
+
198
+ // ✅ Correct
199
+ count() // read
200
+ count.set(5) // write
201
+ count.update(n => n + 1)
202
+ count.peek() // read without subscribing
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Pitfall #9: `provide(ApiCtx, value)` without host
208
+
209
+ **Symptom:** TypeError when trying to provide context.
210
+
211
+ ```ts
212
+ // ❌ AI forgets host
213
+ provide(ApiCtx, myApi);
214
+ inject(ApiCtx);
215
+
216
+ // ✅ Correct: first argument is host (the current component)
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<value>
224
+ return () => html`...`;
225
+ });
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Pitfall #10: expecting SSR
231
+
232
+ **Symptom:** AI writes code assuming the page is pre-rendered on the server.
233
+
234
+ ```ts
235
+ // ❌ This works only in the browser
236
+ const userId = location.pathname.split("/")[2];
237
+
238
+ // ❌ This too works only in the browser
239
+ if (typeof window !== "undefined") { ... } // in Mado, window is ALWAYS available
240
+ ```
241
+
242
+ Mado **does not do SSR with hydration**. Code does not run on the server — there is only `bake` (static prerender at build time) and edge-prerender. Both replace user code with a linkedom environment, but this is **only** for generating HTML with meta tags, not for executing page logic.
243
+
244
+ This means:
245
+ - ✅ `window`, `document`, `location`, `fetch` — available without checks.
246
+ - ❌ Don't write code that tries to "universally work on server and client".
247
+ - ❌ Don't use Next.js patterns (`getServerSideProps`, `headers()`).
248
+
249
+ ---
250
+
251
+ ## Pitfall #11: `useForm()` with a zod/yup resolver
252
+
253
+ **Symptom:** AI wants to plug in a validator.
254
+
255
+ ```ts
256
+ // ❌ No such API
257
+ const f = useForm({ resolver: zodResolver(schema) });
258
+
259
+ // ✅ Correct: HTML5 validation via attributes
260
+ const f = useForm({
261
+ email: { required: true, type: "email" },
262
+ age: { required: true, type: "number", min: 18 },
263
+ });
264
+
265
+ // ✅ Or a custom function if HTML5 isn't enough
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 = "Name must not contain digits";
273
+ }
274
+ return Object.keys(errors).length ? errors : null;
275
+ },
276
+ },
277
+ );
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Pitfall #12: Tailwind / styled-components / CSS Modules
283
+
284
+ **Symptom:** AI suggests standard React CSS solutions.
285
+
286
+ Mado uses **Shadow DOM + `css\`\`` + CSS variables**. Global UI frameworks (Tailwind, Bootstrap-via-classes) **only work in light DOM** (`shadow: false`):
287
+
288
+ ```ts
289
+ // Light-DOM page/screen component, Tailwind classes work
290
+ component("x-admin-page", () => () => html`
291
+ <section class="bg-white shadow-lg rounded-lg p-4">
292
+ ...
293
+ </section>
294
+ `, { shadow: false });
295
+
296
+ // Shadow-DOM component (default) — Tailwind does NOT work.
297
+ // Use css`` or ::part() for external styling.
298
+ component("x-button", () => () => html`<button><slot></slot></button>`, {
299
+ styles: css`
300
+ button {
301
+ background: var(--button-bg, #2563eb);
302
+ color: white;
303
+ padding: .5rem 1rem;
304
+ border-radius: 6px;
305
+ }
306
+ `,
307
+ });
308
+ ```
309
+
310
+ **Themes and customization — via CSS variables**, not classes.
311
+
312
+ ---
313
+
314
+ ## Pitfall #13: `import * as Mado from "@madojs/mado"`
315
+
316
+ **Symptom:** AI wants a namespace import.
317
+
318
+ This works, but duplicates names and tree-shakes poorly. Named imports are preferred:
319
+
320
+ ```ts
321
+ // ✅ Canonical
322
+ import { signal, html, component, css, page } from "@madojs/mado";
323
+
324
+ // ⚠️ Works, but excessive
325
+ import * as Mado from "@madojs/mado";
326
+ Mado.signal(0);
327
+ ```
328
+
329
+ ---
330
+
331
+ ## Pitfall #14: attempting to add a runtime dependency
332
+
333
+ **Symptom:** AI suggests `npm install lodash` / `npm install date-fns` / etc.
334
+
335
+ Mado is **zero runtime deps** by design. If AI wants to add:
336
+ - **lodash** → use native JS (`Object.entries`, `Array.prototype`, `structuredClone`);
337
+ - **date-fns** → use `Intl.DateTimeFormat` and `Intl.RelativeTimeFormat`;
338
+ - **uuid** → `crypto.randomUUID()`;
339
+ - **axios** → native `fetch` + `jsonFetcher()` from Mado;
340
+ - **classnames** → native template literal or an object map.
341
+
342
+ Any runtime dependency is a **violation of the framework's principles**. If you truly cannot avoid it — add it to the user project, not to the Mado core.
343
+
344
+ ---
345
+
346
+ ## Pitfall #15: inline `<style>` inside page templates
347
+
348
+ **Symptom:** AI puts a large `<style>` directly inside a `html\`\`` page.
349
+
350
+ ```ts
351
+ // ❌ Works, but scales poorly and complicates cleanup
352
+ page({
353
+ view: () => html`
354
+ <style>.panel { padding: 1rem; }</style>
355
+ <section class="panel">...</section>
356
+ `,
357
+ });
358
+
359
+ // ✅ Correct: component styles via css``
360
+ component("x-admin-panel", () => () => html`
361
+ <section class="panel">...</section>
362
+ `, {
363
+ styles: css`
364
+ .panel { padding: 1rem; }
365
+ `,
366
+ });
367
+ ```
368
+
369
+ For backend admin route/page screens it is often appropriate to use `shadow: false`, so that
370
+ global layout/form/table utilities work like a regular admin panel. But if
371
+ the layout uses `<slot>` to project the page into the shell, keep the layout in
372
+ Shadow DOM and keep the shell styles in `styles: css\`\``.
373
+
374
+ ---
375
+
376
+ ## Pitfall #16: Shadow DOM links without `data-link`
377
+
378
+ **Symptom:** a link inside a Web Component causes a full page reload or is not
379
+ prefetched.
380
+
381
+ ```ts
382
+ // ❌ Regular link: browser will perform a full reload
383
+ html`<a href="/tickets/42">Open</a>`
384
+
385
+ // ✅ SPA navigation: router() will intercept the click even through Shadow DOM
386
+ html`<a href="/tickets/42" data-link>Open</a>`
387
+ ```
388
+
389
+ Mado finds the link via `event.composedPath()`, so `data-link` works
390
+ inside Shadow DOM as well. Hover-prefetch uses the same path; `data-no-prefetch`
391
+ disables prefetch for a specific link.
392
+
393
+ ---
394
+
395
+ ## Pitfall #17: `resource()` outside component setup
396
+
397
+ **Symptom:** AI creates a resource in module scope to "reuse"
398
+ data between pages.
399
+
400
+ ```ts
401
+ // ❌ No lifecycle cleanup, will emit dev-warning
402
+ const tickets = resource(() => "tickets", () => api.listTickets());
403
+
404
+ component("x-tickets", () => {
405
+ return () => html`${() => tickets.data()?.length ?? 0}`;
406
+ });
407
+
408
+ // ✅ Create resource inside the component setup
409
+ component("x-tickets", () => {
410
+ const tickets = resource(() => "tickets", () => api.listTickets());
411
+ return () => html`${() => tickets.data()?.length ?? 0}`;
412
+ });
413
+ ```
414
+
415
+ This way invalidation subscriptions, abort controllers, and effects will be
416
+ cleaned up when the component disconnects.
417
+
418
+ ---
419
+
420
+ ## Pitfall #18: assuming nested templates don't require cleanup
421
+
422
+ **Symptom:** AI assembles a route outlet or conditional UI from nested
423
+ `TemplateResult`s, and then old elements continue living below the new page.
424
+
425
+ ```ts
426
+ const view = signal(html`<x-home></x-home>`);
427
+
428
+ // ✅ Normal pattern: nested TemplateResult can be returned from a child-binding
429
+ html`${view}`
430
+ ```
431
+
432
+ Starting from v0.3 this is guaranteed by regression tests: when a child-binding is
433
+ replaced, Mado recursively disposes nested template instances/effects. If you see
434
+ pages accumulating in `#app`, that is a core bug, not something you need to
435
+ clean up manually.
436
+
437
+ ---
438
+
439
+ ## Pitfall #19: global CSS utilities inside Shadow DOM
440
+
441
+ **Symptom:** the page looks "unstyled": `.page-head`, `.btn`,
442
+ `.form-grid`, `.metric-grid` are not applied.
443
+
444
+ ```ts
445
+ // ❌ .page-head is declared globally, but x-dashboard defaults to Shadow DOM
446
+ component("x-dashboard", () => () => html`
447
+ <header class="page-head">...</header>
448
+ <div class="metric-grid">...</div>
449
+ `);
450
+
451
+ // ✅ Page/layout/admin-shell components often should be Light DOM
452
+ component("x-dashboard", () => () => html`
453
+ <header class="page-head">...</header>
454
+ <div class="metric-grid">...</div>
455
+ `, { shadow: false });
456
+ ```
457
+
458
+ Rule: Shadow DOM — for leaf widgets and slot-based layouts, Light DOM — for
459
+ route/page/admin-screen components that intentionally use shared
460
+ layout/form/table utilities. Remember: `<slot>` only projects children in
461
+ Shadow DOM; with `shadow: false` it is a regular element.
462
+ More details: [`09-shadow-vs-light-dom.md`](./09-shadow-vs-light-dom.md).
463
+
464
+ ---
465
+
466
+ ## Cheat-sheet for AI
467
+
468
+ | If you want to do… | Correct in Mado |
469
+ |---|---|
470
+ | `useState(0)` | `signal(0)` |
471
+ | `useEffect(() => {...}, [a, b])` | `effect(() => {...})` (auto-deps) |
472
+ | `useEffect(() => return cleanup, [])` | `ctx.onDispose(cleanup)` |
473
+ | `useMemo(() => x, [a])` | `computed(() => x)` |
474
+ | `useCallback(fn, [])` | ordinary function |
475
+ | `useContext(Ctx)` | `inject(host, Ctx)` |
476
+ | `useQuery(['key'], fn)` | `resource(() => 'key', fn)` |
477
+ | `useMutation(fn)` | `mutation(fn, { invalidates: [...] })` |
478
+ | `useRouter().push('/')` | `navigate('/')` |
479
+ | `useRouter().query.q` | `queryParam('q')` |
480
+ | `<input value={v} onChange={...}>` | `<input .value=${v} @input=${...}>` |
481
+ | `{items.map(x => ...)}` | `${() => each(items, x => x.id, x => ...)}` |
482
+ | `useForm({ resolver: zodResolver })` | `useForm({...}, { validate: (v) => ... })` |
483
+ | `class extends HTMLElement` | `component('x-name', setup)` |
484
+ | `@customElement('x')` | `component('x-name', setup)` |
485
+
486
+ If something doesn't fit this list — open `src/` and **read 500 lines**. Seriously. Mado is intentionally small to be readable.
@@ -0,0 +1,56 @@
1
+ # LLM zero-history test
2
+
3
+ This document defines a practical validation test for Mado.
4
+
5
+ The question is not "can an LLM generate frontend code?" It can. The question is:
6
+ can a fresh LLM write idiomatic Mado without falling back to React-shaped code?
7
+
8
+ ## Allowed context
9
+
10
+ For the first pass, give the agent only:
11
+
12
+ - `AGENTS.md`
13
+ - `README.md`
14
+ - `docs/ru/07-llm-pitfalls.md`
15
+ - `examples/basic/README.md` if a minimal API tour is needed
16
+ - specific `examples/showcase/**` files only when the agent asks for a larger app pattern
17
+
18
+ The agent may search targeted APIs in `src/` when blocked, but should not load
19
+ the whole framework into context.
20
+
21
+ ## Task
22
+
23
+ Build `examples/tickets`: a small ticket-admin SPA for a solo/backend developer.
24
+
25
+ Required behavior:
26
+
27
+ - routes: `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*`;
28
+ - in-memory mock API with realistic async delays;
29
+ - list page with `resource()`, `queryParam()` search/status filters, `computed()`,
30
+ and keyed `each()` rows;
31
+ - create and edit flows with `useForm()` + `mutation()` + `invalidates`;
32
+ - local UI state with `signal()`;
33
+ - slotted shell, metric, and badge components for a more realistic admin UI;
34
+ - smoke test importing the built example.
35
+
36
+ ## Failure checklist
37
+
38
+ Look for these after implementation:
39
+
40
+ - JSX, `useState`, `useEffect`, `ref`, `$state`, or class-style components;
41
+ - `${signal()}` or `${signal() + 1}` where a reactive child thunk is required;
42
+ - `disabled=${...}` instead of `?disabled=${...}`;
43
+ - dynamic lists rendered with unkeyed array mapping instead of `each()`;
44
+ - browser ESM imports without `.js`;
45
+ - `resource()` created outside component setup;
46
+ - new runtime dependencies or new public framework APIs.
47
+
48
+ ## Result notes
49
+
50
+ The current `examples/tickets` implementation did not require new public APIs or
51
+ runtime dependencies.
52
+
53
+ The main documentation pressure point remains lifecycle: older examples can make
54
+ it look acceptable to create `resource()` directly in `page.view()`. The tickets
55
+ example uses page-level wrapper components instead, so resources are registered
56
+ inside component setup and clean up with the component.
@@ -0,0 +1,122 @@
1
+ # Shadow DOM vs Light DOM
2
+
3
+ Mado components use Shadow DOM by default. This is a good default for
4
+ self-contained widgets, but it is not the right default for every component in
5
+ an application.
6
+
7
+ ## Rule of Thumb
8
+
9
+ Use **Shadow DOM** for leaf widgets:
10
+
11
+ - buttons, badges, cards, metrics;
12
+ - modals, toasts, small visual components;
13
+ - embed widgets that should not inherit app CSS accidentally;
14
+ - components whose styling should be owned by the component itself.
15
+
16
+ Use **Light DOM** (`{ shadow: false }`) for app structure that wants to share
17
+ global CSS utilities:
18
+
19
+ - route/page components;
20
+ - admin screens with dense table/form layouts;
21
+ - data-heavy screens with tables and forms;
22
+ - components that intentionally share global layout, form and table utilities;
23
+ - places where children should simply remain normal document DOM.
24
+
25
+ ## The Footgun
26
+
27
+ Global CSS does not cross a Shadow DOM boundary.
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 and .metric-grid will not apply inside x-dashboard shadowRoot
37
+ component("x-dashboard", () => () => html`
38
+ <header class="page-head">...</header>
39
+ <div class="metric-grid">...</div>
40
+ `);
41
+ ```
42
+
43
+ Fix it by making the route/page component 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
+ Now global utilities and local scoped styles both work.
59
+
60
+ ## How Styles Behave
61
+
62
+ - `styles: css\`\`` in Shadow DOM is adopted into the component shadowRoot.
63
+ - `styles: css\`\`` with `shadow: false` is scoped to the tag name and adopted
64
+ globally.
65
+ - CSS custom properties (`--accent`, `--bg`, etc.) cross Shadow DOM boundaries.
66
+ - Class selectors like `.btn`, `.form-grid`, `.page-head` do **not** cross
67
+ Shadow DOM boundaries.
68
+ - Slotted children keep their own document styles; the shadow component can only
69
+ target them through `::slotted(...)`.
70
+ - `<slot>` projects children only in Shadow DOM. In a `shadow: false` component
71
+ it is just a normal `<slot>` element and will not move children into that
72
+ place in your layout.
73
+
74
+ ## Recommended App Shape
75
+
76
+ ```ts
77
+ // root and pages: Light DOM
78
+ component("x-app", setup, { shadow: false });
79
+ component("x-users-page", setup, { shadow: false });
80
+
81
+ // slot-based layout: Shadow DOM default, because it owns the shell grid
82
+ component("x-app-layout", setup);
83
+
84
+ // leaf widgets: Shadow DOM default
85
+ component("x-status-badge", setup);
86
+ component("x-stat-card", setup);
87
+ component("x-toast-stack", setup);
88
+ ```
89
+
90
+ This gives backend-admin screens predictable CSS while preserving encapsulation
91
+ for reusable widgets and slot-based shells.
92
+
93
+ If a layout does not need slot projection and should be styled entirely by
94
+ global CSS, `shadow: false` can still be a good choice. If it contains
95
+ `<slot>`, keep Shadow DOM and put the shell styles in that component.
96
+
97
+ ## Routing and Links
98
+
99
+ `data-link` works inside Shadow DOM. The router uses `event.composedPath()`, so
100
+ click interception and hover-prefetch can see links from open shadow roots.
101
+
102
+ ```ts
103
+ component("x-card-link", () => () => html`
104
+ <a href="/app/accounts" data-link>Accounts</a>
105
+ `);
106
+ ```
107
+
108
+ The link can be in Shadow DOM; navigation still stays SPA.
109
+
110
+ ## Showcase Lesson
111
+
112
+ `examples/showcase` uses this split deliberately:
113
+
114
+ - `x-app` and CRM route pages are Light DOM;
115
+ - `x-app-layout` keeps Shadow DOM because it owns a slot-based sidebar/content
116
+ shell;
117
+ - table/form/page utilities live in `styles/global.ts`;
118
+ - leaf components such as `x-stat-card`, `x-status-badge`, `x-modal`, and
119
+ `x-toast-stack` keep Shadow DOM.
120
+
121
+ If a page suddenly looks unstyled, check whether it uses global classes inside a
122
+ Shadow DOM component. That is usually the issue.
@@ -0,0 +1,16 @@
1
+ # Mado docs — English
2
+
3
+ English documentation set.
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) |