@madojs/mado 0.10.1 → 0.11.1

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 (219) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +95 -0
  3. package/README.md +22 -47
  4. package/TODO.md +52 -48
  5. package/dist/src/component.d.ts +2 -1
  6. package/dist/src/component.js +5 -2
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/each.d.ts +1 -1
  9. package/dist/src/each.js +1 -1
  10. package/dist/src/each.js.map +1 -1
  11. package/dist/src/html/bindings.js +3 -3
  12. package/dist/src/html/bindings.js.map +1 -1
  13. package/dist/src/index.d.ts +11 -6
  14. package/dist/src/index.js +5 -3
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/lazy.d.ts +1 -1
  17. package/dist/src/lazy.js +1 -1
  18. package/dist/src/lazy.js.map +1 -1
  19. package/dist/src/page.d.ts +17 -21
  20. package/dist/src/page.js +7 -12
  21. package/dist/src/page.js.map +1 -1
  22. package/dist/src/router/manifest.d.ts +1 -1
  23. package/dist/src/router/manifest.js +21 -13
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/match.d.ts +2 -2
  26. package/dist/src/router/match.js +3 -3
  27. package/dist/src/router/match.js.map +1 -1
  28. package/dist/src/router/navigation.js +1 -1
  29. package/dist/src/router/navigation.js.map +1 -1
  30. package/dist/src/vite/index.d.ts +10 -0
  31. package/dist/src/vite/index.js +33 -0
  32. package/dist/src/vite/index.js.map +1 -0
  33. package/docs/en/00-the-mado-way.md +25 -12
  34. package/docs/en/01-routing.md +90 -142
  35. package/docs/en/02-project-layout.md +59 -53
  36. package/docs/en/03-static-bake.md +5 -6
  37. package/docs/en/05-why-mado.md +6 -6
  38. package/docs/en/06-for-backenders.md +18 -22
  39. package/docs/en/08-llm-zero-history-test.md +9 -14
  40. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  41. package/docs/en/10-app-architecture.md +158 -96
  42. package/docs/en/11-layouts.md +22 -24
  43. package/docs/en/12-auth-and-api.md +89 -182
  44. package/docs/en/13-deployment.md +18 -22
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +11 -12
  47. package/docs/en/18-api-freeze-map.md +6 -4
  48. package/docs/en/20-v1-stability.md +1 -1
  49. package/docs/fr/00-the-mado-way.md +55 -90
  50. package/docs/fr/01-routing.md +70 -152
  51. package/docs/fr/02-project-layout.md +61 -42
  52. package/docs/fr/03-static-bake.md +1 -1
  53. package/docs/fr/05-why-mado.md +6 -6
  54. package/docs/fr/06-for-backenders.md +7 -7
  55. package/docs/fr/08-llm-zero-history-test.md +21 -48
  56. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  57. package/docs/fr/10-app-architecture.md +110 -33
  58. package/docs/fr/11-layouts.md +24 -12
  59. package/docs/fr/12-auth-and-api.md +63 -22
  60. package/docs/fr/13-deployment.md +7 -10
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +2 -2
  63. package/docs/fr/18-api-freeze-map.md +1 -1
  64. package/docs/fr/20-v1-stability.md +1 -1
  65. package/docs/recipes/nginx/README.md +13 -0
  66. package/docs/ru/00-the-mado-way.md +53 -75
  67. package/docs/ru/01-routing.md +68 -143
  68. package/docs/ru/02-project-layout.md +61 -41
  69. package/docs/ru/03-static-bake.md +2 -2
  70. package/docs/ru/05-why-mado.md +6 -6
  71. package/docs/ru/06-for-backenders.md +7 -7
  72. package/docs/ru/08-llm-zero-history-test.md +9 -14
  73. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  74. package/docs/ru/10-app-architecture.md +115 -63
  75. package/docs/ru/11-layouts.md +24 -24
  76. package/docs/ru/12-auth-and-api.md +57 -35
  77. package/docs/ru/13-deployment.md +7 -11
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +12 -6
  80. package/docs/ru/18-api-freeze-map.md +5 -3
  81. package/docs/ru/20-v1-stability.md +1 -1
  82. package/docs/uk/00-the-mado-way.md +70 -44
  83. package/docs/uk/01-routing.md +41 -47
  84. package/docs/uk/02-project-layout.md +68 -41
  85. package/docs/uk/03-static-bake.md +1 -2
  86. package/docs/uk/06-for-backenders.md +3 -3
  87. package/docs/uk/08-llm-zero-history-test.md +22 -24
  88. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  89. package/docs/uk/10-app-architecture.md +72 -31
  90. package/docs/uk/11-layouts.md +25 -12
  91. package/docs/uk/12-auth-and-api.md +58 -22
  92. package/docs/uk/13-deployment.md +4 -3
  93. package/docs/uk/14-testing.md +1 -1
  94. package/docs/uk/18-api-freeze-map.md +1 -1
  95. package/docs/uk/20-v1-stability.md +1 -1
  96. package/llms.txt +14 -15
  97. package/package.json +18 -11
  98. package/scripts/_config.mjs +15 -161
  99. package/scripts/bake.mjs +74 -63
  100. package/scripts/cli/generate.mjs +348 -0
  101. package/scripts/cli/help.mjs +27 -0
  102. package/scripts/cli/index.mjs +79 -0
  103. package/scripts/cli/init.mjs +153 -0
  104. package/scripts/cli/release.mjs +152 -0
  105. package/scripts/cli/run.mjs +96 -0
  106. package/scripts/cli.mjs +2 -621
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +13 -37
  109. package/scripts/size-budget.mjs +5 -2
  110. package/scripts/vite.default.mjs +11 -0
  111. package/starters/default/.editorconfig +12 -0
  112. package/starters/default/README.md +74 -0
  113. package/starters/default/eslint.config.mjs +256 -0
  114. package/starters/default/index.html +13 -0
  115. package/starters/default/package.json +30 -0
  116. package/starters/default/public/favicon.svg +4 -0
  117. package/starters/default/src/app.routes.ts +39 -0
  118. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  119. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  120. package/starters/default/src/main.ts +16 -0
  121. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  122. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  123. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  124. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  125. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  126. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  127. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  128. package/starters/default/src/modules/auth/login.page.ts +62 -0
  129. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  130. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  131. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  132. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  133. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  134. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  135. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  136. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  137. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  138. package/starters/default/src/modules/home/home.page.ts +34 -0
  139. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  140. package/starters/default/src/shared/http/http-client.ts +86 -0
  141. package/starters/default/src/shared/http/http-error.ts +37 -0
  142. package/starters/default/src/shared/http/interceptors.ts +59 -0
  143. package/starters/default/src/shared/lib/format-date.ts +19 -0
  144. package/starters/default/src/shared/styles/content.css +70 -0
  145. package/starters/default/src/shared/styles/reset.css +32 -0
  146. package/starters/default/src/shared/styles/shell.css +57 -0
  147. package/starters/default/src/shared/styles/tokens.css +44 -0
  148. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  149. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  150. package/starters/default/src/styles.d.ts +1 -0
  151. package/starters/default/src/vite-env.d.ts +1 -0
  152. package/starters/default/tsconfig.json +24 -0
  153. package/starters/default/vite.config.ts +9 -0
  154. package/MADO_V1_PLAN.md +0 -179
  155. package/ROADMAP.md +0 -178
  156. package/dist/src/html.d.ts +0 -18
  157. package/dist/src/html.js +0 -17
  158. package/dist/src/html.js.map +0 -1
  159. package/dist/src/router.d.ts +0 -13
  160. package/dist/src/router.js +0 -13
  161. package/dist/src/router.js.map +0 -1
  162. package/scripts/bundle.mjs +0 -212
  163. package/scripts/llm-zero-history-smoke.mjs +0 -93
  164. package/scripts/new.mjs +0 -80
  165. package/scripts/showcase-regression.mjs +0 -392
  166. package/server/serve.mjs +0 -455
  167. package/starters/admin/README.md +0 -63
  168. package/starters/admin/index.html +0 -28
  169. package/starters/admin/mado.config.json +0 -22
  170. package/starters/admin/package.json +0 -24
  171. package/starters/admin/public/favicon.svg +0 -4
  172. package/starters/admin/src/components/x-button.ts +0 -82
  173. package/starters/admin/src/components/x-input.ts +0 -105
  174. package/starters/admin/src/layouts/app.ts +0 -101
  175. package/starters/admin/src/layouts/auth.ts +0 -41
  176. package/starters/admin/src/lib/api.ts +0 -184
  177. package/starters/admin/src/lib/auth.ts +0 -83
  178. package/starters/admin/src/main.ts +0 -15
  179. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  180. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  181. package/starters/admin/src/pages/admin/orders.ts +0 -117
  182. package/starters/admin/src/pages/home.ts +0 -34
  183. package/starters/admin/src/pages/login.ts +0 -70
  184. package/starters/admin/src/pages/not-found.ts +0 -12
  185. package/starters/admin/src/routes.ts +0 -40
  186. package/starters/admin/src/styles/global.ts +0 -86
  187. package/starters/admin/tsconfig.json +0 -15
  188. package/starters/crud/README.md +0 -33
  189. package/starters/crud/index.html +0 -28
  190. package/starters/crud/mado.config.json +0 -20
  191. package/starters/crud/package.json +0 -24
  192. package/starters/crud/src/components/app-shell.ts +0 -56
  193. package/starters/crud/src/components/ticket-detail.ts +0 -33
  194. package/starters/crud/src/components/ticket-form.ts +0 -69
  195. package/starters/crud/src/components/ticket-list.ts +0 -66
  196. package/starters/crud/src/lib/api.ts +0 -76
  197. package/starters/crud/src/main.ts +0 -9
  198. package/starters/crud/src/pages/home.ts +0 -34
  199. package/starters/crud/src/pages/not-found.ts +0 -12
  200. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  201. package/starters/crud/src/pages/ticket-new.ts +0 -7
  202. package/starters/crud/src/pages/tickets.ts +0 -7
  203. package/starters/crud/src/routes.ts +0 -11
  204. package/starters/crud/src/styles/global.ts +0 -155
  205. package/starters/crud/tsconfig.json +0 -15
  206. package/starters/minimal/README.md +0 -21
  207. package/starters/minimal/index.html +0 -28
  208. package/starters/minimal/mado.config.json +0 -20
  209. package/starters/minimal/package.json +0 -24
  210. package/starters/minimal/src/components/app-counter.ts +0 -31
  211. package/starters/minimal/src/main.ts +0 -9
  212. package/starters/minimal/src/pages/home.ts +0 -35
  213. package/starters/minimal/src/pages/not-found.ts +0 -14
  214. package/starters/minimal/src/routes.ts +0 -8
  215. package/starters/minimal/src/styles/global.ts +0 -60
  216. package/starters/minimal/tsconfig.json +0 -15
  217. package/templates/page-detail.ts +0 -63
  218. package/templates/page-form.ts +0 -94
  219. package/templates/page-list.ts +0 -79
