@madojs/mado 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
@@ -1,56 +0,0 @@
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/en/07-llm-pitfalls.md`
15
- - files from the external `madojs-examples` workspace only when the agent asks
16
- 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 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 historical tickets implementation lives in the external examples workspace.
51
- The core repository no longer ships that artifact or a dedicated smoke command;
52
- use this document as a manual evaluation script when updating LLM guidance.
53
-
54
- The main documentation pressure point remains lifecycle: examples should not
55
- make it look acceptable to create long-lived `resource()` instances accidentally
56
- at module scope or in route code that never cleans up.
@@ -1,174 +0,0 @@
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 Mado route layouts (`page({ view: ({ child }) => ... })`) for app zones:
10
- auth shells, admin shells, public shells and embedded shells. These live in
11
- `src/layouts/` and are composed from `src/app.routes.ts`.
12
-
13
- Use Web Components registered with `component()` for reusable UI elements and
14
- widgets. Use plain functions only for small inline template helpers:
15
-
16
- ```ts
17
- const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
18
- ```
19
-
20
- Do not hide app shells inside `main.ts` or generic helper functions. The app
21
- map should show which layout wraps which route group.
22
-
23
- Use **Shadow DOM** for leaf widgets:
24
-
25
- - buttons, badges, cards, metrics;
26
- - modals, toasts, small visual components;
27
- - embed widgets that should not inherit app CSS accidentally;
28
- - components whose styling should be owned by the component itself.
29
-
30
- Use **Light DOM** for app structure that wants to share global CSS:
31
-
32
- - route pages and route layouts;
33
- - admin screens with dense table/form layouts;
34
- - data-heavy screens with tables and forms;
35
- - places where children should simply remain normal document DOM.
36
-
37
- Route layouts receive `child` from Mado, so they do not need `<slot>`.
38
-
39
- ## The Footgun
40
-
41
- Global CSS does not cross a Shadow DOM boundary.
42
-
43
- ```ts
44
- // global.ts
45
- export const globalStyles = css`
46
- .page-head { display: flex; justify-content: space-between; }
47
- .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
48
- `;
49
-
50
- // ❌ .page-head and .metric-grid will not apply inside x-dashboard shadowRoot
51
- component("x-dashboard", () => () => html`
52
- <header class="page-head">...</header>
53
- <div class="metric-grid">...</div>
54
- `);
55
- ```
56
-
57
- Fix it by making the route/page component Light DOM:
58
-
59
- ```ts
60
- component("x-dashboard", () => () => html`
61
- <header class="page-head">...</header>
62
- <div class="metric-grid">...</div>
63
- `, {
64
- shadow: false,
65
- styles: css`
66
- x-dashboard { display: block; }
67
- x-dashboard .panel { padding: 1rem; }
68
- `,
69
- });
70
- ```
71
-
72
- Now global utilities and local scoped styles both work.
73
-
74
- ## How Styles Behave
75
-
76
- - `styles: css\`\`` in Shadow DOM is adopted into the component shadowRoot.
77
- - `styles: css\`\`` with `shadow: false` is scoped to the tag name and adopted
78
- globally.
79
- - CSS custom properties (`--accent`, `--bg`, etc.) cross Shadow DOM boundaries.
80
- - Class selectors like `.btn`, `.form-grid`, `.page-head` do **not** cross
81
- Shadow DOM boundaries.
82
- - Slotted children keep their own document styles; the shadow component can only
83
- target them through `::slotted(...)`.
84
- - `<slot>` projects children only in Shadow DOM. In a `shadow: false` component
85
- it is just a normal `<slot>` element and will not move children into that
86
- place in your layout.
87
-
88
- ## Recommended App Shape
89
-
90
- ```ts
91
- // app zone: route layout, styled by shared/styles/shell.css
92
- export default page({
93
- view: ({ child }) => html`<main class="app-main">${child}</main>`,
94
- });
95
-
96
- // route page: light DOM, styled by shared/styles/content.css
97
- export default page({
98
- view: () => html`<section><h1>Users</h1></section>`,
99
- });
100
-
101
- // leaf widgets: Shadow DOM default
102
- component("x-status-badge", setup);
103
- component("x-stat-card", setup);
104
- component("x-toast-stack", setup);
105
- ```
106
-
107
- This gives admin screens predictable CSS while preserving encapsulation for
108
- reusable leaf widgets.
109
-
110
- The import model is deliberately browser-native:
111
-
112
- ```ts
113
- import "./components/app-layout.js";
114
-
115
- render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
116
- ```
117
-
118
- The import registers the custom element with `customElements.define()`. The
119
- template creates an `<x-app-layout>` element. The browser connects the two.
120
- There is no React-style component value being passed around.
121
-
122
- If you do need a reusable slot-based frame, keep it as a Shadow DOM component
123
- and put frame styles in that component.
124
-
125
- ## Routing and Links
126
-
127
- `data-link` works inside Shadow DOM. The router uses `event.composedPath()`, so
128
- click interception and hover-prefetch can see links from open shadow roots.
129
-
130
- ```ts
131
- component("x-card-link", () => () => html`
132
- <a href="/app/accounts" data-link>Accounts</a>
133
- `);
134
- ```
135
-
136
- The link can be in Shadow DOM; navigation still stays SPA.
137
-
138
- ## Where To Import Components
139
-
140
- Custom elements are global after registration, but registration is still an
141
- explicit JavaScript import.
142
-
143
- ```ts
144
- // main.ts: global app frame
145
- import "./components/app-shell.js";
146
-
147
- // pages/tickets.ts: page-owned feature component
148
- import "../components/ticket-list.js";
149
- ```
150
-
151
- The browser does **not** download `ticket-list.js` just because it sees
152
- `<ticket-list>`. The file must be imported somewhere first. Once imported, it
153
- calls `customElements.define(...)`, and the tag becomes known in the current
154
- document.
155
-
156
- Do not bulk-import every component in `main.ts` "just in case". It works for
157
- tiny demos, but it hides ownership and defeats lazy route loading. Prefer:
158
-
159
- - global app shell/providers in `main.ts`;
160
- - page-owned components in that page file;
161
- - feature-owned shared components in the feature entry page;
162
- - truly global leaf components in `main.ts` only when they are used everywhere.
163
-
164
- ## Starter Lesson
165
-
166
- The default starter uses this split deliberately:
167
-
168
- - route layouts are `page()` files under `src/layouts/`;
169
- - `shell.css` owns app-zone chrome;
170
- - `content.css` owns page-level tables/forms/prose;
171
- - leaf components such as `x-button`, `x-spinner` and badges keep Shadow DOM.
172
-
173
- If a page suddenly looks unstyled, check whether it uses global classes inside a
174
- Shadow DOM component. That is usually the issue.
@@ -1,113 +0,0 @@
1
- # Layouts
2
-
3
- > **One blessed path.** Layouts in Mado are route groups with a shared
4
- > shell. There is exactly one canonical place to declare a layout — your
5
- > `app.routes.ts` manifest. Putting layout code anywhere else (in `main.ts`, in a
6
- > page view, in a global custom-element wrapper) is a bug pattern: the LLM and
7
- > the human both produce visually broken UI when they guess differently.
8
-
9
- ## The canonical recipe
10
-
11
- ```ts
12
- // src/app.routes.ts
13
- import { layout, routes } from "@madojs/mado";
14
- import { requireAuth } from "./modules/auth/auth.public.js";
15
- import { authRoutes } from "./modules/auth/auth.routes.js";
16
- import { billingRoutes } from "./modules/billing/billing.routes.js";
17
-
18
- export const manifest = {
19
- "/": () => import("./modules/home/home.page.js"),
20
- "/login": layout({
21
- layout: () => import("./layouts/auth-shell.layout.js"),
22
- routes: authRoutes,
23
- }),
24
- "/billing": layout({
25
- layout: () => import("./layouts/app-shell.layout.js"),
26
- guard: requireAuth,
27
- routes: billingRoutes,
28
- }),
29
- "*": () => import("./modules/home/not-found.page.js"),
30
- };
31
-
32
- export default routes(manifest);
33
- ```
34
-
35
- A layout is just a `page({ view })` that renders `${ctx.child}` somewhere:
36
-
37
- ```ts
38
- // src/layouts/app.ts
39
- import { html, page } from "@madojs/mado";
40
- import "../components/app-shell.js"; // <x-app-shell> (sidebar + topbar + slot)
41
-
42
- export default page({
43
- view: ({ child }) => html`
44
- <x-app-shell>${child}</x-app-shell>
45
- `,
46
- });
47
- ```
48
-
49
- That is the whole API.
50
-
51
- - **Order of layouts** matters: outer groups wrap inner groups. The order in
52
- the manifest is exactly the order of rendering.
53
- - **One shell per group**, not one shell per page. If you want a different
54
- shell for a subtree, create a new group with its own `layout`.
55
- - **Layouts can be lazy** (`() => import(...)`). They are loaded together
56
- with the page.
57
-
58
- ## Why "one blessed path"
59
-
60
- Without this convention, every page accumulates `<x-app-shell>${...}</x-app-shell>`
61
- boilerplate, the LLM eventually puts the shell wrapper into `main.ts` "to make
62
- it consistent", and the next refactor produces the classic
63
- *"navigation appears below the page content"* screenshot. The layout-group
64
- recipe makes the shell the **outer frame** structurally; there is no way to
65
- re-order it by accident.
66
-
67
- ## Two acceptable alternatives (with caveats)
68
-
69
- These exist for completeness. Reach for them only if you cannot use layout
70
- groups.
71
-
72
- ### a) A single shell with the router slot in `main.ts`
73
-
74
- ```ts
75
- import { html, render } from "@madojs/mado";
76
- import "./components/app-shell.js";
77
- import router from "./routes.js";
78
-
79
- render(html`<x-app-shell>${router.view}</x-app-shell>`, app);
80
- ```
81
-
82
- Caveat: every route now lives inside one shell. You cannot have a centered
83
- login page or a marketing landing page without the admin chrome around it.
84
- Use this only for single-shell apps.
85
-
86
- ### b) Per-page wrapping inside `view`
87
-
88
- ```ts
89
- export default page({
90
- view: () => html`
91
- <x-app-shell>
92
- <h1>Orders</h1>
93
- ...
94
- </x-app-shell>
95
- `,
96
- });
97
- ```
98
-
99
- Caveat: repetition. Every new page must remember the wrapper. The first time
100
- someone forgets it, the layout disappears and the LLM "fixes" it in the wrong
101
- place. **Do not start with this.**
102
-
103
- ## Where to find more
104
-
105
- - `src/page.ts` defines `layout()`, `page()`, `Guard` and `LayoutRoutes`.
106
- - `src/router/manifest.ts` flattens the layout manifest and applies guards
107
- outer → inner before the page renders.
108
- - The default starter (`mado init my-app`) ships with public, auth and
109
- authenticated app zones and is the reference implementation.
110
-
111
- If you ever feel tempted to invent a fourth pattern, write it down in your
112
- project `docs/` first and discuss it with the team. The cost of inconsistency
113
- in this exact spot is higher than the cost of a slightly awkward layout.
@@ -1,124 +0,0 @@
1
- # Auth and API
2
-
3
- The default starter is the blessed recipe. It keeps HTTP mechanics in
4
- `src/shared/http/` and auth state in `src/modules/auth/`.
5
-
6
- ## Shape
7
-
8
- ```txt
9
- src/shared/http/
10
- http-client.ts # one fetch wrapper + query/body/error handling
11
- http-error.ts # one error shape
12
- interceptors.ts # request/response hooks
13
-
14
- src/modules/auth/
15
- auth.connector.ts # /api/auth wire contract, DTO -> domain
16
- auth.service.ts # token/user signals + login/logout/init
17
- auth.guard.ts # requireAuth(), requirePermission()
18
- auth.routes.ts # login route map
19
- auth.public.ts # only public auth surface
20
- _contracts/ # backend DTOs, private to connector
21
- ```
22
-
23
- Every business module follows the same flow:
24
-
25
- ```txt
26
- connector -> resource/mutation -> page
27
- ```
28
-
29
- Pages do not import DTOs. Pages do not call `fetch()` directly. Connectors do
30
- not import Mado reactivity or UI.
31
-
32
- ## HTTP Client
33
-
34
- Use one small HTTP client for the app. It owns:
35
-
36
- - base URL handling;
37
- - JSON request/response defaults;
38
- - query string serialization;
39
- - `HttpError`;
40
- - request/response interceptors.
41
-
42
- Module connectors build on it:
43
-
44
- ```ts
45
- import { httpClient } from "../../shared/http/http-client";
46
- import type { User } from "./auth.types";
47
- import type { LoginResponseDTO } from "./_contracts/auth-api.types";
48
-
49
- const toUser = (dto: LoginResponseDTO["user"]): User => ({
50
- id: dto.id,
51
- email: dto.email,
52
- roles: dto.roles,
53
- permissions: dto.permissions,
54
- });
55
-
56
- export const authApi = {
57
- me: async () => toUser(await httpClient.get<LoginResponseDTO["user"]>("/api/auth/me")),
58
- };
59
- ```
60
-
61
- ## Auth Service
62
-
63
- Auth state is an ES module singleton:
64
-
65
- ```ts
66
- const _user = signal<User | null>(null);
67
- const _token = signal<string | null>(null);
68
-
69
- export const user = () => _user();
70
- export const isAuthed = computed(() => _user() !== null);
71
-
72
- export async function login(creds: Credentials): Promise<void> {
73
- const res = await authApi.login(creds);
74
- _token.set(res.token);
75
- _user.set(res.user);
76
- }
77
- ```
78
-
79
- Expose only what other modules need through `auth.public.ts`.
80
-
81
- ## Guards
82
-
83
- Guards are plain functions:
84
-
85
- ```ts
86
- export function requireAuth(): boolean | string {
87
- if (isAuthed()) return true;
88
- return "/login";
89
- }
90
- ```
91
-
92
- Use them in `src/app.routes.ts`:
93
-
94
- ```ts
95
- "/billing": layout({
96
- layout: () => import("./layouts/app-shell.layout"),
97
- guard: requireAuth,
98
- routes: billingRoutes,
99
- }),
100
- ```
101
-
102
- ## Dev Proxy
103
-
104
- Configure proxying in `vite.config.ts`:
105
-
106
- ```ts
107
- import { defineConfig } from "vite";
108
- import { mado } from "@madojs/mado/vite";
109
-
110
- export default defineConfig({
111
- plugins: [mado()],
112
- server: {
113
- proxy: { "/api": "http://localhost:3000" },
114
- },
115
- });
116
- ```
117
-
118
- ## Rule of Thumb
119
-
120
- - `shared/http` knows HTTP.
121
- - `*.connector.ts` knows one external system.
122
- - `*.resource.ts` knows cache keys and invalidation.
123
- - `*.page.ts` knows UI.
124
- - `*.public.ts` is the only cross-module surface.
@@ -1,100 +0,0 @@
1
- # Bake cookbook
2
-
3
- `mado bake` renders selected routes into static HTML. It is for SEO and fast
4
- first paint, not for server-side hydration.
5
-
6
- ## Minimal baked page
7
-
8
- ```ts
9
- export default page({
10
- head: () => ({ title: "Products", description: "Product catalog" }),
11
- view: ({ data }) => html`
12
- <main>
13
- <h1>Products</h1>
14
- ${data.products.map((p) => html`<article><h2>${p.name}</h2></article>`)}
15
- </main>
16
- `,
17
- bake: {
18
- paths: () => [{}],
19
- data: async () => ({ products: await api.products() }),
20
- revalidate: 3600,
21
- },
22
- });
23
- ```
24
-
25
- For baked pages, use plain arrays in `view()` when possible. Runtime-only
26
- directives such as keyed `each()` are for the browser.
27
-
28
- ## Dynamic routes
29
-
30
- ```ts
31
- export default page<{ slug: string }>({
32
- head: ({ slug }, data) => ({ title: data.title, canonical: `/blog/${slug}` }),
33
- view: ({ data }) => html`<article>${unsafeHTML(data.html)}</article>`,
34
- bake: {
35
- paths: async () => (await api.posts()).map((p) => ({ slug: p.slug })),
36
- data: ({ slug }) => api.post(slug),
37
- },
38
- });
39
- ```
40
-
41
- `unsafeHTML()` is allowed only for trusted or already-sanitized HTML.
42
-
43
- ## Route manifest
44
-
45
- `mado bake` needs the source manifest:
46
-
47
- ```ts
48
- export const manifest = {
49
- "/": () => import("./pages/home.js"),
50
- "/blog/:slug": () => import("./pages/blog-post.js"),
51
- };
52
-
53
- export default routes(manifest);
54
- ```
55
-
56
- ## Output
57
-
58
- By default standalone `mado bake` writes baked pages directly into `out/`.
59
- `mado release` first lets Vite build the production shell and assets, then bake
60
- replaces route HTML in place and writes `out/sitemap.xml`:
61
-
62
- ```bash
63
- mado release
64
- tree out
65
- ```
66
-
67
- The deployable folder is `out/`, not `dist/`.
68
-
69
- Use `mado release --keep-bake-dir` only when you want an extra `out/baked/`
70
- inspection copy during debugging.
71
-
72
- ## Client boot
73
-
74
- Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
75
- client app starts, `render()` replaces the baked DOM with live bindings. The
76
- first response contains SEO/first-paint HTML, then the SPA takes over normally.
77
-
78
- ## Unsupported values
79
-
80
- Bake intentionally fails loudly instead of writing `[object Object]`. If a baked
81
- view throws an unsupported directive error:
82
-
83
- - replace `each()` with `items.map(...)` in baked markup;
84
- - keep interactive-only widgets behind client routes;
85
- - make sure every value can be serialized to static HTML.
86
-
87
- ## Canonical links
88
-
89
- Pass `--base-url` so generated canonical links and sitemap entries point to
90
- production.
91
-
92
- ```bash
93
- mado bake --base-url https://example.com
94
- mado release --base-url https://example.com
95
- ```
96
-
97
- ## When not to bake
98
-
99
- Do not bake pages whose content is user-specific, permission-dependent, or
100
- changes every few seconds. Use a normal SPA route with `resource()` instead.