@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
@@ -0,0 +1,57 @@
1
+ // `live-counter` showcases reactivity: signal-driven text, a click
2
+ // handler that mutates the signal, and an open shadow root that
3
+ // participates in the static snapshot via Declarative Shadow DOM.
4
+ import { component, css, html, signal } from "@madojs/mado";
5
+
6
+ component(
7
+ "live-counter",
8
+ () => {
9
+ const count = signal(0);
10
+ return () => html`
11
+ <div class="counter">
12
+ <button
13
+ class="bump"
14
+ @click=${() => count.update((n) => n + 1)}
15
+ aria-label="Increment"
16
+ >
17
+ +
18
+ </button>
19
+ <span class="value">${() => count()}</span>
20
+ </div>
21
+ `;
22
+ },
23
+ {
24
+ styles: css`
25
+ :host {
26
+ display: inline-block;
27
+ }
28
+ .counter {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 0.75rem;
32
+ padding: 0.5rem 0.75rem;
33
+ border: 1px solid var(--mado-border);
34
+ border-radius: var(--mado-radius);
35
+ background: var(--mado-surface);
36
+ }
37
+ .bump {
38
+ font: inherit;
39
+ width: 2rem;
40
+ height: 2rem;
41
+ border-radius: 999px;
42
+ border: 1px solid var(--mado-border);
43
+ background: var(--mado-bg);
44
+ cursor: pointer;
45
+ }
46
+ .bump:hover {
47
+ background: var(--mado-surface-hover);
48
+ }
49
+ .value {
50
+ font-variant-numeric: tabular-nums;
51
+ font-weight: 600;
52
+ min-width: 2ch;
53
+ text-align: right;
54
+ }
55
+ `,
56
+ },
57
+ );
@@ -0,0 +1,55 @@
1
+ // Browser-safe content module. `paths()` and `initialData()` in
2
+ // `guide.page.ts` import from here, so this file MUST stay in the
3
+ // client bundle — no Node-only APIs, no secrets, no env reads.
4
+ //
5
+ // In a larger app you would replace this with a markdown loader or a
6
+ // public JSON file fetched at build time.
7
+ // `Guide` is a JsonValue-safe shape: only plain string fields so it can
8
+ // be passed to `static.initialData` and survive the snapshot serializer.
9
+ // The explicit index signature satisfies Mado's `JsonValue` constraint
10
+ // without any runtime cost.
11
+ export type Guide = {
12
+ slug: string;
13
+ title: string;
14
+ summary: string;
15
+ body: string;
16
+ [key: string]: string;
17
+ };
18
+
19
+ export const guides: Guide[] = [
20
+ {
21
+ slug: "components",
22
+ title: "Web Components are the app shell",
23
+ summary:
24
+ "Every visual unit in Mado is a real custom element with its own open shadow root.",
25
+ body:
26
+ "Mado does not ship a virtual DOM. The same custom element you " +
27
+ "register with `component()` runs in the live SPA and inside the " +
28
+ "snapshotted Declarative Shadow DOM the static release produces.",
29
+ },
30
+ {
31
+ slug: "signals",
32
+ title: "Signals are the reactivity model",
33
+ summary:
34
+ "Read with `count()`, write with `count.set()`, derive with `computed()`.",
35
+ body:
36
+ "Signals are getter functions. Subscriptions are automatic inside " +
37
+ "`html\\`...\\`` template fragments and `effect()` callbacks, and " +
38
+ "released when the surrounding component or page unmounts.",
39
+ },
40
+ {
41
+ slug: "snapshots",
42
+ title: "Browser-rendered static snapshots",
43
+ summary:
44
+ "`mado release` runs your app in a real Chromium and freezes the " +
45
+ "rendered HTML — including the Shadow DOM — into one file per route.",
46
+ body:
47
+ "The snapshot ships as Declarative Shadow DOM. On first paint the " +
48
+ "client component re-attaches to the same host with zero hydration " +
49
+ "boundary and zero duplicated trees.",
50
+ },
51
+ ];
52
+
53
+ export function findGuide(slug: string): Guide | undefined {
54
+ return guides.find((g) => g.slug === slug);
55
+ }
@@ -1,16 +1,12 @@
1
- // App entry point. The ONLY file allowed to import CSS at runtime.
2
- import "./shared/styles/tokens.css";
3
- import "./shared/styles/reset.css";
4
- import "./shared/styles/shell.css";
5
- import "./shared/styles/content.css";
6
-
1
+ // Single entrypoint: import the app routes and render them into the
2
+ // `#app` host declared in `index.html`. There is no separate "bootstrap"
3
+ // step — the router IS the application.
7
4
  import { html, render } from "@madojs/mado";
