@madojs/mado 0.5.1 → 0.6.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 (107) hide show
  1. package/AGENTS.md +26 -0
  2. package/CHANGELOG.md +265 -0
  3. package/MADO_V1_PLAN.md +179 -0
  4. package/README.md +31 -13
  5. package/ROADMAP.md +28 -7
  6. package/TODO.md +72 -0
  7. package/dist/src/forms.d.ts +37 -4
  8. package/dist/src/forms.js +331 -57
  9. package/dist/src/forms.js.map +1 -1
  10. package/dist/src/html/bindings.d.ts +41 -0
  11. package/dist/src/html/bindings.js +163 -6
  12. package/dist/src/html/bindings.js.map +1 -1
  13. package/dist/src/html.d.ts +2 -0
  14. package/dist/src/html.js +1 -0
  15. package/dist/src/html.js.map +1 -1
  16. package/dist/src/index.d.ts +6 -6
  17. package/dist/src/index.js +2 -2
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/page.d.ts +56 -0
  20. package/dist/src/page.js +17 -0
  21. package/dist/src/page.js.map +1 -1
  22. package/dist/src/resource.js +11 -0
  23. package/dist/src/resource.js.map +1 -1
  24. package/dist/src/router/manifest.d.ts +16 -1
  25. package/dist/src/router/manifest.js +210 -40
  26. package/dist/src/router/manifest.js.map +1 -1
  27. package/dist/src/router/match.d.ts +7 -2
  28. package/dist/src/router/match.js +14 -4
  29. package/dist/src/router/match.js.map +1 -1
  30. package/dist/src/router/navigation.d.ts +10 -0
  31. package/dist/src/router/navigation.js +71 -3
  32. package/dist/src/router/navigation.js.map +1 -1
  33. package/dist/src/signal.d.ts +15 -1
  34. package/dist/src/signal.js +112 -16
  35. package/dist/src/signal.js.map +1 -1
  36. package/docs/en/02-project-layout.md +99 -40
  37. package/docs/en/10-app-architecture.md +141 -0
  38. package/docs/en/11-layouts.md +115 -0
  39. package/docs/en/12-auth-and-api.md +217 -0
  40. package/docs/en/13-deployment.md +192 -0
  41. package/docs/en/14-testing.md +82 -0
  42. package/docs/en/15-error-handling.md +100 -0
  43. package/docs/en/16-bake-cookbook.md +93 -0
  44. package/docs/en/README.md +7 -0
  45. package/docs/fr/10-app-architecture.md +61 -0
  46. package/docs/fr/11-layouts.md +35 -0
  47. package/docs/fr/12-auth-and-api.md +35 -0
  48. package/docs/fr/13-deployment.md +39 -0
  49. package/docs/fr/14-testing.md +41 -0
  50. package/docs/fr/15-error-handling.md +50 -0
  51. package/docs/fr/16-bake-cookbook.md +35 -0
  52. package/docs/fr/README.md +7 -0
  53. package/docs/ru/10-app-architecture.md +100 -0
  54. package/docs/ru/11-layouts.md +47 -0
  55. package/docs/ru/12-auth-and-api.md +53 -0
  56. package/docs/ru/13-deployment.md +60 -0
  57. package/docs/ru/14-testing.md +50 -0
  58. package/docs/ru/15-error-handling.md +56 -0
  59. package/docs/ru/16-bake-cookbook.md +55 -0
  60. package/docs/ru/README.md +7 -0
  61. package/docs/uk/10-app-architecture.md +56 -0
  62. package/docs/uk/11-layouts.md +34 -0
  63. package/docs/uk/12-auth-and-api.md +34 -0
  64. package/docs/uk/13-deployment.md +39 -0
  65. package/docs/uk/14-testing.md +34 -0
  66. package/docs/uk/15-error-handling.md +32 -0
  67. package/docs/uk/16-bake-cookbook.md +36 -0
  68. package/docs/uk/README.md +7 -0
  69. package/llms.txt +9 -1
  70. package/package.json +3 -1
  71. package/scripts/_config.mjs +224 -0
  72. package/scripts/bake.mjs +266 -121
  73. package/scripts/bundle.mjs +133 -67
  74. package/scripts/cli.mjs +195 -27
  75. package/scripts/preview.mjs +125 -21
  76. package/server/serve.mjs +161 -10
  77. package/starters/admin/README.md +63 -0
  78. package/starters/admin/index.html +28 -0
  79. package/starters/admin/mado.config.json +22 -0
  80. package/starters/admin/package.json +24 -0
  81. package/starters/admin/public/favicon.svg +4 -0
  82. package/starters/admin/src/components/x-button.ts +55 -0
  83. package/starters/admin/src/components/x-input.ts +74 -0
  84. package/starters/admin/src/layouts/app.ts +101 -0
  85. package/starters/admin/src/layouts/auth.ts +41 -0
  86. package/starters/admin/src/lib/api.ts +133 -0
  87. package/starters/admin/src/lib/auth.ts +83 -0
  88. package/starters/admin/src/main.ts +15 -0
  89. package/starters/admin/src/pages/admin/dashboard.ts +48 -0
  90. package/starters/admin/src/pages/admin/order-detail.ts +80 -0
  91. package/starters/admin/src/pages/admin/orders.ts +117 -0
  92. package/starters/admin/src/pages/home.ts +34 -0
  93. package/starters/admin/src/pages/login.ts +70 -0
  94. package/starters/admin/src/pages/not-found.ts +12 -0
  95. package/starters/admin/src/routes.ts +40 -0
  96. package/starters/admin/src/styles/global.ts +86 -0
  97. package/starters/admin/tsconfig.json +15 -0
  98. package/starters/crud/index.html +12 -4
  99. package/starters/crud/mado.config.json +20 -0
  100. package/starters/crud/package.json +9 -3
  101. package/starters/crud/src/pages/home.ts +16 -0
  102. package/starters/crud/src/routes.ts +4 -2
  103. package/starters/minimal/index.html +12 -4
  104. package/starters/minimal/mado.config.json +20 -0
  105. package/starters/minimal/package.json +9 -3
  106. package/starters/minimal/src/pages/home.ts +17 -0
  107. package/starters/minimal/src/routes.ts +4 -2
