@madojs/mado 0.11.0 → 0.12.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 (221) hide show
  1. package/AGENTS.md +102 -25
  2. package/CHANGELOG.md +185 -1
  3. package/README.md +137 -172
  4. package/dist/src/component.d.ts +3 -2
  5. package/dist/src/component.js +98 -6
  6. package/dist/src/component.js.map +1 -1
  7. package/dist/src/css.js +34 -5
  8. package/dist/src/css.js.map +1 -1
  9. package/dist/src/head.d.ts +6 -6
  10. package/dist/src/head.js +6 -6
  11. package/dist/src/html/bindings.js +3 -3
  12. package/dist/src/html/bindings.js.map +1 -1
  13. package/dist/src/html/parser.d.ts +1 -1
  14. package/dist/src/html/parser.js +1 -1
  15. package/dist/src/html/template.js +24 -10
  16. package/dist/src/html/template.js.map +1 -1
  17. package/dist/src/index.d.ts +8 -4
  18. package/dist/src/index.js +13 -3
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/page.d.ts +62 -28
  21. package/dist/src/page.js +5 -0
  22. package/dist/src/page.js.map +1 -1
  23. package/dist/src/resource.js +2 -1
  24. package/dist/src/resource.js.map +1 -1
  25. package/dist/src/router/base.d.ts +65 -0
  26. package/dist/src/router/base.js +168 -0
  27. package/dist/src/router/base.js.map +1 -0
  28. package/dist/src/router/manifest.js +75 -31
  29. package/dist/src/router/manifest.js.map +1 -1
  30. package/dist/src/router/match.d.ts +16 -2
  31. package/dist/src/router/match.js +41 -1
  32. package/dist/src/router/match.js.map +1 -1
  33. package/dist/src/router/navigation.js +58 -9
  34. package/dist/src/router/navigation.js.map +1 -1
  35. package/dist/src/static-runtime.d.ts +81 -0
  36. package/dist/src/static-runtime.js +209 -0
  37. package/dist/src/static-runtime.js.map +1 -0
  38. package/dist/src/vite/index.d.ts +31 -3
  39. package/dist/src/vite/index.js +49 -18
  40. package/dist/src/vite/index.js.map +1 -1
  41. package/docs/README.md +5 -8
  42. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  43. package/docs/en/00-the-mado-way.md +1 -1
  44. package/docs/en/01-quickstart.md +183 -0
  45. package/docs/en/10-pages-and-components.md +271 -0
  46. package/docs/en/11-templates-and-signals.md +211 -0
  47. package/docs/en/12-routing.md +229 -0
  48. package/docs/en/13-data.md +225 -0
  49. package/docs/en/14-forms.md +244 -0
  50. package/docs/en/15-static-snapshots.md +181 -0
  51. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +53 -2
  52. package/docs/en/{13-deployment.md → 20-deployment.md} +59 -10
  53. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  54. package/docs/en/23-cookbook.md +292 -0
  55. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +12 -3
  56. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +3 -3
  57. package/docs/en/40-llm-guide.md +776 -0
  58. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +15 -16
  59. package/docs/en/{05-why-mado.md → 42-why-mado.md} +1 -1
  60. package/docs/en/README.md +97 -27
  61. package/docs/recipes/nginx/Containerfile +26 -0
  62. package/docs/recipes/nginx/nginx.conf +42 -0
  63. package/llms.txt +246 -211
  64. package/package.json +17 -10
  65. package/scripts/bake.mjs +4 -568
  66. package/scripts/cli/generate.mjs +55 -5
  67. package/scripts/cli/help.mjs +11 -5
  68. package/scripts/cli/index.mjs +20 -4
  69. package/scripts/cli/init.mjs +7 -2
  70. package/scripts/cli/release.mjs +20 -68
  71. package/scripts/docs-lint.mjs +170 -0
  72. package/scripts/package-smoke.mjs +67 -9
  73. package/scripts/preview.mjs +95 -6
  74. package/scripts/size-budget.mjs +5 -1
  75. package/scripts/static/browser.mjs +654 -0
  76. package/scripts/static/discover.mjs +281 -0
  77. package/scripts/static/output.mjs +141 -0
  78. package/scripts/static/serialize.mjs +212 -0
  79. package/scripts/static/server.mjs +167 -0
  80. package/scripts/static.mjs +191 -0
  81. package/starters/default/README.md +57 -55
  82. package/starters/default/package.json +3 -9
  83. package/starters/default/src/app.routes.ts +20 -33
  84. package/starters/default/src/components/feature-card.component.ts +40 -0
  85. package/starters/default/src/components/live-counter.component.ts +57 -0
  86. package/starters/default/src/content/guides.ts +55 -0
  87. package/starters/default/src/main.ts +9 -13
  88. package/starters/default/src/pages/app.page.ts +41 -0
  89. package/starters/default/src/pages/guide.page.ts +40 -0
  90. package/starters/default/src/pages/home.page.ts +49 -0
  91. package/starters/default/src/pages/not-found.page.ts +18 -0
  92. package/starters/default/src/styles/document.css +39 -0
  93. package/starters/default/src/styles/reset.css +37 -0
  94. package/starters/default/src/styles/tokens.css +30 -0
  95. package/starters/default/src/styles.d.ts +3 -1
  96. package/starters/default/src/vite-env.d.ts +1 -1
  97. package/starters/default/vite.config.ts +14 -1
  98. package/starters/modular/README.md +142 -0
  99. package/starters/modular/index.html +13 -0
  100. package/starters/modular/package.json +30 -0
  101. package/starters/modular/src/app.routes.ts +39 -0
  102. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +8 -5
  103. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  104. package/starters/modular/src/main.ts +16 -0
  105. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +3 -3
  106. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  107. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  108. package/starters/modular/src/styles.d.ts +1 -0
  109. package/starters/modular/src/vite-env.d.ts +1 -0
  110. package/starters/modular/tsconfig.json +24 -0
  111. package/starters/modular/vite.config.ts +131 -0
  112. package/TODO.md +0 -79
  113. package/docs/en/01-routing.md +0 -152
  114. package/docs/en/02-project-layout.md +0 -124
  115. package/docs/en/03-static-bake.md +0 -249
  116. package/docs/en/04-ide-setup.md +0 -162
  117. package/docs/en/07-llm-pitfalls.md +0 -724
  118. package/docs/en/08-llm-zero-history-test.md +0 -56
  119. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  120. package/docs/en/11-layouts.md +0 -113
  121. package/docs/en/12-auth-and-api.md +0 -124
  122. package/docs/en/16-bake-cookbook.md +0 -100
  123. package/docs/en/17-shadow-dom-forms.md +0 -192
  124. package/docs/fr/00-the-mado-way.md +0 -85
  125. package/docs/fr/01-routing.md +0 -120
  126. package/docs/fr/02-project-layout.md +0 -84
  127. package/docs/fr/03-static-bake.md +0 -289
  128. package/docs/fr/04-ide-setup.md +0 -162
  129. package/docs/fr/05-why-mado.md +0 -193
  130. package/docs/fr/06-for-backenders.md +0 -438
  131. package/docs/fr/07-llm-pitfalls.md +0 -625
  132. package/docs/fr/08-llm-zero-history-test.md +0 -38
  133. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  134. package/docs/fr/10-app-architecture.md +0 -138
  135. package/docs/fr/11-layouts.md +0 -47
  136. package/docs/fr/12-auth-and-api.md +0 -76
  137. package/docs/fr/13-deployment.md +0 -57
  138. package/docs/fr/14-testing.md +0 -41
  139. package/docs/fr/15-error-handling.md +0 -50
  140. package/docs/fr/16-bake-cookbook.md +0 -88
  141. package/docs/fr/17-shadow-dom-forms.md +0 -196
  142. package/docs/fr/18-api-freeze-map.md +0 -63
  143. package/docs/fr/19-reactivity-ordering.md +0 -97
  144. package/docs/fr/20-v1-stability.md +0 -88
  145. package/docs/fr/README.md +0 -27
  146. package/docs/ru/00-the-mado-way.md +0 -84
  147. package/docs/ru/01-routing.md +0 -119
  148. package/docs/ru/02-project-layout.md +0 -84
  149. package/docs/ru/03-static-bake.md +0 -250
  150. package/docs/ru/04-ide-setup.md +0 -144
  151. package/docs/ru/05-why-mado.md +0 -193
  152. package/docs/ru/06-for-backenders.md +0 -428
  153. package/docs/ru/07-llm-pitfalls.md +0 -624
  154. package/docs/ru/08-llm-zero-history-test.md +0 -57
  155. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  156. package/docs/ru/10-app-architecture.md +0 -152
  157. package/docs/ru/11-layouts.md +0 -47
  158. package/docs/ru/12-auth-and-api.md +0 -75
  159. package/docs/ru/13-deployment.md +0 -66
  160. package/docs/ru/14-testing.md +0 -50
  161. package/docs/ru/15-error-handling.md +0 -56
  162. package/docs/ru/16-bake-cookbook.md +0 -95
  163. package/docs/ru/17-shadow-dom-forms.md +0 -193
  164. package/docs/ru/18-api-freeze-map.md +0 -64
  165. package/docs/ru/19-reactivity-ordering.md +0 -95
  166. package/docs/ru/20-v1-stability.md +0 -82
  167. package/docs/ru/README.md +0 -25
  168. package/docs/uk/00-the-mado-way.md +0 -82
  169. package/docs/uk/01-routing.md +0 -76
  170. package/docs/uk/02-project-layout.md +0 -73
  171. package/docs/uk/03-static-bake.md +0 -48
  172. package/docs/uk/04-ide-setup.md +0 -26
  173. package/docs/uk/05-why-mado.md +0 -34
  174. package/docs/uk/06-for-backenders.md +0 -55
  175. package/docs/uk/07-llm-pitfalls.md +0 -145
  176. package/docs/uk/08-llm-zero-history-test.md +0 -34
  177. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  178. package/docs/uk/10-app-architecture.md +0 -97
  179. package/docs/uk/11-layouts.md +0 -47
  180. package/docs/uk/12-auth-and-api.md +0 -70
  181. package/docs/uk/13-deployment.md +0 -40
  182. package/docs/uk/14-testing.md +0 -34
  183. package/docs/uk/15-error-handling.md +0 -32
  184. package/docs/uk/16-bake-cookbook.md +0 -36
  185. package/docs/uk/17-shadow-dom-forms.md +0 -193
  186. package/docs/uk/18-api-freeze-map.md +0 -61
  187. package/docs/uk/19-reactivity-ordering.md +0 -95
  188. package/docs/uk/20-v1-stability.md +0 -83
  189. package/docs/uk/README.md +0 -27
  190. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  191. /package/docs/en/{15-error-handling.md → 21-error-handling.md} +0 -0
  192. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  193. /package/starters/{default → modular}/.editorconfig +0 -0
  194. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  195. /package/starters/{default → modular}/public/favicon.svg +0 -0
  196. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  197. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  198. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  199. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  200. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  201. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  202. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  203. /package/starters/{default → modular}/src/modules/auth/login.page.ts +0 -0
  204. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  205. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  206. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  207. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  208. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  209. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  210. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  211. /package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +0 -0
  212. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  213. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  214. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  215. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  216. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  217. /package/starters/{default → modular}/src/shared/styles/reset.css +0 -0
  218. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  219. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  220. /package/starters/{default → modular}/src/shared/ui/x-button.component.ts +0 -0
  221. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
