@madojs/mado 0.5.0 → 0.6.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 (122) hide show
  1. package/AGENTS.md +49 -1
  2. package/CHANGELOG.md +188 -0
  3. package/MADO_V1_PLAN.md +179 -0
  4. package/README.md +53 -14
  5. package/ROADMAP.md +36 -5
  6. package/TODO.md +72 -0
  7. package/dist/src/forms.d.ts +41 -7
  8. package/dist/src/forms.js +334 -59
  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/router/manifest.d.ts +16 -1
  23. package/dist/src/router/manifest.js +181 -38
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/match.d.ts +7 -2
  26. package/dist/src/router/match.js +14 -4
  27. package/dist/src/router/match.js.map +1 -1
  28. package/dist/src/router/navigation.d.ts +10 -0
  29. package/dist/src/router/navigation.js +73 -12
  30. package/dist/src/router/navigation.js.map +1 -1
  31. package/dist/src/signal.d.ts +15 -1
  32. package/dist/src/signal.js +112 -16
  33. package/dist/src/signal.js.map +1 -1
  34. package/docs/en/02-project-layout.md +99 -40
  35. package/docs/en/05-why-mado.md +1 -1
  36. package/docs/en/06-for-backenders.md +1 -1
  37. package/docs/en/07-llm-pitfalls.md +1 -1
  38. package/docs/en/09-shadow-vs-light-dom.md +60 -0
  39. package/docs/en/10-app-architecture.md +141 -0
  40. package/docs/en/11-layouts.md +115 -0
  41. package/docs/en/12-auth-and-api.md +217 -0
  42. package/docs/en/13-deployment.md +192 -0
  43. package/docs/en/14-testing.md +82 -0
  44. package/docs/en/15-error-handling.md +100 -0
  45. package/docs/en/16-bake-cookbook.md +93 -0
  46. package/docs/en/README.md +7 -0
  47. package/docs/fr/05-why-mado.md +1 -1
  48. package/docs/fr/06-for-backenders.md +1 -1
  49. package/docs/fr/07-llm-pitfalls.md +1 -1
  50. package/docs/fr/09-shadow-vs-light-dom.md +63 -0
  51. package/docs/fr/10-app-architecture.md +61 -0
  52. package/docs/fr/11-layouts.md +35 -0
  53. package/docs/fr/12-auth-and-api.md +35 -0
  54. package/docs/fr/13-deployment.md +39 -0
  55. package/docs/fr/14-testing.md +41 -0
  56. package/docs/fr/15-error-handling.md +50 -0
  57. package/docs/fr/16-bake-cookbook.md +35 -0
  58. package/docs/fr/README.md +7 -0
  59. package/docs/ru/05-why-mado.md +2 -2
  60. package/docs/ru/06-for-backenders.md +1 -1
  61. package/docs/ru/09-shadow-vs-light-dom.md +60 -0
  62. package/docs/ru/10-app-architecture.md +100 -0
  63. package/docs/ru/11-layouts.md +47 -0
  64. package/docs/ru/12-auth-and-api.md +53 -0
  65. package/docs/ru/13-deployment.md +60 -0
  66. package/docs/ru/14-testing.md +50 -0
  67. package/docs/ru/15-error-handling.md +56 -0
  68. package/docs/ru/16-bake-cookbook.md +55 -0
  69. package/docs/ru/README.md +7 -0
  70. package/docs/uk/06-for-backenders.md +2 -2
  71. package/docs/uk/09-shadow-vs-light-dom.md +91 -24
  72. package/docs/uk/10-app-architecture.md +56 -0
  73. package/docs/uk/11-layouts.md +34 -0
  74. package/docs/uk/12-auth-and-api.md +34 -0
  75. package/docs/uk/13-deployment.md +39 -0
  76. package/docs/uk/14-testing.md +34 -0
  77. package/docs/uk/15-error-handling.md +32 -0
  78. package/docs/uk/16-bake-cookbook.md +36 -0
  79. package/docs/uk/README.md +7 -0
  80. package/llms.txt +24 -1
  81. package/package.json +3 -1
  82. package/scripts/_config.mjs +224 -0
  83. package/scripts/bake.mjs +217 -120
  84. package/scripts/bundle.mjs +110 -67
  85. package/scripts/cli.mjs +127 -16
  86. package/scripts/preview.mjs +22 -12
  87. package/server/serve.mjs +101 -11
  88. package/starters/admin/README.md +63 -0
  89. package/starters/admin/index.html +21 -0
  90. package/starters/admin/mado.config.json +22 -0
  91. package/starters/admin/package.json +22 -0
  92. package/starters/admin/public/favicon.svg +4 -0
  93. package/starters/admin/src/components/x-button.ts +55 -0
  94. package/starters/admin/src/components/x-input.ts +74 -0
  95. package/starters/admin/src/layouts/app.ts +101 -0
  96. package/starters/admin/src/layouts/auth.ts +41 -0
  97. package/starters/admin/src/lib/api.ts +133 -0
  98. package/starters/admin/src/lib/auth.ts +83 -0
  99. package/starters/admin/src/main.ts +15 -0
  100. package/starters/admin/src/pages/admin/dashboard.ts +48 -0
  101. package/starters/admin/src/pages/admin/order-detail.ts +78 -0
  102. package/starters/admin/src/pages/admin/orders.ts +117 -0
  103. package/starters/admin/src/pages/home.ts +25 -0
  104. package/starters/admin/src/pages/login.ts +70 -0
  105. package/starters/admin/src/pages/not-found.ts +12 -0
  106. package/starters/admin/src/routes.ts +40 -0
  107. package/starters/admin/src/styles/global.ts +86 -0
  108. package/starters/admin/tsconfig.json +15 -0
  109. package/starters/crud/README.md +14 -2
  110. package/starters/crud/mado.config.json +20 -0
  111. package/starters/crud/package.json +9 -4
  112. package/starters/crud/src/components/app-shell.ts +13 -8
  113. package/starters/crud/src/main.ts +1 -4
  114. package/starters/crud/src/pages/ticket-detail.ts +1 -0
  115. package/starters/crud/src/pages/ticket-new.ts +1 -0
  116. package/starters/crud/src/pages/tickets.ts +1 -0
  117. package/starters/crud/src/routes.ts +4 -2
  118. package/starters/minimal/README.md +4 -2
  119. package/starters/minimal/mado.config.json +20 -0
  120. package/starters/minimal/package.json +8 -3
  121. package/starters/minimal/src/components/app-counter.ts +1 -1
  122. 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.