@@ -36,11 +36,11 @@ If your case does not fall into the last point — Mado is most likely not the b
36
36
  | Size | ~6 KB | ~16 KB |
37
37
  | Age / support | ~10 years, Google | 6 months, single author |
38
38
  | Reactivity | `@property` decorators + manual `requestUpdate` | signals (`signal`/`computed`/`effect`) out of the box |
39
- | Router | none, you need to find one (`@lit-labs/router`, etc) | included: `routes()` + nested + prefetch + sync-cache |
39
+ | Router | none, you need to find one (`@lit-labs/router`, etc) | included: `routes()` + layout groups + prefetch |
40
40
  | Data fetching | none, you need to assemble it | `resource()` + `mutation()` + glob invalidation |
41
41
  | Forms | none | `useForm()` with HTML-like constraints |
42
42
  | SEO / static | complex (`@lit-labs/ssr`) | `bake` (linkedom) + edge-prerender |
43
- | Build | needs esbuild/rollup/webpack | `tsc` is enough |
43
+ | Build | needs framework-specific build plugins | Vite transport + native runtime |
44
44
  | Code style | classes + decorators | functions + tagged templates |
45
45
  | Ecosystem | real (Shoelace, Material Web, etc.) | none |
46
46
  | When to choose | writing a design system / Web Components library for embedding | writing a full application, want everything in one box |
