@madojs/mado 0.6.1 → 0.8.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.
- package/AGENTS.md +82 -30
- package/CHANGELOG.md +150 -3
- package/dist/src/component.d.ts +17 -4
- package/dist/src/component.js +43 -4
- package/dist/src/component.js.map +1 -1
- package/dist/src/forms.js +4 -1
- package/dist/src/forms.js.map +1 -1
- package/dist/src/page.d.ts +12 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.js +7 -1
- package/dist/src/router/manifest.js.map +1 -1
- package/docs/en/07-llm-pitfalls.md +197 -60
- package/docs/en/08-llm-zero-history-test.md +1 -1
- package/docs/en/17-shadow-dom-forms.md +192 -0
- package/docs/en/README.md +20 -19
- package/docs/fr/07-llm-pitfalls.md +196 -60
- package/docs/fr/17-shadow-dom-forms.md +196 -0
- package/docs/fr/README.md +20 -19
- package/docs/ru/07-llm-pitfalls.md +198 -61
- package/docs/ru/08-llm-zero-history-test.md +39 -38
- package/docs/ru/09-shadow-vs-light-dom.md +97 -81
- package/docs/ru/17-shadow-dom-forms.md +193 -0
- package/docs/ru/README.md +20 -19
- package/docs/uk/07-llm-pitfalls.md +64 -3
- package/docs/uk/17-shadow-dom-forms.md +193 -0
- package/docs/uk/README.md +20 -19
- package/llms.txt +50 -1
- package/package.json +2 -2
- package/scripts/bake.mjs +25 -19
- package/scripts/cli.mjs +22 -33
- package/starters/admin/src/components/x-button.ts +40 -13
- package/starters/admin/src/components/x-input.ts +50 -19
- package/starters/admin/src/lib/api.ts +55 -4
package/AGENTS.md
CHANGED
|
@@ -91,6 +91,36 @@ component("x-timer", (ctx) => {
|
|
|
91
91
|
|
|
92
92
|
**`resource()`, `effect()`, and subscriptions inside `setup()` hook into the lifecycle automatically** — no need to write onDispose for them.
|
|
93
93
|
|
|
94
|
+
### 4b. Reactive attributes — `ctx.attr()`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// ❌ NO (reading once, never reactive)
|
|
98
|
+
component("x-badge", ({ host }) => () => {
|
|
99
|
+
const variant = host.getAttribute("variant") ?? "default";
|
|
100
|
+
return html`<span class=${variant}>...</span>`;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ❌ NO (MutationObserver boilerplate)
|
|
104
|
+
component("x-badge", ({ host, onDispose }) => {
|
|
105
|
+
const variant = signal(host.getAttribute("variant") ?? "default");
|
|
106
|
+
const obs = new MutationObserver(() =>
|
|
107
|
+
variant.set(host.getAttribute("variant") ?? "default"),
|
|
108
|
+
);
|
|
109
|
+
obs.observe(host, { attributes: true, attributeFilter: ["variant"] });
|
|
110
|
+
onDispose(() => obs.disconnect());
|
|
111
|
+
return () => html`<span class=${variant}>...</span>`;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ✅ YES — one line, reactive, no cleanup needed
|
|
115
|
+
component("x-badge", ({ attr }) => {
|
|
116
|
+
const variant = attr("variant", "default");
|
|
117
|
+
return () => html`<span class=${() => `badge-${variant()}`}>...</span>`;
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`ctx.attr(name, defaultValue?)` returns a `Signal<string>` that auto-updates.
|
|
122
|
+
The attribute is automatically added to `observedAttributes`.
|
|
123
|
+
|
|
94
124
|
### 5. Reactive value in template child position = function
|
|
95
125
|
|
|
96
126
|
The most common AI mistake:
|
|
@@ -99,13 +129,13 @@ The most common AI mistake:
|
|
|
99
129
|
const count = signal(0);
|
|
100
130
|
|
|
101
131
|
// ❌ NOT REACTIVE — count() is read once
|
|
102
|
-
html`<div>${count() * 2}</div
|
|
132
|
+
html`<div>${count() * 2}</div>`;
|
|
103
133
|
|
|
104
134
|
// ✅ REACTIVE — the function will be called when count changes
|
|
105
|
-
html`<div>${() => count() * 2}</div
|
|
135
|
+
html`<div>${() => count() * 2}</div>`;
|
|
106
136
|
|
|
107
137
|
// ✅ ALSO OK — the signal itself is a function, Mado recognizes it
|
|
108
|
-
html`<div>${count}</div
|
|
138
|
+
html`<div>${count}</div>`;
|
|
109
139
|
```
|
|
110
140
|
|
|
111
141
|
**Rule of thumb:** if there is a signal call (with parentheses) inside `${...}`, wrap it in `() => ...`.
|
|
@@ -114,17 +144,17 @@ html`<div>${count}</div>`
|
|
|
114
144
|
|
|
115
145
|
```ts
|
|
116
146
|
// string/number → attribute
|
|
117
|
-
html`<a href=${url}>...</a
|
|
147
|
+
html`<a href=${url}>...</a>`;
|
|
118
148
|
|
|
119
149
|
// DOM property (objects, numbers without serialization, .value for input)
|
|
120
|
-
html`<input .value=${user.name}
|
|
121
|
-
html`<my-list .items=${arr}
|
|
150
|
+
html`<input .value=${user.name} />`;
|
|
151
|
+
html`<my-list .items=${arr}></my-list>`;
|
|
122
152
|
|
|
123
153
|
// boolean attribute (toggle)
|
|
124
|
-
html`<button ?disabled=${isLoading}>...</button
|
|
154
|
+
html`<button ?disabled=${isLoading}>...</button>`;
|
|
125
155
|
|
|
126
156
|
// event
|
|
127
|
-
html`<button @click=${fn}>...</button
|
|
157
|
+
html`<button @click=${fn}>...</button>`;
|
|
128
158
|
```
|
|
129
159
|
|
|
130
160
|
Common mistake: `disabled=${loading()}` — this attempts to set a **string** attribute `disabled="true"` or `disabled="false"`, which does not work correctly. **Use `?disabled=`.**
|
|
@@ -135,10 +165,19 @@ Common mistake: `disabled=${loading()}` — this attempts to set a **string** at
|
|
|
135
165
|
import { each } from "@madojs/mado";
|
|
136
166
|
|
|
137
167
|
// ❌ Works, but no keyed reconciliation → loses focus on reorder
|
|
138
|
-
html`<ul
|
|
168
|
+
html`<ul>
|
|
169
|
+
${() => items().map((t) => html`<li>${t.name}</li>`)}
|
|
170
|
+
</ul>`;
|
|
139
171
|
|
|
140
172
|
// ✅ Correct: keyed, reuses DOM nodes
|
|
141
|
-
html`<ul
|
|
173
|
+
html`<ul>
|
|
174
|
+
${() =>
|
|
175
|
+
each(
|
|
176
|
+
items(),
|
|
177
|
+
(t) => t.id,
|
|
178
|
+
(t) => html`<li>${t.name}</li>`,
|
|
179
|
+
)}
|
|
180
|
+
</ul>`;
|
|
142
181
|
```
|
|
143
182
|
|
|
144
183
|
### 8. Routing — `routes()` + `page()`
|
|
@@ -198,14 +237,20 @@ import { useForm } from "@madojs/mado";
|
|
|
198
237
|
|
|
199
238
|
const f = useForm({
|
|
200
239
|
email: { required: true, type: "email" },
|
|
201
|
-
age:
|
|
240
|
+
age: { required: true, type: "number", min: 18 },
|
|
202
241
|
});
|
|
203
242
|
|
|
204
243
|
html`
|
|
205
|
-
<form
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
244
|
+
<form
|
|
245
|
+
@submit=${f.onSubmit(async (v) => {
|
|
246
|
+
await api.save(v);
|
|
247
|
+
})}
|
|
248
|
+
>
|
|
249
|
+
<input name="email" @input=${f.onInput} @blur=${f.onBlur} />
|
|
250
|
+
${() =>
|
|
251
|
+
f.touched().email && f.errors().email
|
|
252
|
+
? html`<small>${f.errors().email}</small>`
|
|
253
|
+
: null}
|
|
209
254
|
<button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
|
|
210
255
|
</form>
|
|
211
256
|
`;
|
|
@@ -220,16 +265,23 @@ import { component, css, html } from "@madojs/mado";
|
|
|
220
265
|
|
|
221
266
|
component("x-card", () => () => html`<div><slot></slot></div>`, {
|
|
222
267
|
styles: css`
|
|
223
|
-
:host {
|
|
224
|
-
|
|
225
|
-
|
|
268
|
+
:host {
|
|
269
|
+
display: block;
|
|
270
|
+
padding: 1rem;
|
|
271
|
+
}
|
|
272
|
+
div {
|
|
273
|
+
background: var(--bg);
|
|
274
|
+
}
|
|
275
|
+
::slotted(h2) {
|
|
276
|
+
margin: 0;
|
|
277
|
+
}
|
|
226
278
|
`,
|
|
227
279
|
});
|
|
228
280
|
|
|
229
281
|
// Light DOM (without Shadow), global styles:
|
|
230
282
|
component("x-shell", () => () => html`...`, {
|
|
231
|
-
shadow: false,
|
|
232
|
-
styles: css`x-shell header { ... }`,
|
|
283
|
+
shadow: false, // disables Shadow DOM
|
|
284
|
+
styles: css`x-shell header { ... }`, // selectors are written as usual
|
|
233
285
|
});
|
|
234
286
|
```
|
|
235
287
|
|
|
@@ -314,17 +366,17 @@ When generating an app, prefer the blessed production shape from
|
|
|
314
366
|
|
|
315
367
|
## Where to find specific answers
|
|
316
368
|
|
|
317
|
-
| Question
|
|
318
|
-
|
|
319
|
-
| How does reactivity work?
|
|
320
|
-
| How are templates parsed?
|
|
321
|
-
| How does the router work?
|
|
322
|
-
| How does resource + cache work?
|
|
323
|
-
| How do forms work?
|
|
369
|
+
| Question | File |
|
|
370
|
+
| -------------------------------- | -------------------------------- |
|
|
371
|
+
| How does reactivity work? | `src/signal.ts` (283 lines) |
|
|
372
|
+
| How are templates parsed? | `src/html.ts` (1013 lines) |
|
|
373
|
+
| How does the router work? | `src/router.ts` (~530 lines) |
|
|
374
|
+
| How does resource + cache work? | `src/resource.ts` (297 lines) |
|
|
375
|
+
| How do forms work? | `src/forms.ts` (212 lines) |
|
|
324
376
|
| How should an app be structured? | `docs/en/10-app-architecture.md` |
|
|
325
|
-
| How should errors be handled?
|
|
326
|
-
| How should bake be used?
|
|
327
|
-
| When something goes wrong
|
|
377
|
+
| How should errors be handled? | `docs/en/15-error-handling.md` |
|
|
378
|
+
| How should bake be used? | `docs/en/16-bake-cookbook.md` |
|
|
379
|
+
| When something goes wrong | `docs/en/07-llm-pitfalls.md` |
|
|
328
380
|
|
|
329
381
|
## Before committing
|
|
330
382
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,140 @@
|
|
|
4
4
|
|
|
5
5
|
Nothing yet.
|
|
6
6
|
|
|
7
|
+
## 0.8.0
|
|
8
|
+
|
|
9
|
+
Core reliability fixes from "Pulse" stress-test (Round 2): Kanban 210 cards,
|
|
10
|
+
Gantt 500-task computed chain, rapid navigation, field arrays with server
|
|
11
|
+
populate. Three critical issues found and resolved.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`ctx.attr()` — MutationObserver fallback.** `observedAttributes` is read
|
|
16
|
+
once at `customElements.define()` time. Attributes registered via `ctx.attr()`
|
|
17
|
+
inside `setup()` were too late for the browser's `attributeChangedCallback`.
|
|
18
|
+
Now a single `MutationObserver` per instance covers all `ctx.attr()` attributes
|
|
19
|
+
and auto-disconnects on component removal. This was a silent failure — the
|
|
20
|
+
signal read the initial value correctly but never updated on external changes
|
|
21
|
+
like `?disabled=${() => !form.isValid()}`.
|
|
22
|
+
|
|
23
|
+
- **`useForm().array().items()` — reactive reads.** The internal `read()`
|
|
24
|
+
function used `values.peek()` (untracked) instead of `values()`. Effects and
|
|
25
|
+
templates calling `items()` never re-ran when the array changed via
|
|
26
|
+
`append()` / `replace()` / `remove()`. Field arrays populated from server
|
|
27
|
+
data showed empty lists. One-line fix: `values.peek()` → `values()`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`onDispose` in `PageContext`.** `page()` view now receives `onDispose(fn)` —
|
|
32
|
+
tied to the same lifecycle as `resource()` / `effect()` but for manual
|
|
33
|
+
subscriptions (`setInterval`, WebSocket, EventSource) that aren't auto-managed.
|
|
34
|
+
Cleaned up automatically on navigation.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export default page({
|
|
38
|
+
view: ({ onDispose }) => {
|
|
39
|
+
const id = setInterval(pollInbox, 3000);
|
|
40
|
+
onDispose(() => clearInterval(id));
|
|
41
|
+
return html`...`;
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **`ctx.attr()` regression test** confirming that external `setAttribute()`
|
|
47
|
+
after `connectedCallback` correctly updates the reactive signal via the
|
|
48
|
+
MutationObserver fallback path.
|
|
49
|
+
|
|
50
|
+
### Notes
|
|
51
|
+
|
|
52
|
+
- Core reactivity engine passes all stress checks: diamond dependency (500
|
|
53
|
+
tasks, 1 recompute per batch), rapid navigation (20× board↔issue, 0 broken
|
|
54
|
+
states), `persisted()` cross-tab sync via BroadcastChannel.
|
|
55
|
+
- Bundle size for a full Pulse app (8 pages, kanban, gantt, inbox, settings):
|
|
56
|
+
**36.7 KB gzip** / 31.8 KB brotli.
|
|
57
|
+
- 141 tests, 138 pass, 0 fail, 3 skipped (browser-only).
|
|
58
|
+
|
|
59
|
+
## 0.7.0
|
|
60
|
+
|
|
61
|
+
Reactive component props, Shadow DOM + Forms fixes, deterministic releases,
|
|
62
|
+
and `mado serve` unification. Motivated by stress-test findings in a real-world
|
|
63
|
+
admin panel (see `MADO_TEST_REPORT.md`).
|
|
64
|
+
|
|
65
|
+
### Breaking Changes
|
|
66
|
+
|
|
67
|
+
- **`mado serve` in app-mode** no longer uses the legacy `serveStaticProject()`
|
|
68
|
+
fallback. It now always goes through `server/serve.mjs`, which means
|
|
69
|
+
`--host`, `--port`, `mado.config.json` dev.proxy, and HMR all work for
|
|
70
|
+
generated apps. If you relied on the old no-HMR behaviour, pass
|
|
71
|
+
`NO_HMR=1 mado serve`.
|
|
72
|
+
|
|
73
|
+
### Added — Framework
|
|
74
|
+
|
|
75
|
+
- **`ctx.attr(name, defaultValue?)`** — reactive attribute accessor for
|
|
76
|
+
components. Returns a `Signal<string>` that auto-updates when the attribute
|
|
77
|
+
changes on the host element via `attributeChangedCallback`. No more
|
|
78
|
+
`MutationObserver` boilerplate in every component.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
component("x-badge", ({ attr }) => {
|
|
82
|
+
const variant = attr("variant", "default");
|
|
83
|
+
return () =>
|
|
84
|
+
html`<span class=${() => `badge-${variant()}`}><slot></slot></span>`;
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Attributes used with `ctx.attr()` are automatically added to
|
|
89
|
+
`observedAttributes`.
|
|
90
|
+
|
|
91
|
+
### Added — Starters
|
|
92
|
+
|
|
93
|
+
- **`apiFetcher<T>()`** in `starters/admin/src/lib/api.ts` — a fetcher for
|
|
94
|
+
`resource()` that attaches the Bearer token from memory. Use for protected
|
|
95
|
+
endpoints instead of the plain `jsonFetcher()`.
|
|
96
|
+
- **`x-button`**: now bridges Shadow DOM → Light DOM form submit via
|
|
97
|
+
`form.requestSubmit()`. Buttons inside Shadow DOM cannot natively trigger
|
|
98
|
+
`<form>` submit in Light DOM — this is now handled automatically.
|
|
99
|
+
- **`x-button`**: uses `ctx.attr("disabled")` for reactive disabled state.
|
|
100
|
+
External `?disabled=${() => !form.isValid()}` now correctly enables/disables
|
|
101
|
+
the inner button.
|
|
102
|
+
- **`x-input`**: proxies `.name` and `.value` DOM properties on the host
|
|
103
|
+
element so that `useForm().onInput` works after Shadow DOM event retargeting.
|
|
104
|
+
|
|
105
|
+
### Added — CLI / Build
|
|
106
|
+
|
|
107
|
+
- **`mado release --no-clean`**: release now cleans the entire `out/` directory
|
|
108
|
+
before building (deterministic artifacts). Pass `--no-clean` to opt out.
|
|
109
|
+
Previously stale assets, removed bake routes, and deleted public files could
|
|
110
|
+
linger in the deploy artifact.
|
|
111
|
+
- **`scripts/bake.mjs`**: `<title>` now falls back to `page.title` if
|
|
112
|
+
`head().title` is not explicitly set. Previously baked HTML kept the template
|
|
113
|
+
`<title>` from `index.html` — a critical SEO gap.
|
|
114
|
+
|
|
115
|
+
### Added — Documentation
|
|
116
|
+
|
|
117
|
+
- **`docs/en/17-shadow-dom-forms.md`** — full recipe for using `useForm()` with
|
|
118
|
+
Shadow DOM components (proxy properties, form submit bridge, ctx.attr()).
|
|
119
|
+
- **`llms.txt`**: added `ctx.attr()` section, `apiFetcher` recipe, and Shadow
|
|
120
|
+
DOM + Forms guidance.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **`x-button` in starters**: the disabled state was read once from
|
|
125
|
+
`host.hasAttribute("disabled")` in the render function — never updating when
|
|
126
|
+
the attribute changed externally. Every form using `?disabled` on `x-button`
|
|
127
|
+
was broken from the start.
|
|
128
|
+
- **`x-input` in starters**: `useForm().onInput` received `undefined` for
|
|
129
|
+
`name` and `value` because Shadow DOM retargets `e.target` from the inner
|
|
130
|
+
`<input>` to `<x-input>`, which had no DOM properties.
|
|
131
|
+
- **`jsonFetcher()`**: the admin starter relied on `jsonFetcher()` for protected
|
|
132
|
+
endpoints but it sends no Authorization header. Documented the pattern and
|
|
133
|
+
added `apiFetcher()`.
|
|
134
|
+
- **`mado serve`**: app-mode did not respect `--host`, `--port`, or config
|
|
135
|
+
settings. All flag pass-through now goes through `server/serve.mjs`.
|
|
136
|
+
- **`mado release`**: stale files from deleted bake routes or removed public
|
|
137
|
+
assets could remain in `out/`. Now cleans `out/` fully before building.
|
|
138
|
+
- **`mado bake`**: `<title>` was not set in baked HTML if only `page.title`
|
|
139
|
+
was defined (without `head().title`).
|
|
140
|
+
|
|
7
141
|
## 0.6.1
|
|
8
142
|
|
|
9
143
|
Starter & release-pipeline hardening pass. No public API breaks.
|
|
@@ -12,6 +146,7 @@ starter / bundle / bake / dev-server contour. All fixes verified by
|
|
|
12
146
|
regression tests added in this release.
|
|
13
147
|
|
|
14
148
|
### Fixed
|
|
149
|
+
|
|
15
150
|
- **Starters**: every `index.html` in `starters/{admin,crud,minimal}/` now
|
|
16
151
|
uses root-absolute paths in the importmap and entry `<script>` tag
|
|
17
152
|
(`/node_modules/@madojs/mado/...`, `/dist/main.js`). Relative paths
|
|
@@ -20,7 +155,7 @@ regression tests added in this release.
|
|
|
20
155
|
`/admin/orders/dist/main.js` → 404 → blank page). Inline comments in each
|
|
21
156
|
file explain the trap so it does not get reverted.
|
|
22
157
|
- **Starters/admin**: `pages/admin/order-detail.ts` now uses `each(items,
|
|
23
|
-
|
|
158
|
+
key, render)` instead of `o.items.map(...)`, matching `llms.txt` rule #3
|
|
24
159
|
and the framework's own pitfalls documentation.
|
|
25
160
|
- **`scripts/bundle.mjs`**: cleans stale hashed assets before every build.
|
|
26
161
|
Previously each `mado bundle` / `mado release` left old `main-<hash>.js`
|
|
@@ -57,6 +192,7 @@ regression tests added in this release.
|
|
|
57
192
|
had succeeded.
|
|
58
193
|
|
|
59
194
|
### Added
|
|
195
|
+
|
|
60
196
|
- **`mado dev` / `mado serve` flag pass-through**: `cli.mjs` now splits
|
|
61
197
|
positional arguments from flags via `splitDevArgs()`, so calls like
|
|
62
198
|
`mado dev --host 127.0.0.1`, `mado dev showcase --port 6000` and
|
|
@@ -71,7 +207,7 @@ regression tests added in this release.
|
|
|
71
207
|
- **`scripts/bake.mjs`**: fails loudly when the manifest exists but no
|
|
72
208
|
page declares `bake: { paths, data }`. The previous behaviour produced
|
|
73
209
|
`0 pages + sitemap.xml` silently with exit code 0, making `mado
|
|
74
|
-
|
|
210
|
+
release` look successful while shipping only the SPA shell with no
|
|
75
211
|
SEO-friendly HTML. The new warning prints the skipped routes, a
|
|
76
212
|
worked example bake snippet, and exits non-zero. Override with
|
|
77
213
|
`MADO_BAKE_ALLOW_EMPTY=1` for intentional SPA-only deploys.
|
|
@@ -99,12 +235,14 @@ regression tests added in this release.
|
|
|
99
235
|
response.
|
|
100
236
|
|
|
101
237
|
### Changed
|
|
238
|
+
|
|
102
239
|
- **`server/serve.mjs`** default host is now `localhost` (was implicitly
|
|
103
240
|
`0.0.0.0`). LAN exposure is opt-in via `mado dev --host 0.0.0.0` or
|
|
104
241
|
`HOST=0.0.0.0`. The startup banner shows both the bound host and a
|
|
105
242
|
click-friendly URL (`localhost` substituted when bound to `0.0.0.0`).
|
|
106
243
|
|
|
107
244
|
### Notes
|
|
245
|
+
|
|
108
246
|
- No public API changes; no migrations required. Apps that previously
|
|
109
247
|
worked on a fresh-out-of-the-box `mado init` did so only because
|
|
110
248
|
someone manually fixed the starter's relative paths and dev deps —
|
|
@@ -124,12 +262,13 @@ pipeline, core hardening and v1 recipe docs.
|
|
|
124
262
|
Phase 1 — Repo-vs-app split:
|
|
125
263
|
|
|
126
264
|
### Added
|
|
265
|
+
|
|
127
266
|
- `MADO_V1_PLAN.md` — executable tracker for the v1 push.
|
|
128
267
|
- `scripts/_config.mjs` — single configuration loader (defaults < `mado.config.json`
|
|
129
268
|
< CLI flags). Exports `loadConfig`, `detectContext`, `parseFlags`,
|
|
130
269
|
`resolveProjectPath`. [v1 F1.1]
|
|
131
270
|
- `mado release` command: one-shot `typecheck + build + bundle + bake + copy
|
|
132
|
-
|
|
271
|
+
public/ → out/` pipeline so apps have exactly one command to ship. [v1 F1.3]
|
|
133
272
|
- `mado.config.json` shipped in the `minimal` and `crud` starters with the
|
|
134
273
|
default app-mode layout (`src/routes.ts`, `index.html`, `out/`). [v1 F1.4]
|
|
135
274
|
- Tests: `test/config-loader.test.mjs`, `test/bake-cli.test.mjs` (11 + 3
|
|
@@ -137,6 +276,7 @@ Phase 1 — Repo-vs-app split:
|
|
|
137
276
|
flags, and the no-more-silent-`[object Object]` contract). [v1 F1.6]
|
|
138
277
|
|
|
139
278
|
### Changed
|
|
279
|
+
|
|
140
280
|
- `scripts/bake.mjs` now reads configuration from `mado.config.json` and
|
|
141
281
|
accepts `--entry`, `--template`, `--out`, `--base-url` flags. In app-mode
|
|
142
282
|
defaults are `src/routes.ts` + `index.html` + `out/baked/`; the
|
|
@@ -158,6 +298,7 @@ Phase 1 — Repo-vs-app split:
|
|
|
158
298
|
Phase 2 — One blessed way:
|
|
159
299
|
|
|
160
300
|
### Added
|
|
301
|
+
|
|
161
302
|
- `layout()` factory in `src/page.ts` (alias of `nested()`) plus `Guard` and
|
|
162
303
|
`GuardResult` types. Exported from the public API. [v1 F2.1 / F2.3]
|
|
163
304
|
- Route guards: nested groups and individual pages accept `guard: Guard | Guard[]`.
|
|
@@ -182,6 +323,7 @@ Phase 2 — One blessed way:
|
|
|
182
323
|
Phase 3 — Bake first-class + Release pipeline:
|
|
183
324
|
|
|
184
325
|
### Added
|
|
326
|
+
|
|
185
327
|
- `mado release` writes `_redirects` (`/* /index.html 200`) and `_headers`
|
|
186
328
|
(immutable for `/assets/*`, no-cache for HTML) into `out/` when they do not
|
|
187
329
|
exist, so Cloudflare Pages / Netlify deploys "just work". [v1 F3.7]
|
|
@@ -195,6 +337,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
195
337
|
and copied `public/` assets are all present. [v1 F3.10]
|
|
196
338
|
|
|
197
339
|
### Changed
|
|
340
|
+
|
|
198
341
|
- `scripts/preview.mjs` now reads `mado.config.json` (`build.out`, `dev.port`),
|
|
199
342
|
refuses to auto-build by default in app-mode, and asks the user to run
|
|
200
343
|
`mado release` first. Legacy auto-build is opt-in via `PREVIEW_AUTOBUILD=1`
|
|
@@ -209,6 +352,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
209
352
|
dependencies. The startup banner prints the active proxy table. [v1 F3.6]
|
|
210
353
|
|
|
211
354
|
### Deferred to v0.7
|
|
355
|
+
|
|
212
356
|
- `mado dev` does not yet serve baked routes inline. Workaround: run
|
|
213
357
|
`mado release && mado preview`. [v1 F3.2]
|
|
214
358
|
- `mado check` (bake-safety scan over `bake:` routes) is not exposed yet.
|
|
@@ -218,6 +362,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
218
362
|
Phase 4 — Core hardening:
|
|
219
363
|
|
|
220
364
|
### Added
|
|
365
|
+
|
|
221
366
|
- `computed(fn, { equals })` option to suppress subscriber reruns when an
|
|
222
367
|
observed computed recomputes to an equal value. [v1 F4.3]
|
|
223
368
|
- HTML directives: `unsafeHTML()`, `ref()`, `classMap()` and `styleMap()` are
|
|
@@ -239,6 +384,7 @@ Phase 4 — Core hardening:
|
|
|
239
384
|
its regression tests. [v1 F4.9]
|
|
240
385
|
|
|
241
386
|
### Changed
|
|
387
|
+
|
|
242
388
|
- `computed()` now releases dependency subscriptions after unobserved reads and
|
|
243
389
|
after the last subscriber is disposed, avoiding long-lived stale subscriptions
|
|
244
390
|
in the signal graph. [v1 F4.1]
|
|
@@ -257,6 +403,7 @@ Phase 4 — Core hardening:
|
|
|
257
403
|
Phase 5 — Documentation:
|
|
258
404
|
|
|
259
405
|
### Added
|
|
406
|
+
|
|
260
407
|
- `docs/en/10-app-architecture.md`, `14-testing.md`, `15-error-handling.md`
|
|
261
408
|
and `16-bake-cookbook.md` complete the v1 English recipe set. [v1 F5.1-F5.4]
|
|
262
409
|
- `AGENTS.md` now includes an "App architecture for LLM" section and `llms.txt`
|
package/dist/src/component.d.ts
CHANGED
|
@@ -15,13 +15,25 @@
|
|
|
15
15
|
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
16
|
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
17
|
*/
|
|
18
|
-
import { type Disposer } from "./signal.js";
|
|
18
|
+
import { type Signal, type Disposer } from "./signal.js";
|
|
19
19
|
import { html, type TemplateResult } from "./html.js";
|
|
20
20
|
import { type CSSResult } from "./css.js";
|
|
21
21
|
export interface ComponentContext {
|
|
22
22
|
host: HTMLElement;
|
|
23
23
|
/** Run cleanup when the component is removed. */
|
|
24
24
|
onDispose(fn: Disposer): void;
|
|
25
|
+
/**
|
|
26
|
+
* Reactive attribute accessor. Returns a Signal<string> that updates
|
|
27
|
+
* automatically whenever the attribute changes on the host element.
|
|
28
|
+
*
|
|
29
|
+
* const variant = ctx.attr("variant", "primary");
|
|
30
|
+
* return () => html`<div class=${variant()}>…</div>`;
|
|
31
|
+
*
|
|
32
|
+
* No MutationObserver boilerplate needed — the signal updates via
|
|
33
|
+
* attributeChangedCallback. The attribute name is automatically added to
|
|
34
|
+
* observedAttributes if not already listed.
|
|
35
|
+
*/
|
|
36
|
+
attr(name: string, defaultValue?: string): Signal<string>;
|
|
25
37
|
}
|
|
26
38
|
export type SetupFn = (ctx: ComponentContext) => () => TemplateResult;
|
|
27
39
|
export type StyleInput = string | CSSResult | Array<string | CSSResult>;
|
|
@@ -38,9 +50,10 @@ export interface ComponentOptions {
|
|
|
38
50
|
/**
|
|
39
51
|
* List of observed attributes.
|
|
40
52
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
53
|
+
* Attributes listed here are reflected to host[attr] via
|
|
54
|
+
* attributeChangedCallback and also power ctx.attr() reactive signals.
|
|
55
|
+
* You only need to list them here if you use the legacy property reflection
|
|
56
|
+
* pattern. ctx.attr() automatically registers any attribute it tracks.
|
|
44
57
|
*/
|
|
45
58
|
observedAttributes?: readonly string[];
|
|
46
59
|
}
|
package/dist/src/component.js
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
16
|
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
17
|
*/
|
|
18
|
-
import { effect } from "./signal.js";
|
|
18
|
+
import { signal, effect } from "./signal.js";
|
|
19
19
|
import { html, render } from "./html.js";
|
|
20
20
|
import { adopt, scopeStyles } from "./css.js";
|
|
21
|
-
import { createLifecycle, runInLifecycle } from "./lifecycle.js";
|
|
21
|
+
import { createLifecycle, runInLifecycle, } from "./lifecycle.js";
|
|
22
22
|
import { warnOnce } from "./diagnostics.js";
|
|
23
23
|
export function component(tagName, setup, options = {}) {
|
|
24
24
|
if (!tagName.includes("-")) {
|
|
@@ -36,18 +36,22 @@ export function component(tagName, setup, options = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
const useShadow = options.shadow !== false;
|
|
38
38
|
const observed = options.observedAttributes ?? [];
|
|
39
|
+
// Collect attribute names that ctx.attr() will track. These are merged with
|
|
40
|
+
// options.observedAttributes to form the final static observedAttributes.
|
|
41
|
+
const attrSignalNames = new Set(observed);
|
|
39
42
|
// Normalize styles to an array of CSSStyleSheet once.
|
|
40
43
|
// Sheets are shared across all instances — memory is not duplicated.
|
|
41
44
|
const stylesheets = normalizeStyles(options.styles, tagName, useShadow);
|
|
42
45
|
class MadoElement extends HTMLElement {
|
|
43
46
|
static get observedAttributes() {
|
|
44
|
-
return [...
|
|
47
|
+
return [...attrSignalNames];
|
|
45
48
|
}
|
|
46
49
|
#root;
|
|
47
50
|
#renderer = null;
|
|
48
51
|
#effectDispose = null;
|
|
49
52
|
#lifecycle = null;
|
|
50
53
|
#connected = false;
|
|
54
|
+
#attrSignals = new Map();
|
|
51
55
|
constructor() {
|
|
52
56
|
super();
|
|
53
57
|
this.#root = useShadow ? this.attachShadow({ mode: "open" }) : this;
|
|
@@ -69,14 +73,44 @@ export function component(tagName, setup, options = {}) {
|
|
|
69
73
|
// getCurrentLifecycle() and register its own cleanup.
|
|
70
74
|
const lifecycle = createLifecycle();
|
|
71
75
|
this.#lifecycle = lifecycle;
|
|
76
|
+
const host = this;
|
|
72
77
|
const ctx = {
|
|
73
78
|
host: this,
|
|
74
79
|
// ctx.onDispose proxies to lifecycle — the single source of truth
|
|
75
80
|
// for component cleanups (including auto-cleanup from
|
|
76
81
|
// resource(), navigator listeners, etc.).
|
|
77
82
|
onDispose: (fn) => lifecycle.onDispose(fn),
|
|
83
|
+
attr(name, defaultValue = "") {
|
|
84
|
+
let s = host.#attrSignals.get(name);
|
|
85
|
+
if (!s) {
|
|
86
|
+
s = signal(host.getAttribute(name) ?? defaultValue);
|
|
87
|
+
host.#attrSignals.set(name, s);
|
|
88
|
+
// Record for future instances (HMR) — helps observedAttributes
|
|
89
|
+
// on hot-reloaded re-defines.
|
|
90
|
+
attrSignalNames.add(name);
|
|
91
|
+
}
|
|
92
|
+
return s;
|
|
93
|
+
},
|
|
78
94
|
};
|
|
79
95
|
this.#renderer = runInLifecycle(lifecycle, () => setup(ctx));
|
|
96
|
+
// After setup(), install a single MutationObserver for all attrs
|
|
97
|
+
// registered via ctx.attr(). This is necessary because
|
|
98
|
+
// observedAttributes is read once at customElements.define() time —
|
|
99
|
+
// attrs added by ctx.attr() during setup are too late for the
|
|
100
|
+
// browser's attributeChangedCallback mechanism. The observer bridges
|
|
101
|
+
// the gap for the current and all future instances.
|
|
102
|
+
if (this.#attrSignals.size > 0) {
|
|
103
|
+
const attrNames = [...this.#attrSignals.keys()];
|
|
104
|
+
const obs = new MutationObserver((mutations) => {
|
|
105
|
+
for (const m of mutations) {
|
|
106
|
+
const s = this.#attrSignals.get(m.attributeName);
|
|
107
|
+
if (s)
|
|
108
|
+
s.set(this.getAttribute(m.attributeName) ?? "");
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
obs.observe(this, { attributes: true, attributeFilter: attrNames });
|
|
112
|
+
lifecycle.onDispose(() => obs.disconnect());
|
|
113
|
+
}
|
|
80
114
|
this.#effectDispose = effect(() => {
|
|
81
115
|
render(this.#renderer(), this.#root);
|
|
82
116
|
});
|
|
@@ -89,7 +123,12 @@ export function component(tagName, setup, options = {}) {
|
|
|
89
123
|
this.#connected = false;
|
|
90
124
|
}
|
|
91
125
|
attributeChangedCallback(name, _old, value) {
|
|
92
|
-
//
|
|
126
|
+
// Update ctx.attr() signal if it exists for this attribute.
|
|
127
|
+
const s = this.#attrSignals.get(name);
|
|
128
|
+
if (s) {
|
|
129
|
+
s.set(value ?? "");
|
|
130
|
+
}
|
|
131
|
+
// Legacy reflection: reflect attribute to property.
|
|
93
132
|
this[name] = value;
|
|
94
133
|
}
|
|
95
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"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"}
|
package/dist/src/forms.js
CHANGED
|
@@ -277,7 +277,10 @@ export function useForm(schema, options = {}) {
|
|
|
277
277
|
validate: validateAll,
|
|
278
278
|
array(name) {
|
|
279
279
|
const read = () => {
|
|
280
|
-
|
|
280
|
+
// Use values() (tracked) — not values.peek() — so that effects and
|
|
281
|
+
// computed() that call items() re-run when the array changes via
|
|
282
|
+
// append/remove/replace/etc.
|
|
283
|
+
const value = getPath(values(), name);
|
|
281
284
|
return Array.isArray(value) ? value : [];
|
|
282
285
|
};
|
|
283
286
|
const write = (items) => {
|