@@ -33,7 +33,7 @@ If your case does not fall into the last point — Mado is most likely not the b
33
33
  | Reactivity | `@property` decorators + manual `requestUpdate` | signals (`signal`/`computed`/`effect`) out of the box |
34
34
  | Router | none, you need to find one (`@lit-labs/router`, etc) | included: `routes()` + nested + prefetch + sync-cache |
35
35
  | Data fetching | none, you need to assemble it | `resource()` + `mutation()` + glob invalidation |
36
- | Forms | none | `useForm()` on native HTML5 validation |
36
+ | Forms | none | `useForm()` with HTML-like constraints |
37
37
  | SEO / static | complex (`@lit-labs/ssr`) | `bake` (linkedom) + edge-prerender |
38
38
  | Build | needs esbuild/rollup/webpack | `tsc` is enough |
39
39
  | Code style | classes + decorators | functions + tagged templates |
@@ -188,7 +188,7 @@ We register the `<x-counter>` tag in the browser — it becomes a "function" tha
188
188
 
189
189
  ## Forms — like `form.Validate()` on the backend
190
190
 
191
- Mado uses **native HTML5 validation**, plus adds state tracking.
191
+ Mado uses **schema-based validation close to native HTML constraints**, plus adds state tracking.
192
192
 
193
193
  ```ts
194
194
  import { useForm } from "@madojs/mado";
@@ -256,7 +256,7 @@ This means:
256
256
  // ❌ No such API
257
257
  const f = useForm({ resolver: zodResolver(schema) });
258
258
 
259
- // ✅ Correct: HTML5 validation via attributes
259
+ // ✅ Correct: HTML-like validation through useForm schema
260
260
  const f = useForm({
261
261
  email: { required: true, type: "email" },
262
262
  age: { required: true, type: "number", min: 18 },
@@ -6,6 +6,19 @@ an application.
6
6
 
7
7
  ## Rule of Thumb
8
8
 
9
+ In Mado, layouts are components too. If a file represents a visible reusable
10
+ part of the app tree — app shell, sidebar, modal, table, page section — prefer a
11
+ Web Component registered with `component()`.
12
+
13
+ Use plain functions only for small inline template helpers:
14
+
15
+ ```ts
16
+ const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
17
+ ```
18
+
19
+ Do not use functions for app shells in public examples. They work, but they
20
+ hide the browser model instead of teaching it.
21
+
9
22
  Use **Shadow DOM** for leaf widgets:
10
23
 
11
24
  - buttons, badges, cards, metrics;
@@ -22,6 +35,15 @@ global CSS utilities:
22
35
  - components that intentionally share global layout, form and table utilities;
23
36
  - places where children should simply remain normal document DOM.
24
37
 
38
+ Use **Shadow DOM** for slot-based layouts:
39
+
40
+ - app shells that render `<slot>`;
41
+ - sidebar/content wrappers;
42
+ - reusable layout frames that own their own grid/header/sidebar CSS.
43
+
44
+ `<slot>` is a Shadow DOM feature. In a `shadow: false` component, `<slot>` is
45
+ just a normal element and does not move children into that position.
46
+
25
47
  ## The Footgun
26
48
 
27
49
  Global CSS does not cross a Shadow DOM boundary.
@@ -90,6 +112,18 @@ component("x-toast-stack", setup);
90
112
  This gives backend-admin screens predictable CSS while preserving encapsulation
91
113
  for reusable widgets and slot-based shells.
92
114
 
115
+ The import model is deliberately browser-native:
116
+
117
+ ```ts
118
+ import "./components/app-layout.js";
119
+
120
+ render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
121
+ ```
122
+
123
+ The import registers the custom element with `customElements.define()`. The
124
+ template creates an `<x-app-layout>` element. The browser connects the two.
125
+ There is no React-style component value being passed around.
126
+
93
127
  If a layout does not need slot projection and should be styled entirely by
94
128
  global CSS, `shadow: false` can still be a good choice. If it contains
95
129
  `<slot>`, keep Shadow DOM and put the shell styles in that component.
@@ -107,6 +141,32 @@ component("x-card-link", () => () => html`
107
141
 
108
142
  The link can be in Shadow DOM; navigation still stays SPA.
109
143
 
144
+ ## Where To Import Components
145
+
146
+ Custom elements are global after registration, but registration is still an
147
+ explicit JavaScript import.
148
+
149
+ ```ts
150
+ // main.ts: global app frame
151
+ import "./components/app-shell.js";
152
+
153
+ // pages/tickets.ts: page-owned feature component
154
+ import "../components/ticket-list.js";
155
+ ```
156
+
157
+ The browser does **not** download `ticket-list.js` just because it sees
158
+ `<ticket-list>`. The file must be imported somewhere first. Once imported, it
159
+ calls `customElements.define(...)`, and the tag becomes known in the current
160
+ document.
161
+
162
+ Do not bulk-import every component in `main.ts` "just in case". It works for
163
+ tiny demos, but it hides ownership and defeats lazy route loading. Prefer:
164
+
165
+ - global app shell/providers in `main.ts`;
166
+ - page-owned components in that page file;
167
+ - feature-owned shared components in the feature entry page;
168
+ - truly global leaf components in `main.ts` only when they are used everywhere.
169
+
110
170
  ## Showcase Lesson
111
171
 
112
172
  `examples/showcase` uses this split deliberately: