@madojs/mado 0.10.1 → 0.11.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 (217) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +68 -0
  3. package/README.md +18 -45
  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/index.d.ts +11 -6
  12. package/dist/src/index.js +5 -3
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/lazy.d.ts +1 -1
  15. package/dist/src/lazy.js +1 -1
  16. package/dist/src/lazy.js.map +1 -1
  17. package/dist/src/page.d.ts +17 -21
  18. package/dist/src/page.js +7 -12
  19. package/dist/src/page.js.map +1 -1
  20. package/dist/src/router/manifest.d.ts +1 -1
  21. package/dist/src/router/manifest.js +21 -13
  22. package/dist/src/router/manifest.js.map +1 -1
  23. package/dist/src/router/match.d.ts +2 -2
  24. package/dist/src/router/match.js +3 -3
  25. package/dist/src/router/match.js.map +1 -1
  26. package/dist/src/router/navigation.js +1 -1
  27. package/dist/src/router/navigation.js.map +1 -1
  28. package/dist/src/vite/index.d.ts +10 -0
  29. package/dist/src/vite/index.js +33 -0
  30. package/dist/src/vite/index.js.map +1 -0
  31. package/docs/en/00-the-mado-way.md +25 -12
  32. package/docs/en/01-routing.md +90 -142
  33. package/docs/en/02-project-layout.md +59 -53
  34. package/docs/en/03-static-bake.md +5 -6
  35. package/docs/en/05-why-mado.md +6 -6
  36. package/docs/en/06-for-backenders.md +18 -22
  37. package/docs/en/08-llm-zero-history-test.md +9 -14
  38. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  39. package/docs/en/10-app-architecture.md +158 -96
  40. package/docs/en/11-layouts.md +22 -24
  41. package/docs/en/12-auth-and-api.md +89 -182
  42. package/docs/en/13-deployment.md +18 -22
  43. package/docs/en/14-testing.md +4 -4
  44. package/docs/en/16-bake-cookbook.md +11 -12
  45. package/docs/en/18-api-freeze-map.md +6 -4
  46. package/docs/en/20-v1-stability.md +1 -1
  47. package/docs/fr/00-the-mado-way.md +55 -90
  48. package/docs/fr/01-routing.md +70 -152
  49. package/docs/fr/02-project-layout.md +61 -42
  50. package/docs/fr/03-static-bake.md +1 -1
  51. package/docs/fr/05-why-mado.md +6 -6
  52. package/docs/fr/06-for-backenders.md +7 -7
  53. package/docs/fr/08-llm-zero-history-test.md +21 -48
  54. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  55. package/docs/fr/10-app-architecture.md +110 -33
  56. package/docs/fr/11-layouts.md +24 -12
  57. package/docs/fr/12-auth-and-api.md +63 -22
  58. package/docs/fr/13-deployment.md +7 -10
  59. package/docs/fr/14-testing.md +1 -1
  60. package/docs/fr/16-bake-cookbook.md +2 -2
  61. package/docs/fr/18-api-freeze-map.md +1 -1
  62. package/docs/fr/20-v1-stability.md +1 -1
  63. package/docs/recipes/nginx/README.md +13 -0
  64. package/docs/ru/00-the-mado-way.md +53 -75
  65. package/docs/ru/01-routing.md +68 -143
  66. package/docs/ru/02-project-layout.md +61 -41
  67. package/docs/ru/03-static-bake.md +2 -2
  68. package/docs/ru/05-why-mado.md +6 -6
  69. package/docs/ru/06-for-backenders.md +7 -7
  70. package/docs/ru/08-llm-zero-history-test.md +9 -14
  71. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  72. package/docs/ru/10-app-architecture.md +115 -63
  73. package/docs/ru/11-layouts.md +24 -24
  74. package/docs/ru/12-auth-and-api.md +57 -35
  75. package/docs/ru/13-deployment.md +7 -11
  76. package/docs/ru/14-testing.md +1 -1
  77. package/docs/ru/16-bake-cookbook.md +12 -6
  78. package/docs/ru/18-api-freeze-map.md +5 -3
  79. package/docs/ru/20-v1-stability.md +1 -1
  80. package/docs/uk/00-the-mado-way.md +70 -44
  81. package/docs/uk/01-routing.md +41 -47
  82. package/docs/uk/02-project-layout.md +68 -41
  83. package/docs/uk/03-static-bake.md +1 -2
  84. package/docs/uk/06-for-backenders.md +3 -3
  85. package/docs/uk/08-llm-zero-history-test.md +22 -24
  86. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  87. package/docs/uk/10-app-architecture.md +72 -31
  88. package/docs/uk/11-layouts.md +25 -12
  89. package/docs/uk/12-auth-and-api.md +58 -22
  90. package/docs/uk/13-deployment.md +4 -3
  91. package/docs/uk/14-testing.md +1 -1
  92. package/docs/uk/18-api-freeze-map.md +1 -1
  93. package/docs/uk/20-v1-stability.md +1 -1
  94. package/llms.txt +14 -15
  95. package/package.json +18 -11
  96. package/scripts/_config.mjs +15 -161
  97. package/scripts/bake.mjs +67 -57
  98. package/scripts/cli/generate.mjs +348 -0
  99. package/scripts/cli/help.mjs +27 -0
  100. package/scripts/cli/index.mjs +79 -0
  101. package/scripts/cli/init.mjs +153 -0
  102. package/scripts/cli/release.mjs +152 -0
  103. package/scripts/cli/run.mjs +96 -0
  104. package/scripts/cli.mjs +2 -621
  105. package/scripts/package-smoke.mjs +4 -1
  106. package/scripts/preview.mjs +13 -37
  107. package/scripts/size-budget.mjs +5 -2
  108. package/scripts/vite.default.mjs +11 -0
  109. package/starters/default/.editorconfig +12 -0
  110. package/starters/default/README.md +74 -0
  111. package/starters/default/eslint.config.mjs +256 -0
  112. package/starters/default/index.html +13 -0
  113. package/starters/default/package.json +30 -0
  114. package/starters/default/public/favicon.svg +4 -0
  115. package/starters/default/src/app.routes.ts +39 -0
  116. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  117. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  118. package/starters/default/src/main.ts +16 -0
  119. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  120. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  121. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  122. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  123. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  124. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  125. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  126. package/starters/default/src/modules/auth/login.page.ts +62 -0
  127. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  128. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  129. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  130. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  131. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  132. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  133. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  134. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  135. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  136. package/starters/default/src/modules/home/home.page.ts +34 -0
  137. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  138. package/starters/default/src/shared/http/http-client.ts +86 -0
  139. package/starters/default/src/shared/http/http-error.ts +37 -0
  140. package/starters/default/src/shared/http/interceptors.ts +59 -0
  141. package/starters/default/src/shared/lib/format-date.ts +19 -0
  142. package/starters/default/src/shared/styles/content.css +70 -0
  143. package/starters/default/src/shared/styles/reset.css +32 -0
  144. package/starters/default/src/shared/styles/shell.css +57 -0
  145. package/starters/default/src/shared/styles/tokens.css +44 -0
  146. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  147. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  148. package/starters/default/src/styles.d.ts +1 -0
  149. package/starters/default/src/vite-env.d.ts +1 -0
  150. package/starters/default/tsconfig.json +24 -0
  151. package/starters/default/vite.config.ts +9 -0
  152. package/MADO_V1_PLAN.md +0 -179
  153. package/ROADMAP.md +0 -178
  154. package/dist/src/html.d.ts +0 -18
  155. package/dist/src/html.js +0 -17
  156. package/dist/src/html.js.map +0 -1
  157. package/dist/src/router.d.ts +0 -13
  158. package/dist/src/router.js +0 -13
  159. package/dist/src/router.js.map +0 -1
  160. package/scripts/bundle.mjs +0 -212
  161. package/scripts/llm-zero-history-smoke.mjs +0 -93
  162. package/scripts/new.mjs +0 -80
  163. package/scripts/showcase-regression.mjs +0 -392
  164. package/server/serve.mjs +0 -455
  165. package/starters/admin/README.md +0 -63
  166. package/starters/admin/index.html +0 -28
  167. package/starters/admin/mado.config.json +0 -22
  168. package/starters/admin/package.json +0 -24
  169. package/starters/admin/public/favicon.svg +0 -4
  170. package/starters/admin/src/components/x-button.ts +0 -82
  171. package/starters/admin/src/components/x-input.ts +0 -105
  172. package/starters/admin/src/layouts/app.ts +0 -101
  173. package/starters/admin/src/layouts/auth.ts +0 -41
  174. package/starters/admin/src/lib/api.ts +0 -184
  175. package/starters/admin/src/lib/auth.ts +0 -83
  176. package/starters/admin/src/main.ts +0 -15
  177. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  178. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  179. package/starters/admin/src/pages/admin/orders.ts +0 -117
  180. package/starters/admin/src/pages/home.ts +0 -34
  181. package/starters/admin/src/pages/login.ts +0 -70
  182. package/starters/admin/src/pages/not-found.ts +0 -12
  183. package/starters/admin/src/routes.ts +0 -40
  184. package/starters/admin/src/styles/global.ts +0 -86
  185. package/starters/admin/tsconfig.json +0 -15
  186. package/starters/crud/README.md +0 -33
  187. package/starters/crud/index.html +0 -28
  188. package/starters/crud/mado.config.json +0 -20
  189. package/starters/crud/package.json +0 -24
  190. package/starters/crud/src/components/app-shell.ts +0 -56
  191. package/starters/crud/src/components/ticket-detail.ts +0 -33
  192. package/starters/crud/src/components/ticket-form.ts +0 -69
  193. package/starters/crud/src/components/ticket-list.ts +0 -66
  194. package/starters/crud/src/lib/api.ts +0 -76
  195. package/starters/crud/src/main.ts +0 -9
  196. package/starters/crud/src/pages/home.ts +0 -34
  197. package/starters/crud/src/pages/not-found.ts +0 -12
  198. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  199. package/starters/crud/src/pages/ticket-new.ts +0 -7
  200. package/starters/crud/src/pages/tickets.ts +0 -7
  201. package/starters/crud/src/routes.ts +0 -11
  202. package/starters/crud/src/styles/global.ts +0 -155
  203. package/starters/crud/tsconfig.json +0 -15
  204. package/starters/minimal/README.md +0 -21
  205. package/starters/minimal/index.html +0 -28
  206. package/starters/minimal/mado.config.json +0 -20
  207. package/starters/minimal/package.json +0 -24
  208. package/starters/minimal/src/components/app-counter.ts +0 -31
  209. package/starters/minimal/src/main.ts +0 -9
  210. package/starters/minimal/src/pages/home.ts +0 -35
  211. package/starters/minimal/src/pages/not-found.ts +0 -14
  212. package/starters/minimal/src/routes.ts +0 -8
  213. package/starters/minimal/src/styles/global.ts +0 -60
  214. package/starters/minimal/tsconfig.json +0 -15
  215. package/templates/page-detail.ts +0 -63
  216. package/templates/page-form.ts +0 -94
  217. package/templates/page-list.ts +0 -79
