@madojs/mado 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +95 -0
  3. package/README.md +22 -47
  4. package/TODO.md +52 -48
  5. package/dist/src/component.d.ts +2 -1
  6. package/dist/src/component.js +5 -2
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/each.d.ts +1 -1
  9. package/dist/src/each.js +1 -1
  10. package/dist/src/each.js.map +1 -1
  11. package/dist/src/html/bindings.js +3 -3
  12. package/dist/src/html/bindings.js.map +1 -1
  13. package/dist/src/index.d.ts +11 -6
  14. package/dist/src/index.js +5 -3
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/lazy.d.ts +1 -1
  17. package/dist/src/lazy.js +1 -1
  18. package/dist/src/lazy.js.map +1 -1
  19. package/dist/src/page.d.ts +17 -21
  20. package/dist/src/page.js +7 -12
  21. package/dist/src/page.js.map +1 -1
  22. package/dist/src/router/manifest.d.ts +1 -1
  23. package/dist/src/router/manifest.js +21 -13
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/match.d.ts +2 -2
  26. package/dist/src/router/match.js +3 -3
  27. package/dist/src/router/match.js.map +1 -1
  28. package/dist/src/router/navigation.js +1 -1
  29. package/dist/src/router/navigation.js.map +1 -1
  30. package/dist/src/vite/index.d.ts +10 -0
  31. package/dist/src/vite/index.js +33 -0
  32. package/dist/src/vite/index.js.map +1 -0
  33. package/docs/en/00-the-mado-way.md +25 -12
  34. package/docs/en/01-routing.md +90 -142
  35. package/docs/en/02-project-layout.md +59 -53
  36. package/docs/en/03-static-bake.md +5 -6
  37. package/docs/en/05-why-mado.md +6 -6
  38. package/docs/en/06-for-backenders.md +18 -22
  39. package/docs/en/08-llm-zero-history-test.md +9 -14
  40. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  41. package/docs/en/10-app-architecture.md +158 -96
  42. package/docs/en/11-layouts.md +22 -24
  43. package/docs/en/12-auth-and-api.md +89 -182
  44. package/docs/en/13-deployment.md +18 -22
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +11 -12
  47. package/docs/en/18-api-freeze-map.md +6 -4
  48. package/docs/en/20-v1-stability.md +1 -1
  49. package/docs/fr/00-the-mado-way.md +55 -90
  50. package/docs/fr/01-routing.md +70 -152
  51. package/docs/fr/02-project-layout.md +61 -42
  52. package/docs/fr/03-static-bake.md +1 -1
  53. package/docs/fr/05-why-mado.md +6 -6
  54. package/docs/fr/06-for-backenders.md +7 -7
  55. package/docs/fr/08-llm-zero-history-test.md +21 -48
  56. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  57. package/docs/fr/10-app-architecture.md +110 -33
  58. package/docs/fr/11-layouts.md +24 -12
  59. package/docs/fr/12-auth-and-api.md +63 -22
  60. package/docs/fr/13-deployment.md +7 -10
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +2 -2
  63. package/docs/fr/18-api-freeze-map.md +1 -1
  64. package/docs/fr/20-v1-stability.md +1 -1
  65. package/docs/recipes/nginx/README.md +13 -0
  66. package/docs/ru/00-the-mado-way.md +53 -75
  67. package/docs/ru/01-routing.md +68 -143
  68. package/docs/ru/02-project-layout.md +61 -41
  69. package/docs/ru/03-static-bake.md +2 -2
  70. package/docs/ru/05-why-mado.md +6 -6
  71. package/docs/ru/06-for-backenders.md +7 -7
  72. package/docs/ru/08-llm-zero-history-test.md +9 -14
  73. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  74. package/docs/ru/10-app-architecture.md +115 -63
  75. package/docs/ru/11-layouts.md +24 -24
  76. package/docs/ru/12-auth-and-api.md +57 -35
  77. package/docs/ru/13-deployment.md +7 -11
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +12 -6
  80. package/docs/ru/18-api-freeze-map.md +5 -3
  81. package/docs/ru/20-v1-stability.md +1 -1
  82. package/docs/uk/00-the-mado-way.md +70 -44
  83. package/docs/uk/01-routing.md +41 -47
  84. package/docs/uk/02-project-layout.md +68 -41
  85. package/docs/uk/03-static-bake.md +1 -2
  86. package/docs/uk/06-for-backenders.md +3 -3
  87. package/docs/uk/08-llm-zero-history-test.md +22 -24
  88. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  89. package/docs/uk/10-app-architecture.md +72 -31
  90. package/docs/uk/11-layouts.md +25 -12
  91. package/docs/uk/12-auth-and-api.md +58 -22
  92. package/docs/uk/13-deployment.md +4 -3
  93. package/docs/uk/14-testing.md +1 -1
  94. package/docs/uk/18-api-freeze-map.md +1 -1
  95. package/docs/uk/20-v1-stability.md +1 -1
  96. package/llms.txt +14 -15
  97. package/package.json +18 -11
  98. package/scripts/_config.mjs +15 -161
  99. package/scripts/bake.mjs +74 -63
  100. package/scripts/cli/generate.mjs +348 -0
  101. package/scripts/cli/help.mjs +27 -0
  102. package/scripts/cli/index.mjs +79 -0
  103. package/scripts/cli/init.mjs +153 -0
  104. package/scripts/cli/release.mjs +152 -0
  105. package/scripts/cli/run.mjs +96 -0
  106. package/scripts/cli.mjs +2 -621
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +13 -37
  109. package/scripts/size-budget.mjs +5 -2
  110. package/scripts/vite.default.mjs +11 -0
  111. package/starters/default/.editorconfig +12 -0
  112. package/starters/default/README.md +74 -0
  113. package/starters/default/eslint.config.mjs +256 -0
  114. package/starters/default/index.html +13 -0
  115. package/starters/default/package.json +30 -0
  116. package/starters/default/public/favicon.svg +4 -0
  117. package/starters/default/src/app.routes.ts +39 -0
  118. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  119. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  120. package/starters/default/src/main.ts +16 -0
  121. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  122. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  123. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  124. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  125. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  126. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  127. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  128. package/starters/default/src/modules/auth/login.page.ts +62 -0
  129. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  130. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  131. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  132. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  133. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  134. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  135. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  136. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  137. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  138. package/starters/default/src/modules/home/home.page.ts +34 -0
  139. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  140. package/starters/default/src/shared/http/http-client.ts +86 -0
  141. package/starters/default/src/shared/http/http-error.ts +37 -0
  142. package/starters/default/src/shared/http/interceptors.ts +59 -0
  143. package/starters/default/src/shared/lib/format-date.ts +19 -0
  144. package/starters/default/src/shared/styles/content.css +70 -0
  145. package/starters/default/src/shared/styles/reset.css +32 -0
  146. package/starters/default/src/shared/styles/shell.css +57 -0
  147. package/starters/default/src/shared/styles/tokens.css +44 -0
  148. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  149. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  150. package/starters/default/src/styles.d.ts +1 -0
  151. package/starters/default/src/vite-env.d.ts +1 -0
  152. package/starters/default/tsconfig.json +24 -0
  153. package/starters/default/vite.config.ts +9 -0
  154. package/MADO_V1_PLAN.md +0 -179
  155. package/ROADMAP.md +0 -178
  156. package/dist/src/html.d.ts +0 -18
  157. package/dist/src/html.js +0 -17
  158. package/dist/src/html.js.map +0 -1
  159. package/dist/src/router.d.ts +0 -13
  160. package/dist/src/router.js +0 -13
  161. package/dist/src/router.js.map +0 -1
  162. package/scripts/bundle.mjs +0 -212
  163. package/scripts/llm-zero-history-smoke.mjs +0 -93
  164. package/scripts/new.mjs +0 -80
  165. package/scripts/showcase-regression.mjs +0 -392
  166. package/server/serve.mjs +0 -455
  167. package/starters/admin/README.md +0 -63
  168. package/starters/admin/index.html +0 -28
  169. package/starters/admin/mado.config.json +0 -22
  170. package/starters/admin/package.json +0 -24
  171. package/starters/admin/public/favicon.svg +0 -4
  172. package/starters/admin/src/components/x-button.ts +0 -82
  173. package/starters/admin/src/components/x-input.ts +0 -105
  174. package/starters/admin/src/layouts/app.ts +0 -101
  175. package/starters/admin/src/layouts/auth.ts +0 -41
  176. package/starters/admin/src/lib/api.ts +0 -184
  177. package/starters/admin/src/lib/auth.ts +0 -83
  178. package/starters/admin/src/main.ts +0 -15
  179. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  180. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  181. package/starters/admin/src/pages/admin/orders.ts +0 -117
  182. package/starters/admin/src/pages/home.ts +0 -34
  183. package/starters/admin/src/pages/login.ts +0 -70
  184. package/starters/admin/src/pages/not-found.ts +0 -12
  185. package/starters/admin/src/routes.ts +0 -40
  186. package/starters/admin/src/styles/global.ts +0 -86
  187. package/starters/admin/tsconfig.json +0 -15
  188. package/starters/crud/README.md +0 -33
  189. package/starters/crud/index.html +0 -28
  190. package/starters/crud/mado.config.json +0 -20
  191. package/starters/crud/package.json +0 -24
  192. package/starters/crud/src/components/app-shell.ts +0 -56
  193. package/starters/crud/src/components/ticket-detail.ts +0 -33
  194. package/starters/crud/src/components/ticket-form.ts +0 -69
  195. package/starters/crud/src/components/ticket-list.ts +0 -66
  196. package/starters/crud/src/lib/api.ts +0 -76
  197. package/starters/crud/src/main.ts +0 -9
  198. package/starters/crud/src/pages/home.ts +0 -34
  199. package/starters/crud/src/pages/not-found.ts +0 -12
  200. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  201. package/starters/crud/src/pages/ticket-new.ts +0 -7
  202. package/starters/crud/src/pages/tickets.ts +0 -7
  203. package/starters/crud/src/routes.ts +0 -11
  204. package/starters/crud/src/styles/global.ts +0 -155
  205. package/starters/crud/tsconfig.json +0 -15
  206. package/starters/minimal/README.md +0 -21
  207. package/starters/minimal/index.html +0 -28
  208. package/starters/minimal/mado.config.json +0 -20
  209. package/starters/minimal/package.json +0 -24
  210. package/starters/minimal/src/components/app-counter.ts +0 -31
  211. package/starters/minimal/src/main.ts +0 -9
  212. package/starters/minimal/src/pages/home.ts +0 -35
  213. package/starters/minimal/src/pages/not-found.ts +0 -14
  214. package/starters/minimal/src/routes.ts +0 -8
  215. package/starters/minimal/src/styles/global.ts +0 -60
  216. package/starters/minimal/tsconfig.json +0 -15
  217. package/templates/page-detail.ts +0 -63
  218. package/templates/page-form.ts +0 -94
  219. package/templates/page-list.ts +0 -79