package/llms.txt CHANGED
@@ -1,281 +1,316 @@
1
1
  # Mado
2
2
 
3
- > A calm browser-native SPA framework for internal tools, admin panels and business apps.
4
- > Routing, forms, state, data fetching and prerendering. Zero runtime dependencies; generated apps use `typescript`, `esbuild` and `linkedom` as dev tooling.
5
-
6
- Mado is a focused frontend framework for admin panels, internal tools and CRUD-heavy SPA. It deliberately avoids React patterns (no JSX, no hooks, no VDOM, no Vite). Target audience: backend developers and small teams who want a complete app stack without frontend infrastructure overhead.
7
-
8
- ## Key things an AI assistant needs to know
9
-
10
- - **This is NOT React, NOT Lit, NOT Solid.** When generating code for Mado, **do not use JSX, `useState`, `useEffect`, classes, decorators, `requestUpdate`**. That will be an error.
11
- - **All templates are tagged template `html\`...\``** (not JSX). Allowed bindings inside: `${value}`, `attr=${v}`, `@evt=${fn}`, `.prop=${v}`, `?attr=${flag}`.
12
- - **Reactivity via signals.** `signal()`, `computed()`, `effect()`. A signal is a getter function (`count()`), not an object field (`count.value`).
13
- - **Components are Web Components.** Registered via `component('x-name', setupFn, options)`. Names must include a hyphen (`x-foo`, `my-button`).
14
- - **Component files register tags as side effects.** The browser does not auto-import files by tag name. If `<x-card>` works, some imported module already ran `customElements.define("x-card", ...)`.
15
- - **Cleanup via `ctx.onDispose(fn)`** in setup, not via return from effect.
16
- - **Page cleanup via `onDispose`** in view: `view: ({ onDispose }) => { ... onDispose(() => cleanup()); }`. Only needed for raw APIs (setInterval, WebSocket). `resource()`/`effect()` auto-cleanup.
17
- - **`untracked()` in page view async** — functions called synchronously in `view()` that read signals must wrap reads in `untracked()` to avoid effect cycles with the router.
18
- - **Reactive attributes via `ctx.attr(name, default?)`** — returns a Signal<string> that auto-updates when the attribute changes via a per-instance `MutationObserver`. No `observedAttributes` option, no boilerplate.
19
- - **Public imports only.** App code imports from `@madojs/mado` and optionally side-effect `@madojs/mado/devtools.js`. Other package subpaths and `dist/src/*` are internal.
20
- - **Layouts are stateless wrappers.** Use route-manifest `layout()` and render `${child}` inside shared chrome. Put per-page state in pages/components/resources, not in layout view locals keyed by route identity.
21
- - **Bake is a static meta-shell/prerender pass.** It is not SSR with hydration and not a Next-style SSG runtime.
3
+ > A calm native-first web framework for both static sites and live
4
+ > SPAs. One Web Component model, one page model, one release command.
5
+ > Browser-native source, Vite as the canonical development and delivery
6
+ > transport, zero runtime dependencies.
7
+
8
+ Current stable release: **0.12.0**.
9
+
10
+ Mado deliberately rejects compilers (no JSX, no special template
11
+ language), VDOM diffing, and SSR/hydration. Static documents are
12
+ captured by a real headless Chromium during `mado release`, including
13
+ the Shadow DOM through Declarative Shadow DOM (DSD), and the live SPA
14
+ then atomically replaces the static tree with no hydration protocol or
15
+ node reconciliation.
16
+
17
+ ## What an AI assistant must never invent
18
+
19
+ - **This is NOT React, NOT Next, NOT Lit, NOT Solid.** Do not write
20
+ JSX, `useState`, `useEffect`, decorators, classes-with-render or
21
+ `requestUpdate`. Those patterns will not compile.
22
+ - **Vite is the canonical transport.** Every starter, the CLI and the
23
+ static snapshot pipeline run through `@madojs/mado/vite`. The
24
+ framework runtime reads `import.meta.env.BASE_URL` once and exposes
25
+ it through `appBase` / `routeUrl()`.
26
+ <!-- docs-lint:allow-legacy-mention -->
27
+ - **There is no `bake` API.** The old `bake.paths`, `bake.data`,
28
+ `bake.revalidate`, `npm run bake`, "Smart Static" and "no Chromium"
29
+ language refer to a removed system. A page becomes static by
30
+ declaring `static: true` (or `static: { paths, initialData }`); the
31
+ CLI command is `mado static`, normally invoked as part of
32
+ `mado release`.
33
+ <!-- /docs-lint:allow-legacy-mention -->
34
+ - **`page()` is a semantic document.** It renders in the light DOM and
35
+ owns route + load + head + view + optional static declaration.
36
+ `component()` is the unit of Web Component encapsulation; by default
37
+ it attaches an open Shadow DOM. `{ shadow: false }` exists as an
38
+ advanced escape hatch when a custom element MUST live in the light
39
+ DOM (e.g. to participate in a parent form). Pages are never built
40
+ with `component()` and components never participate in routing.
41
+ <!-- docs-lint:allow-legacy-mention -->
42
+ - **A compatible Chromium is required at release time.** `mado static`
43
+ spawns Playwright (`playwright-core`) and captures real DOM. There is
44
+ no `linkedom`-based renderer, no `npm run bake`, no "meta-shell".
45
+ <!-- /docs-lint:allow-legacy-mention -->
22
46
 