@@ -44,6 +44,16 @@ export interface Computed<T> {
44
44
  (): T;
45
45
  peek(): T;
46
46
  }
47
+ export interface ComputedOptions<T> {
48
+ /**
49
+ * Equality check used when an observed computed is invalidated.
50
+ *
51
+ * If the new value is equal to the previous value, subscribers are not
52
+ * notified. Defaults to always notifying on dependency invalidation, which
53
+ * preserves the classic lazy dirty-flag behavior.
54
+ */
55
+ equals?: (prev: T, next: T) => boolean;
56
+ }
47
57
  /**
48
58
  * Lazy computed based on a dirty-flag:
49
59
  * - fn is NOT called until the computed is read;
@@ -58,10 +68,14 @@ export interface Computed<T> {
58
68
  * instead of actually recomputing we mark ourselves dirty and
59
69
  * propagate to subscribers.
60
70
  */
61
- export declare function computed<T>(fn: () => T): Computed<T>;
71
+ export declare function computed<T>(fn: () => T, options?: ComputedOptions<T>): Computed<T>;
62
72
  export type Disposer = () => void;
63
73
  export declare function effect(fn: () => void | Disposer): Disposer;
64
74
  /**
65
75
  * Execute a function outside of tracking — reading signals will not create a subscription.
66
76
  */
67
77
  export declare function untracked<T>(fn: () => T): T;
78
+ export declare const _testHooks: {
79
+ subscriberCount(source: object): number;
80
+ dependencyCount(source: object): number;
81
+ };
@@ -23,7 +23,48 @@
23
23
  *
24
24
  * batch(() => { a.set(1); b.set(2); });
25
25
  */
26
+ const MAX_FLUSH_RUNS_PER_SUBSCRIBER = 100;
26
27
  let activeTracker = null;
