@madojs/mado 0.10.0 → 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 (219) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +98 -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/html/template.js +10 -0
  12. package/dist/src/html/template.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 -52
  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 +25 -26
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +17 -10
  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 +74 -48
  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 +30 -12
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +57 -4
  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 +75 -48
  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 +19 -13
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +48 -8
  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 +71 -58
  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 -560
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +17 -61
  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
@@ -1,198 +1,63 @@
1
1
  # Shadow DOM vs Light DOM
2
2
 
3
- Компоненты Mado по умолчанию используют Shadow DOM. Это хороший дефолт для
4
- самодостаточных виджетов, но не для каждого компонента в приложении.
3
+ Mado components используют Shadow DOM по умолчанию. Это хороший default для
4
+ самодостаточных виджетов, но app zones и страницы обычно должны оставаться
5
+ простыми light DOM templates.
5
6
 
6
- ## Правило большого пальца
7
+ ## Правило
7
8
 
8
- В Mado layout это тоже component. Если файл описывает видимую переиспользуемую
9
- часть UI-дерева — app shell, sidebar, modal, table, page section — по умолчанию
10
- делайте Web Component через `component()`.
11
-
12
- Обычные функции оставляйте для маленьких inline helpers:
9
+ Используйте route layouts для app zones:
13
10
 
14
11
  ```ts
15
- const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
12
+ export default page({
13
+ view: ({ child }) => html`<main class="app-main">${child}</main>`,
14
+ });
16
15
  ```
17
16
 
18
- Не стоит делать app shell функцией в публичных примерах. Это работает, но
19
- прячет browser model вместо того, чтобы её объяснять.
20
-
21
- Используйте **Shadow DOM** для leaf-виджетов:
22
-
23
- - кнопки, бейджи, карточки, метрики;
24
- - модалы, тосты, маленькие визуальные компоненты;
25
- - embed-виджеты, которые не должны наследовать CSS приложения случайно;
26
- - компоненты, стили которых принадлежат самому компоненту.
27
-
28
- Используйте **Light DOM** (`{ shadow: false }`) для структуры приложения,
29
- которая хочет разделять глобальные CSS-утилиты:
30
-
31
- - route/page компоненты;
32
- - admin-экраны с плотными таблицами/формами;
33
- - data-heavy экраны с таблицами и формами;
34
- - компоненты, которые намеренно используют глобальные layout/form/table утилиты;
35
- - места, где children должны оставаться обычным document DOM.
36
-
37
- Используйте **Shadow DOM** для slot-based layouts:
38
-
39
- - app shells с `<slot>`;
40
- - sidebar/content wrappers;
41
- - reusable layout frames, которые владеют своим grid/header/sidebar CSS.
42
-
43
- `<slot>` — это feature Shadow DOM. В компоненте с `shadow: false` тег `<slot>`
44
- становится обычным DOM-элементом и не переносит children в это место layout.
45
-
46
- ## Подвох
47
-
48
- Глобальный CSS не пересекает границу Shadow DOM.
49
-
50
- ```ts
51
- // global.ts
52
- export const globalStyles = css`
53
- .page-head {
54
- display: flex;
55
- justify-content: space-between;
56
- }
57
- .metric-grid {
58
- display: grid;
59
- grid-template-columns: repeat(4, 1fr);
60
- }
61
- `;
62
-
63
- // ❌ .page-head и .metric-grid НЕ применятся внутри shadowRoot x-dashboard
64
- component(
65
- "x-dashboard",
66
- () => () => html`
67
- <header class="page-head">...</header>
68
- <div class="metric-grid">...</div>
69
- `,
70
- );
71
- ```
17
+ Такие файлы живут в `src/layouts/` и подключаются в `src/app.routes.ts` через
18
+ `layout()`. Они стилизуются обычным CSS из `src/shared/styles/shell.css`.
72
19
 
73
- Решение — сделать route/page компонент Light DOM:
20
+ Используйте page files для screens:
74
21
 