@@ -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.
@@ -1,34 +1,32 @@
1
1
  # Layouts
2
2
 
3
- > **One blessed path.** Layouts in Mado are nested-route groups with a shared
3
+ > **One blessed path.** Layouts in Mado are route groups with a shared
4
4
  > shell. There is exactly one canonical place to declare a layout — your
5
- > `routes.ts` manifest. Putting layout code anywhere else (in `main.ts`, in a
5
+ > `app.routes.ts` manifest. Putting layout code anywhere else (in `main.ts`, in a
6
6
  > page view, in a global custom-element wrapper) is a bug pattern: the LLM and
7
7
  > the human both produce visually broken UI when they guess differently.
8
8
 
9
9
  ## The canonical recipe
10
10
 
11
11
  ```ts
12
- // src/routes.ts
12
+ // src/app.routes.ts
13
13
  import { layout, routes } from "@madojs/mado";
14
- import { requireAuth } from "./lib/auth.js";
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";
15
17
 
16
18
  export const manifest = {
17
- "/": () => import("./pages/home.js"), // no layout
19
+ "/": () => import("./modules/home/home.page.js"),
18
20
  "/login": layout({
19
- layout: () => import("./layouts/auth.js"), // centered card
20
- routes: { "/": () => import("./pages/login.js") },
21
+ layout: () => import("./layouts/auth-shell.layout.js"),
22
+ routes: authRoutes,
21
23
  }),
22
- "/admin": layout({
23
- layout: () => import("./layouts/app.js"), // admin shell
24
- guard: requireAuth, // ← see 12-auth-and-api.md
25
- routes: {
26
- "/": () => import("./pages/admin/dashboard.js"),
27
- "/orders": () => import("./pages/admin/orders.js"),
28
- "/orders/:id": () => import("./pages/admin/order-detail.js"),
29
- },
24
+ "/billing": layout({
25
+ layout: () => import("./layouts/app-shell.layout.js"),
26
+ guard: requireAuth,
27
+ routes: billingRoutes,
30
28
  }),
31
- "*": () => import("./pages/not-found.js"),
29
+ "*": () => import("./modules/home/not-found.page.js"),
32
30
  };
33
31
 
34
32
  export default routes(manifest);
@@ -62,14 +60,14 @@ That is the whole API.
62
60
  Without this convention, every page accumulates `<x-app-shell>${...}</x-app-shell>`
63
61
  boilerplate, the LLM eventually puts the shell wrapper into `main.ts` "to make
64
62
  it consistent", and the next refactor produces the classic
65
- *"navigation appears below the page content"* screenshot. The nested-routes
63
+ *"navigation appears below the page content"* screenshot. The layout-group
66
64
  recipe makes the shell the **outer frame** structurally; there is no way to
67
65
  re-order it by accident.
68
66
 
69
67
  ## Two acceptable alternatives (with caveats)
70
68
 
71
- These exist for completeness. Reach for them only if you cannot use nested
72
- routes.
69
+ These exist for completeness. Reach for them only if you cannot use layout
70
+ groups.
73
71
 
74
72
  ### a) A single shell with the router slot in `main.ts`
75
73
 
@@ -104,12 +102,12 @@ place. **Do not start with this.**
104
102
 
105
103
  ## Where to find more
106
104
 
107
- - `src/page.ts` defines `layout()`, `page()`, `Guard` and `NestedRoutes`.
108
- - `src/router/manifest.ts` flattens the nested manifest and applies guards
105
+ - `src/page.ts` defines `layout()`, `page()`, `Guard` and `LayoutRoutes`.
106
+ - `src/router/manifest.ts` flattens the layout manifest and applies guards
109
107
  outer → inner before the page renders.
110
- - The `admin` starter (`mado init my-app --starter admin`) ships with three
111
- groups (`/`, `/login`, `/admin`) and is the reference implementation.
108
+ - The default starter (`mado init my-app`) ships with public, auth and
109
+ authenticated app zones and is the reference implementation.
112
110
 
113
111
  If you ever feel tempted to invent a fourth pattern, write it down in your
114
112
  project `docs/` first and discuss it with the team. The cost of inconsistency
115
- in this exact spot is higher than the cost of a slightly awkward layout.
113
+ in this exact spot is higher than the cost of a slightly awkward layout.