23
47
  ## Critical template rules
24
48
 
25
- 1. **A reactive value in a template = getter function.**
26
- ```ts
27
- // ❌ NOT REACTIVE: read once at render time
28
- html`<div>${count() * 2}</div>`
29
- // REACTIVE: subscription
30
- html`<div>${() => count() * 2}</div>`
31
- ```
32
- The signal itself can also be inserted directly Mado will recognise it as a function:
33
- ```ts
34
- html`<div>${count}</div>` // ✅ ok, count is a function
35
- ```
36
-
37
- 2. **DOM properties vs HTML attributes:**
38
- - `attr=${v}` setAttribute (strings/numbers only);
39
- - `.prop=${v}` DOM property (objects, arrays, numbers without serialisation);
40
- - `?attr=${flag}` → boolean attribute (toggle attribute);
41
- - `@evt=${fn}` addEventListener.
42
-
43
- ```ts
44
- html`<input ?disabled=${isLoading} .value=${user.name} @input=${onInput}>`
45
- ```
46
-
47
- 3. **Lists — must use `each(items, keyFn, renderFn)`**, not `.map()` without a key:
48
- ```ts
49
- import { each } from "@madojs/mado";
50
- html`<ul>${() => each(users(), u => u.id, u => html`<li>${u.name}</li>`)}</ul>`
51
- ```
52
-
53
- 4. **Parser hard errors:**
54
- - no dynamic `${...}` child slots inside `<script>`, `<style>`, `<textarea>`, or `<title>`;
55
- - no nested SVG-only `html` templates inside an outer `<svg>`.
56
-
57
- ```ts
58
- // ❌ throws: RAW_TEXT elements cannot host child slots
59
- html`<textarea>${draft}</textarea>`;
60
-
61
- // ✅ bind the DOM property instead
62
- html`<textarea .value=${draft}></textarea>`;
63
-
64
- // ❌ throws: nested SVG template loses namespace context
65
- html`<svg>${html`<circle r="5"></circle>`}</svg>`;
66
-
67
- // ✅ keep SVG internals in one template
68
- html`<svg viewBox="0 0 10 10"><circle r="5"></circle></svg>`;
69
- ```
70
-
71
- ## Canonical imports
49
+ `html\`...\`` is a tagged template parser, not JSX. Only these bindings
50
+ are recognised:
51
+
52
+ - `${value}` — child content (text, nodes, arrays, nested templates, `each`)
53
+ - `attr=${v}` — HTML attribute (string/number)
54
+ - `.prop=${v}` — DOM property (objects, arrays, numbers without serialisation)
55
+ - `?attr=${flag}` — boolean attribute (toggle)
56
+ - `@event=${fn}` event listener
57
+
58
+ ```ts
59
+ // ❌ not reactive — `count()` runs once at render
60
+ html`<div>${count() * 2}</div>`
61
+
62
+ // subscription the function is re-run on signal change
63
+ html`<div>${() => count() * 2}</div>`
64
+
65
+ // the signal itself works because it is already a function
66
+ html`<div>${count}</div>`
67
+ ```
68
+
69
+ Lists must use `each(items, keyFn, renderFn)` for stable identity:
70
+
71
+ ```ts
72
+ import { each } from "@madojs/mado";
73
+ html`<ul>${() => each(users(), (u) => u.id, (u) => html`<li>${u.name}</li>`)}</ul>`
74
+ ```
75
+
76
+ The parser refuses dynamic `${...}` child slots inside `<script>`,
77
+ `<style>`, `<textarea>`, `<title>`, and refuses nested SVG-only `html`
78
+ templates inside an outer `<svg>` (namespace context is lost).
79
+
80
+ ## Public API surface
72
81
 
