@madojs/mado 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
@@ -9,27 +9,29 @@
9
9
  //
10
10
  // Layouts live in src/layouts/ because they describe APP ZONES, not domains.
11
11
 
12
- import { html, page } from "@madojs/mado";
12
+ import { html, page, routeUrl } from "@madojs/mado";
13
13
 
14
14
  import { isAuthed, logout, user } from "../modules/auth/auth.public";
15
- import "../shared/ui/x-button.component";
16
15
 
17
16
  export default page({
18
17
  title: "Mado App",
18
+ // Every internal anchor uses `data-link` + `routeUrl()` so SPA
19
+ // navigation intercepts the click and the href stays correct under
20
+ // any Vite `base` (e.g. deploying the app under `/admin/`).
19
21
  view: ({ child }) => html`
20
22
  <div class="layout layout--app">
21
23
  <header class="app-header">
22
- <a href="/" class="brand">Mado App</a>
24
+ <a data-link href=${routeUrl("/")} class="brand">Mado App</a>
23
25
  <nav>
24
- <a href="/billing/invoices">Invoices</a>
26
+ <a data-link href=${routeUrl("/billing/invoices")}>Invoices</a>
25
27
  ${() =>
26
28
  isAuthed()
27
29
  ? html`<span class="who">${() => user()?.email ?? ""}</span>
28
- <x-button variant="ghost" @click=${logout}>Sign out</x-button>`
29
- : html`<a href="/login">Sign in</a>`}
30
+ <button class="button button--ghost" type="button" @click=${logout}>Sign out</button>`
31
+ : html`<a data-link href=${routeUrl("/login")}>Sign in</a>`}
30
32
  </nav>
31
33
  </header>
32
34
  <main class="app-main">${child}</main>
33
35
  </div>
34
36
  `,
35
- });
37
+ });
@@ -4,14 +4,14 @@
4
4
  // auth ZONE. If tomorrow a `password-reset` module appears, it will reuse
5
5
  // this same shell.
6
6
 
7
- import { html, page } from "@madojs/mado";
7
+ import { html, page, routeUrl } from "@madojs/mado";
8
8
 
9
9
  export default page({
10
10
  title: "Sign in",
11
11
  view: ({ child }) => html`
12
12
  <div class="layout layout--auth">
13
- <a href="/" class="brand">Mado App</a>
13
+ <a data-link href=${routeUrl("/")} class="brand">Mado App</a>
14
14
  <main class="auth-main">${child}</main>
15
15
  </div>
16
16
  `,
17
- });
17
+ });
@@ -0,0 +1,16 @@
1
+ // App entry point. The ONLY file allowed to import CSS at runtime.
2
+ import "./shared/styles/tokens.css";
3
+ import "./shared/styles/reset.css";
4
+ import "./shared/styles/shell.css";
5
+ import "./shared/styles/content.css";
6
+
7
+ import { html, render } from "@madojs/mado";
8
+
9
+ import { init as initAuth } from "./modules/auth/auth.service";
10
+ import appRoutes from "./app.routes";
11
+
12
+ // One-off boot order: init cross-cutting modules first, then render the
13
+ // router view into #app.
14
+ await initAuth();
15
+
16
+ render(html`${appRoutes.view}`, document.getElementById("app")!);
@@ -1,7 +1,5 @@
1
1
  import { html, navigate, page, signal, useForm } from "@madojs/mado";
2
2
 
3
- import "../../shared/ui/x-button.component";
4
-
5
3
  import { login } from "./auth.service";
6
4
 
7
5
  // 1. LOCAL STATE
@@ -16,15 +14,12 @@ import { login } from "./auth.service";
16
14
  export default page({
17
15
  title: "Sign in",
18
16
  view: () => {
19
- const submitting = signal(false);
20
17
  const error = signal<string | null>(null);
21
18
  const form = useForm({
22
- email: { required: true, type: "email" },
23
- password: { required: true, minLength: 6 },
19
+ initial: { email: "", password: "" },
24
20
  });
25
21
 
26
22
  const onSubmit = form.onSubmit(async (values) => {
27
- submitting.set(true);
28
23
  error.set(null);
29
24
  try {
30
25
  await login({
@@ -34,8 +29,6 @@ export default page({
34
29
  navigate("/");
35
30
  } catch (err) {
36
31
  error.set(err instanceof Error ? err.message : "Login failed");
37
- } finally {
38
- submitting.set(false);
39
32
  }
40
33
  });
41
34
 
@@ -49,12 +42,12 @@ export default page({
49
42
  </label>
50
43
  <label>
51
44
  Password
52
- <input name="password" type="password" required @input=${form.onInput} />
45
+ <input name="password" type="password" required minlength="6" @input=${form.onInput} />
53
46
  </label>
54
47
  ${() => (error() ? html`<p class="error">${error()}</p>` : null)}
55
- <x-button ?disabled=${() => submitting() || !form.isValid()}>
56
- ${() => (submitting() ? "Signing in…" : "Sign in")}
57
- </x-button>
48
+ <button class="button" type="submit" ?disabled=${() => form.submitting() || !form.isValid()}>
49
+ ${() => (form.submitting() ? "Signing in…" : "Sign in")}
50
+ </button>
58
51
  </form>
59
52
  </section>
60
53
  `;
@@ -1,8 +1,7 @@
1
1
  import { html, page, signal } from "@madojs/mado";
2
2
 
3
3
  import { formatDate, formatMoney } from "../../../shared/lib/format-date";
4
- import "../../../shared/ui/x-button.component";
5
- import "../../../shared/ui/x-spinner.component";
4
+ import "@/shared/ui/x-spinner.component";
6
5
 
7
6
  import { hasPermission } from "../../auth/auth.public";
8
7
 
@@ -58,9 +57,9 @@ export default page({
58
57
  <dt>Issued</dt><dd>${formatDate(i.issuedAt)}</dd>
59
58
  </dl>
60
59
  ${i.status === "pending" && hasPermission("billing.invoices.pay")
61
- ? html`<x-button ?disabled=${paying} @click=${onPay}>
60
+ ? html`<button class="button" type="button" ?disabled=${paying} @click=${onPay}>
62
61
  ${() => (paying() ? "Paying…" : "Pay now")}
63
- </x-button>`
62
+ </button>`
64
63
  : null}
65
64
  `;
66
65
  })()}
@@ -1,7 +1,7 @@
1
- import { each, html, page, signal, untracked } from "@madojs/mado";
1
+ import { each, html, page, routeUrl, signal, untracked } from "@madojs/mado";
2
2
 
3
3
  import { formatDate, formatMoney } from "../../../shared/lib/format-date";
4
- import "../../../shared/ui/x-spinner.component";
4
+ import "@/shared/ui/x-spinner.component";
5
5
 
6
6
  import "../components/invoice-status-badge.component";
7
7
  import { useInvoices } from "../data/invoices.resource";
@@ -50,7 +50,7 @@ export default page({
50
50
  (i) => html`
51
51
  <tr>
52
52
  <td>
53
- <a href=${`/billing/invoices/${i.id}`}>${i.number}</a>
53
+ <a data-link href=${routeUrl(`/billing/invoices/${i.id}`)}>${i.number}</a>
54
54
  </td>
55
55
  <td>${i.customerEmail}</td>
56
56
  <td>${formatMoney(i.amount, i.currency)}</td>
@@ -59,7 +59,7 @@ export default page({
59
59
  </td>
60
60
  <td>${formatDate(i.issuedAt)}</td>
61
61
  <td>
62
- <a href=${`/billing/invoices/${i.id}`}>Open</a>
62
+ <a data-link href=${routeUrl(`/billing/invoices/${i.id}`)}>Open</a>
63
63
  </td>
64
64
  </tr>
65
65
  `,
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // A page should be read top-to-bottom and understood without jumping files.
8
8
 
9
- import { html, page } from "@madojs/mado";
9
+ import { html, page, routeUrl } from "@madojs/mado";
10
10
 
11
11
  // 1. LOCAL STATE — none
12
12
  // 2. DATA — none
@@ -14,20 +14,18 @@ import { html, page } from "@madojs/mado";
14
14
 
15
15
  // 4. VIEW
16
16
  export default page({
17
+ static: true,
17
18
  title: "Home",
18
19
  head: () => ({
19
20
  description: "A modular Mado application.",
20
21
  }),
21
- bake: {
22
- paths: () => [{}],
23
- data: () => ({}),
24
- },
25
22
  view: () => html`
26
23
  <section>
27
24
  <h1>Mado App</h1>
28
25
  <p>
29
- Welcome. Try <a href="/billing/invoices">billing</a> or
30
- <a href="/login">sign in</a>.
26
+ Welcome. Try
27
+ <a data-link href=${routeUrl("/billing/invoices")}>billing</a>
28
+ or <a data-link href=${routeUrl("/login")}>sign in</a>.
31
29
  </p>
32
30
  </section>
33
31
  `,
@@ -0,0 +1,14 @@
1
+ import { html, page, routeUrl } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "Not Found",
5
+ view: () => html`
6
+ <section>
7
+ <h1>404</h1>
8
+ <p>
9
+ This route does not exist.
10
+ <a data-link href=${routeUrl("/")}>Go home</a>.
11
+ </p>
12
+ </section>
13
+ `,
14
+ });
@@ -29,4 +29,24 @@ a:hover {
29
29
  button {
30
30
  font-family: inherit;
31
31
  font-size: inherit;
32
- }
32
+ }
33
+
34
+ .button {
35
+ padding: var(--space-2) var(--space-4);
36
+ border: 1px solid transparent;
37
+ border-radius: var(--radius-md);
38
+ background: var(--color-primary);
39
+ color: var(--color-primary-contrast);
40
+ cursor: pointer;
41
+ }
42
+
43
+ .button--ghost {
44
+ border-color: var(--color-border);
45
+ background: transparent;
46
+ color: var(--color-text);
47
+ }
48
+
49
+ .button:disabled {
50
+ cursor: not-allowed;
51
+ opacity: 0.55;
52
+ }
@@ -0,0 +1 @@
1
+ declare module "*.css";
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": [
5
+ "./src/*"
6
+ ]
7
+ },
8
+ "target": "ES2022",
9
+ "module": "ES2022",
10
+ "moduleResolution": "Bundler",
11
+ "lib": [
12
+ "ES2022",
13
+ "DOM",
14
+ "DOM.Iterable"
15
+ ],
16
+ "strict": true,
17
+ "noImplicitOverride": true,
18
+ "noUncheckedIndexedAccess": true,
19
+ "exactOptionalPropertyTypes": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "esModuleInterop": true,
23
+ "resolveJsonModule": true,
24
+ "skipLibCheck": true,
25
+ "useDefineForClassFields": true,
26
+ "experimentalDecorators": false,
27
+ "isolatedModules": true,
28
+ "verbatimModuleSyntax": true,
29
+ "noEmit": true
30
+ },
31
+ "include": ["src/**/*.ts", "src/**/*.d.ts"],
32
+ "exclude": ["node_modules", "out"]
33
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "types": ["node"],
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
@@ -0,0 +1,132 @@
1
+ import { defineConfig, type Plugin } from "vite";
2
+ import { mado } from "@madojs/mado/vite";
3
+
4
+ /**
5
+ * Tiny in-memory mock for the auth + billing API used by this starter.
6
+ *
7
+ * Mounted only in `dev` so `mado release` ships a clean bundle that
8
+ * talks to your real backend. The endpoint shape mirrors what
9
+ * `auth.connector.ts` and `stripe.connector.ts` expect: any change in
10
+ * one must be mirrored in the other.
11
+ *
12
+ * Disable by removing this plugin or setting `MADO_MOCK_API=0`.
13
+ */
14
+ function devApiMock(): Plugin {
15
+ const users = new Map<string, { password: string; user: unknown }>([
16
+ [
17
+ "demo@mado.dev",
18
+ {
19
+ password: "demo123",
20
+ user: {
21
+ id: "u_demo",
22
+ email: "demo@mado.dev",
23
+ roles: ["user"],
24
+ permissions: ["billing.read", "billing.invoices.pay"],
25
+ },
26
+ },
27
+ ],
28
+ ]);
29
+ const invoices = Array.from({ length: 6 }, (_, i) => ({
30
+ id: `in_${1000 + i}`,
31
+ number: `INV-${1000 + i}`,
32
+ customer_email: "demo@mado.dev",
33
+ amount_due: 1000 * (i + 1),
34
+ currency: "usd",
35
+ status: i % 3 === 0 ? "paid" : i % 3 === 1 ? "pending" : "draft",
36
+ created: Math.floor(Date.now() / 1000) - i * 86_400,
37
+ }));
38
+ let token: string | null = null;
39
+ const json = (res: import("http").ServerResponse, status: number, body: unknown) => {
40
+ res.statusCode = status;
41
+ res.setHeader("content-type", "application/json");
42
+ res.end(JSON.stringify(body));
43
+ };
44
+ const readJson = (req: import("http").IncomingMessage) =>
45
+ new Promise<Record<string, unknown>>((resolve) => {
46
+ let buf = "";
47
+ req.on("data", (chunk) => (buf += chunk));
48
+ req.on("end", () => {
49
+ try {
50
+ resolve(buf ? JSON.parse(buf) : {});
51
+ } catch {
52
+ resolve({});
53
+ }
54
+ });
55
+ });
56
+
57
+ return {
58
+ name: "starter:dev-api-mock",
59
+ apply: "serve",
60
+ configureServer(server) {
61
+ if (process.env.MADO_MOCK_API === "0") return;
62
+ server.middlewares.use(async (req, res, next) => {
63
+ const url = req.url ?? "";
64
+ if (!url.startsWith("/api/")) return next();
65
+
66
+ // ---- auth -----------------------------------------------------
67
+ if (url === "/api/auth/login" && req.method === "POST") {
68
+ const body = await readJson(req);
69
+ const entry = users.get(String(body.email ?? ""));
70
+ if (!entry || entry.password !== body.password) {
71
+ return json(res, 401, { error: "invalid_credentials" });
72
+ }
73
+ token = `tok_${Math.random().toString(36).slice(2)}`;
74
+ return json(res, 200, { token, user: entry.user });
75
+ }
76
+ if (url === "/api/auth/me" && req.method === "GET") {
77
+ if (!token) return json(res, 401, { error: "unauthenticated" });
78
+ const entry = users.values().next().value;
79
+ if (!entry) return json(res, 500, { error: "mock_user_missing" });
80
+ return json(res, 200, entry.user);
81
+ }
82
+ if (url === "/api/auth/logout" && req.method === "POST") {
83
+ token = null;
84
+ return json(res, 204, null);
85
+ }
86
+
87
+ // ---- billing --------------------------------------------------
88
+ if (url.startsWith("/api/billing/stripe/invoices")) {
89
+ const id = url.match(/\/invoices\/([^/?]+)/)?.[1];
90
+ if (req.method === "POST" && url.endsWith("/pay") && id) {
91
+ const inv = invoices.find((x) => x.id === id);
92
+ if (!inv) return json(res, 404, { error: "not_found" });
93
+ inv.status = "paid";
94
+ return json(res, 200, inv);
95
+ }
96
+ if (id && req.method === "GET") {
97
+ const inv = invoices.find((x) => x.id === id);
98
+ return inv
99
+ ? json(res, 200, inv)
100
+ : json(res, 404, { error: "not_found" });
101
+ }
102
+ if (req.method === "GET") {
103
+ return json(res, 200, { data: invoices, has_more: false });
104
+ }
105
+ }
106
+
107
+ return next();
108
+ });
109
+ },
110
+ };
111
+ }
112
+
113
+ export default defineConfig({
114
+ resolve: {
115
+ tsconfigPaths: true,
116
+ },
117
+ plugins: [
118
+ devApiMock(),
119
+ mado({
120
+ // Public origin used to build absolute URLs for static snapshots
121
+ // (sitemap, canonical, OpenGraph). Combined with Vite's `base`,
122
+ // the canonical for a route is `site + base + pathname`.
123
+ //
124
+ // REQUIRED when any page declares `static`. Set to your deployment
125
+ // origin (e.g. https://your-app.example) before running
126
+ // `mado release`. Override per environment via:
127
+ // mado release --base-url https://staging.example
128
+ // MADO_SITE=https://staging.example mado release
129
+ // site: "https://your-app.example",
130
+ }),
131
+ ],
132
+ });
package/TODO.md DELETED
@@ -1,79 +0,0 @@
1
- # TODO
2
-
3
- Backlog, not a roadmap. Keep this file small and honest: ideas stay here only
4
- while they are plausible next work. Promote concrete work to issues or release
5
- notes when it becomes real.
6
-
7
- ## Before Public Demo / Release
8
-
9
- - [ ] Run the default starter as a real demo app and feed every friction point
10
- back into docs, CLI generators, or starter structure.
11
- - [ ] Add focused tests for every `mado new` generator template.
12
- - [ ] Audit EN/RU/FR docs after each tooling/runtime change; no old `dist/`,
13
- `tsc-only`, custom-bundler, or legacy example wording should remain.
14
- - [ ] Keep `mado preview` boring and reliable inside generated apps.
15
- - [ ] Add a short maintainer-only release checklist if manual npm publish stays
16
- in use.
17
-
18
- ## CLI / DX
19
-
20
- - [ ] Decide whether `npx mado create my-app` is worth adding after package
21
- naming and install friction are clear.
22
- - [ ] Consider short aliases over `mado new` only after real usage shows they
23
- save enough typing (`mado page`, `mado component`, etc.).
24
- - [ ] Split preview/static-server internals only if `scripts/preview.mjs` grows.
25
- - [ ] Typed navigation inferred from route patterns.
26
- - [ ] Editor highlighting docs should prefer existing tooling first; build a
27
- Mado-specific VS Code extension only if current HTML/CSS template support is
28
- not enough.
29
-
30
- ## Runtime / App API
31
-
32
- - [ ] Mutation rollback helper on top of `resource().mutate()` and `mutation()`.
33
- - [ ] Document loading/error shell patterns before adding an `<x-async>` API.
34
- - [ ] `liveResource()` over SSE / WebSocket after a real app needs live data.
35
- - [ ] i18n helper once browser-native message formatting is realistic.
36
- - [ ] Accessibility helpers: focus trap, live region, click outside.
37
-
38
- ## Styling
39
-
40
- - [ ] Docs for `::part`, `::slotted()` and `:host-context()`.
41
- - [ ] Document Vite CSS options, including `css.transformer: "lightningcss"`,
42
- for apps that want Lightning CSS.
43
- - [ ] Optional tiny utilities stylesheet only if the starter/demo repeats the
44
- same small classes often enough to justify it.
45
- - [ ] CSP-friendly style mode.
46
-
47
- ## Bake / Smart Static
48
-
49
- - [ ] Add bake regression coverage for layout groups, dynamic params, sitemap,
50
- and hard-refresh deploy paths.
51
- - [ ] Incremental bake cache after a real content-heavy app needs it.
52
- - [ ] Webhook re-bake endpoint only after incremental bake has a concrete use.
53
- - [ ] RSS / Atom feed generator for docs/blog/demo content.
54
-
55
- ## Operations
56
-
57
- - [ ] Benchmark against Lit / Solid / Preact.
58
- - [ ] Source-map notes and a minimal dev error overlay.
59
- - [ ] `eslint-plugin-mado` rules only if docs/starter conventions are not enough
60
- to prevent recurring mistakes.
61
- - [ ] Timeout / retry options for resource fetchers after real API friction.
62
- - [ ] Schema validation hook for resources after choosing a dependency-free
63
- story or a clearly optional integration.
64
- - [ ] PWA scaffold only if the public demo or a real app needs offline behavior.
65
-
66
- ## Community / GitHub
67
-
68
- - [ ] Minimal landing site built with Mado.
69
- - [ ] Public live demo: CRUD starter and showcase hosted from Mado itself.
70
- - [ ] Case study: real React page -> Mado + bake.
71
- - [ ] Convert contribution-ready TODO items into GitHub issues with labels
72
- (`good first issue`, `help wanted`, `docs`, `cli`, `ci`) after the first
73
- public feedback round.
74
- - [ ] Configure npm Trusted Publishing for GitHub Actions after verifying the
75
- first manual publishes.
76
- - [ ] Decide the release flow: continue manual npm publish for patch releases or
77
- publish from signed `v*` tags through `.github/workflows/release.yml`.
78
- - [ ] Improve generated release notes with PR links/authors when releases start
79
- going through pull requests.
@@ -1,38 +0,0 @@
1
- /**
2
- * lazy() — deferred module loading with a reactive placeholder.
3
- *
4
- * Why: heavy components (charts, code editors, modals)
5
- * don't need to be loaded on startup. lazy() gives a TemplateResult function
6
- * that returns a fallback until loaded, then the real template.
7
- *
8
- * const Chart = lazy(
9
- * () => import('./components/chart.js'),
10
- * { fallback: () => html`<x-spinner/>` },
11
- * );
12
- *
13
- * html`<div>${Chart({ data })}</div>`
14
- *
15
- * Module contract: must default-export a function
16
- * (props) => TemplateResult.
17
- *
18
- * Loading is cached (one import() per loader reference).
19
- */
20
- import type { TemplateResult } from "./html/template-types.js";
21
- export interface LazyOptions<P> {
22
- /** What to show while the module is loading. */
23
- fallback?: (props: P) => TemplateResult;
24
- /** What to show if the import failed. Default — empty string. */
25
- error?: (err: Error, props: P) => TemplateResult;
26
- }
27
- type Renderer<P> = (props: P) => TemplateResult;
28
- /**
29
- * Create a lazy component.
30
- * Takes a dynamic import, returns a render function.
31
- *
32
- * const Modal = lazy(() => import('./modal.js'));
33
- * html`${open() ? Modal({ onClose }) : null}`
34
- */
35
- export declare function lazy<P = unknown>(loader: () => Promise<{
36
- default: Renderer<P>;
37
- }>, options?: LazyOptions<P>): (props: P) => TemplateResult;
38
- export {};
package/dist/src/lazy.js DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * lazy() — deferred module loading with a reactive placeholder.
3
- *
4
- * Why: heavy components (charts, code editors, modals)
5
- * don't need to be loaded on startup. lazy() gives a TemplateResult function
6
- * that returns a fallback until loaded, then the real template.
7
- *
8
- * const Chart = lazy(
9
- * () => import('./components/chart.js'),
10
- * { fallback: () => html`<x-spinner/>` },
11
- * );
12
- *
13
- * html`<div>${Chart({ data })}</div>`
14
- *
15
- * Module contract: must default-export a function
16
- * (props) => TemplateResult.
17
- *
18
- * Loading is cached (one import() per loader reference).
19
- */
20
- import { signal } from "./signal.js";
21
- import { html } from "./html/template.js";
22
- const cache = new WeakMap();
23
- /**
24
- * Create a lazy component.
25
- * Takes a dynamic import, returns a render function.
26
- *
27
- * const Modal = lazy(() => import('./modal.js'));
28
- * html`${open() ? Modal({ onClose }) : null}`
29
- */
30
- export function lazy(loader, options = {}) {
31
- return (props) => {
32
- // One signal trigger per call instance; the html binder will resubscribe
33
- // when the state changes.
34
- const trig = signal(0);
35
- const state = ensureLoaded(loader);
36
- if (state.state === "loading") {
37
- // subscribe: when state transitions to ready/error, trigger trig
38
- const wait = waitFor(loader);
39
- wait.then(() => trig.update((n) => n + 1), () => trig.update((n) => n + 1));
40
- }
41
- return html `${() => {
42
- trig(); // subscription
43
- const s = ensureLoaded(loader);
44
- if (s.state === "ready" && s.renderer)
45
- return s.renderer(props);
46
- if (s.state === "error" && s.err) {
47
- return options.error?.(s.err, props) ?? "";
48
- }
49
- return options.fallback?.(props) ?? "";
50
- }}`;
51
- };
52
- }
53
- function ensureLoaded(loader) {
54
- const existing = cache.get(loader);
55
- if (existing)
56
- return existing;
57
- const fresh = { state: "loading" };
58
- cache.set(loader, fresh);
59
- loader().then((mod) => {
60
- fresh.state = "ready";
61
- fresh.renderer = mod.default;
62
- }, (err) => {
63
- fresh.state = "error";
64
- fresh.err = err instanceof Error ? err : new Error(String(err));
65
- });
66
- return fresh;
67
- }
68
- function waitFor(loader) {
69
- // return the same Promise as ensureLoaded — otherwise we'd create a new one.
70
- // The browser actually caches the same module, so the second call returns instantly.
71
- return loader();
72
- }
73
- //# sourceMappingURL=lazy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/lazy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAkB1C,MAAM,KAAK,GAAG,IAAI,OAAO,EAA8B,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,MAA+C,EAC/C,UAA0B,EAAE;IAE5B,OAAO,CAAC,KAAQ,EAAE,EAAE;QAClB,yEAAyE;QACzE,0BAA0B;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,YAAY,CAAI,MAAM,CAAC,CAAC;QAEtC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC9B,iEAAiE;YACjE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI,CACP,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAC/B,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAChC,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAA,GAAG,GAAG,EAAE;YACjB,IAAI,EAAE,CAAC,CAAC,eAAe;YACvB,MAAM,CAAC,GAAG,YAAY,CAAI,MAAM,CAAC,CAAC;YAClC,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ;gBAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACjC,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7C,CAAC;YACD,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACzC,CAAC,EAAE,CAAC;IACN,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,MAA+C;IAE/C,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAA2B,CAEzC,CAAC;IACd,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,KAAK,GAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACjD,KAAK,CAAC,GAAG,CAAC,MAA2B,EAAE,KAA2B,CAAC,CAAC;IACpE,MAAM,EAAE,CAAC,IAAI,CACX,CAAC,GAAG,EAAE,EAAE;QACN,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC;QACtB,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC;IAC/B,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;QACf,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC;QACtB,KAAK,CAAC,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC,CACF,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CACd,MAA+C;IAE/C,6EAA6E;IAC7E,qFAAqF;IACrF,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC"}