75
22
  ```ts
76
- component(
77
- "x-dashboard",
78
- () => () => html`
79
- <header class="page-head">...</header>
80
- <div class="metric-grid">...</div>
81
- `,
82
- {
83
- shadow: false,
84
- styles: css`
85
- x-dashboard {
86
- display: block;
87
- }
88
- x-dashboard .panel {
89
- padding: 1rem;
90
- }
91
- `,
92
- },
93
- );
23
+ export default page({
24
+ view: () => html`<section><h1>Users</h1></section>`,
25
+ });
94
26
  ```
95
27
 
96
- Теперь глобальные утилиты и локальные scoped-стили работают вместе.
97
-
98
- ## Как ведут себя стили
28
+ Page-level tables, forms, prose and simple states стилизуются из
29
+ `src/shared/styles/content.css`.
99
30
 
100
- - `styles: css\`\`` в Shadow DOM адоптируется в shadowRoot компонента.
101
- - `styles: css\`\``с`shadow: false` скоупится по имени тега и адоптируется
102
- глобально.
103
- - CSS custom properties (`--accent`, `--bg` и т.д.) пересекают границу Shadow DOM.
104
- - Селекторы по классу (`.btn`, `.form-grid`, `.page-head`) **не** пересекают
105
- границу Shadow DOM.
106
- - Slotted-children сохраняют свои стили из документа; shadow-компонент может
107
- таргетировать их только через `::slotted(...)`.
108
- - `<slot>` проецирует children только в Shadow DOM. В компоненте с `shadow: false`
109
- это обычный `<slot>` элемент, который не перемещает children в своё место.
31
+ Используйте Shadow DOM components для leaf widgets:
110
32
 
111
- ## Рекомендованная архитектура
33
+ - buttons, badges, cards, metrics;
34
+ - spinners, modals, toasts;
35
+ - widgets, которые должны владеть своим CSS.
112
36
 
113
37
  ```ts
114
- // root и pages: Light DOM
115
- component("x-app", setup, { shadow: false });
116
- component("x-users-page", setup, { shadow: false });
117
-
118
- // slot-based layout: Shadow DOM по умолчанию, потому что владеет shell grid
119
- component("x-app-layout", setup);
120
-
121
- // leaf widgets: Shadow DOM по умолчанию
122
- component("x-status-badge", setup);
123
- component("x-stat-card", setup);
124
- component("x-toast-stack", setup);
125
- ```
126
-
127
- Это даёт backend-admin экранам предсказуемый CSS, сохраняя инкапсуляцию
128
- для переиспользуемых виджетов и slot-based shells.
129
-
130
- Import model специально browser-native:
131
-
132
- ```ts
133
- import "./components/app-layout.js";
134
-
135
- render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
136
- ```
137
-
138
- Import регистрирует custom element через `customElements.define()`. Template
139
- создаёт `<x-app-layout>` элемент. Дальше браузер сам связывает тег с классом
140
- компонента. Тут нет React-style component value, который передаётся как функция.
141
-
142
- Если layout не нуждается в slot projection и должен стилизоваться полностью
143
- глобальным CSS, `shadow: false` — хороший выбор. Если он содержит `<slot>`,
144
- оставьте Shadow DOM и поместите стили shell в этот компонент.
145
-
146
- ## Маршрутизация и ссылки
147
-
148
- `data-link` работает внутри Shadow DOM. Роутер использует `event.composedPath()`,
149
- поэтому перехват кликов и hover-prefetch видят ссылки из open shadow roots.
150
-
151
- ```ts
152
- component(
153
- "x-card-link",
154
- () => () => html` <a href="/app/accounts" data-link>Accounts</a> `,
155
- );
156
- ```
157
-
158
- Ссылка может быть внутри Shadow DOM — навигация всё равно остаётся SPA.
159
-
160
- ## Где импортировать компоненты
161
-
162
- Custom elements становятся глобальными после регистрации, но регистрация всё
163
- равно остаётся явным JavaScript import.
164
-
165
- ```ts
166
- // main.ts: global app frame
167
- import "./components/app-shell.js";
168
-
169
- // pages/tickets.ts: component, которым владеет эта page
170
- import "../components/ticket-list.js";
38
+ component("x-status-badge", ({ attr }) => {
39
+ const status = attr("status", "draft");
40
+ return () => html`<span>${status}</span>`;
41
+ }, {
42
+ styles: css`
43
+ :host { display: inline-block; }
44
+ span { color: var(--color-text-muted); }
45
+ `,
46
+ });
171
47
  ```