73
82
  ```ts
74
- // Everything you normally need — from one place:
75
83
  import {
76
- signal, computed, effect, batch,
77
- html, render,
78
- each,
79
- component,
80
- css, cssVars,
84
+ // reactivity
85
+ signal, computed, effect, untracked, batch, flushSync,
86
+ // rendering + templates
87
+ html, render, each, list, unsafeHTML, ref, classMap, styleMap,
88
+ // components
89
+ component, css, cssVars,
90
+ // routing
81
91
  routes, router, navigate, queryParam, prefetchPath,
82
- page, layout,
83
- resource, mutation, invalidate, jsonFetcher,
92
+ routeUrl, appBase,
93
+ // pages
94
+ page, layout, applyHead,
95
+ // data
96
+ resource, mutation, invalidate, jsonFetcher, HttpError,
97
+ // forms / context / persistence / lazy
84
98
  useForm,
85
99
  createContext, provide, inject,
86
- persisted,
87
- lazy,
100
+ persisted, lazy,
88
101
  } from "@madojs/mado";
102
+
103
+ // Side-effect-only:
104
+ import "@madojs/mado/devtools.js";
105
+
106
+ // Vite plugin:
107
+ import { mado } from "@madojs/mado/vite";
89
108
  ```
90
109
 
91
- ## Component ctx.attr() reactive attributes
110
+ Everything else under the package is internal and subject to change
111
+ without notice (see `docs/en/30-api-freeze-map.md`).
112
+
113
+ ## Pages and the static snapshot pipeline
114
+
115
+ A page declares its participation in static capture through the
116
+ `static` field on `page({ ... })`:
92
117
 