@@ -1,117 +0,0 @@
1
- // Orders list. Demonstrates queryParam() filters and each() keyed table rows.
2
-
3
- import {
4
- each,
5
- html,
6
- jsonFetcher,
7
- page,
8
- queryParam,
9
- resource,
10
- } from "@madojs/mado";
11
-
12
- interface Order {
13
- id: string;
14
- customer: string;
15
- total: number;
16
- status: "open" | "paid" | "shipped" | "cancelled";
17
- }
18
-
19
- export default page({
20
- title: "Orders",
21
- view: () => {
22
- const status = queryParam("status", "");
23
- const search = queryParam("q", "");
24
-
25
- const orders = resource(
26
- () => {
27
- const params = new URLSearchParams();
28
- if (status()) params.set("status", status());
29
- if (search()) params.set("q", search());
30
- const qs = params.toString();
31
- return `/api/admin/orders${qs ? `?${qs}` : ""}`;
32
- },
33
- jsonFetcher<Order[]>(),
34
- { staleTime: 5_000 },
35
- );
36
-
37
- return html`
38
- <header style="display:flex;align-items:center;gap:var(--space-3);margin:0 0 16px;">
39
- <h1 style="margin:0;">Orders</h1>
40
- <span class="spacer"></span>
41
- <input
42
- type="search"
43
- placeholder="Search…"
44
- .value=${search}
45
- @input=${(e: Event) => {
46
- const t = e.target as HTMLInputElement;
47
- search.set(t.value);
48
- }}
49
- style="padding:6px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg);color:var(--fg);"
50
- >
51
- <select
52
- .value=${status}
53
- @change=${(e: Event) => {
54
- const t = e.target as HTMLSelectElement;
55
- status.set(t.value);
56
- }}
57
- style="padding:6px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg);color:var(--fg);"
58
- >
59
- <option value="">All statuses</option>
60
- <option value="open">Open</option>
61
- <option value="paid">Paid</option>
62
- <option value="shipped">Shipped</option>
63
- <option value="cancelled">Cancelled</option>
64
- </select>
65
- </header>
66
-
67
- <div class="card" style="padding:0;overflow:hidden;">
68
- ${() => {
69
- if (orders.loading() && !orders.data())
70
- return html`<p class="muted" style="padding:16px;">Loading…</p>`;
71
- if (orders.error())
72
- return html`<p style="color:var(--danger);padding:16px;">
73
- ${orders.error()?.message}
74
- </p>`;
75
- const list = orders.data() ?? [];
76
- if (list.length === 0)
77
- return html`<p class="muted" style="padding:24px;text-align:center;">
78
- No orders match the current filters.
79
- </p>`;
80
- return html`
81
- <table style="width:100%;border-collapse:collapse;">
82
- <thead>
83
- <tr style="text-align:left;border-bottom:1px solid var(--border);">
84
- <th style="padding:10px 14px;">ID</th>
85
- <th style="padding:10px 14px;">Customer</th>
86
- <th style="padding:10px 14px;">Status</th>
87
- <th style="padding:10px 14px;text-align:right;">Total</th>
88
- </tr>
89
- </thead>
90
- <tbody>
91
- ${() =>
92
- each(
93
- list,
94
- (o) => o.id,
95
- (o) => html`
96
- <tr style="border-bottom:1px solid var(--border);">
97
- <td style="padding:10px 14px;">
98
- <a href="/admin/orders/${o.id}" data-link>${o.id}</a>
99
- </td>
100
- <td style="padding:10px 14px;">${o.customer}</td>
101
- <td style="padding:10px 14px;">
102
- <span class="muted">${o.status}</span>
103
- </td>
104
- <td style="padding:10px 14px;text-align:right;font-variant-numeric:tabular-nums;">
105
- $${o.total.toFixed(2)}
106
- </td>
107
- </tr>
108
- `,
109
- )}
110
- </tbody>
111
- </table>
112
- `;
113
- }}
114
- </div>
115
- `;
116
- },
117
- });
@@ -1,34 +0,0 @@
1
- // Public landing page. Demonstrates that the marketing surface can live
2
- // alongside the admin app without a guard, and can be baked for SEO.
3
- //
4
- // `bake` is declared so that `mado bake` (and `mado release`) actually
5
- // prerender at least one static page out of the box. Without it the
6
- // release output ships only the SPA shell with no SEO-friendly HTML
7
- // for crawlers landing on "/".
8
-
9
- import { html, page } from "@madojs/mado";
10
-
11
- export default page({
12
- title: "Welcome",
13
- head: () => ({
14
- description: "An admin app scaffold built with Mado.",
15
- og: {
16
- title: "__APP_NAME__",
17
- description: "An admin app scaffold built with Mado.",
18
- type: "website",
19
- },
20
- }),
21
- bake: {
22
- paths: () => [{}],
23
- data: () => ({}),
24
- },
25
- view: () => html`
26
- <main style="max-width:720px;margin:0 auto;padding:64px 24px;">
27
- <h1>__APP_NAME__</h1>
28
- <p>This is the public landing page.</p>
29
- <p>
30
- <a href="/admin" data-link>Open the admin app →</a>
31
- </p>
32
- </main>
33
- `,
34
- });
@@ -1,70 +0,0 @@
1
- // Login page. Reads `?return=` to bounce the user back where they were
2
- // blocked by `requireAuth`.
3
-
4
- import { html, navigate, page, queryParam, signal, useForm } from "@madojs/mado";
5
- import { ApiError } from "../lib/api.js";
6
- import { login } from "../lib/auth.js";
7
- import "../components/x-input.js";
8
- import "../components/x-button.js";
9
-
10
- export default page({
11
- title: "Sign in",
12
- view: () => {
13
- const returnTo = queryParam("return", "/admin");
14
- const serverError = signal<string | null>(null);
15
-
16
- const form = useForm({
17
- email: { required: true, type: "email" as const },
18
- password: { required: true, min: 4 },
19
- });
20
-
21
- const onSubmit = form.onSubmit(async (values) => {
22
- serverError.set(null);
23
- try {
24
- await login({
25
- email: String(values.email ?? ""),
26
- password: String(values.password ?? ""),
27
- });
28
- navigate(returnTo(), { replace: true });
29
- } catch (e) {
30
- if (e instanceof ApiError && e.status === 401) {
31
- serverError.set("Invalid email or password.");
32
- } else {
33
- serverError.set("Something went wrong. Try again.");
34
- }
35
- }
36
- });
37
-
38
- return html`
39
- <h1 style="margin:0 0 16px;">Sign in</h1>
40
- <p class="muted" style="margin:0 0 24px;">
41
- Enter your credentials to continue.
42
- </p>
43
- <form @submit=${onSubmit} class="stack">
44
- <x-input
45
- label="Email"
46
- name="email"
47
- type="email"
48
- required
49
- @input=${form.onInput}
50
- @blur=${form.onBlur}
51
- ></x-input>
52
- <x-input
53
- label="Password"
54
- name="password"
55
- type="password"
56
- required
57
- @input=${form.onInput}
58
- @blur=${form.onBlur}
59
- ></x-input>
60
- ${() =>
61
- serverError()
62
- ? html`<small style="color:var(--danger);">${serverError()}</small>`
63
- : null}
64
- <x-button
65
- ?disabled=${() => !form.isValid() || form.submitting()}
66
- >${() => (form.submitting() ? "Signing in…" : "Sign in")}</x-button>
67
- </form>
68
- `;
69
- },
70
- });
@@ -1,12 +0,0 @@
1
- import { html, page } from "@madojs/mado";
2
-
3
- export default page({
4
- title: "Not found",
5
- view: () => html`
6
- <main style="max-width:560px;margin:0 auto;padding:80px 24px;text-align:center;">
7
- <h1 style="margin:0 0 12px;">404</h1>
8
- <p class="muted">This page does not exist.</p>
9
- <p><a href="/" data-link>← Back home</a></p>
10
- </main>
11
- `,
12
- });
@@ -1,40 +0,0 @@
1
- // The blessed routes manifest for an admin app.
2
- //
3
- // "/" → public landing
4
- // "/login" → centered auth layout
5
- // "/admin/*" → admin shell with sidebar/topbar, guarded by requireAuth
6
- // "*" → 404
7
- //
8
- // Layouts and guards live inside the layout() blocks. There is exactly one
9
- // canonical place to put a shell: a layout() in this manifest. Do not wrap
10
- // route output in main.ts or in custom-element wrappers — that path causes
11
- // the "shell-below-content" bug described in the v1 plan.
12
- //
13
- // Bake: `manifest` is exported separately for `mado bake` to discover pages
14
- // that declare `bake: { paths, data }`. Without this named export `mado bake`
15
- // fails with a clear error.
16
-
17
- import { layout, routes } from "@madojs/mado";
18
- import { requireAuth } from "./lib/auth.js";
19
-
20
- export const manifest = {
21
- "/": () => import("./pages/home.js"),
22
- "/login": layout({
23
- layout: () => import("./layouts/auth.js"),
24
- routes: {
25
- "/": () => import("./pages/login.js"),
26
- },
27
- }),
28
- "/admin": layout({
29
- layout: () => import("./layouts/app.js"),
30
- guard: requireAuth,
31
- routes: {
32
- "/": () => import("./pages/admin/dashboard.js"),
33
- "/orders": () => import("./pages/admin/orders.js"),
34
- "/orders/:id": () => import("./pages/admin/order-detail.js"),
35
- },
36
- }),
37
- "*": () => import("./pages/not-found.js"),
38
- };
39
-
40
- export default routes(manifest);
@@ -1,86 +0,0 @@
1
- // Global design tokens + a tiny utility layer for admin UIs.
2
- //
3
- // Tokens are CSS custom properties on :root so layouts and components can pick
4
- // them up without a CSS preprocessor. Light/dark via prefers-color-scheme.
5
- //
6
- // Keep this file intentionally small. Components should style themselves with
7
- // Shadow DOM. Light DOM utilities (.row, .stack, .card) are here only because
8
- // they show up in every admin page and would be noisy to redefine per component.
9
-
10
- const css = `
11
- :root {
12
- color-scheme: light dark;
13
-
14
- --bg: #ffffff;
15
- --bg-elevated: #f7f8fa;
16
- --fg: #0f172a;
17
- --fg-muted: #475569;
18
- --border: #e2e8f0;
19
- --accent: #1f6feb;
20
- --accent-fg: #ffffff;
21
- --danger: #b91c1c;
22
- --success: #15803d;
23
-
24
- --radius: 8px;
25
- --radius-sm: 6px;
26
- --space-1: 4px;
27
- --space-2: 8px;
28
- --space-3: 12px;
29
- --space-4: 16px;
30
- --space-5: 24px;
31
- --space-6: 32px;
32
-
33
- --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI",
34
- Roboto, Inter, Helvetica, Arial, sans-serif;
35
-
36
- --shadow-1: 0 1px 2px rgba(15, 23, 42, .05),
37
- 0 1px 1px rgba(15, 23, 42, .04);
38
- }
39
-
40
- @media (prefers-color-scheme: dark) {
41
- :root {
42
- --bg: #0b1220;
43
- --bg-elevated: #111a2e;
44
- --fg: #e6eefc;
45
- --fg-muted: #9aa6bd;
46
- --border: #1f2a44;
47
- --accent: #3b82f6;
48
- --accent-fg: #0b1220;
49
- }
50
- }
51
-
52
- * { box-sizing: border-box; }
53
- html, body { margin: 0; padding: 0; }
54
- body {
55
- background: var(--bg);
56
- color: var(--fg);
57
- font-family: var(--font-sans);
58
- font-size: 14px;
59
- line-height: 1.5;
60
- min-height: 100vh;
61
- }
62
-
63
- a { color: var(--accent); text-decoration: none; }
64
- a:hover { text-decoration: underline; }
65
-
66
- .row { display: flex; align-items: center; gap: var(--space-3); }
67
- .stack { display: flex; flex-direction: column; gap: var(--space-3); }
68
- .spacer { flex: 1; }
69
-
70
- .card {
71
- background: var(--bg-elevated);
72
- border: 1px solid var(--border);
73
- border-radius: var(--radius);
74
- padding: var(--space-5);
75
- box-shadow: var(--shadow-1);
76
- }
77
-
78
- .muted { color: var(--fg-muted); }
79
- `;
80
-
81
- if (typeof document !== "undefined" && !document.getElementById("admin-global-style")) {
82
- const tag = document.createElement("style");
83
- tag.id = "admin-global-style";
84
- tag.textContent = css;
85
- document.head.appendChild(tag);
86
- }
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022", "DOM"],
7
- "strict": true,
8
- "skipLibCheck": true,
9
- "outDir": "dist",
10
- "rootDir": "src",
11
- "declaration": false,
12
- "sourceMap": true
13
- },
14
- "include": ["src/**/*.ts"]
15
- }
@@ -1,33 +0,0 @@
1
- # __APP_NAME__
2
-
3
- Generated with the Mado CRUD starter.
4
-
5
- ```bash
6
- npm install
7
- npm run dev
8
- ```
9
-
10
- Open http://localhost:5173.
11
-
12
- Use `npm run build` for a production TypeScript build and `npm run serve` to
13
- serve an already-built app.
14
-
15
- This starter demonstrates:
16
-
17
- - lazy routes;
18
- - Web Components through `component()`;
19
- - an app shell component with Shadow DOM and `<slot>`;
20
- - query params through `queryParam()`;
21
- - async data through `resource()`;
22
- - mutations with invalidation;
23
- - forms through `useForm()`;
24
- - keyed tables through `each()`.
25
-
26
- `x-app-shell` uses Shadow DOM because it owns the page frame and projects route
27
- pages through `<slot>`. Page/table/form components can still use global styles:
28
- slotted route pages remain normal document DOM.
29
-
30
- Component imports are explicit registration side effects. `main.ts` imports the
31
- global app shell. Feature pages import the components they render, for example
32
- `src/pages/tickets.ts` imports `../components/ticket-list.js` before rendering
33
- `<ticket-list>`.
@@ -1,28 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>__APP_NAME__</title>
7
- <link rel="icon" type="image/svg+xml" href="/favicon.svg">
8
- <!--
9
- Paths below MUST be root-absolute (start with "/"), not "./...".
10
- Mado is an SPA: hard-refreshing /users/42 still serves this same
11
- index.html, and the browser resolves "./dist/main.js" against the
12
- current URL → /users/dist/main.js → 404 → blank page.
13
- Root-absolute paths always resolve to /dist/main.js regardless of route.
14
- -->
15
- <script type="importmap">
16
- {
17
- "imports": {
18
- "@madojs/mado": "/node_modules/@madojs/mado/dist/src/index.js",
19
- "@madojs/mado/": "/node_modules/@madojs/mado/dist/src/"
20
- }
21
- }
22
- </script>
23
- </head>
24
- <body>
25
- <div id="app"></div>
26
- <script type="module" src="/dist/main.js"></script>
27
- </body>
28
- </html>
@@ -1,20 +0,0 @@
1
- {
2
- "dev": {
3
- "port": 5173,
4
- "proxy": {}
5
- },
6
- "build": {
7
- "out": "out",
8
- "dist": "dist",
9
- "publicDir": "public"
10
- },
11
- "bake": {
12
- "entry": "src/routes.ts",
13
- "template": "index.html",
14
- "baseUrl": "https://example.com"
15
- },
16
- "bundle": {
17
- "splitting": true,
18
- "compress": ["gz", "br"]
19
- }
20
- }
@@ -1,24 +0,0 @@
1
- {
2
- "name": "__PACKAGE_NAME__",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "build": "mado build",
8
- "typecheck": "mado typecheck",
9
- "dev": "mado dev",
10
- "serve": "mado serve",
11
- "bundle": "mado bundle",
12
- "bake": "mado bake",
13
- "release": "mado release",
14
- "preview": "mado preview"
15
- },
16
- "dependencies": {
17
- "@madojs/mado": "__MADOJS_VERSION__"
18
- },
19
- "devDependencies": {
20
- "esbuild": "^0.28.0",
21
- "linkedom": "^0.18.12",
22
- "typescript": "^6.0.3"
23
- }
24
- }
@@ -1,56 +0,0 @@
1
- import { component, css, html } from "@madojs/mado";
2
-
3
- component(
4
- "x-app-shell",
5
- () => () => html`
6
- <header>
7
- <a class="brand" href="/" data-link>__APP_NAME__</a>
8
- <nav>
9
- <a href="/" data-link>Home</a>
10
- <a href="/tickets" data-link>Tickets</a>
11
- <a href="/tickets/new" data-link>New ticket</a>
12
- </nav>
13
- </header>
14
- <main>
15
- <slot></slot>
16
- </main>
17
- `,
18
- {
19
- styles: css`
20
- :host {
21
- min-height: 100vh;
22
- display: block;
23
- }
24
-
25
- header {
26
- display: flex;
27
- align-items: center;
28
- justify-content: space-between;
29
- gap: 1rem;
30
- border-bottom: 1px solid var(--line);
31
- background: white;
32
- padding: 0.9rem 1.25rem;
33
- }
34
-
35
- main {
36
- display: block;
37
- }
38
-
39
- nav {
40
- display: flex;
41
- gap: 0.75rem;
42
- flex-wrap: wrap;
43
- }
44
-
45
- a {
46
- color: #334155;
47
- text-decoration: none;
48
- }
49
-
50
- .brand {
51
- color: #111827;
52
- font-weight: 800;
53
- }
54
- `,
55
- },
56
- );
@@ -1,33 +0,0 @@
1
- import { component, html, resource } from "@madojs/mado";
2
- import { api } from "../lib/api.js";
3
-
4
- component("ticket-detail", ({ host }) => {
5
- const id = () => host.getAttribute("ticket-id") ?? "";
6
- const ticket = resource(
7
- () => `tickets/${id()}`,
8
- () => api.getTicket(id()),
9
- );
10
-
11
- return () => html`
12
- <section class="page narrow">
13
- ${() => ticket.loading()
14
- ? html`<p>Loading...</p>`
15
- : ticket.error()
16
- ? html`<p class="error">${ticket.error()?.message}</p>`
17
- : html`
18
- <a href="/tickets" data-link>Back to tickets</a>
19
- <h1>${ticket.data()?.title}</h1>
20
- <dl>
21
- <dt>Customer</dt>
22
- <dd>${ticket.data()?.customer}</dd>
23
- <dt>Status</dt>
24
- <dd><span class=${`badge ${ticket.data()?.status}`}>${ticket.data()?.status}</span></dd>
25
- <dt>Priority</dt>
26
- <dd>${ticket.data()?.priority}</dd>
27
- <dt>Notes</dt>
28
- <dd>${ticket.data()?.notes}</dd>
29
- </dl>
30
- `}
31
- </section>
32
- `;
33
- }, { shadow: false });
@@ -1,69 +0,0 @@
1
- import { component, html, mutation, navigate, useForm } from "@madojs/mado";
2
- import { api, type TicketInput } from "../lib/api.js";
3
-
4
- component("ticket-form", () => {
5
- const form = useForm({
6
- title: { required: true, default: "" },
7
- customer: { required: true, default: "" },
8
- status: { required: true, default: "open" },
9
- priority: { required: true, default: "normal" },
10
- notes: { default: "" },
11
- });
12
-
13
- const createTicket = mutation(
14
- (input: TicketInput) => api.createTicket(input),
15
- { invalidates: ["tickets*"] },
16
- );
17
-
18
- const submit = form.onSubmit(async (values) => {
19
- const ticket = await createTicket.run(values as TicketInput);
20
- navigate(`/tickets/${ticket.id}`);
21
- });
22
-
23
- return () => html`
24
- <section class="page narrow">
25
- <h1>New ticket</h1>
26
- <form @submit=${submit}>
27
- <label>
28
- Title
29
- <input name="title" @input=${form.onInput} @blur=${form.onBlur}>
30
- ${() => form.touched().title && form.errors().title ? html`<small>${form.errors().title}</small>` : null}
31
- </label>
32
-
33
- <label>
34
- Customer
35
- <input name="customer" @input=${form.onInput} @blur=${form.onBlur}>
36
- ${() => form.touched().customer && form.errors().customer ? html`<small>${form.errors().customer}</small>` : null}
37
- </label>
38
-
39
- <label>
40
- Status
41
- <select name="status" @input=${form.onInput} @blur=${form.onBlur}>
42
- <option value="open">Open</option>
43
- <option value="pending">Pending</option>
44
- <option value="closed">Closed</option>
45
- </select>
46
- </label>
47
-
48
- <label>
49
- Priority
50
- <select name="priority" @input=${form.onInput} @blur=${form.onBlur}>
51
- <option value="low">Low</option>
52
- <option value="normal" selected>Normal</option>
53
- <option value="high">High</option>
54
- </select>
55
- </label>
56
-
57
- <label>
58
- Notes
59
- <textarea name="notes" rows="4" @input=${form.onInput}></textarea>
60
- </label>
61
-
62
- <div class="actions">
63
- <a href="/tickets" data-link>Cancel</a>
64
- <button ?disabled=${() => !form.isValid() || form.submitting()}>Create ticket</button>
65
- </div>
66
- </form>
67
- </section>
68
- `;
69
- }, { shadow: false });