172
48
 
173
- Браузер **не** скачивает `ticket-list.js` только потому, что увидел
174
- `<ticket-list>`. Файл должен быть где-то импортирован. После import он вызывает
175
- `customElements.define(...)`, и тег становится известен текущему document.
176
-
177
- Не стоит bulk-import всех компонентов в `main.ts` «на всякий случай». Для маленьких
178
- demo это работает, но прячет ownership и ломает lazy route loading. Лучше:
179
-
180
- - global app shell/providers импортировать в `main.ts`;
181
- - components, которыми владеет одна page, импортировать в этой page;
182
- - shared components feature-а импортировать в feature entry page;
183
- - truly global leaf components импортировать в `main.ts` только если они реально
184
- используются везде.
185
-
186
- ## Урок из showcase
187
-
188
- `examples/showcase` использует это разделение намеренно:
189
-
190
- - `x-app` и CRM route pages — Light DOM;
191
- - `x-app-layout` остаётся в Shadow DOM, потому что владеет slot-based
192
- sidebar/content shell;
193
- - table/form/page утилиты живут в `styles/global.ts`;
194
- - leaf-компоненты типа `x-stat-card`, `x-status-badge`, `x-modal` и
195
- `x-toast-stack` остаются в Shadow DOM.
49
+ ## Как ведут себя стили
196
50
 
197
- Если страница внезапно выглядит без стилей, проверьте не используете ли вы
198
- глобальные классы внутри Shadow DOM компонента. Обычно проблема именно в этом.
51
+ - `tokens.css` задает CSS custom properties; `var(...)` проходит через Shadow
52
+ DOM.
53
+ - `reset.css`, `shell.css`, `content.css` применяются только к document/light
54
+ DOM.
55
+ - Class selectors вроде `.data`, `.app-main`, `.error` не проходят внутрь
56
+ Shadow DOM.
57
+ - Component-local styles пишутся в ``css`...` `` внутри `component()` options.
58
+ - Если Shadow component принимает children, используйте `<slot>` и стилизуйте
59
+ frame внутри component styles.
60
+
61
+ Если page выглядит без стилей, почти всегда вы использовали global classes
62
+ внутри Shadow DOM component. Вынесите markup в page/layout или перенесите CSS в
63
+ component styles.
@@ -1,100 +1,152 @@
1
1
  # Архитектура приложения
2
2
 
3
- Базовая форма production-приложения на Mado должна быть скучной: один route
4
- manifest, один shell, один API-клиент, один auth-модуль и страницы, которые
5
- импортируют свои компоненты.
3
+ Официальный starter — каноничная production-форма Mado-приложения. Это не
4
+ framework внутри framework: только файлы, imports, Mado primitives и ESLint
5
+ boundaries.
6
6
 
7
7
  ## Структура
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
- ├── components/
22
- ├── lib/
23
- │ ├── api.ts
24
- └── auth.ts
25
- └── styles/
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/
26
39
  ```
27
40
 
28
- `lib/` хранит бизнес-логику, `layouts/` оборачивает группы роутов,
29
- `components/` хранит переиспользуемые UI-теги, а `pages/` содержит один файл
30
- на страницу.
41
+ ## App Map
31
42
 
32
- ## Entry point
33
-
34
- ```ts
35
- import { html, render } from "@madojs/mado";
36
- import "./styles/global.js";
37
- import "./components/x-button.js";
38
- import routesApi from "./routes.js";
39
-
40
- render(html`${routesApi.view}`, document.getElementById("app")!);
41
- ```
42
-
43
- В `main.ts` импортируй только глобальные провайдеры, стили и маленькие общие
44
- компоненты. Feature-компоненты импортирует страница, которая их рендерит.
45
-
46
- ## Routes
43
+ `src/app.routes.ts` карта всего приложения. Modules экспортируют plain route
44
+ maps; app routes решают, какой shell и guard оборачивают каждую зону.
47
45
 
48
46
  ```ts
49
47
  import { layout, routes } from "@madojs/mado";
50
- 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";
51
51
 