93
118
  ```ts
94
- component("x-badge", ({ attr }) => {
95
- const variant = attr("variant", "default"); // Signal<string>, auto-updates
96
- const size = attr("size", "md");
119
+ import { html, page } from "@madojs/mado";
97
120
 
98
- return () => html`<span class=${() => `badge-${variant()} size-${size()}`}>
99
- <slot></slot>
100
- </span>`;
121
+ export default page({
122
+ static: true, // capture as HTML at release
123
+ title: "Mado Keyboard",
124
+ head: () => ({ description: "Canonical product page." }),
125
+ view: () => html`<h1>Welcome</h1>`,
101
126
  });
102
127
  ```
103
128
 
104
- No MutationObserver boilerplate. The parent can bind with `?disabled=${...}` or
105
- plain attribute changes and the component re-renders reactively.
129
+ Dynamic static routes provide `paths()` and `initialData()`:
106
130
 
107
- ## Shadow DOM + Forms
108
-
109
- Custom inputs in Shadow DOM need two things for `useForm()` compatibility:
131
+ ```ts
132
+ export default page({
133
+ static: {
134
+ paths: async () => [{ slug: "alpha" }, { slug: "beta" }],
135
+ initialData: async ({ slug }) => loadGuide(slug),
136
+ },
137
+ title: ({ slug }) => `Guide: ${slug}`,
138
+ view: (ctx) => html`<article>${ctx.data}</article>`,
139
+ });
140
+ ```
110
141
 
111
- 1. **Proxy properties** — expose `name` and `value` on the host:
112
- ```ts
113
- Object.defineProperty(host, "name", { get: () => host.getAttribute("name") ?? "" });
114
- Object.defineProperty(host, "value", { get: () => host.shadowRoot?.querySelector("input")?.value ?? "" });
115
- ```
142
+ `mado release` does the full pipeline:
116
143
 
117
- 2. **Form submit bridge** — buttons inside Shadow DOM can't submit Light DOM forms:
118
- ```ts
119
- @click=${() => { const form = host.closest("form"); if (form) form.requestSubmit(); }}
120
- ```
144
+ 1. Vite builds the SPA.
145
+ 2. `mado static` discovers every `static: true | { ... }` page through
146
+ the routes manifest.
147
+ 3. A real headless Chromium navigates each page through an internal
148
+ capture server.
149
+ 4. The captured HTML — including DSD-serialised Shadow DOM, canonical /
150
+ `og:url` fallback and the seed `<script type="application/json">` —
151
+ is written into `out/<route>/index.html`.
152
+ 5. SPA-only routes keep the `_mado/spa.html` fallback.
153
+ 6. The internal `_mado/build.json` bridge is dropped from the deployed
154
+ artefact.
121
155
 
122
- See `docs/en/17-shadow-dom-forms.md` for the full recipe.
156
+ On first paint the live SPA takes over atomically: Mado replaces the
157
+ static tree with the live tree, no hydration protocol or node
158
+ reconciliation. The seed JSON is consumed once so `page.load()` returns
159
+ the same data the server already rendered.
123
160
 
124
- ## Auth fetcher for resource()
161
+ ## Internal links and base path
125
162
 
126
- The admin starter provides `apiFetcher<T>()` in `lib/api.ts` for protected endpoints:
163
+ Vite's `base` flows automatically into `appBase` and `routeUrl()`. App
164
+ code must use `routeUrl()` for every internal anchor so the URL stays
165
+ correct under `/`, `/mado/` or any sub-path deployment:
127
166
 
128
167
  ```ts
129
- import { resource } from "@madojs/mado";
130
- import { apiFetcher } from "../lib/api.js";
168
+ import { routeUrl } from "@madojs/mado";
131
169
 
132
- const stats = resource(() => "/api/admin/stats", apiFetcher<Stats>());
170
+ html`<a data-link href=${routeUrl("/users/42")}>User</a>`;
171
+ html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/mado/"
133
172
  ```
