@madojs/mado 0.8.0 → 0.10.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 +81 -4
- package/CHANGELOG.md +202 -1
- package/README.md +184 -242
- package/ROADMAP.md +174 -79
- package/TODO.md +8 -5
- package/dist/src/component.d.ts +2 -12
- package/dist/src/component.js +30 -29
- package/dist/src/component.js.map +1 -1
- package/dist/src/diagnostics.d.ts +0 -4
- package/dist/src/diagnostics.js +1 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/forms.js +17 -0
- package/dist/src/forms.js.map +1 -1
- package/dist/src/html/bindings.js +35 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.js +60 -3
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/lifecycle.js +18 -0
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/persisted.js +43 -9
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +13 -6
- package/dist/src/resource.js +83 -16
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/manifest.d.ts +0 -3
- package/dist/src/router/manifest.js +23 -2
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/navigation.js +56 -2
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/router.d.ts +1 -1
- package/dist/src/router.js +1 -1
- package/dist/src/router.js.map +1 -1
- package/dist/src/signal.d.ts +0 -4
- package/dist/src/signal.js +56 -7
- package/dist/src/signal.js.map +1 -1
- package/docs/en/00-the-mado-way.md +23 -12
- package/docs/en/03-static-bake.md +1 -2
- package/docs/en/05-why-mado.md +78 -68
- package/docs/en/06-for-backenders.md +80 -55
- package/docs/en/07-llm-pitfalls.md +101 -0
- package/docs/en/08-llm-zero-history-test.md +5 -0
- package/docs/en/18-api-freeze-map.md +63 -0
- package/docs/en/19-reactivity-ordering.md +93 -0
- package/docs/en/20-v1-stability.md +83 -0
- package/docs/en/README.md +3 -0
- package/docs/fr/00-the-mado-way.md +25 -13
- package/docs/fr/03-static-bake.md +1 -2
- package/docs/fr/06-for-backenders.md +6 -0
- package/docs/fr/07-llm-pitfalls.md +2 -0
- package/docs/fr/08-llm-zero-history-test.md +5 -0
- package/docs/fr/18-api-freeze-map.md +63 -0
- package/docs/fr/19-reactivity-ordering.md +97 -0
- package/docs/fr/20-v1-stability.md +88 -0
- package/docs/fr/README.md +3 -0
- package/docs/ru/00-the-mado-way.md +24 -11
- package/docs/ru/03-static-bake.md +2 -3
- package/docs/ru/06-for-backenders.md +6 -0
- package/docs/ru/07-llm-pitfalls.md +2 -0
- package/docs/ru/08-llm-zero-history-test.md +5 -0
- package/docs/ru/18-api-freeze-map.md +62 -0
- package/docs/ru/19-reactivity-ordering.md +95 -0
- package/docs/ru/20-v1-stability.md +82 -0
- package/docs/ru/README.md +3 -0
- package/docs/uk/00-the-mado-way.md +3 -1
- package/docs/uk/06-for-backenders.md +5 -0
- package/docs/uk/07-llm-pitfalls.md +2 -0
- package/docs/uk/08-llm-zero-history-test.md +5 -0
- package/docs/uk/18-api-freeze-map.md +61 -0
- package/docs/uk/19-reactivity-ordering.md +95 -0
- package/docs/uk/20-v1-stability.md +83 -0
- package/docs/uk/README.md +3 -0
- package/llms.txt +63 -7
- package/package.json +10 -5
- package/scripts/bake.mjs +0 -1
- package/scripts/bundle.mjs +6 -6
- package/scripts/cli.mjs +17 -0
- package/scripts/llm-zero-history-smoke.mjs +93 -0
- package/scripts/new.mjs +1 -1
- package/scripts/package-smoke.mjs +74 -0
- package/scripts/size-budget.mjs +88 -0
- package/starters/admin/package.json +2 -2
- package/starters/crud/package.json +2 -2
- package/starters/minimal/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
## Project at a glance
|
|
11
11
|
|
|
12
|
-
- **Mado** — SPA framework
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- **Mado** — a calm browser-native SPA framework for internal tools, admin panels and business apps.
|
|
13
|
+
- Built on Web Components + signals + tagged-template `html`.
|
|
14
|
+
- Zero runtime dependencies. Generated apps use dev tooling (`typescript`,
|
|
15
|
+
`esbuild`, `linkedom`) for build/bundle/bake/release.
|
|
16
|
+
- Small TypeScript core in `src/`; production size budgets are enforced in CI.
|
|
15
17
|
|
|
16
18
|
## HARD RULES — violation = bug
|
|
17
19
|
|
|
@@ -119,7 +121,9 @@ component("x-badge", ({ attr }) => {
|
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
`ctx.attr(name, defaultValue?)` returns a `Signal<string>` that auto-updates.
|
|
122
|
-
|
|
124
|
+
Internally Mado uses a per-instance `MutationObserver` for attributes registered
|
|
125
|
+
during `setup()`. The observer auto-disconnects on component removal via
|
|
126
|
+
lifecycle cleanup.
|
|
123
127
|
|
|
124
128
|
### 5. Reactive value in template child position = function
|
|
125
129
|
|
|
@@ -180,6 +184,29 @@ html`<ul>
|
|
|
180
184
|
</ul>`;
|
|
181
185
|
```
|
|
182
186
|
|
|
187
|
+
### 7b. Parser hard errors
|
|
188
|
+
|
|
189
|
+
Mado fails loudly for template shapes that cannot be represented safely.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// ❌ NO — slots inside RAW_TEXT elements are a parser error
|
|
193
|
+
html`<textarea>${draft}</textarea>`;
|
|
194
|
+
html`<title>${title}</title>`;
|
|
195
|
+
|
|
196
|
+
// ✅ YES — use properties or page/head APIs
|
|
197
|
+
html`<textarea .value=${draft}></textarea>`;
|
|
198
|
+
page({ title: ({ id }) => `User ${id}`, view: () => html`<main></main>` });
|
|
199
|
+
|
|
200
|
+
// ❌ NO — nested SVG-only templates lose namespace context
|
|
201
|
+
html`<svg>${html`<path d=${d}></path>`}</svg>`;
|
|
202
|
+
|
|
203
|
+
// ✅ YES — keep the SVG in one template or in its own component
|
|
204
|
+
html`<svg viewBox="0 0 10 10"><path d=${d}></path></svg>`;
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
No dynamic `${...}` child slots inside `<script>`, `<style>`, `<textarea>`,
|
|
208
|
+
or `<title>`. Keep SVG internals in one `<svg>...</svg>` template.
|
|
209
|
+
|
|
183
210
|
### 8. Routing — `routes()` + `page()`
|
|
184
211
|
|
|
185
212
|
```ts
|
|
@@ -202,6 +229,31 @@ export default page<{ id: string }>({
|
|
|
202
229
|
- Each page is a **separate file** in `pages/` with `export default page({...})`.
|
|
203
230
|
- Import via `() => import("./pages/foo.js")` — this enables code-splitting via ESM.
|
|
204
231
|
- Programmatic navigation: `import { navigate } from "@madojs/mado"; navigate("/users/42")`.
|
|
232
|
+
- Layouts are declared in the route manifest via `layout()`. Treat
|
|
233
|
+
`layout.view({ child })` as a stateless wrapper around `${child}` and shared
|
|
234
|
+
chrome. Put per-page state in pages/components/resources, not in layout view
|
|
235
|
+
locals that depend on route identity.
|
|
236
|
+
- **`onDispose`** — cleanup hook for page views. Use for `setInterval`, `WebSocket`, `EventSource`. `resource()` and `effect()` are auto-cleaned.
|
|
237
|
+
- **`untracked()`** — required when reading signals inside async functions called synchronously from `view()`. Without it, the signal subscribes the router's render effect → infinite loop.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
// page with polling and cleanup
|
|
241
|
+
import { page, html, signal, untracked } from "@madojs/mado";
|
|
242
|
+
export default page({
|
|
243
|
+
view: ({ onDispose }) => {
|
|
244
|
+
const data = signal(null);
|
|
245
|
+
const poll = async () => {
|
|
246
|
+
// untracked: don't subscribe the router's render effect
|
|
247
|
+
const res = await fetch("/api/status");
|
|
248
|
+
data.set(await res.json());
|
|
249
|
+
};
|
|
250
|
+
const id = setInterval(poll, 5000);
|
|
251
|
+
onDispose(() => clearInterval(id)); // ← cleaned up on navigation
|
|
252
|
+
poll(); // initial call
|
|
253
|
+
return html`<div>${() => JSON.stringify(data())}</div>`;
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
205
257
|
|
|
206
258
|
### 9. Data fetching — `resource()` / `mutation()`
|
|
207
259
|
|
|
@@ -226,6 +278,14 @@ const save = mutation<User, User>(
|
|
|
226
278
|
await save.run(newUser);
|
|
227
279
|
```
|
|
228
280
|
|
|
281
|
+
- A resource key is the cache identity. Same key means shared cache and deduped
|
|
282
|
+
in-flight request; use distinct keys for distinct data or auth scope.
|
|
283
|
+
- `mutation().run()` is concurrent by default. `loading()` stays true while any
|
|
284
|
+
run is in flight. Use `{ abortPrevious: true }` only for search-as-you-type or
|
|
285
|
+
"latest request wins" flows.
|
|
286
|
+
- Invalidation is best-effort after a successful mutation; invalidation errors
|
|
287
|
+
are logged but do not turn the mutation itself into a failure.
|
|
288
|
+
|
|
229
289
|
### 10. Forms — `useForm()`
|
|
230
290
|
|
|
231
291
|
```ts
|
|
@@ -325,10 +385,24 @@ Rules:
|
|
|
325
385
|
- Tiny leaf components used everywhere → importing in `main.ts` is acceptable.
|
|
326
386
|
- Do **not** bulk-import every component "just in case".
|
|
327
387
|
|
|
388
|
+
### 14. Bake — meta shell, not SSR/SSG runtime
|
|
389
|
+
|
|
390
|
+
`mado bake` is a static meta-shell/prerender pass for SEO and first paint. It is
|
|
391
|
+
not SSR with hydration and not a Next-style SSG runtime.
|
|
392
|
+
|
|
393
|
+
- Baked HTML must be deterministic from `params`, `bake.data`, and plain values.
|
|
394
|
+
- Do not rely on browser-only effects, timers, relative `fetch`, or keyed
|
|
395
|
+
runtime directives such as `each()` during bake.
|
|
396
|
+
- Use `page({ head, bake })` for meta/JSON-LD/sitemap data; render dynamic,
|
|
397
|
+
personalized, real-time, or auth-dependent content in the client SPA.
|
|
398
|
+
|
|
328
399
|
## SOFT GUIDELINES — recommended, but not critical
|
|
329
400
|
|
|
330
401
|
- **TypeScript strict.** Use `noUncheckedIndexedAccess`-aware code (with `!` or a type guard).
|
|
331
402
|
- **Import using `.js`** (not `.ts`) — this is required by ES modules in the browser: `import { foo } from "./bar.js"`.
|
|
403
|
+
- **Public imports only.** App code imports from `@madojs/mado` and, when
|
|
404
|
+
needed, side-effect `@madojs/mado/devtools.js`. Other package subpaths and
|
|
405
|
+
`dist/src/*` are internal.
|
|
332
406
|
- **One file = one responsibility.** Don't put 5 components in one file "because they're all small".
|
|
333
407
|
- **Do not add runtime dependencies** (`npm install` in `dependencies`). This violates the framework's principle.
|
|
334
408
|
- **JSDoc on public functions** is required. Comments explain "why", not "what".
|
|
@@ -376,6 +450,9 @@ When generating an app, prefer the blessed production shape from
|
|
|
376
450
|
| How should an app be structured? | `docs/en/10-app-architecture.md` |
|
|
377
451
|
| How should errors be handled? | `docs/en/15-error-handling.md` |
|
|
378
452
|
| How should bake be used? | `docs/en/16-bake-cookbook.md` |
|
|
453
|
+
| What API is stable? | `docs/en/18-api-freeze-map.md` |
|
|
454
|
+
| What ordering is guaranteed? | `docs/en/19-reactivity-ordering.md` |
|
|
455
|
+
| What does v1 stability mean? | `docs/en/20-v1-stability.md` |
|
|
379
456
|
| When something goes wrong | `docs/en/07-llm-pitfalls.md` |
|
|
380
457
|
|
|
381
458
|
## Before committing
|
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,208 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 0.10.0 - 2026-06-12
|
|
6
|
+
|
|
7
|
+
Surface-cleanup and API-lock release from the v1 tracker Phase B: legacy public
|
|
8
|
+
surface is removed, package exports are closed, docs/LLM guidance match the real
|
|
9
|
+
API, and CI now protects package, size, release and LLM-smoke contracts.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Component attribute changes no longer clobber host properties (B1).**
|
|
14
|
+
Legacy `observedAttributes` reflection used to write `this[name] = value`
|
|
15
|
+
from `attributeChangedCallback`, overwriting `.prop=` bindings and custom
|
|
16
|
+
host state such as `.value`. Attribute changes now update only `ctx.attr()`
|
|
17
|
+
signals; `ctx.attr()` is the canonical reactive attribute API.
|
|
18
|
+
|
|
19
|
+
- **Removed `component(..., { observedAttributes })` (B2).** `ctx.attr()` is now
|
|
20
|
+
the single reactive-attribute API. It installs a per-instance observer for the
|
|
21
|
+
attributes used during setup, so component options no longer carry a second
|
|
22
|
+
attribute mechanism.
|
|
23
|
+
|
|
24
|
+
- **Package exports are explicit (B3).** The npm package no longer exports
|
|
25
|
+
`./*`. Public imports are limited to `@madojs/mado` and
|
|
26
|
+
`@madojs/mado/devtools.js`; internal files such as `lifecycle.js` are no
|
|
27
|
+
longer package subpaths.
|
|
28
|
+
|
|
29
|
+
- **Internal `_testHooks` are stripped from declarations (B4).** Runtime hooks
|
|
30
|
+
remain available to the repository's own tests, but emitted `.d.ts` files no
|
|
31
|
+
longer advertise them as public API; the router barrel also no longer
|
|
32
|
+
re-exports manifest test hooks.
|
|
33
|
+
|
|
34
|
+
- **README now states the explicit No list (B5).** The project boundary is
|
|
35
|
+
documented: no SSR hydration, template compiler, separate store library,
|
|
36
|
+
Suspense, router plugin system, built-in i18n/animation/virtual-scroll
|
|
37
|
+
primitives, or non-evergreen browser support. Browser baseline is pinned to
|
|
38
|
+
Baseline 2023.
|
|
39
|
+
|
|
40
|
+
- **`resource()` dedupes in-flight requests by key (B6).** Concurrent resources
|
|
41
|
+
with the same key now share one fetch. If the same in-flight key is used with
|
|
42
|
+
different fetcher functions, Mado warns once because the cache key is likely
|
|
43
|
+
too broad. README/docs now spell out resource key discipline.
|
|
44
|
+
|
|
45
|
+
- **`each()` warns on duplicate keys (B7).** The positional-suffix fallback is
|
|
46
|
+
preserved so every item still renders, but duplicate keys now produce a
|
|
47
|
+
`warnOnce` diagnostic because they are almost always a data bug.
|
|
48
|
+
|
|
49
|
+
- **API freeze map published (B8).** `docs/en/18-api-freeze-map.md` now defines
|
|
50
|
+
the stable root API, the public devtools subpath, and the internal modules
|
|
51
|
+
that are not protected by SemVer.
|
|
52
|
+
|
|
53
|
+
- **Reactivity ordering contract published (B9).** `docs/en/19-reactivity-ordering.md`
|
|
54
|
+
documents signal/effect/batch ordering, nested-template update reuse and
|
|
55
|
+
component teardown timing. A new invariant test pins nested-batch effect
|
|
56
|
+
scheduling.
|
|
57
|
+
|
|
58
|
+
- **v1 stability contract published (B10).** `docs/en/20-v1-stability.md`
|
|
59
|
+
defines what SemVer protects after v1 and what remains internal or
|
|
60
|
+
implementation-specific, including bundle byte output and internal module
|
|
61
|
+
layout.
|
|
62
|
+
|
|
63
|
+
- **Agent and LLM guidance synced to the real API (B11).** `AGENTS.md`,
|
|
64
|
+
`.clinerules`, `.cursorrules` and `llms.txt` now document C7 parser hard
|
|
65
|
+
errors, C6 mutation concurrency, stateless `layout.view` wrappers, public
|
|
66
|
+
package imports and `bake` as a static meta-shell rather than SSR/SSG runtime.
|
|
67
|
+
|
|
68
|
+
- **`mado init` writes required dev dependencies (B12).** Generated apps now
|
|
69
|
+
include `typescript`, `esbuild` and `linkedom` as dev dependencies, sourced
|
|
70
|
+
from the package's own tool versions. README/agent wording now says zero
|
|
71
|
+
**runtime** dependencies instead of implying no build tooling exists.
|
|
72
|
+
|
|
73
|
+
- **Size budgets are enforced in CI (B13).** `npm run size` bundles the full
|
|
74
|
+
public API and the showcase app, then fails on gzip regressions above the
|
|
75
|
+
current budgets: public API < 16 KiB, showcase app < 42 KiB.
|
|
76
|
+
|
|
77
|
+
- **Published tarball smoke test added (B14).** `npm run package:smoke` packs
|
|
78
|
+
the package, installs the tarball in a temp project, checks that public
|
|
79
|
+
imports work and `@madojs/mado/lifecycle.js` is blocked with
|
|
80
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`, then scaffolds a clean app and runs
|
|
81
|
+
`mado release`.
|
|
82
|
+
|
|
83
|
+
- **Release output is deterministic (B15).** `bake-stamp` was removed from baked
|
|
84
|
+
HTML, and the release pipeline test now runs `mado release` twice on the same
|
|
85
|
+
input and compares the entire `out/` tree byte-for-byte.
|
|
86
|
+
|
|
87
|
+
- **LLM zero-history test is a CI smoke (B16).** `npm run llm:smoke` validates
|
|
88
|
+
that `llms.txt` retains the key Mado guidance, checks the committed
|
|
89
|
+
`examples/tickets` artifact for required APIs and forbidden React-shaped
|
|
90
|
+
patterns, then builds and runs the tickets smoke test.
|
|
91
|
+
|
|
92
|
+
- **Localized docs synced for Phase B (B17).** RU/FR/UK docs now include the
|
|
93
|
+
API freeze map, reactivity ordering and v1 stability pages, plus the Phase B
|
|
94
|
+
updates for resource key discipline, deterministic bake metadata and the
|
|
95
|
+
LLM-smoke CI proxy.
|
|
96
|
+
|
|
97
|
+
## 0.9.0 - 2026-06-12
|
|
98
|
+
|
|
99
|
+
Correctness release from the v1 tracker Phase A: C1-C8 are closed with focused
|
|
100
|
+
regression tests.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **`mutation().run()` is concurrent by default (C6).** Previously a `run()`
|
|
105
|
+
aborted any previous in-flight run, so two quick submits of different entities
|
|
106
|
+
through one mutation cancelled the first POST client-side — its `invalidates`
|
|
107
|
+
never fired even though the server had likely applied it. Mutations are now
|
|
108
|
+
concurrent: each `run()` has its own `AbortController`, `loading` is an
|
|
109
|
+
in-flight counter (true until the last run settles), and aborting the previous
|
|
110
|
+
run is opt-in via `mutation(fetcher, { abortPrevious: true })` for
|
|
111
|
+
search-as-you-type. `reset()` aborts all in-flight runs. **Behavioural change**
|
|
112
|
+
(done before the v1 API freeze). Regression test:
|
|
113
|
+
`test/mutation-concurrent.test.mjs`.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- **Lifecycle/router defect pack is closed (C8).** `onDispose()` registered
|
|
118
|
+
after a lifecycle was already disposed now runs immediately instead of being
|
|
119
|
+
dropped. SPA link interception now respects `target="_blank"` and `download`.
|
|
120
|
+
Same-path `#hash` navigation scrolls to its anchor instead of being swallowed
|
|
121
|
+
by signal deduplication. Guard redirects now have a per-tick loop detector
|
|
122
|
+
that reports and halts mutually-redirecting routes. Regression test:
|
|
123
|
+
`test/lifecycle-router-pack.test.mjs`.
|
|
124
|
+
|
|
125
|
+
- **Parser fails loudly instead of silently dropping bindings (C7).** A `${}`
|
|
126
|
+
slot inside a RAW_TEXT element (`<textarea>`/`<title>`/`<style>`/`<script>`)
|
|
127
|
+
was silently ignored — an LLM writing `<textarea>${draft}</textarea>` got
|
|
128
|
+
neither an error nor a render. And a nested `html\`<path …>\`` for `<svg>` was
|
|
129
|
+
parsed in the HTML namespace, producing an invisible element. The parser now
|
|
130
|
+
throws a clear, fixable error in both cases (the RAW_TEXT message points at
|
|
131
|
+
`.value=`; the SVG message says to keep SVG content in one `<svg>…</svg>`
|
|
132
|
+
template). A self-contained `<svg>` still works. Regression test:
|
|
133
|
+
`test/html-rawtext-svg.test.mjs`.
|
|
134
|
+
|
|
135
|
+
- **Forms: stale async validation no longer lands on a shifted field-array row
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
(C5).** `useForm().array()` mutations (`remove`/`move`/`replace`/…) shift
|
|
139
|
+
indices, but an in-flight `validateAsync` for e.g. `items.2.title` still
|
|
140
|
+
matched its per-path generation guard and wrote its result onto a row that had
|
|
141
|
+
moved — a red error "jumping" onto a neighbouring valid row after a delete.
|
|
142
|
+
Array writes now bump the validation generation for every in-flight path under
|
|
143
|
+
the array prefix, so stale results are discarded. Row identity remains
|
|
144
|
+
positional. Regression test: `test/forms-array-stale-async.test.mjs`.
|
|
145
|
+
|
|
146
|
+
- **`computed({ equals })` no longer breaks `batch()` atomicity (C4).** An
|
|
147
|
+
|
|
148
|
+
observed `equals`-computed recomputed eagerly inside `set()`, so during
|
|
149
|
+
`batch(() => { x.set(2); y.set(2) })` a computed reading both `x` and `y` ran
|
|
150
|
+
on half-applied state `(new x, old y)` — observing an inconsistent snapshot,
|
|
151
|
+
potentially notifying with it, and running once per `set()`. An observed
|
|
152
|
+
`equals`-computed invalidated inside a batch now defers its recompute+compare
|
|
153
|
+
to a queue drained at the end of the outermost `batch()`, so it runs once on
|
|
154
|
+
fully-applied state. Behaviour outside a batch is unchanged. Regression test:
|
|
155
|
+
`test/signal-batch-equals.test.mjs`.
|
|
156
|
+
|
|
157
|
+
- **`update()` reuses nested templates instead of recreating them (C3).** A
|
|
158
|
+
|
|
159
|
+
renderer returning a nested `html\`\`` (e.g. a conditional form block) rebuilt
|
|
160
|
+
its entire subtree on every change of any signal it read, because `renderChild`
|
|
161
|
+
always did clear + re-instantiate for a single `TemplateResult` — unlike
|
|
162
|
+
`each()` and `render()`, which compare `_strings` and patch in place. Focus and
|
|
163
|
+
`<input>` values inside such blocks were lost and listeners re-attached.
|
|
164
|
+
`renderChild` now reuses the existing instance via `update()` when the new
|
|
165
|
+
value is a single `TemplateResult` with matching `_strings`, preserving DOM
|
|
166
|
+
identity; a structurally different template still rebuilds. Regression test:
|
|
167
|
+
`test/update-nested-reuse.test.mjs`.
|
|
168
|
+
|
|
169
|
+
- **`persisted()` cross-tab sync no longer ping-pongs, and `destroy()` is
|
|
170
|
+
|
|
171
|
+
complete (C2).** For object/array values, every cross-tab message produced a
|
|
172
|
+
new structured-clone identity, so the publisher effect re-broadcast each
|
|
173
|
+
received value forever (a single change generated 80+ messages in the
|
|
174
|
+
regression test). Cross-tab values are now echo-suppressed by their serialized
|
|
175
|
+
form, so an arriving value is never re-published. `destroy()` previously only
|
|
176
|
+
closed the channel and cleared storage while leaving the write/publish effects
|
|
177
|
+
alive — so the next `set()` re-created the key; it now disposes both effects,
|
|
178
|
+
clears the debounce timer, and marks the signal inert. `persisted()` also
|
|
179
|
+
registers `destroy()` with the active component/page lifecycle, so a persisted
|
|
180
|
+
signal created inside `setup()` no longer leaks. Regression test:
|
|
181
|
+
`test/persisted-crosstab.test.mjs`.
|
|
182
|
+
|
|
183
|
+
- **`each()` reorder no longer destroys custom-element state (C1).** Moving a
|
|
184
|
+
connected node (which keyed `each()` does via `insertBefore`) fires
|
|
185
|
+
`disconnectedCallback` → `connectedCallback` synchronously, and the old
|
|
186
|
+
|
|
187
|
+
`disconnectedCallback` tore the component down immediately — re-running
|
|
188
|
+
`setup()` and wiping every signal/resource/timer plus focus and `<input>`
|
|
189
|
+
values on a shuffle. Teardown is now deferred to a microtask and cancelled if
|
|
190
|
+
the element is re-inserted in the same tick, so a keyed move preserves state;
|
|
191
|
+
a genuine removal still disposes. `each()` also uses `Node.prototype.moveBefore`
|
|
192
|
+
(Chrome 133+) when available, which relocates connected nodes without firing
|
|
193
|
+
lifecycle callbacks at all. Regression test: `test/each-component-state.test.mjs`.
|
|
194
|
+
|
|
195
|
+
### Docs / planning
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
- **Road to v1 re-sequenced around correctness.** Added
|
|
199
|
+
[`MADO_V1_TRACKER.md`](./MADO_V1_TRACKER.md), the active task-by-task tracker
|
|
200
|
+
derived from the `FABLE_REPORT.md` audit: phase A `v0.9` (correctness fixes
|
|
201
|
+
C1–C8, TDD: reproducing test → fix), phase B `v0.10` (surface cleanup, explicit
|
|
202
|
+
exports map, API freeze map), phase C `v1.0-rc` (live demo + external
|
|
203
|
+
dogfooding), phase D `v1.0` (freeze). `ROADMAP.md` now gates its
|
|
204
|
+
product-surface milestones behind a new "Milestone 0"; `TODO.md` points at the
|
|
205
|
+
tracker and drops items it now owns (exports policy, size reporting).
|
|
206
|
+
|
|
6
207
|
|
|
7
208
|
## 0.8.0
|
|
8
209
|
|