52
52
  export const manifest = {
53
- "/": () => import("./pages/home.js"),
53
+ "/": () => import("./modules/home/home.page"),
54
54
  "/login": layout({
55
- layout: () => import("./layouts/auth.js"),
56
- routes: { "/": () => import("./pages/login.js") },
55
+ layout: () => import("./layouts/auth-shell.layout"),
56
+ routes: authRoutes,
57
57
  }),
58
- "/admin": layout({
59
- layout: () => import("./layouts/app.js"),
58
+ "/billing": layout({
59
+ layout: () => import("./layouts/app-shell.layout"),
60
60
  guard: requireAuth,
61
- routes: {
62
- "/": () => import("./pages/admin/dashboard.js"),
63
- "/orders": () => import("./pages/admin/orders.js"),
64
- },
61
+ routes: billingRoutes,
65
62
  }),
66
- "*": () => import("./pages/not-found.js"),
63
+ "*": () => import("./modules/home/not-found.page"),
67
64
  };
68
65
 
69
66
  export default routes(manifest);
70
67
  ```
71
68
 
72
- `export const manifest` нужен для `mado bake`.
69
+ Rules:
73
70
 
74
- ## API, forms, release
71
+ - Export `manifest`, чтобы `mado bake` мог найти bakeable pages.
72
+ - Modules не вызывают `layout()`.
73
+ - Layouts описывают app zones, не домены.
74
+ - Router не прячется в custom element или втором shell в `main.ts`.
75
75
 
76
- Держи один API-клиент и один auth-модуль. Для списков используй `resource()`,
77
- для записей `mutation(..., { invalidates })`, для форм `useForm()`.
76
+ ## File Forms
78
77
 
79
- ```ts
80
- const form = useForm({
81
- email: { required: true, type: "email" },
82
- "items.*.title": { required: true },
83
- });
84
- const items = form.array("items");
85
- ```
78
+ | Suffix | Role |
79
+ | --- | --- |
80
+ | `*.page.ts` | route page, default `page({...})` |
81
+ | `*.layout.ts` | app-zone wrapper, default `page(...)` |
82
+ | `*.connector.ts` | one external API system |
83
+ | `*.resource.ts` | `resource()` and `mutation()` layer |
84
+ | `*.service.ts` | module singleton state |
85
+ | `*.guard.ts` | route guard |
86
+ | `*.routes.ts` | module-local route map |
87
+ | `*.public.ts` | only public module surface |
88
+ | `*.types.ts` | domain types |
89
+ | `*.component.ts` | Web Component registration |
86
90
 
87
- Разработка:
91
+ Page-local signals, resources and forms live inside `view()`. Module-wide state
92
+ lives in `*.service.ts`.
88
93
 
89
- ```bash
90
- mado dev
94
+ ## Data Flow
95
+
96
+ ```txt
97
+ shared/http/http-client.ts
98
+
99
+ modules/<x>/api/*.connector.ts DTO -> domain mapping
100
+
101
+ modules/<x>/data/*.resource.ts cache keys + mutations
102
+
103
+ modules/<x>/pages/*.page.ts UI consumes domain types
91
104
  ```
92
105
 
93
- Продакшен:
106
+ ## Module Boundaries
107
+
108
+ Модуль opaque, кроме `<module>.public.ts`. Другие modules импортируют через этот
109
+ файл или никак. DTO внешних систем лежат в `_contracts/` и остаются private для
110
+ connector.
111
+
112
+ Default starter enforces this with ESLint:
113
+
114
+ - no barrels (`index.ts`);
115
+ - no `export *`;
116
+ - no CSS imports outside `src/main.ts`;
117
+ - no cross-module imports except public surfaces;
118
+ - no `_contracts` imports outside connectors.
119
+
120
+ ## Styles
121
+
122
+ | File | Role |
123
+ | --- | --- |
124
+ | `src/shared/styles/tokens.css` | design tokens as CSS custom properties |
125
+ | `src/shared/styles/reset.css` | document/light DOM reset |
126
+ | `src/shared/styles/shell.css` | app-zone layouts from `src/layouts/` |
127
+ | `src/shared/styles/content.css` | page-level forms, tables, prose and states |
128
+
129
+ Reusable leaf components keep their own styles in ``css`...` `` inside
130
+ `component()` options and depend on tokens, not global classes.
131
+
132
+ `vite.config.ts` opts into Vite Lightning CSS transformer. Mado does not own
133
+ prefixing, CSS lowering or minification.
134
+
135
+ ## CLI
136
+
137
+ Use `mado new` for boring file scaffolding:
94
138
 
95
139
  ```bash
96
- mado release
97
- rsync -avz out/ user@server:/var/www/app/
140
+ mado new module billing
141
+ mado new page billing/pages/invoices-list
142
+ mado new connector billing/api/stripe
143
+ mado new resource billing/data/invoices
144
+ mado new service billing/cart
145
+ mado new form billing/invoice
146
+ mado new component billing/components/invoice-status-badge
147
+ mado new guard billing/billing
148
+ mado new layout app-shell
98
149
  ```
99
150
 
100
- Деплоится только `out/`. `dist/` внутренний результат `tsc`.
151
+ The generator writes files only. It does not edit `app.routes.ts`, does not scan
152
+ the filesystem, and refuses to overwrite existing files.
@@ -1,47 +1,47 @@
1
1
  # Layouts
2
2
 
3
- В Mado blessed-способ для layoutnested route group в `routes.ts`.
4
- Не заворачивай каждую страницу вручную и не клади общий shell в `main.ts`,
5
- если в приложении есть разные зоны вроде public/login/admin.
3
+ Blessed layout способ в Mado — route group в `src/app.routes.ts`.
4
+ Не заворачивайте каждую страницу вручную и не кладите общий shell в `main.ts`,
5
+ если есть разные зоны: public, auth, app, embed.
6
6
 
7
7
  ```ts
8
8
  import { layout, routes } from "@madojs/mado";
9
- import { requireAuth } from "./lib/auth.js";
9
+ import { requireAuth } from "./modules/auth/auth.public";
10
+ import { authRoutes } from "./modules/auth/auth.routes";
11
+ import { billingRoutes } from "./modules/billing/billing.routes";
10
12
 
11
13
  export const manifest = {
12
- "/": () => import("./pages/home.js"),
14
+ "/": () => import("./modules/home/home.page.js"),
13
15
  "/login": layout({
14
- layout: () => import("./layouts/auth.js"),
15
- routes: { "/": () => import("./pages/login.js") },
16
+ layout: () => import("./layouts/auth-shell.layout.js"),
17
+ routes: authRoutes,
16
18
  }),
17
- "/admin": layout({
18
- layout: () => import("./layouts/app.js"),
19
+ "/billing": layout({
20
+ layout: () => import("./layouts/app-shell.layout.js"),
19
21
  guard: requireAuth,
20
- routes: {
21
- "/": () => import("./pages/admin/dashboard.js"),
22
- "/orders": () => import("./pages/admin/orders.js"),
23
- },
22
+ routes: billingRoutes,
24
23
  }),
25
- "*": () => import("./pages/not-found.js"),
24
+ "*": () => import("./modules/home/not-found.page.js"),
26
25
  };
27
26
 
28
27
  export default routes(manifest);
29
28
  ```
30
29
 
31
- Layout — это обычная `page({ view })`, которая рендерит `child`:
30
+ Layout — обычный `page({ view })`, который рендерит `child`:
32
31
 
33
32
  ```ts
34
33
  export default page({
35
- view: ({ child }) => html`<x-app-shell>${child}</x-app-shell>`,
34
+ view: ({ child }) => html`
35
+ <div class="layout layout--app">
36
+ <main class="app-main">${child}</main>
37
+ </div>
38
+ `,
36
39
  });
37
40
  ```
38
41
 
39
- Правила:
42
+ Rules:
40
43
 
41
- - один shell на группу, не на каждую страницу;
42
- - outer layouts оборачивают inner layouts;
43
- - guard на группе защищает всю поддеревянную часть;
44
- - layout можно lazy-load через `() => import(...)`.
45
-
46
- Single-shell wrapper в `main.ts` допустим только для приложений, где абсолютно
47
- все роуты живут в одной оболочке.
44
+ - one shell per route group, not per page;
45
+ - modules export plain route maps and do not call `layout()`;
46
+ - guard on a group protects the whole subtree;
47
+ - layout view stays stateless; page-local state lives in pages/components/resources.
@@ -1,53 +1,75 @@
1
1
  # Auth and API
2
2
 
3
- Mado не навязывает auth, но starter `admin` дает blessed recipe:
3
+ Default starter blessed recipe. HTTP mechanics живут в `src/shared/http/`,
4
+ auth state — в `src/modules/auth/`.
4
5
 
5
- - `src/lib/api.ts` — один HTTP boundary, `ApiError`, refresh-on-401;
6
- - `src/lib/auth.ts` — `accessToken`, `restoreSession()`, `login()`,
7
- `logout()`, `requireAuth` guard.
6
+ ```txt
7
+ src/shared/http/
8
+ http-client.ts
9
+ http-error.ts
10
+ interceptors.ts
8
11
 
9
- Модель:
12
+ src/modules/auth/
13
+ auth.connector.ts
14
+ auth.service.ts
15
+ auth.guard.ts
16
+ auth.routes.ts
17
+ auth.public.ts
18
+ _contracts/
19
+ ```
20
+
21
+ Flow для business module:
22
+
23
+ ```txt
24
+ connector -> resource/mutation -> page
25
+ ```
26
+
27
+ Pages не импортируют DTOs и не вызывают `fetch()` напрямую. Connectors не
28
+ импортируют Mado reactivity или UI.
10
29
 
11
- - access token хранится в памяти через `signal`, не в `localStorage`;
12
- - HttpOnly refresh cookie восстанавливает сессию после reload;
13
- - все запросы идут через один API-клиент;
14
- - protected routes закрываются group guard.
30
+ ## Auth Service
31
+
32
+ Auth state ES module singleton:
15
33
 
16
34
  ```ts
17
- export const requireAuth: Guard = async ({ path }) => {
18
- if (accessToken()) return;
19
- if (await restoreSession()) return;
20
- return { redirect: `/login?return=${encodeURIComponent(path)}`, replace: true };
21
- };
35
+ const _user = signal<User | null>(null);
36
+ const _token = signal<string | null>(null);
37
+
38
+ export const user = () => _user();
39
+ export const isAuthed = computed(() => _user() !== null);
22
40
  ```
23
41
 
24
- В route manifest:
42
+ Expose only what other modules need through `auth.public.ts`.
43
+
44
+ ## Guards
25
45
 
26
46
  ```ts
27
- "/admin": layout({
28
- layout: () => import("./layouts/app.js"),
29
- guard: requireAuth,
30
- routes: { "/": () => import("./pages/admin/dashboard.js") },
31
- }),
47
+ export function requireAuth(): boolean | string {
48
+ if (isAuthed()) return true;
49
+ return "/login";
50
+ }
32
51
  ```
33
52
 
34
- Backend contract по умолчанию:
53
+ Use in `src/app.routes.ts`:
35
54
 
36
- | Endpoint | Response | Notes |
37
- |---|---|---|
38
- | `POST /api/auth/login` | `{ accessToken }` | ставит HttpOnly refresh cookie |
39
- | `POST /api/auth/refresh` | `{ accessToken }` | читает refresh cookie |
40
- | `POST /api/auth/logout` | `204` | очищает cookie |
55
+ ```ts
56
+ "/billing": layout({
57
+ layout: () => import("./layouts/app-shell.layout"),
58
+ guard: requireAuth,
59
+ routes: billingRoutes,
60
+ }),
61
+ ```
41
62
 
42
- Для dev proxy:
63
+ ## Dev Proxy
43
64
 
44
- ```jsonc
45
- {
46
- "dev": {
47
- "proxy": { "/api": "http://localhost:3000" }
48
- }
49
- }
65
+ ```ts
66
+ export default defineConfig({
67
+ plugins: [mado()],
68
+ server: {
69
+ proxy: { "/api": "http://localhost:3000" },
70
+ },
71
+ });
50
72
  ```
51
73
 
52
- Если backend использует другую схему auth, меняй только `api.ts`/`auth.ts`.
53
- Страницы должны оставаться невинными.
74
+ Rule: `shared/http` knows HTTP, connectors know one external system, resources
75
+ know cache keys, pages know UI, `*.public.ts` is the cross-module surface.