134
173
 
135
- Unlike `jsonFetcher()`, `apiFetcher()` attaches the Bearer token from memory.
136
- Use `jsonFetcher()` for public endpoints, `apiFetcher()` for anything behind auth.
174
+ The router only intercepts anchors that opt in with `data-link`. Bare
175
+ `<a href>` performs a full document load (intentional, for foreign URLs
176
+ and downloads).
177
+
178
+ ## Components are Shadow DOM by default
137
179
 
138
- ## Resource and mutation semantics
180
+ ```ts
181
+ import { component, css, html, signal } from "@madojs/mado";
139
182
 
140
- - A `resource()` key is the cache identity. Same key means shared cache and
141
- deduped in-flight request; use distinct keys for distinct data or auth scope.
142
- - `mutation().run()` is concurrent by default. `loading()` stays true while any
143
- run is in flight. Use `{ abortPrevious: true }` only for search-as-you-type or
144
- "latest request wins" flows.
145
- - `invalidates` runs after a successful mutation and is best-effort: errors are
146
- logged, but the mutation result stays successful.
183
+ component(
184
+ "x-counter",
185
+ () => {
186
+ const n = signal(0);
187
+ return () => html`<button @click=${() => n.set(n() + 1)}>${n}</button>`;
188
+ },
189
+ {
190
+ styles: css`
191
+ :host { display: inline-block; }
192
+ button { padding: .5rem 1rem; }
193
+ `,
194
+ },
195
+ );
196
+ ```
147
197
 
148
- ## Layouts and bake
198
+ - Custom element names MUST include a hyphen.
199
+ - Setup runs once per host; the returned function is the reactive view.
200
+ - `ctx.attr("name", "default")` returns a `Signal<string>` that
201
+ auto-updates on attribute changes.
202
+ - `ctx.onDispose(fn)` registers cleanup for raw APIs (`setInterval`,
203
+ `WebSocket`); `resource()` / `effect()` clean themselves up.
204
+ - `{ shadow: false }` exists for components that MUST live in the light
205
+ DOM (form participation, host-level styling). It is an escape hatch,
206
+ not the default.
149
207
 
150
- Use `layout()` in `src/app.routes.ts` for shared shells. Modules export plain
151
- route maps; app routes decide which layout and guard wrap each zone. A layout
152
- view should be a pure wrapper around `${child}` and shared chrome:
208
+ ## Resources and mutations
153
209
 
154
210
  ```ts
155
- export default page({
156
- view: ({ child }) => html`<x-app-shell>${child}</x-app-shell>`,
211
+ const users = resource(
212
+ () => `/api/users?page=${page()}`,
213
+ jsonFetcher<User[]>(),
214
+ { staleTime: 30_000 },
215
+ );
216
+
217
+ const create = mutation((u) => api.create(u), {
218
+ invalidates: ["/api/users*"],
157
219
  });
158
220
  ```
159
221
 
160
- Do not create route-specific state in layout view locals. Put it in pages,
161
- components, resources, or app-level contexts.
222
+ The key is cache identity: same key shared cache and de-duped
223
+ in-flight request. `mutation().run()` is concurrent by default;
224
+ `{ abortPrevious: true }` keeps only the latest in flight.
225
+ `invalidates` runs after a successful mutation and is best-effort.
226
+
227
+ ## Static snapshot constraints
162
228
 
163
- `mado bake` renders selected routes to static HTML for SEO and first paint. It
164
- does not hydrate server-rendered code. Baked views must be deterministic from
165
- `params`, `bake.data`, and plain values. Avoid browser-only effects, timers,
166
- relative `fetch`, and runtime directives like keyed `each()` during bake.
229
+ - `paths()` and `initialData()` run during discovery AND ship in the
230
+ client bundle. Keep them browser-safe; never read secrets.
231
+ - Pages marked `static` cannot use guards (route- or layout-level)
232
+ they are public by definition.
233
+ - `*` and other wildcard routes cannot be static.
234
+ - Discovery uses Vite SSR as a control plane only; nothing is rendered
235
+ in Node. The actual capture happens in headless Chromium.
167
236
 
168
- ## Canonical "Hello world"
237
+ ## Canonical "hello world"
169
238
 
170
239
  ```ts
171
240
  // src/main.ts
172
241
  import { html, render } from "@madojs/mado";
173
- import routesApi from "./app.routes.js";
174
- render(html`${routesApi.view}`, document.getElementById("app")!);
242
+ import appRoutes from "./app.routes";
243
+ render(html`${appRoutes.view}`, document.getElementById("app")!);
175
244
 
176
245
  // src/app.routes.ts
177
246
  import { routes } from "@madojs/mado";
247
+ import home from "./pages/home.page";
178
248
  export const manifest = {
179
- "/": () => import("./modules/home/home.page.js"),
180
- "*": () => import("./modules/home/not-found.page.js"),
249
+ "/": home,
250
+ "*": () => import("./pages/not-found.page"),
181
251
  };
182
252
  export default routes(manifest);
183
253
 
184
- // src/modules/home/home.page.ts
185
- import { page, component, html, css, signal } from "@madojs/mado";
186
-
187
- component("x-counter", () => {
188
- const count = signal(0);
189
- return () => html`
190
- <button @click=${() => count.update(n => n + 1)}>${count}</button>
191
- `;
192
- }, { styles: css`button { padding: .5rem 1rem; }` });
193
-
254
+ // src/pages/home.page.ts
255
+ import { html, page, routeUrl } from "@madojs/mado";
194
256
  export default page({
257
+ static: true,
195
258
  title: "Home",
196
- view: () => html`<x-counter></x-counter>`,
259
+ view: () => html`
260
+ <main>
261
+ <h1>Mado App</h1>
262
+ <a data-link href=${routeUrl("/about")}>About</a>
263
+ </main>
264
+ `,
197
265
  });
198
266
  ```