28
+ class SubscriberSet extends Set {
29
+ onEmpty;
30
+ emptyScheduled = false;
31
+ constructor(onEmpty) {
32
+ super();
33
+ this.onEmpty = onEmpty;
34
+ }
35
+ add(entry) {
36
+ super.add(entry);
37
+ this.emptyScheduled = false;
38
+ return this;
39
+ }
40
+ delete(entry) {
41
+ const deleted = super.delete(entry);
42
+ if (deleted)
43
+ this.queueEmpty();
44
+ return deleted;
45
+ }
46
+ clear() {
47
+ const hadEntries = this.size > 0;
48
+ super.clear();
49
+ if (hadEntries)
50
+ this.queueEmpty();
51
+ }
52
+ queueEmpty() {
53
+ if (!this.onEmpty || this.size > 0 || this.emptyScheduled)
54
+ return;
55
+ this.emptyScheduled = true;
56
+ queueMicrotask(() => {
57
+ this.emptyScheduled = false;
58
+ if (this.size === 0)
59
+ this.onEmpty?.();
60
+ });
61
+ }
62
+ }
63
+ function cleanupTracker(tracker) {
64
+ for (const dep of tracker.deps)
65
+ dep.delete(tracker.entry);
66
+ tracker.deps.clear();
67
+ }
27
68
  // ---------- Scheduler ----------
28
69
  const pending = new Set();
29
70
  let batchDepth = 0;
@@ -39,11 +80,20 @@ function schedule(sub) {
39
80
  }