8
-
9
- import { init as initAuth } from "./modules/auth/auth.service";
5
+ import "./components/feature-card.component";
6
+ import "./components/live-counter.component";
10
7
  import appRoutes from "./app.routes";
8
+ import "./styles/tokens.css";
9
+ import "./styles/reset.css";
10
+ import "./styles/document.css";
11
11
 
12
- // One-off boot order: init cross-cutting modules first, then render the
13
- // router view into #app.
14
- await initAuth();
15
-
16
- render(html`${appRoutes.view}`, document.getElementById("app")!);
12
+ render(html`${appRoutes.view}`, document.getElementById("app")!);
@@ -0,0 +1,41 @@
1
+ // SPA-only interactive route. No `static:` declaration, so
2
+ // `mado release` keeps it out of the sitemap and serves it through
3
+ // the `_mado/spa.html` fallback at runtime — the canonical home for
4
+ // in-app surfaces that should not appear in search results.
5
+ import { html, page, routeUrl, signal } from "@madojs/mado";
6
+
7
+ export default page({
8
+ title: "App",
9
+ head: () => ({
10
+ description: "A live SPA route demonstrating signals and components.",
11
+ }),
12
+ view: () => {
13
+ const name = signal("world");
14
+ return html`
15
+ <main class="document">
16
+ <p><a data-link href=${routeUrl("/")}>← Home</a></p>
17
+ <h1>Hello, ${() => name()}!</h1>
18
+ <p>
19
+ <label>
20
+ Your name:
21
+ <input
22
+ type="text"
23
+ .value=${() => name()}
24
+ @input=${(e: Event) =>
25
+ name.set((e.target as HTMLInputElement).value)}
26
+ />
27
+ </label>
28
+ </p>
29
+ <p>The same component model runs in the static landing AND here:</p>
30
+ <feature-card title="Live + static, one model">
31
+ Each <code>feature-card</code> below the title is a real custom
32
+ element with its own open shadow root. It works inside the
33
+ server-rendered snapshot and inside this SPA-only route without
34
+ changing a single line.
35
+ </feature-card>
36
+ <p>And here is a tiny reactive component you can drive locally:</p>
37
+ <p><live-counter></live-counter></p>
38
+ </main>
39
+ `;
40
+ },
41
+ });
@@ -0,0 +1,40 @@
1
+ // Dynamic static page. `static.paths()` enumerates the slugs that
2
+ // the snapshot pipeline materialises at build time; `initialData()`
3
+ // seeds the rendered HTML so neither the snapshot nor the live boot
4
+ // has to re-fetch the guide body.
5
+ import { html, page, routeUrl } from "@madojs/mado";
6
+ import { findGuide, guides, type Guide } from "../content/guides";
7
+
8
+ type Params = { slug: string };
9
+
10
+ export default page<Params, Guide | null, Guide | null>({
11
+ static: {
12
+ paths: () => guides.map((g) => ({ slug: g.slug })),
13
+ initialData: (params) => findGuide(params.slug) ?? null,
14
+ },
15
+ title: (params) => findGuide(params.slug)?.title ?? "Guide not found",
16
+ head: (_params, seed) => {
17
+ if (!seed) return { title: "Guide not found" };
18
+ return { title: seed.title, description: seed.summary };
19
+ },
20
+ view: ({ data }) => {
21
+ if (!data) {
22
+ return html`
23
+ <main class="document">
24
+ <h1>Guide not found</h1>
25
+ <p><a data-link href=${routeUrl("/")}>Back home</a></p>
26
+ </main>
27
+ `;
28
+ }
29
+ return html`
30
+ <main class="document">
31
+ <p><a data-link href=${routeUrl("/")}>← Home</a></p>
32
+ <article>
33
+ <h1>${data.title}</h1>
34
+ <p class="lead">${data.summary}</p>
35
+ <p>${data.body}</p>
36
+ </article>
37
+ </main>
38
+ `;
39
+ },
40
+ });
@@ -0,0 +1,49 @@
1
+ // Public landing page. `static: true` opts the route into the
2
+ // browser-rendered snapshot pipeline — `mado release` captures this
3
+ // view into `out/index.html` with the rendered Shadow DOM inlined.
4
+ import { html, page, routeUrl } from "@madojs/mado";
5
+ import { guides } from "../content/guides";
6
+
7
+ export default page({
8
+ static: true,
9
+ title: "Mado — a calm native-first web framework",
10
+ head: () => ({
11
+ description:
12
+ "Build with real Web Components, signals, routing, data and forms. " +
13
+ "Ship live SPAs and browser-rendered static documents from one " +
14
+ "component model.",
15
+ }),
16
+ view: () => html`
17
+ <main class="document">
18
+ <header class="hero">
19
+ <h1>Mado</h1>
20
+ <p class="lead">
21
+ A calm native-first web framework for sites and apps.
22
+ </p>
23
+ <p>
24
+ <strong>One component model. One page model. One release command.</strong>
25
+ </p>
26
+ </header>
27
+
28
+ <section class="features">
29
+ ${guides.map(
30
+ (guide) => html`
31
+ <feature-card title=${guide.title}>
32
+ ${guide.summary}
33
+ <a data-link href=${routeUrl(`/guide/${guide.slug}`)}>
34
+ Read the guide →
35
+ </a>
36
+ </feature-card>
37
+ `,
38
+ )}
39
+ </section>
40
+
41
+ <section class="cta">
42
+ <h2>Open the app</h2>
43
+ <p>
44
+ <a data-link href=${routeUrl("/app")}>Launch the live SPA →</a>
45
+ </p>
46
+ </section>
47
+ </main>
48
+ `,
49
+ });
@@ -0,0 +1,18 @@
1
+ // Catch-all 404. Not statically captured; the SPA shell serves it
2
+ // whenever a deep link does not match a known route.
3
+ import { html, page, routeUrl } from "@madojs/mado";
4
+
5
+ export default page({
6
+ title: "Page not found",
7
+ head: () => ({
8
+ description: "The page you are looking for does not exist.",
9
+ meta: [{ name: "robots", content: "noindex" }],
10
+ }),
11
+ view: ({ path }) => html`
12
+ <main class="document">
13
+ <h1>404</h1>
14
+ <p>No page is registered for <code>${() => path()}</code>.</p>
15
+ <p><a data-link href=${routeUrl("/")}>Back home</a></p>
16
+ </main>
17
+ `,
18
+ });
@@ -0,0 +1,39 @@
1
+ /* Document-level layout: header, sections, hero. Component styles
2
+ live inside their own shadow roots, this stylesheet styles the
3
+ light-DOM wrappers only. */
4
+ .document {
5
+ max-width: 56rem;
6
+ margin: 0 auto;
7
+ padding: 3rem 1.5rem;
8
+ display: grid;
9
+ gap: 2.5rem;
10
+ }
11
+
12
+ .hero h1 {
13
+ margin: 0 0 0.5rem;
14
+ font-size: 3rem;
15
+ letter-spacing: -0.01em;
16
+ }
17
+
18
+ .hero .lead {
19
+ margin: 0 0 0.75rem;
20
+ font-size: 1.25rem;
21
+ color: var(--mado-fg-muted);
22
+ }
23
+
24
+ .features {
25
+ display: grid;
26
+ gap: 1rem;
27
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
28
+ }
29
+
30
+ .cta {
31
+ padding: 1.5rem;
32
+ border: 1px dashed var(--mado-border);
33
+ border-radius: var(--mado-radius);
34
+ background: var(--mado-surface);
35
+ }
36
+
37
+ article p {
38
+ margin: 0.75rem 0;
39
+ }
@@ -0,0 +1,37 @@
1
+ /* Tiny modern reset. Keep it boring so nothing here surprises a new
2
+ contributor. */
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ }
13
+
14
+ body {
15
+ background: var(--mado-bg);
16
+ color: var(--mado-fg);
17
+ font-family: var(--mado-font-body);
18
+ line-height: 1.5;
19
+ }
20
+
21
+ a {
22
+ color: var(--mado-accent);
23
+ }
24
+
25
+ button,
26
+ input,
27
+ select,
28
+ textarea {
29
+ font: inherit;
30
+ color: inherit;
31
+ }
32
+
33
+ code {
34
+ background: var(--mado-surface);
35
+ padding: 0.05rem 0.3rem;
36
+ border-radius: 4px;
37
+ }
@@ -0,0 +1,30 @@
1
+ /* Design tokens. Override these to re-skin the app without touching
2
+ component CSS. Custom properties penetrate shadow roots, so every
3
+ component reads them automatically. */
4
+ :root {
5
+ --mado-bg: #fafafa;
6
+ --mado-surface: #ffffff;
7
+ --mado-surface-hover: #f1f5f9;
8
+ --mado-fg: #0f172a;
9
+ --mado-fg-muted: #475569;
10
+ --mado-border: #e2e8f0;
11
+ --mado-accent: #1d4ed8;
12
+
13
+ --mado-radius: 0.625rem;
14
+
15
+ --mado-font-body:
16
+ system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
17
+ sans-serif;
18
+ }
19
+
20
+ @media (prefers-color-scheme: dark) {
21
+ :root {
22
+ --mado-bg: #0b1220;
23
+ --mado-surface: #111827;
24
+ --mado-surface-hover: #1f2937;
25
+ --mado-fg: #e2e8f0;
26
+ --mado-fg-muted: #94a3b8;
27
+ --mado-border: #1f2937;
28
+ --mado-accent: #60a5fa;
29
+ }
30
+ }
@@ -1 +1,3 @@
1
- declare module "*.css";
1
+ // Allow CSS imports in TS modules. Vite handles the side-effect at
2
+ // build time; the runtime sees a no-op module.
3
+ declare module "*.css";
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "types": ["node"],
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
@@ -2,8 +2,18 @@ import { defineConfig } from "vite";
2
2
  import { mado } from "@madojs/mado/vite";