199
267
 
200
- ## Component registration imports
201
-
202
- ```ts
203
- // main.ts: global shell/provider components only
204
- import "./components/app-shell.js";
205
-
206
- // pages/tickets.ts: feature/page-owned components
207
- import "../components/ticket-list.js";
208
- ```
209
-
210
- Do not bulk-import every component in `main.ts` "just in case". Import the
211
- registration near the route/page that owns the tag. This preserves lazy loading
212
- and makes the code readable to humans and LLMs.
268
+ ## Starters
213
269
 
214
- ## Canonical CRUD pattern (for backend developers)
270
+ - `mado init my-app` — universal starter (default). One Shadow Component
271
+ shared between a static landing page and a live SPA route. Folders:
272
+ `src/pages`, `src/components`, `src/content`.
273
+ - `mado init my-app --starter modular` — long-lived reference
274
+ architecture: auth shell, guarded zones, billing module, HTTP client,
275
+ module boundaries. Folders: `src/modules/<name>/`, `src/layouts/`,
276
+ `src/shared/`. A dev-only mock API is mounted from
277
+ `vite.config.ts`; disable with `MADO_MOCK_API=0`.
215
278
 
216
- ```ts
217
- import { page, html, resource, mutation, each, useForm, navigate } from "@madojs/mado";
218
- import { api } from "../lib/api.js";
279
+ ## Documentation
219
280
 
220
- const users = resource(() => "/api/users", () => api.list(), { staleTime: 30_000 });
221
- const create = mutation((u) => api.create(u), { invalidates: ["/api/users*"] });
281
+ Canonical docs (English only): https://github.com/madojs/mado/tree/main/docs/en
222
282
 
223
- export default page({
224
- view: () => {
225
- const f = useForm({ name: { required: true } });
226
- return html`
227
- <form @submit=${f.onSubmit(async v => { await create.run(v); navigate("/"); })}>
228
- <input name="name" @input=${f.onInput}>
229
- <button ?disabled=${() => !f.isValid()}>Create</button>
230
- </form>
231
- ${() => each(users.data() ?? [], u => u.id, u => html`<li>${u.name}</li>`)}
232
- `;
233
- },
234
- });
235
- ```
283
+ Highlights:
236
284
 
237
- ## Documentation
285
+ - `docs/en/00-the-mado-way.md` — philosophy and boundaries
286
+ - `docs/en/01-quickstart.md` — scaffold + dev loop + IDE setup
287
+ - `docs/en/10-pages-and-components.md` — the one rule (page vs component)
288
+ - `docs/en/11-templates-and-signals.md` — bindings, signals, lifecycle
289
+ - `docs/en/12-routing.md` — router, layouts, guards, `routeUrl()`
290
+ - `docs/en/13-data.md` — `resource()` / `mutation()` / auth
291
+ - `docs/en/14-forms.md` — `useForm()` + Shadow DOM inputs
292
+ - `docs/en/15-static-snapshots.md` — static snapshots guide
293
+ - `docs/en/16-app-architecture.md` — canonical app layout
294
+ - `docs/en/20-deployment.md` — deployment recipes (including base)
295
+ - `docs/en/23-cookbook.md` — static snapshots cookbook
296
+ - `docs/en/30-api-freeze-map.md` — stable public API vs internal
297
+ - `docs/en/40-llm-guide.md` — LLM pitfalls and zero-history test
238
298
 
239
- - README: https://github.com/madojs/mado#readme full description + cookbook
240
- - docs/en/00-the-mado-way.md philosophy and applicability boundaries
241
- - docs/en/01-routing.md — detailed router documentation
242
- - docs/en/02-project-layout.md — recommended project structure
243
- - docs/en/03-static-bake.md — SEO without SSR (static build / edge prerender)
244
- - docs/en/04-ide-setup.md — VS Code / WebStorm configuration
245
- - docs/en/05-why-mado.md — honest comparison with Lit / Solid / Svelte / htmx / Alpine / React
246
- - docs/en/06-for-backenders.md — mental model in 10 minutes for Go/Rust/.NET/Java developers
247
- - docs/en/07-llm-pitfalls.md — common mistakes when generating Mado code
248
- - docs/en/10-app-architecture.md — canonical app file layout, routes, auth/API and release shape
249
- - docs/en/11-layouts.md — blessed layout recipe
250
- - docs/en/12-auth-and-api.md — blessed auth/API client recipe
251
- - docs/en/13-deployment.md — deployment recipes and cache rules
252
- - docs/en/14-testing.md — testing strategy and commands
253
- - docs/en/15-error-handling.md — route/data/action error boundaries
254
- - docs/en/16-bake-cookbook.md — static bake recipes and failure modes
255
- - docs/en/17-shadow-dom-forms.md — Shadow DOM + useForm() patterns (proxy properties, form submit bridge)
256
- - docs/en/18-api-freeze-map.md — stable public API vs internal implementation details
257
- - docs/en/19-reactivity-ordering.md — signal ordering, batching and teardown guarantees
258
- - docs/en/20-v1-stability.md — v1 SemVer contract and what remains internal
259
- - starters/default/ — canonical generated app shape
299
+ The repository is English-only. Older `docs/fr`, `docs/uk`, `docs/ru`
300
+ trees were removed at v0.12 and survive only in git history.
260
301
 