40
81
  function flush() {
41
82
  flushScheduled = false;
83
+ const runCounts = new Map();
42
84
  // guard against Set modification during iteration
43
85
  while (pending.size > 0) {
44
86
  const subs = [...pending];
45
87
  pending.clear();
46
88
  for (const sub of subs) {
89
+ const runs = (runCounts.get(sub) ?? 0) + 1;
90
+ runCounts.set(sub, runs);
91
+ if (runs > MAX_FLUSH_RUNS_PER_SUBSCRIBER) {
92
+ // eslint-disable-next-line no-console
93
+ console.error("[mado] effect cycle detected: subscriber re-ran more than " +
94
+ `${MAX_FLUSH_RUNS_PER_SUBSCRIBER} times in one flush.`);
95
+ continue;
96
+ }
47
97
  try {
48
98
  sub();
49
99
  }
@@ -81,7 +131,7 @@ export function flushSync() {
81
131
  }
82
132
  export function signal(initial) {
83
133
  let value = initial;
84
- const subscribers = new Set();
134
+ const subscribers = new SubscriberSet();
85
135
  const read = (() => {
86
136
  if (activeTracker) {
87
137
  subscribers.add(activeTracker.entry);
@@ -115,6 +165,7 @@ export function signal(initial) {
115
165
  };
116
166
  read.update = (fn) => read.set(fn(value));
117
167
  read.peek = () => value;
168
+ debugInfo.set(read, { subscribers });
118
169
  return read;
119
170
  }
120
171
  /**
@@ -131,17 +182,55 @@ export function signal(initial) {
131
182
  * instead of actually recomputing we mark ourselves dirty and
132
183
  * propagate to subscribers.
133
184
  */
134
- export function computed(fn) {
135
- const subscribers = new Set();
185
+ export function computed(fn, options = {}) {
136
186
  let value = undefined;
137
187
  let dirty = true;
188
+ let hasValue = false;
189
+ let computing = false;
138
190
  const onInvalidate = () => {
139
191
  // dep changed → mark dirty synchronously and cascade.
140
192
  // Sync subscribers (other computed) are triggered immediately — they also
141
193
  // set dirty without delay. Async (effects) go through the scheduler.
142
194
  if (dirty)
143
195
  return;
196
+ if (subscribers.size === 0) {
197
+ dirty = true;
198
+ suspend();
199
+ return;
200
+ }
201
+ if (options.equals && hasValue) {
202
+ const prevValue = value;
203
+ recompute();
204
+ if (options.equals(prevValue, value))
205
+ return;
206
+ notifySubscribers();
207
+ return;
208
+ }
144
209
  dirty = true;
210
+ notifySubscribers();
211
+ };
212
+ const tracker = {
213
+ deps: new Set(),
214
+ entry: { run: onInvalidate, sync: true },
215
+ };
216
+ const subscribers = new SubscriberSet(() => {
217
+ suspend();
218
+ });
219
+ const suspend = () => {
220
+ if (subscribers.size > 0)
221
+ return;
222
+ cleanupTracker(tracker);
223
+ dirty = true;
224
+ };
225
+ const queueSuspendIfUnobserved = () => {
226
+ if (subscribers.size > 0)
227
+ return;
228
+ queueMicrotask(() => {
229
+ if (subscribers.size === 0)
230
+ suspend();
231
+ });
232
+ };
233
+ const notifySubscribers = () => {
145
234
  const snapshot = [...subscribers];
146
235
  for (const e of snapshot) {
147
236
  if (e.sync) {
@@ -158,23 +247,24 @@ export function computed(fn) {
158
247
  }
159
248
  }
160
249
  };
161
- const tracker = {
162
- deps: new Set(),
163
- entry: { run: onInvalidate, sync: true },
164
- };
165
250
  const recompute = () => {
166
- for (const dep of tracker.deps)
167
- dep.delete(tracker.entry);
168
- tracker.deps.clear();
251
+ if (computing) {
252
+ throw new Error("[mado] computed cycle detected");
253
+ }
254
+ cleanupTracker(tracker);
169
255
  const prev = activeTracker;
170
256
  activeTracker = tracker;
257
+ computing = true;
171
258
  try {
172
259
  value = fn();
173
260
  }
174
261
  finally {
262
+ computing = false;
175
263
  activeTracker = prev;
176
264
  }
177
265
  dirty = false;
266
+ hasValue = true;
267
+ queueSuspendIfUnobserved();
178
268
  };
179
269
  const read = (() => {
180
270
  if (activeTracker) {
@@ -190,14 +280,13 @@ export function computed(fn) {
190
280
  recompute();
191
281
  return value;
192
282
  };
283
+ debugInfo.set(read, { subscribers, tracker });
193
284
  return read;
194
285
  }
195
286
  export function effect(fn) {
196
287
  let cleanup;
197
288
  const run = () => {
198
- for (const dep of tracker.deps)
199
- dep.delete(tracker.entry);
200
- tracker.deps.clear();
289
+ cleanupTracker(tracker);
201
290
  if (typeof cleanup === "function")
202
291
  cleanup();
203
292
  const prev = activeTracker;
@@ -215,9 +304,7 @@ export function effect(fn) {
215
304
  };
216
305
  run();
217
306
  return () => {
218
- for (const dep of tracker.deps)
219
- dep.delete(tracker.entry);
220
- tracker.deps.clear();
307
+ cleanupTracker(tracker);
221
308
  if (typeof cleanup === "function")
222
309
  cleanup();
223
310
  };
@@ -235,4 +322,13 @@ export function untracked(fn) {
235
322
  activeTracker = prev;
236
323
  }
237
324
  }
325
+ const debugInfo = new WeakMap();
326
+ export const _testHooks = {
327
+ subscriberCount(source) {
328
+ return debugInfo.get(source)?.subscribers.size ?? 0;
329
+ },
330
+ dependencyCount(source) {
331
+ return debugInfo.get(source)?.tracker?.deps.size ?? 0;
332
+ },
333
+ };
238
334
  //# sourceMappingURL=signal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"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"}
@@ -1,58 +1,117 @@
1
1
  # Project layout
2
2
 
3
- Every new Mado project has the same structure. This is a **mandatory** convention.
3
+ Every Mado app uses the same shape. This is a **mandatory** convention — it
4
+ exists so that you, your teammates, and any LLM assistant always know where
5
+ things live.
4
6
 
5
7
  ```
6
8
  my-app/
7
- ├── package.json # exactly 1 dep: typescript (esbuild optional)
8
- ├── tsconfig.json # with paths "@madojs/mado" import without relative paths
9
- ├── Dockerfile + nginx.conf # copied from Mado/ on scaffold
10
- ├── .gitlab-ci.yml | .github/workflows/ci.yml
11
- ├── server/serve.mjs # dev-server from Mado, no deps
12
- ├── scripts/
13
- │ ├── bundle.mjs # esbuild prod bundle
14
- │ └── new.mjs # page scaffolder
15
- ├── templates/ # templates for new.mjs
16
- ├── docs/ # project docs (can copy our guides)
17
- ├── public/ # static assets (favicon, manifests)
9
+ ├── package.json # exactly one runtime dep: @madojs/mado
10
+ ├── tsconfig.json # strict TS, ES2022, Bundler resolution
11
+ ├── mado.config.json # single config file (dev/build/bake/bundle)
12
+ ├── index.html # SPA shell (also the template for `mado bake`)
13
+ ├── public/ # static assets (favicons, images, robots.txt)
18
14
  └── src/
19
- ├── main.ts # entry: providers + mount <x-app>
20
- ├── routes.ts # route manifest
21
- ├── pages/ # one page = one file = `export default page({...})`
22
- ├── components/ # reusable components (x-*)
23
- ├── layouts/ # layout pages (for nested)
15
+ ├── main.ts # entry: mount router into #app
16
+ ├── routes.ts # route manifest (default + named `manifest`)
17
+ ├── layouts/ # `page({ child })` layouts for nested routes
18
+ ├── pages/ # one page = one file
19
+ ├── components/ # reusable x-* Web Components
24
20
  └── lib/
25
- ├── api.ts # all fetch wrappers
26
- ├── contexts.ts # createContext(...)
27
- ├── theme.ts # themes
28
- └── ... # utilities, types, business rules
21
+ ├── api.ts # API client + error type
22
+ ├── auth.ts # auth recipe (token + guard)
23
+ └── ... # contexts, helpers, business rules
29
24
  ```
30
25
 
26
+ ## The three artifact states (read this once, never wonder again)
27
+
28
+ | Folder | What it is | Who writes | Who reads | Deploy? |
29
+ |-------------|----------------------------------------------------------------|-------------------|----------------------------|-------------------|
30
+ | `src/` | your source (TypeScript) | you | `tsc`, `esbuild` | ❌ no |
31
+ | `dist/` | `tsc` output — native ESM `.js` for the browser | `mado build` | `mado dev` (during dev) | ❌ no (internal) |
32
+ | `public/` | static assets you authored (favicon, images, robots.txt) | you | `mado release` copies it | ✅ via `out/` |
33
+ | `out/` | **the deploy artifact**: SPA shell + bundles + baked HTML | `mado release` | nginx / CDN / Cloudflare | ✅ **yes** |
34
+
35
+ One-liner to remember:
36
+ > Develop with `mado dev`. To ship: run `mado release`, then upload `out/`.
37
+
38
+ `mado release` = `typecheck` + `build` (tsc → `dist/`) + `bundle` (esbuild
39
+ → `out/assets/`) + `bake` (HTML → `out/baked/`) + copy `public/*` → `out/`.
40
+
41
+ You almost never need to look inside `dist/`. It exists so the dev browser can
42
+ load native ESM modules without a bundler during development. In production
43
+ the equivalent code is bundled and hashed into `out/assets/`.
44
+
45
+ ### Quick deployment matrix
46
+
47
+ | Target | Command | Where it goes |
48
+ |-----------------------|--------------------------------------|----------------------------|
49
+ | VPS + nginx | `mado release && rsync -avz out/ …` | `/var/www/<app>/` |
50
+ | Cloudflare Pages | `mado release && wrangler pages deploy out` | CF Pages |
51
+ | Netlify / S3 / GH Pages | `mado release && upload out/*` | any static host |
52
+
53
+ See `docs/en/13-deployment.md` for full recipes.
54
+
31
55
  ## Where to put a new file?
32
56
 
33
- | What | Where |
34
- |---|---|
35
- | Page for a new URL | `src/pages/foo.ts` + add to `src/routes.ts` |
36
- | Reusable UI widget | `src/components/foo-bar.ts` |
37
- | API wrapper | `src/lib/api.ts` (add a method) |
38
- | Global context (theme, user, i18n) | `src/lib/<name>.ts` |
39
- | Pure function without UI | `src/lib/util/<name>.ts` |
57
+ | What | Where |
58
+ |-------------------------------------|------------------------------------------------------|
59
+ | Page for a new URL | `src/pages/<name>.ts` + add to `src/routes.ts` |
60
+ | Layout for a group of routes | `src/layouts/<name>.ts` (referenced from `routes.ts`)|
61
+ | Reusable UI widget | `src/components/<x-name>.ts` |
62
+ | API call | `src/lib/api.ts` (add a method) |
63
+ | Auth/session | `src/lib/auth.ts` |
64
+ | Global context (theme, user, i18n) | `src/lib/<name>.ts` |
65
+ | Pure function with no UI | `src/lib/util/<name>.ts` |
66
+ | Static image / favicon | `public/<file>` |
40
67
 
41
- If you don't know where — that is a signal that **the architecture is suffering**.
42
- Ask the team, **record** the answer in `docs/`.
68
+ If you don't know where — that is a signal that **the architecture is
69
+ suffering**. Ask the team and **record** the answer in `docs/`. Don't invent a
70
+ new top-level folder.
43
71
 
44
72
  ## Naming rules
45
73
 
46
- | What | Style | Example |
47
- |---|---|---|
48
- | File | kebab-case | `user-profile.ts` |
49
- | Component tag | `x-` + kebab | `<x-user-profile>` |
50
- | Context | PascalCase + `Ctx` | `ThemeCtx`, `AuthCtx` |
51
- | Signal | camelCase | `userId`, `isLoggedIn` |
52
- | Page function (internal component) | `x-<route>-page` | `<x-posts-page>` |
74
+ | What | Style | Example |
75
+ |-------------------------------------|----------------------|------------------------|
76
+ | File | kebab-case | `user-profile.ts` |
77
+ | Component tag | `x-` + kebab | `<x-user-profile>` |
78
+ | Context | PascalCase + `Ctx` | `ThemeCtx`, `AuthCtx` |
79
+ | Signal | camelCase | `userId`, `isLoggedIn` |
80
+ | Page-internal element | `x-<route>-page` | `<x-posts-page>` |
81
+
82
+ ## `mado.config.json` in one screen
83
+
84
+ ```jsonc
85
+ {
86
+ "dev": {
87
+ "port": 5173,
88
+ "proxy": { "/api": "http://localhost:3000" } // dev → backend
89
+ },
90
+ "build": {
91
+ "out": "out",
92
+ "dist": "dist",
93
+ "publicDir": "public"
94
+ },
95
+ "bake": {
96
+ "entry": "src/routes.ts",
97
+ "template": "index.html",
98
+ "baseUrl": "https://example.com"
99
+ },
100
+ "bundle": {
101
+ "splitting": true,
102
+ "compress": ["gz", "br"]
103
+ }
104
+ }
105
+ ```
106
+
107
+ Precedence: built-in defaults < `mado.config.json` < CLI flags
108
+ (< legacy env vars). All keys are optional.
53
109
 
54
- ## What does NOT go in src/
110
+ ## What does NOT go in `src/`
55
111
 
56
112
  - ❌ Build tool configs (webpack, rollup, vite) — we don't have any.
57
- - ❌ `.env` files — env is read from `process.env`/`import.meta.env` in `lib/config.ts`.
58
- - Tests mixed with code — all in `test/`.
113
+ - ❌ `.env` files — read env in `src/lib/config.ts` from `import.meta.env` /
114
+ `process.env` and import that one module everywhere.
115
+ - ❌ Tests mixed with code — put them in `test/`.
116
+ - ❌ `examples/` folder — the framework repository has examples, your app
117
+ does not need one.
@@ -0,0 +1,141 @@
1
+ # App architecture
2
+
3
+ This is the default shape for a production Mado app. It is intentionally boring:
4
+ one route manifest, one shell, one API client, one auth module, and page files
5
+ that own their feature components.
6
+
7
+ ## File tree
8
+
9
+ ```txt
10
+ src/
11
+ ├── main.ts
12
+ ├── routes.ts
13
+ ├── layouts/
14
+ │ ├── app.ts
15
+ │ └── auth.ts
16
+ ├── pages/
17
+ │ ├── home.ts
18
+ │ ├── login.ts
19
+ │ ├── not-found.ts
20
+ │ └── admin/
21
+ │ ├── dashboard.ts
22
+ │ ├── orders.ts
23
+ │ └── order-detail.ts
24
+ ├── components/
25
+ │ ├── x-button.ts
26
+ │ └── x-input.ts
27
+ ├── lib/
28
+ │ ├── api.ts
29
+ │ └── auth.ts
30
+ └── styles/
31
+ └── global.ts
32
+ ```
33
+
34
+ Keep business logic in `lib/`, route wrapping in `layouts/`, and UI leaves in
35
+ `components/`. A page should import the components it renders.
36
+
37
+ ## Entry point
38
+
39
+ ```ts
40
+ // src/main.ts
41
+ import { html, render } from "@madojs/mado";
42
+ import "./styles/global.js";
43
+ import "./components/x-button.js";
44
+ import routesApi from "./routes.js";
45
+
46
+ render(html`${routesApi.view}`, document.getElementById("app")!);
47
+ ```
48
+
49
+ Import global providers and tiny shared components here. Do not bulk-import
50
+ every feature component.
51
+
52
+ ## Routes
53
+
54
+ ```ts
55
+ // src/routes.ts
56
+ import { layout, routes } from "@madojs/mado";
57
+ import { requireAuth } from "./lib/auth.js";
58
+
59
+ export const manifest = {
60
+ "/": () => import("./pages/home.js"),
61
+ "/login": layout({
62
+ layout: () => import("./layouts/auth.js"),
63
+ routes: { "/": () => import("./pages/login.js") },
64
+ }),
65
+ "/admin": layout({
66
+ layout: () => import("./layouts/app.js"),
67
+ guard: requireAuth,
68
+ routes: {
69
+ "/": () => import("./pages/admin/dashboard.js"),
70
+ "/orders": () => import("./pages/admin/orders.js"),
71
+ "/orders/:id": () => import("./pages/admin/order-detail.js"),
72
+ },
73
+ }),
74
+ "*": () => import("./pages/not-found.js"),
75
+ };
76
+
77
+ export default routes(manifest, {
78
+ errorPage: (err) => html`<main><h1>Something went wrong</h1><pre>${err.message}</pre></main>`,
79
+ });
80
+ ```
81
+
82
+ Exporting `manifest` lets `mado bake` inspect the same route table.
83
+
84
+ ## API and auth
85
+
86
+ Use one API client and one auth module. The admin starter ships a complete
87
+ version with token storage, a single-flight refresh request, and a route guard.
88
+
89
+ ```ts
90
+ // pages/admin/orders.ts
91
+ import { each, html, page, resource } from "@madojs/mado";
92
+ import { api } from "../../lib/api.js";
93
+
94
+ const orders = resource(() => "/api/orders", () => api.get("/orders"));
95
+
96
+ export default page({
97
+ title: "Orders",
98
+ view: () => html`
99
+ <main>
100
+ <h1>Orders</h1>
101
+ <ul>
102
+ ${() => each(orders.data() ?? [], o => o.id, o => html`<li>${o.number}</li>`)}
103
+ </ul>
104
+ </main>
105
+ `,
106
+ });
107
+ ```
108
+
109
+ Mutations should declare invalidation near the write:
110
+
111
+ ```ts
112
+ const save = mutation((payload) => api.post("/orders", payload), {
113
+ invalidates: ["/api/orders*"],
114
+ });
115
+ ```
116
+
117
+ ## Forms
118
+
119
+ Prefer one `useForm()` per user workflow.
120
+
121
+ ```ts
122
+ const form = useForm({
123
+ email: { required: true, type: "email" },
124
+ "items.*.title": { required: true },
125
+ });
126
+ const items = form.array("items");
127
+ ```
128
+
129
+ Use dotted paths for arrays (`items.0.title`) and keep async validation in
130
+ `validateAsync` when it talks to the backend.
131
+
132
+ ## Release
133
+
134
+ Local development uses `mado dev`. Production uses exactly one artifact:
135
+
136
+ ```bash
137
+ mado release
138
+ rsync -avz out/ user@server:/var/www/app/
139
+ ```
140
+
141
+ `out/` is the deploy folder. `dist/` is internal build output.