@@ -60,14 +60,14 @@ If your case does not fall into the last point — Mado is most likely not the b
60
60
  | Reactivity | signals (same class of ideas) | signals |
61
61
  | Templates | JSX (compiled to reactive expressions) | tagged template `html\`\`` |
62
62
  | Component model | functions, Solid virtual nodes | Web Components |
63
- | Build | Vite + babel-plugin-solid required | `tsc` only |
63
+ | Build | Vite + babel-plugin-solid required | Vite for dev/build, no runtime dependencies |
64
64
  | Router | `@solidjs/router` | included |
65
65
  | Data | `createResource` | `resource()` |
66
66
  | SSR | seriously supported (SolidStart) | intentionally none |
67
67
  | Ecosystem | growing, ~50 packages | none |
68
- | When to choose | need top performance + JSX + willing to configure the build | want to run without a build / minimal infrastructure |
68
+ | When to choose | need top performance + JSX + willing to configure the build | want browser-native runtime with simple tooling |
69
69
 
70
- **Honest pitch:** _"Solid is technically faster and more mature. But Solid requires Vite + a babel plugin. Mado requires nothing but `tsc` it's 'open VS Code, F5, and work'. If that difference isn't critical go with Solid."_
70
+ **Honest pitch:** _"Solid is technically faster and more mature. Mado is smaller in concept: browser-native runtime, Web Components, tagged templates, and Vite doing the boring dev/build work. If you want JSX and a larger ecosystem, go with Solid."_
71
71
 