261
302
  ## What Mado does NOT do (intentionally)
262
303
 
263
- - ❌ JSX → tagged templates instead
264
- - ❌ Virtual DOM → fine-grained signal updates
265
- - ❌ SSR with hydration `bake` static meta-shell or edge-prerender for SEO
266
- - ❌ Hooks and rules of hooks → signals
267
- - ❌ Runtime framework dependencies generated apps use Vite as dev/build tooling
268
- - ❌ React-Router / TanStack built-in 500-line `routes()`
269
- - ❌ React-Query / SWR → built-in `resource()`
270
- - ❌ Formik / RHF → built-in `useForm()` (HTML5 validation)
271
- - ❌ Redux / Zustand → `signal()` + `createContext`
272
- - ❌ Decoratorsplain functions
273
-
274
- ## Version
275
-
276
- `0.10.0` — pre-1.0 API-lock release. Phase B closed the public surface,
277
- package exports, docs and CI gates. SemVer is not guaranteed on minor versions
278
- before 1.0.
304
+ - ❌ JSX → tagged template `html\`...\``
305
+ - ❌ Virtual DOM → fine-grained signal updates
306
+ - ❌ SSR with hydration browser-rendered static snapshots + atomic takeover
307
+ - ❌ React-style hooks → signals
308
+ - ❌ Framework-specific compiler → none; the source is plain TS / Web Components
309
+ - ❌ Runtime framework dependencies zero (Vite is dev/build tooling)
310
+ - ❌ React-Router / TanStack → built-in `routes()`
311
+ - ❌ React-Query / SWR → built-in `resource()` / `mutation()`
312
+ - ❌ Formik / RHF built-in `useForm()`
313
+ - ❌ Redux / Zustand `signal()` + `createContext`
279
314
 
280
315
  ## License
281
316
 
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@madojs/mado",
3
- "version": "0.11.0",
4
- "description": "Mado — a calm browser-native SPA framework for internal tools, admin panels and business apps. Routing, forms, state and data fetching without frontend infrastructure overhead.",
3
+ "version": "0.12.0",
4
+ "description": "A calm native-first web framework for sites and apps. Web Components, signals, routing, data, forms and browser-rendered static snapshots. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "framework",
9
- "spa",
9
+ "native-first",
10
10
  "web-components",
11
+ "declarative-shadow-dom",
11
12
  "signals",
12
13
  "tagged-templates",
13
14
  "router",
14
15
  "vite",
15
- "tsc",
16
- "minimal",
17
- "lit-alternative",
18
- "solid-alternative"
16
+ "static-snapshot",
17
+ "spa",
18
+ "minimal"
19
19
  ],
20
20
  "homepage": "https://github.com/madojs/mado#readme",
21
21
  "repository": {
@@ -53,20 +53,23 @@
53
53
  "README.md",
54
54
  "LICENSE",
55
55
  "CHANGELOG.md",
56
- "TODO.md",
57
56
  "AGENTS.md",
58
57
  "llms.txt",
59
- "docs/**/*.md"
58
+ "docs/README.md",
59
+ "docs/en/**/*.md",
60
+ "docs/architecture/**/*.md",
61
+ "docs/recipes/**/*"
60
62
  ],
61
63
  "scripts": {
62
64
  "build": "node scripts/cli.mjs build",
63
65
  "watch": "node scripts/cli.mjs watch",
64
66
  "dev": "node scripts/cli.mjs dev",
65
- "bake": "node scripts/cli.mjs bake",
67
+ "static": "node scripts/cli.mjs static",
66
68
  "preview": "node scripts/cli.mjs preview",
67
69
  "new": "node scripts/cli.mjs new",
68
70
  "size": "node scripts/size-budget.mjs",
69
71
  "package:smoke": "node scripts/package-smoke.mjs",
72
+ "docs:lint": "node scripts/docs-lint.mjs",
70
73
  "test": "node scripts/cli.mjs test",
71
74
  "typecheck": "node scripts/cli.mjs typecheck",
72
75
  "clean": "rm -rf dist out",
@@ -82,9 +85,13 @@
82
85
  "vite": "^8.0.16"
83
86
  },
84
87
  "peerDependencies": {
88
+ "playwright-core": "^1.47.0",
85
89
  "vite": "^8.0.0"
86
90
  },
87
91
  "peerDependenciesMeta": {
92
+ "playwright-core": {
93
+ "optional": true
94
+ },
88
95
  "vite": {
89
96
  "optional": true
90
97
  }