3
3
 
4
4
  export default defineConfig({
5
- plugins: [mado()],
6
- css: {
7
- transformer: "lightningcss",
8
- },
5
+ plugins: [
6
+ mado({
7
+ // Public origin used to build absolute URLs for static snapshots
8
+ // (sitemap, canonical, OpenGraph). Combined with Vite's `base`,
9
+ // the canonical for a route is `site + base + pathname`.
10
+ //
11
+ // REQUIRED when any page declares `static`. Set to your deployment
12
+ // origin (e.g. https://your-app.example) before running
13
+ // `mado release`. Override per environment via:
14
+ // mado release --base-url https://staging.example
15
+ // MADO_SITE=https://staging.example mado release
16
+ // site: "https://your-app.example",
17
+ }),
18
+ ],
9
19
  });
@@ -0,0 +1,142 @@
1
+ # Mado modular starter
2
+
3
+ Reference architecture for long-lived modular business applications:
4
+ admin panels, internal tools, SaaS backoffice. The starter ships the
5
+ opinions a team of more than one person will eventually adopt anyway —
6
+ auth shell, guarded zones, module boundaries, HTTP client and
7
+ contracts.
8
+
9
+ If you are building a website, a landing page, a docs site or any
10
+ small interactive app, prefer the universal starter:
11
+
12
+ ```bash
13
+ mado init my-app # universal default
14
+ mado init my-app --starter modular # this starter
15
+ ```
16
+
17
+ Both starters target the same Mado runtime. They only differ in how
18
+ much structure is pre-created.
19
+
20
+ ## Commands
21
+
22
+ ```bash
23
+ npm install
24
+ npm run dev # Vite dev server
25
+ npm run build # Vite production SPA build
26
+ npm run release # vite build + browser-rendered snapshots → out/
27
+ npm run preview # serve out/ like a real static host
28
+ ```
29
+
30
+ `npm run release` requires a public origin so it can build absolute
31
+ canonical URLs. Set it once in `vite.config.ts`:
32
+
33
+ ```ts
34
+ mado({ site: "https://your-app.example" })
35
+ ```
36
+
37
+ …or override per environment:
38
+
39
+ ```bash
40
+ mado release --base-url https://staging.example
41
+ ```
42
+
43
+ The public home page is captured as a static document; auth and
44
+ billing surfaces stay SPA-only and are served through the
45
+ `_mado/spa.html` fallback.
46
+
47
+ ## Dev API mock
48
+
49
+ `vite.config.ts` mounts a tiny in-memory mock for the endpoints used by
50
+ this starter so `npm run dev` is runnable out of the box:
51
+
52
+ | Method + path | Behaviour |
53
+ | ---------------------------------------- | ---------------------------------------------- |
54
+ | `POST /api/auth/login` | accepts `demo@mado.dev` / `demo123` |
55
+ | `GET /api/auth/me` | current user (after login) |
56
+ | `POST /api/auth/logout` | clears the in-memory session |
57
+ | `GET /api/billing/stripe/invoices` | lists six seeded invoices |
58
+ | `GET /api/billing/stripe/invoices/:id` | returns a single invoice |
59
+ | `POST /api/billing/stripe/invoices/:id/pay` | flips status to `paid` |
60
+
61
+ The mock only runs under `vite dev`, so `mado release` ships a clean
62
+ bundle that talks to your real backend. Disable the mock with
63
+ `MADO_MOCK_API=0` or remove the `devApiMock()` plugin entry.
64
+
65
+ When you wire up your real backend, mirror the request/response shape
66
+ in `auth.connector.ts` and `stripe.connector.ts`. The contract types in
67
+ `modules/<name>/_contracts/` are the source of truth.
68
+
69
+ ## Shape
70
+
71
+ ```txt
72
+ public/ static assets copied by Vite
73
+ src/
74
+ main.ts imports global CSS and mounts the router
75
+ app.routes.ts app map: zones, layouts, guards and modules
76
+ layouts/ app-zone shells
77
+ shared/
78
+ http/ HTTP client and interceptors
79
+ lib/ pure utilities
80
+ styles/ tokens, reset, shell and content CSS
81
+ ui/ reusable x-* components
82
+ modules/ business modules
83
+ <name>/
84
+ <name>.routes.ts
85
+ <name>.public.ts
86
+ <name>.types.ts
87
+ pages/
88
+ data/
89
+ api/
90
+ components/
91
+ _contracts/
92
+ ```
93
+
94
+ ## CSS Contract
95
+
96
+ - `tokens.css` defines CSS custom properties and is safe for Shadow DOM
97
+ components through `var(...)`.
98
+ - `reset.css` normalizes the document/light DOM surface.
99
+ - `shell.css` styles app-zone layouts from `src/layouts/`.
100
+ - `content.css` styles page-level light DOM: forms, tables, prose and
101
+ simple states.
102
+ - Reusable leaf components keep their own styles in ``css`...` ``
103
+ inside `component()` options.
104
+
105
+ Vite 8's standard pipeline owns CSS transforms/minification; Mado itself does
106
+ not own CSS processing.
107
+
108
+ ## Internal links
109
+
110
+ Always emit internal links through `routeUrl()` so they pick up the
111
+ deployed Vite `base` automatically:
112
+
113
+ ```ts
114
+ import { routeUrl } from "@madojs/mado";
115
+
116
+ html`<a data-link href=${routeUrl("/billing/invoices")}>Invoices</a>`
117
+ ```
118
+
119
+ The router only intercepts anchors that opt in with `data-link`; bare
120
+ `<a href>` falls through to a full document load.
121
+
122
+ ## Generate files
123
+
124
+ ```bash
125
+ npm run new -- module billing
126
+ npm run new -- page billing/pages/invoices-list
127
+ npm run new -- connector billing/api/stripe
128
+ npm run new -- resource billing/data/invoices
129
+ npm run new -- service billing/cart
130
+ npm run new -- form billing/invoice
131
+ npm run new -- component billing/components/invoice-status-badge
132
+ npm run new -- guard billing/billing
133
+ npm run new -- layout admin-shell
134
+ ```
135
+
136
+ The generator writes files only. Wire new routes in
137
+ `src/app.routes.ts` or the module route map by hand.
138
+
139
+ ## More
140
+
141
+ The full architecture guide lives in the framework docs:
142
+ https://github.com/madojs/mado/tree/main/docs/en
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/favicon.svg" />
7
+ <title>Mado App</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "mado dev",
8
+ "build": "mado build",
9
+ "typecheck": "mado typecheck",
10
+ "test": "npm run typecheck && npm run lint",
11
+ "release": "mado release",
12
+ "preview": "mado preview",
13
+ "lint": "eslint \"src/**/*.ts\"",
14
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
15
+ "new": "mado new"
16
+ },
17
+ "dependencies": {
18
+ "@madojs/mado": "__MADOJS_VERSION__"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.12.0",
22
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
23
+ "@typescript-eslint/parser": "^8.0.0",
24
+ "eslint": "^9.0.0",
25
+ "eslint-plugin-boundaries": "^5.0.0",
26
+ "playwright-core": "^1.61.1",
27
+ "typescript": "^6.0.3",
28
+ "vite": "^8.1.4"
29
+ }
30
+ }
@@ -0,0 +1,39 @@
1
+ // Single source of truth for the application route table.
2
+ //
3
+ // This file is the APP MAP. Reading it = understanding the app.
4
+ //
5
+ // One rule:
6
+ // Modules export plain `routes` maps. The choice of SHELL and GUARD for
7
+ // each zone of the app is made HERE, by wrapping a module's routes with
8
+ // a `layout({...})` block.
9
+ //
10
+ // `manifest` is exported separately so `mado static` can discover pages that
11
+ // declare `static`.
12
+
13
+ import { layout, routes } from "@madojs/mado";
14
+
15
+ import { requireAuth } from "./modules/auth/auth.public";
16
+ import { authRoutes } from "./modules/auth/auth.routes";
17
+ import { billingRoutes } from "./modules/billing/billing.routes";
18
+
19
+ export const manifest = {
20
+ // Public landing (no shell, no guard).
21
+ "/": () => import("./modules/home/home.page"),
22
+
23
+ // AUTH ZONE — centered card, no guard.
24
+ "/login": layout({
25
+ layout: () => import("./layouts/auth-shell.layout"),
26
+ routes: authRoutes,
27
+ }),
28
+
29
+ // APP ZONE — header + nav, guarded by requireAuth.
30
+ "/billing": layout({
31
+ layout: () => import("./layouts/app-shell.layout"),
32
+ guard: requireAuth,
33
+ routes: billingRoutes,
34
+ }),
35
+
36
+ "*": () => import("./modules/home/not-found.page"),
37
+ };
38
+
39
+ export default routes(manifest);