72
72
  ---
73
73
 
@@ -162,7 +162,7 @@ I won't dwell on this for long, because React is in a **different weight class**
162
162
 
163
163
  Not size, not performance, not signals — everything has better competitors.
164
164
 
165
- > **"Open the source and read it in an evening. ~3500 lines, 12 modules. If something breaks — you don't go to an issue with 3000 comments. You go to `src/router.ts` and read 500 lines."**
165
+ > **"Open the source and read it in an evening. ~3500 lines, small modules. If something breaks — you don't go to an issue with 3000 comments. You go to `src/router/` and read the code."**
166
166
 
167
167
  This is called **ownership** — you own the code, rather than depending on someone else's.
168
168
 
@@ -14,7 +14,7 @@ Mado is structured **like an HTTP server**. Seriously:
14
14
  | ----------------------------------- | ---------------------------------------------- |
15
15
  | HTTP router (chi, axum, mux) | `routes()` — path manifest |
16
16
  | Handler `func(req, resp)` | `page({ view: (ctx) => html\`...\` })` |
17
- | Middleware | `layout` in `nested()` (wraps the handler) |
17
+ | Middleware | `layout()` route group (wraps the handler) |
18
18
  | Template engine (Jinja, Handlebars) | `html\`\`` tagged template |
19
19
  | HTTP client with cache | `resource()` — fetch + cache + invalidation |
20
20
  | Reactive variable / atom | `signal()` — reactive getter |
@@ -61,7 +61,7 @@ http.ListenAndServe(":8080", r)
61
61
  ### Mado — the same thing
62
62
 
63
63
  ```ts
64
- // src/routes.ts
64
+ // src/app.routes.ts
65
65
  import { routes } from "@madojs/mado";
66
66
 
67
67
  export default routes({
@@ -71,7 +71,7 @@ export default routes({
71
71
  ```
72
72
 
73
73
  ```ts
74
- // src/pages/home.ts
74
+ // src/modules/<module>/pages/home.ts
75
75
  import { page, html } from "@madojs/mado";
76
76
  export default page({
77
77
  view: () => html`<h1>Hello</h1>`,
@@ -79,7 +79,7 @@ export default page({
79
79
  ```
80
80
 
81
81
  ```ts
82
- // src/pages/user.ts
82
+ // src/modules/<module>/pages/user.ts
83
83
  import { page, html } from "@madojs/mado";
84
84
  export default page<{ id: string }>({
85
85
  view: ({ params }) => html`<h1>User ${params.id}</h1>`,
@@ -266,7 +266,7 @@ This is like `context.WithValue` / `ctx.Value` in Go, but reactive.
266
266
  If you're used to server-side rendering for SEO, in Mado this is solved differently: **prerender at build time**.
267
267
 
268
268
  ```ts
269
- // src/pages/product.ts
269
+ // src/modules/<module>/pages/product.ts
270
270
  export default page({
271
271
  bake: {
272
272
  paths: () => api.allProductSlugs(), // build-time fetch
@@ -369,37 +369,33 @@ export default page({
369
369
  ```
370
370
 
371
371
  ```ts
372
- // src/routes.ts
373
- import { routes, nested } from "@madojs/mado";
372
+ // src/app.routes.ts
373
+ import { layout, routes } from "@madojs/mado";
374
374
 
375
375
  export default routes({
376
376
  "/login": () => import("./pages/login.js"),
377
377
 
378
- "/app/*": nested({
378
+ "/app": layout({
379
379
  layout: () => import("./layouts/auth-layout.js"),
380
380
  routes: {
381
- dashboard: () => import("./pages/dashboard.js"),
382
- users: () => import("./pages/users.js"),
381
+ "/dashboard": () => import("./pages/dashboard.js"),
382
+ "/users": () => import("./pages/users.js"),
383
383
  },
384
384
  }),
385
385
  });
386
386
  ```
387
387
 
388
- ### Global API client (like a singleton in Go)
388
+ ### Shared HTTP client (like a small transport package in Go)
389
389
 
390
390
  ```ts
391
- // src/lib/api.ts
392
- export class ApiClient {
393
- constructor(private base: string) {}
394
- get<T>(path: string): Promise<T> {
395
- return fetch(this.base + path).then((r) => r.json());
396
- }
397
- }
398
-
399
- export const api = new ApiClient("/api");
391
+ // src/shared/http/http-client.ts
392
+ export const httpClient = {
393
+ get: <T>(path: string): Promise<T> =>
394
+ fetch(path).then((r) => r.json() as Promise<T>),
395
+ };
400
396
  ```
401
397
 
402
- Used directly via `import { api } from '...'` or through `createContext` for testability.
398
+ Module connectors build on this transport and map DTOs to domain types.
403
399
 
404
400
  ---
405
401
 
@@ -442,6 +438,6 @@ Everything else — standard browser + TypeScript.
442
438
  - **[`01-routing.md`](./01-routing.md)** — the router in detail.
443
439
  - **[`02-project-layout.md`](./02-project-layout.md)** — project structure.
444
440
  - **[`03-static-bake.md`](./03-static-bake.md)** — SEO without SSR.
445
- - **[`examples/showcase/`](../../examples/showcase/)** — full example (landing + admin).
441
+ - **External `madojs-examples` workspace** — full demos (landing + admin).
446
442
 
447
443
  If something is unclear — open an issue, or just open the source. It really is readable in an evening.
@@ -12,15 +12,15 @@ For the first pass, give the agent only:
12
12
  - `AGENTS.md`
13
13
  - `README.md`
14
14
  - `docs/en/07-llm-pitfalls.md`
15
- - `examples/basic/README.md` if a minimal API tour is needed
16
- - specific `examples/showcase/**` files only when the agent asks for a larger app pattern
15
+ - files from the external `madojs-examples` workspace only when the agent asks
16
+ for a larger app pattern
17
17
 
18
18
  The agent may search targeted APIs in `src/` when blocked, but should not load
19
19
  the whole framework into context.
20
20
 
21
21
  ## Task
22
22
 
23
- Build `examples/tickets`: a small ticket-admin SPA for a solo/backend developer.
23
+ Build a small ticket-admin SPA for a solo/backend developer.
24
24
 
25
25
  Required behavior:
26
26
 
@@ -47,15 +47,10 @@ Look for these after implementation:
47
47
 
48
48
  ## Result notes
49
49
 
50
- The current `examples/tickets` implementation did not require new public APIs or
51
- runtime dependencies.
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.
52
53
 
53
- CI runs `npm run llm:smoke` as a deterministic proxy for this task: it verifies
54
- that `llms.txt` still contains the key guidance, checks the committed
55
- `examples/tickets` artifact against the required Mado API surface and failure
56
- patterns, then builds and runs `test/tickets-smoke.test.mjs`.
57
-
58
- The main documentation pressure point remains lifecycle: older examples can make
59
- it look acceptable to create `resource()` directly in `page.view()`. The tickets
60
- example uses page-level wrapper components instead, so resources are registered
61
- inside component setup and clean up with the component.
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.
@@ -6,18 +6,19 @@ an application.
6
6
 
7
7
  ## Rule of Thumb
8
8
 
9
- In Mado, layouts are components too. If a file represents a visible reusable
10
- part of the app tree — app shell, sidebar, modal, table, page section prefer a
11
- Web Component registered with `component()`.
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
12
 
13
- Use plain functions only for small inline template helpers:
13
+ Use Web Components registered with `component()` for reusable UI elements and
14
+ widgets. Use plain functions only for small inline template helpers:
14
15
 
15
16
  ```ts
16
17
  const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
17
18
  ```
18
19
 
19
- Do not use functions for app shells in public examples. They work, but they
20
- hide the browser model instead of teaching it.
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.
21
22
 
22
23
  Use **Shadow DOM** for leaf widgets:
23
24
 
@@ -26,23 +27,14 @@ Use **Shadow DOM** for leaf widgets:
26
27
  - embed widgets that should not inherit app CSS accidentally;
27
28
  - components whose styling should be owned by the component itself.
28
29
 
29
- Use **Light DOM** (`{ shadow: false }`) for app structure that wants to share
30
- global CSS utilities:
30
+ Use **Light DOM** for app structure that wants to share global CSS:
31
31
 
32
- - route/page components;
32
+ - route pages and route layouts;
33
33
  - admin screens with dense table/form layouts;
34
34
  - data-heavy screens with tables and forms;
35
- - components that intentionally share global layout, form and table utilities;
36
35
  - places where children should simply remain normal document DOM.
37
36
 
38
- Use **Shadow DOM** for slot-based layouts:
39
-
40
- - app shells that render `<slot>`;
41
- - sidebar/content wrappers;
42
- - reusable layout frames that own their own grid/header/sidebar CSS.
43
-
44
- `<slot>` is a Shadow DOM feature. In a `shadow: false` component, `<slot>` is
45
- just a normal element and does not move children into that position.
37
+ Route layouts receive `child` from Mado, so they do not need `<slot>`.
46
38
 
47
39
  ## The Footgun
48
40
 
@@ -96,12 +88,15 @@ Now global utilities and local scoped styles both work.
96
88
  ## Recommended App Shape
97
89
 
98
90
  ```ts
99
- // root and pages: Light DOM
100
- component("x-app", setup, { shadow: false });
101
- component("x-users-page", setup, { shadow: false });
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
+ });
102
95
 
103
- // slot-based layout: Shadow DOM default, because it owns the shell grid
104
- component("x-app-layout", setup);
96
+ // route page: light DOM, styled by shared/styles/content.css
97
+ export default page({
98
+ view: () => html`<section><h1>Users</h1></section>`,
99
+ });
105
100
 
106
101
  // leaf widgets: Shadow DOM default
107
102
  component("x-status-badge", setup);
@@ -109,8 +104,8 @@ component("x-stat-card", setup);
109
104
  component("x-toast-stack", setup);
110
105
  ```
111
106
 
112
- This gives backend-admin screens predictable CSS while preserving encapsulation
113
- for reusable widgets and slot-based shells.
107
+ This gives admin screens predictable CSS while preserving encapsulation for
108
+ reusable leaf widgets.
114
109
 
115
110
  The import model is deliberately browser-native:
116
111
 
@@ -124,9 +119,8 @@ The import registers the custom element with `customElements.define()`. The
124
119
  template creates an `<x-app-layout>` element. The browser connects the two.
125
120
  There is no React-style component value being passed around.
126
121
 
127
- If a layout does not need slot projection and should be styled entirely by
128
- global CSS, `shadow: false` can still be a good choice. If it contains
129
- `<slot>`, keep Shadow DOM and put the shell styles in that component.
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.
130
124
 
131
125
  ## Routing and Links
132
126
 
@@ -167,16 +161,14 @@ tiny demos, but it hides ownership and defeats lazy route loading. Prefer:
167
161
  - feature-owned shared components in the feature entry page;
168
162
  - truly global leaf components in `main.ts` only when they are used everywhere.
169
163
 
170
- ## Showcase Lesson
164
+ ## Starter Lesson
171
165
 
172
- `examples/showcase` uses this split deliberately:
166
+ The default starter uses this split deliberately:
173
167
 
174
- - `x-app` and CRM route pages are Light DOM;
175
- - `x-app-layout` keeps Shadow DOM because it owns a slot-based sidebar/content
176
- shell;
177
- - table/form/page utilities live in `styles/global.ts`;
178
- - leaf components such as `x-stat-card`, `x-status-badge`, `x-modal`, and
179
- `x-toast-stack` keep Shadow DOM.
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.
180
172
 
181
173
  If a page suddenly looks unstyled, check whether it uses global classes inside a
182
174
  Shadow DOM component. That is usually the issue.
@@ -1,141 +1,203 @@
1
1
  # App architecture
2
2
 
3
- This is the default shape for a production Mado app. It is intentionally boring:
4
- one route manifest, one shell, one API client, one auth module, and page files
5
- that own their feature components.
3
+ The official starter is the canonical production shape for Mado apps. It is
4
+ not a demo architecture and not a framework inside the framework: it is plain
5
+ files, imports, Mado primitives, and ESLint boundaries.
6
6
 
7
- ## File tree
7
+ ## File Tree
8
8
 
9
9
  ```txt
10
10
  src/
11
11
  ├── main.ts
12
- ├── routes.ts
12
+ ├── app.routes.ts
13
13
  ├── layouts/
14
- │ ├── app.ts
15
- │ └── auth.ts
16
- ├── pages/
17
- │ ├── home.ts
18
- │ ├── login.ts
19
- │ ├── not-found.ts
20
- │ └── admin/
21
- │ ├── dashboard.ts
22
- ├── orders.ts
23
- └── order-detail.ts
24
- ├── components/
25
- │ ├── x-button.ts
26
- └── x-input.ts
27
- ├── lib/
28
- │ ├── api.ts
29
- │ └── auth.ts
30
- └── styles/
31
- └── global.ts
14
+ │ ├── app-shell.layout.ts
15
+ │ └── auth-shell.layout.ts
16
+ ├── shared/
17
+ │ ├── http/
18
+ │ ├── lib/
19
+ │ ├── styles/
20
+ │ └── ui/
21
+ └── modules/
22
+ ├── auth/
23
+ ├── auth.routes.ts
24
+ ├── auth.public.ts
25
+ │ ├── auth.service.ts
26
+ ├── auth.connector.ts
27
+ ├── auth.guard.ts
28
+ │ ├── login.page.ts
29
+ │ └── _contracts/
30
+ └── billing/
31
+ ├── billing.routes.ts
32
+ ├── billing.public.ts
33
+ ├── billing.types.ts
34
+ ├── api/
35
+ ├── data/
36
+ ├── pages/
37
+ ├── components/
38
+ └── _contracts/
32
39
  ```
33
40
 
34
- Keep business logic in `lib/`, route wrapping in `layouts/`, and UI leaves in
35
- `components/`. A page should import the components it renders.
41
+ ## The App Map
36
42
 
37
- ## Entry point
43
+ `src/app.routes.ts` is the whole application map. Modules export plain route
44
+ maps; app routes decide which shell and guard wrap each zone.
38
45
 
39
46
  ```ts
40
- // src/main.ts
41
- import { html, render } from "@madojs/mado";
42
- import "./styles/global.js";
43
- import "./components/x-button.js";
44
- import routesApi from "./routes.js";
45
-
46
- render(html`${routesApi.view}`, document.getElementById("app")!);
47
- ```
48
-
49
- Import global providers and tiny shared components here. Do not bulk-import
50
- every feature component.
51
-
52
- ## Routes
53
-
54
- ```ts
55
- // src/routes.ts
56
47
  import { layout, routes } from "@madojs/mado";
57
- import { requireAuth } from "./lib/auth.js";
48
+ import { requireAuth } from "./modules/auth/auth.public";
49
+ import { authRoutes } from "./modules/auth/auth.routes";
50
+ import { billingRoutes } from "./modules/billing/billing.routes";
58
51
 
59
52
  export const manifest = {
60
- "/": () => import("./pages/home.js"),
53
+ "/": () => import("./modules/home/home.page"),
61
54
  "/login": layout({
62
- layout: () => import("./layouts/auth.js"),
63
- routes: { "/": () => import("./pages/login.js") },
55
+ layout: () => import("./layouts/auth-shell.layout"),
56
+ routes: authRoutes,
64
57
  }),
65
- "/admin": layout({
66
- layout: () => import("./layouts/app.js"),
58
+ "/billing": layout({
59
+ layout: () => import("./layouts/app-shell.layout"),
67
60
  guard: requireAuth,
68
- routes: {
69
- "/": () => import("./pages/admin/dashboard.js"),
70
- "/orders": () => import("./pages/admin/orders.js"),
71
- "/orders/:id": () => import("./pages/admin/order-detail.js"),
72
- },
61
+ routes: billingRoutes,
73
62
  }),
74
- "*": () => import("./pages/not-found.js"),
63
+ "*": () => import("./modules/home/not-found.page"),
75
64
  };
76
65
 
77
- export default routes(manifest, {
78
- errorPage: (err) => html`<main><h1>Something went wrong</h1><pre>${err.message}</pre></main>`,
79
- });
66
+ export default routes(manifest);
80
67
  ```
81
68
 
82
- Exporting `manifest` lets `mado bake` inspect the same route table.
69
+ Rules:
83
70
 
84
- ## API and auth
71
+ - Export `manifest` so `mado bake` can discover bakeable pages.
72
+ - Modules never call `layout()`.
73
+ - Layouts describe app zones, not domains.
74
+ - Do not hide the router inside a custom element or a second shell in
75
+ `main.ts`.
85
76
 
86
- Use one API client and one auth module. The admin starter ships a complete
87
- version with token storage, a single-flight refresh request, and a route guard.
77
+ ## File Forms
88
78
 
89
- ```ts
90
- // pages/admin/orders.ts
91
- import { each, html, page, resource } from "@madojs/mado";
92
- import { api } from "../../lib/api.js";
93
-
94
- const orders = resource(() => "/api/orders", () => api.get("/orders"));
95
-
96
- export default page({
97
- title: "Orders",
98
- view: () => html`
99
- <main>
100
- <h1>Orders</h1>
101
- <ul>
102
- ${() => each(orders.data() ?? [], o => o.id, o => html`<li>${o.number}</li>`)}
103
- </ul>
104
- </main>
105
- `,
106
- });
79
+ The suffix tells you the shape:
80
+
81
+ | Suffix | Role |
82
+ | ----------------- | ------------------------------------- |
83
+ | `*.page.ts` | route page, default `page({...})` |
84
+ | `*.layout.ts` | app-zone wrapper, default `page(...)` |
85
+ | `*.connector.ts` | one external API system |
86
+ | `*.resource.ts` | `resource()` and `mutation()` layer |
87
+ | `*.service.ts` | module singleton state |
88
+ | `*.guard.ts` | route guard |
89
+ | `*.routes.ts` | module-local route map |
90
+ | `*.public.ts` | only public module surface |
91
+ | `*.types.ts` | domain types |
92
+ | `*.component.ts` | Web Component registration |
93
+
94
+ Page-local signals, resources and forms live inside `view()`. Module-wide
95
+ state lives in `*.service.ts`.
96
+
97
+ ## Data Flow
98
+
99
+ ```txt
100
+ shared/http/http-client.ts
101
+
102
+ modules/<x>/api/*.connector.ts DTO -> domain mapping
103
+
104
+ modules/<x>/data/*.resource.ts cache keys + mutations
105
+
106
+ modules/<x>/pages/*.page.ts UI consumes domain types
107
107
  ```
108
108
 
109
- Mutations should declare invalidation near the write:
109
+ Connectors talk to the wire and return domain types. Resources own cache keys
110
+ and invalidation. Pages render and call resources/mutations.
110
111
 
111
- ```ts
112
- const save = mutation((payload) => api.post("/orders", payload), {
113
- invalidates: ["/api/orders*"],
114
- });
112
+ ## Module Boundaries
113
+
114
+ Each module is opaque except for `<module>.public.ts`. Other modules import
115
+ through that file or not at all. DTOs live under `_contracts/` and stay private
116
+ to the connector that understands that external system.
117
+
118
+ The default starter enforces this with ESLint:
119
+
120
+ - no barrels (`index.ts`);
121
+ - no `export *`;
122
+ - no CSS imports outside `src/main.ts`;
123
+ - no cross-module imports except public surfaces;
124
+ - no `_contracts` imports outside connectors.
125
+
126
+ ## Styles
127
+
128
+ The starter uses plain CSS plus component-local ``css`...` ``. The split is
129
+ intentional:
130
+
131
+ | File | Role |
132
+ | ---- | ---- |
133
+ | `src/shared/styles/tokens.css` | design tokens as CSS custom properties |
134
+ | `src/shared/styles/reset.css` | document/light DOM reset |
135
+ | `src/shared/styles/shell.css` | app-zone layouts from `src/layouts/` |
136
+ | `src/shared/styles/content.css` | page-level forms, tables, prose and states |
137
+
138
+ Reusable leaf components keep their own styles in ``css`...` `` inside
139
+ `component()` options. They should depend on tokens (`var(--color-text)`)
140
+ rather than global classes.
141
+
142
+ `vite.config.ts` opts into Vite's Lightning CSS transformer. Mado does not own
143
+ prefixing, CSS lowering or minification.
144
+
145
+ ## Growth
146
+
147
+ Start flat when a module is small:
148
+
149
+ ```txt
150
+ modules/auth/
151
+ auth.types.ts
152
+ auth.routes.ts
153
+ auth.public.ts
154
+ auth.service.ts
155
+ auth.connector.ts
156
+ login.page.ts
115
157
  ```
116
158
 
117
- ## Forms
159
+ When a module grows, group by role without changing suffixes:
118
160
 
119
- Prefer one `useForm()` per user workflow.
161
+ ```txt
162
+ modules/billing/
163
+ pages/
164
+ data/
165
+ api/
166
+ components/
167
+ forms/
168
+ _contracts/
169
+ ```
120
170
 
121
- ```ts
122
- const form = useForm({
123
- email: { required: true, type: "email" },
124
- "items.*.title": { required: true },
125
- });
126
- const items = form.array("items");
171
+ If a module becomes too large, split it by subdomain and keep communication
172
+ through public surfaces.
173
+
174
+ ## CLI
175
+
176
+ Use `mado new` for boring file scaffolding:
177
+
178
+ ```bash
179
+ mado new module billing
180
+ mado new page billing/pages/invoices-list
181
+ mado new connector billing/api/stripe
182
+ mado new resource billing/data/invoices
183
+ mado new service billing/cart
184
+ mado new form billing/invoice
185
+ mado new component billing/components/invoice-status-badge
186
+ mado new guard billing/billing
187
+ mado new layout app-shell
127
188
  ```
128
189
 
129
- Use dotted paths for arrays (`items.0.title`) and keep async validation in
130
- `validateAsync` when it talks to the backend.
190
+ The generator only writes new files. It does not edit `app.routes.ts`, does
191
+ not scan the filesystem, and refuses to overwrite existing files.
131
192
 
132
193
  ## Release
133
194
 
134
- Local development uses `mado dev`. Production uses exactly one artifact:
195
+ Local development uses Vite through `mado dev`. Production uses one deploy
196
+ artifact:
135
197
 
136
198
  ```bash
137
199
  mado release
138
200
  rsync -avz out/ user@server:/var/www/app/
139
201
  ```
140
202
 
141
- `out/` is the deploy folder. `dist/` is internal build output.
203
+ `out/` is the deploy folder. `dist/` is internal package output.