@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.
Files changed (83) hide show
  1. package/AGENTS.md +81 -4
  2. package/CHANGELOG.md +202 -1
  3. package/README.md +184 -242
  4. package/ROADMAP.md +174 -79
  5. package/TODO.md +8 -5
  6. package/dist/src/component.d.ts +2 -12
  7. package/dist/src/component.js +30 -29
  8. package/dist/src/component.js.map +1 -1
  9. package/dist/src/diagnostics.d.ts +0 -4
  10. package/dist/src/diagnostics.js +1 -0
  11. package/dist/src/diagnostics.js.map +1 -1
  12. package/dist/src/forms.js +17 -0
  13. package/dist/src/forms.js.map +1 -1
  14. package/dist/src/html/bindings.js +35 -3
  15. package/dist/src/html/bindings.js.map +1 -1
  16. package/dist/src/html/parser.js +60 -3
  17. package/dist/src/html/parser.js.map +1 -1
  18. package/dist/src/lifecycle.js +18 -0
  19. package/dist/src/lifecycle.js.map +1 -1
  20. package/dist/src/persisted.js +43 -9
  21. package/dist/src/persisted.js.map +1 -1
  22. package/dist/src/resource.d.ts +13 -6
  23. package/dist/src/resource.js +83 -16
  24. package/dist/src/resource.js.map +1 -1
  25. package/dist/src/router/manifest.d.ts +0 -3
  26. package/dist/src/router/manifest.js +23 -2
  27. package/dist/src/router/manifest.js.map +1 -1
  28. package/dist/src/router/navigation.js +56 -2
  29. package/dist/src/router/navigation.js.map +1 -1
  30. package/dist/src/router.d.ts +1 -1
  31. package/dist/src/router.js +1 -1
  32. package/dist/src/router.js.map +1 -1
  33. package/dist/src/signal.d.ts +0 -4
  34. package/dist/src/signal.js +56 -7
  35. package/dist/src/signal.js.map +1 -1
  36. package/docs/en/00-the-mado-way.md +23 -12
  37. package/docs/en/03-static-bake.md +1 -2
  38. package/docs/en/05-why-mado.md +78 -68
  39. package/docs/en/06-for-backenders.md +80 -55
  40. package/docs/en/07-llm-pitfalls.md +101 -0
  41. package/docs/en/08-llm-zero-history-test.md +5 -0
  42. package/docs/en/18-api-freeze-map.md +63 -0
  43. package/docs/en/19-reactivity-ordering.md +93 -0
  44. package/docs/en/20-v1-stability.md +83 -0
  45. package/docs/en/README.md +3 -0
  46. package/docs/fr/00-the-mado-way.md +25 -13
  47. package/docs/fr/03-static-bake.md +1 -2
  48. package/docs/fr/06-for-backenders.md +6 -0
  49. package/docs/fr/07-llm-pitfalls.md +2 -0
  50. package/docs/fr/08-llm-zero-history-test.md +5 -0
  51. package/docs/fr/18-api-freeze-map.md +63 -0
  52. package/docs/fr/19-reactivity-ordering.md +97 -0
  53. package/docs/fr/20-v1-stability.md +88 -0
  54. package/docs/fr/README.md +3 -0
  55. package/docs/ru/00-the-mado-way.md +24 -11
  56. package/docs/ru/03-static-bake.md +2 -3
  57. package/docs/ru/06-for-backenders.md +6 -0
  58. package/docs/ru/07-llm-pitfalls.md +2 -0
  59. package/docs/ru/08-llm-zero-history-test.md +5 -0
  60. package/docs/ru/18-api-freeze-map.md +62 -0
  61. package/docs/ru/19-reactivity-ordering.md +95 -0
  62. package/docs/ru/20-v1-stability.md +82 -0
  63. package/docs/ru/README.md +3 -0
  64. package/docs/uk/00-the-mado-way.md +3 -1
  65. package/docs/uk/06-for-backenders.md +5 -0
  66. package/docs/uk/07-llm-pitfalls.md +2 -0
  67. package/docs/uk/08-llm-zero-history-test.md +5 -0
  68. package/docs/uk/18-api-freeze-map.md +61 -0
  69. package/docs/uk/19-reactivity-ordering.md +95 -0
  70. package/docs/uk/20-v1-stability.md +83 -0
  71. package/docs/uk/README.md +3 -0
  72. package/llms.txt +63 -7
  73. package/package.json +10 -5
  74. package/scripts/bake.mjs +0 -1
  75. package/scripts/bundle.mjs +6 -6
  76. package/scripts/cli.mjs +17 -0
  77. package/scripts/llm-zero-history-smoke.mjs +93 -0
  78. package/scripts/new.mjs +1 -1
  79. package/scripts/package-smoke.mjs +74 -0
  80. package/scripts/size-budget.mjs +88 -0
  81. package/starters/admin/package.json +2 -2
  82. package/starters/crud/package.json +2 -2
  83. package/starters/minimal/package.json +2 -2
@@ -598,6 +598,105 @@ See [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md) for the full pattern.
598
598
 
599
599
  ---
600
600
 
601
+ ## Pitfall #23: signal reads in async functions called from `view()` create effect cycles
602
+
603
+ **Symptom:** `[mado] effect cycle detected: subscriber re-ran more than 100 times in one flush.`
604
+
605
+ The router calls `page.view()` inside a reactive effect. Any signal read
606
+ **synchronously** during `view()` subscribes the router's render effect. If that
607
+ signal is then written (e.g. `loading.set(true)`) — the router re-runs `view()`,
608
+ which reads the signal again → infinite loop.
609
+
610
+ ```ts
611
+ // ❌ INFINITE LOOP — loadMore reads signals inside the router's effect
612
+ export default page({
613
+ view: () => {
614
+ const cursor = signal<string | null>("start");
615
+ const loading = signal(false);
616
+
617
+ const loadMore = async () => {
618
+ if (cursor() === null || loading()) return; // ← subscribes render effect!
619
+ loading.set(true); // ← re-triggers render → loadMore() → ∞
620
+ const res = await fetch(`/api/items?cursor=${cursor()}`);
621
+ // ...
622
+ };
623
+
624
+ loadMore(); // called synchronously during view()
625
+ return html`...`;
626
+ },
627
+ });
628
+
629
+ // ✅ CORRECT — wrap signal reads in untracked()
630
+ import { untracked } from "@madojs/mado";
631
+
632
+ export default page({
633
+ view: () => {
634
+ const cursor = signal<string | null>("start");
635
+ const loading = signal(false);
636
+
637
+ const loadMore = async () => {
638
+ const c = untracked(() => cursor());
639
+ if (c === null || untracked(() => loading())) return;
640
+ loading.set(true);
641
+ const res = await fetch(`/api/items?cursor=${c}`);
642
+ // ...
643
+ };
644
+
645
+ loadMore();
646
+ return html`...`;
647
+ },
648
+ });
649
+ ```
650
+
651
+ **Rule:** Any function that reads signals AND is called synchronously during
652
+ `view()` initialization must use `untracked()` for those reads. This includes:
653
+
654
+ - Data fetching / loadMore functions
655
+ - IntersectionObserver callbacks set up during init
656
+ - Timer/polling setup functions that check state
657
+
658
+ Signals read inside the **returned template** (`html\`...\``) are fine — they are
659
+ wrapped in a child-binding function `${() => ...}` which creates its own effect.
660
+
661
+ ---
662
+
663
+ ## Pitfall #24: `setInterval` / manual subscriptions in `page()` view without cleanup
664
+
665
+ **Symptom:** After navigating away, timers/subscriptions keep running (zombie intervals,
666
+ server logs show polling requests from pages the user already left).
667
+
668
+ ```ts
669
+ // ❌ ZOMBIE — interval survives navigation
670
+ export default page({
671
+ view: () => {
672
+ const tick = signal(0);
673
+ setInterval(() => tick.update((n) => n + 1), 3000); // never cleaned up!
674
+ return html`<div>${tick}</div>`;
675
+ },
676
+ });
677
+
678
+ // ✅ CORRECT — use onDispose for cleanup
679
+ export default page({
680
+ view: ({ onDispose }) => {
681
+ const tick = signal(0);
682
+ const id = setInterval(() => tick.update((n) => n + 1), 3000);
683
+ onDispose(() => clearInterval(id));
684
+ return html`<div>${tick}</div>`;
685
+ },
686
+ });
687
+ ```
688
+
689
+ **Note:** `resource()` and `effect()` created inside `view()` are automatically
690
+ cleaned up on navigation (they register with the page lifecycle). Only raw
691
+ browser APIs need explicit `onDispose()`:
692
+
693
+ - `setInterval` / `setTimeout`
694
+ - `addEventListener` (on window/document)
695
+ - `WebSocket` / `EventSource`
696
+ - `IntersectionObserver` / `ResizeObserver`
697
+
698
+ ---
699
+
601
700
  ## Cheat-sheet for AI
602
701
 
603
702
  | If you want to do… | Correct in Mado |
@@ -619,5 +718,7 @@ See [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md) for the full pattern.
619
718
  | `@customElement('x')` | `component('x-name', setup)` |
620
719
  | `host.getAttribute('x')` in render | `ctx.attr('x', default)` (reactive) |
621
720
  | `jsonFetcher()` with auth | `apiFetcher()` (attaches Bearer token) |
721
+ | `setInterval` in page view | `onDispose(() => clearInterval(id))` |
722
+ | signal read in view() async init | `untracked(() => cursor())` |
622
723
 
623
724
  If something doesn't fit this list — open `src/` and **read 500 lines**. Seriously. Mado is intentionally small to be readable.
@@ -50,6 +50,11 @@ Look for these after implementation:
50
50
  The current `examples/tickets` implementation did not require new public APIs or
51
51
  runtime dependencies.
52
52
 
53
+ CI runs `npm run llm:smoke` as a deterministic proxy for this task: it verifies
54
+ that `llms.txt` still contains the key guidance, checks the committed
55
+ `examples/tickets` artifact against the required Mado API surface and failure
56
+ patterns, then builds and runs `test/tickets-smoke.test.mjs`.
57
+
53
58
  The main documentation pressure point remains lifecycle: older examples can make
54
59
  it look acceptable to create `resource()` directly in `page.view()`. The tickets
55
60
  example uses page-level wrapper components instead, so resources are registered
@@ -0,0 +1,63 @@
1
+ # API freeze map
2
+
3
+ > What is public, what is internal, and what SemVer will protect at v1.
4
+
5
+ Mado's v1 contract is intentionally small. Import application code from the
6
+ package root:
7
+
8
+ ```ts
9
+ import { component, html, resource, routes, signal } from "@madojs/mado";
10
+ ```
11
+
12
+ The only public package subpath is the side-effect devtools module:
13
+
14
+ ```ts
15
+ import "@madojs/mado/devtools.js";
16
+ ```
17
+
18
+ Everything else under `dist/src/` is an implementation detail, even when it is
19
+ visible in the repository.
20
+
21
+ ## Stable public API
22
+
23
+ These names are public and protected by SemVer once v1 ships:
24
+
25
+ - Reactivity: `signal`, `computed`, `effect`, `untracked`, `batch`,
26
+ `flushSync`.
27
+ - Templates and directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
28
+ `ref`, `classMap`, `styleMap`.
29
+ - Components and CSS: `component`, `css`, `cssVars`.
30
+ - Routing and pages: `routes`, `router`, `page`, `layout`, `nested`,
31
+ `navigate`, `queryParam`, `prefetchPath`.
32
+ - Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
+ - Forms: `useForm`.
34
+ - Head and persistence: `applyHead`, `persisted`.
35
+ - Context: `createContext`, `provide`, `inject`.
36
+ - Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
37
+ `getCurrentLifecycle`.
38
+ - Public TypeScript types exported from `@madojs/mado`.
39
+
40
+ ## Internal or unstable
41
+
42
+ These are not public API:
43
+
44
+ - Package subpaths other than `@madojs/mado` and
45
+ `@madojs/mado/devtools.js`.
46
+ - Template parser/binding internals such as `html/parser.js`,
47
+ `html/bindings.js`, `ChildState`, and `EachEntry`.
48
+ - Router implementation modules such as `router/match.js`,
49
+ `router/navigation.js`, and `router/manifest.js`.
50
+ - Diagnostics internals and all `_testHooks`.
51
+ - Exact generated bundle text, chunk names, and internal file layout.
52
+
53
+ The repository's tests may import internal files through relative `dist/` paths.
54
+ Application code should not.
55
+
56
+ ## What can change
57
+
58
+ Patch and minor releases may add new root exports, options, diagnostics, docs,
59
+ or starter files. They may also change internals, emitted bundle shape, and
60
+ implementation details as long as the stable API and documented behavior remain
61
+ compatible.
62
+
63
+ Breaking changes to the stable API require a major version.
@@ -0,0 +1,93 @@
1
+ # Reactivity ordering
2
+
3
+ > The small set of ordering guarantees Mado treats as public behaviour.
4
+
5
+ Mado's reactivity is synchronous for reads and scheduled for side effects. The
6
+ goal is boring, predictable UI updates rather than a large scheduling model.
7
+
8
+ ## Signals
9
+
10
+ `signal(value)` returns a getter function. Calling `set(next)` changes the value
11
+ immediately unless `Object.is(previous, next)` is true.
12
+
13
+ ```ts
14
+ const count = signal(0);
15
+ count.set(1);
16
+ count(); // 1, immediately
17
+ ```
18
+
19
+ Computed values are marked before effects run, so an effect that reads a
20
+ computed value observes the current dependencies, not stale cached data.
21
+
22
+ ## Effects
23
+
24
+ `effect(fn)` runs once immediately. Later dependency changes schedule one effect
25
+ run in a microtask. Tests can call `flushSync()` to drain that queue
26
+ synchronously.
27
+
28
+ If an effect returns a cleanup function, Mado runs that cleanup before the next
29
+ effect run and again when the effect disposer is called.
30
+
31
+ ```ts
32
+ const stop = effect(() => {
33
+ const id = setInterval(tick, 1000);
34
+ return () => clearInterval(id);
35
+ });
36
+
37
+ stop();
38
+ ```
39
+
40
+ In components and pages, prefer `ctx.onDispose()` / page `onDispose()` for
41
+ unmount cleanup. Effect cleanup is per-run cleanup.
42
+
43
+ ## Batch
44
+
45
+ `batch(fn)` groups signal writes into one subscriber pass. Effects do not run
46
+ until the outermost batch exits, including nested batches.
47
+
48
+ ```ts
49
+ batch(() => {
50
+ first.set("Ada");
51
+ batch(() => last.set("Lovelace"));
52
+ });
53
+ // effects see only the final pair
54
+ ```
55
+
56
+ Observed `computed({ equals })` values also preserve batch atomicity: they
57
+ recompute once after the outermost batch, on the fully applied state. They must
58
+ not observe a half-applied batch such as `(new x, old y)`.
59
+
60
+ ## DOM updates
61
+
62
+ `render(result, container)` reuses the existing template instance when the next
63
+ render has the same template strings. For child bindings that return a nested
64
+ `html```, Mado applies the same rule: same template strings update in place,
65
+ different template strings rebuild that branch.
66
+
67
+ This means unrelated signal changes do not recreate an `<input>` inside a
68
+ stable nested template, so focus, DOM state and listeners survive.
69
+
70
+ Lists should use `each(items, key, renderItem)`. Keys define DOM identity.
71
+ Duplicate keys warn in development and fall back to a positional suffix so every
72
+ item still renders, but duplicate keys are a data bug.
73
+
74
+ ## Component teardown
75
+
76
+ Custom elements may receive `disconnectedCallback()` followed by
77
+ `connectedCallback()` during a same-tick move. Mado defers component teardown to
78
+ a microtask and cancels it when the element is reconnected, so keyed reorders
79
+ preserve component state. A genuine removal still runs lifecycle cleanup on the
80
+ next microtask.
81
+
82
+ ## Not guaranteed
83
+
84
+ Mado does not guarantee the exact number of internal scheduler microtasks, the
85
+ order of independent effects that do not share dependencies, generated bundle
86
+ shape, or internal module layout. Those are implementation details.
87
+
88
+ The invariant tests for this contract live in:
89
+
90
+ - `test/reactivity-ordering.test.mjs`
91
+ - `test/signal-batch-equals.test.mjs`
92
+ - `test/update-nested-reuse.test.mjs`
93
+ - `test/each-component-state.test.mjs`
@@ -0,0 +1,83 @@
1
+ # v1 stability
2
+
3
+ > What Mado promises after v1, and what remains free to evolve.
4
+
5
+ Mado v1 means the public app-facing contract is stable enough for real
6
+ business apps. It does not mean every internal file, generated byte, starter
7
+ copy, or diagnostic string is frozen forever.
8
+
9
+ Read this together with:
10
+
11
+ - [API freeze map](./18-api-freeze-map.md)
12
+ - [Reactivity ordering](./19-reactivity-ordering.md)
13
+
14
+ ## Stable under SemVer
15
+
16
+ After v1, Mado treats these as SemVer-protected:
17
+
18
+ - Public exports from `@madojs/mado`.
19
+ - Public TypeScript types exported from `@madojs/mado`.
20
+ - The `@madojs/mado/devtools.js` side-effect subpath.
21
+ - Template binding syntax: child `${}`, `@event`, `.prop`, `?boolean`,
22
+ attribute bindings, directives and `each()`.
23
+ - Signal semantics documented in the reactivity ordering guide.
24
+ - Component lifecycle semantics: setup once per connection lifetime, deferred
25
+ teardown for same-tick moves, cleanup via `ctx.onDispose`.
26
+ - Router/page/resource/form contracts documented in the English docs.
27
+ - CLI command names and broad command intent (`build`, `dev`, `release`,
28
+ `bake`, `bundle`, `preview`, `init`, `new`).
29
+
30
+ Breaking these requires a major version.
31
+
32
+ ## Allowed in minor releases
33
+
34
+ Minor releases may add:
35
+
36
+ - New root exports.
37
+ - New options on existing APIs.
38
+ - New diagnostics and warnings.
39
+ - New starters, examples, docs and CLI flags.
40
+ - Performance improvements and internal implementation rewrites.
41
+
42
+ Minor releases should not require existing correct apps to change code.
43
+
44
+ ## Allowed in patch releases
45
+
46
+ Patch releases may fix bugs, tighten diagnostics, improve docs, and make
47
+ compatible implementation changes. A patch may change timing only when the old
48
+ timing was an undocumented bug and the change preserves the documented
49
+ reactivity ordering contract.
50
+
51
+ ## Not stable
52
+
53
+ These are intentionally not SemVer-protected:
54
+
55
+ - Internal package subpaths other than `@madojs/mado/devtools.js`.
56
+ - Files under `src/`, `dist/src/`, and implementation module boundaries.
57
+ - `_testHooks`, diagnostics internals and warning codes.
58
+ - Exact generated JavaScript text, chunk names, sourcemap content and bundle
59
+ byte layout.
60
+ - Internal parser, binding, router and resource cache data structures.
61
+ - Starter app visual copy and demo data.
62
+
63
+ Apps should not import internal files or assert exact bundle output.
64
+
65
+ ## Bundle and release output
66
+
67
+ Mado will keep a size budget and deterministic release tests, but v1 stability
68
+ does not freeze byte-for-byte bundler output. Hashes, chunk boundaries and
69
+ generated asset names may change as long as the documented deployment contract
70
+ continues to work.
71
+
72
+ ## If a release breaks you
73
+
74
+ If an update breaks code that uses only public exports and documented behaviour,
75
+ treat it as a bug. Open an issue with:
76
+
77
+ - the Mado version before and after;
78
+ - the public API involved;
79
+ - a minimal reproduction;
80
+ - whether the break is runtime behaviour, TypeScript types, CLI output or docs.
81
+
82
+ If the break depends on an internal subpath or exact generated output, it may
83
+ still be worth reporting, but it is not considered a SemVer break.
package/docs/en/README.md CHANGED
@@ -22,3 +22,6 @@ English documentation set.
22
22
  | Error handling | [15-error-handling.md](./15-error-handling.md) |
23
23
  | Bake cookbook | [16-bake-cookbook.md](./16-bake-cookbook.md) |
24
24
  | Shadow DOM + Forms | [17-shadow-dom-forms.md](./17-shadow-dom-forms.md) |
25
+ | API freeze map | [18-api-freeze-map.md](./18-api-freeze-map.md) |
26
+ | Reactivity ordering | [19-reactivity-ordering.md](./19-reactivity-ordering.md) |
27
+ | v1 stability | [20-v1-stability.md](./20-v1-stability.md) |
@@ -2,9 +2,12 @@
2
2
 
3
3
  > Une seule bonne façon. Des contrats stricts. Pas de magie.
4
4
 
5
- Mado n'est pas seulement un framework c'est un **ensemble de conventions**. Si vous les
6
- respectez, le projet reste compréhensible même avec 200 écrans et 5 développeurs. Si vous
7
- les enfreignez les types et le linter vous le diront immédiatement.
5
+ Mado est un framework pour les équipes qui construisent des panneaux d'admin,
6
+ des outils internes et des SPA métier des apps qui doivent être simples à
7
+ créer et ennuyeuses à maintenir. Pour cela, il impose un **ensemble de
8
+ conventions**. Si vous les respectez, le projet reste compréhensible même avec
9
+ 200 écrans et 5 développeurs. Si vous les enfreignez — les types et le linter
10
+ vous le diront immédiatement.
8
11
 
9
12
  ## Principes
10
13
 
@@ -42,13 +45,21 @@ tous écrire de la même manière.
42
45
 
43
46
  ```ts
44
47
  // src/components/user-card.ts
45
- import { component, html, css } from '@madojs/mado';
46
-
47
- component('x-user-card', () => {
48
- return () => html`<div class="card"><slot/></div>`;
49
- }, {
50
- styles: css`.card { padding: 1rem; }`,
51
- });
48
+ import { component, html, css } from "@madojs/mado";
49
+
50
+ component(
51
+ "x-user-card",
52
+ () => {
53
+ return () => html`<div class="card"><slot /></div>`;
54
+ },
55
+ {
56
+ styles: css`
57
+ .card {
58
+ padding: 1rem;
59
+ }
60
+ `,
61
+ },
62
+ );
52
63
  ```
53
64
 
54
65
  `import './components/user-card.js'` **enregistre** le composant via
@@ -63,7 +74,7 @@ component('x-user-card', () => {
63
74
  const user = resource(() => `/api/users/${id()}`, jsonFetcher());
64
75
 
65
76
  // écriture → mutation
66
- const save = mutation(api.save, { invalidates: ['/api/users*'] });
77
+ const save = mutation(api.save, { invalidates: ["/api/users*"] });
67
78
  ```
68
79
 
69
80
  Cela fournit la mise en cache, l'annulation, la gestion des erreurs et l'invalidation automatique.
@@ -72,11 +83,11 @@ Cela fournit la mise en cache, l'annulation, la gestion des erreurs et l'invalid
72
83
 
73
84
  ```ts
74
85
  // src/pages/user-profile.ts
75
- import { page, html, resource, jsonFetcher } from '@madojs/mado';
86
+ import { page, html, resource, jsonFetcher } from "@madojs/mado";
76
87
 
77
88
  export default page({
78
89
  title: ({ id }) => `Utilisateur #${id}`,
79
- view: ({ params }) => html`...`,
90
+ view: ({ params }) => html`...`,
80
91
  });
81
92
  ```
82
93
 
@@ -101,6 +112,7 @@ Voir [`01-routing.md`](./01-routing.md).
101
112
  ## En cas de doute
102
113
 
103
114
  Si vous vous demandez "quelle est la meilleure façon ici ?" — c'est un signal que :
115
+
104
116
  1. Soit il existe un helper intégré que vous ne connaissez pas (consultez `docs/`).
105
117
  2. Soit c'est une nouvelle situation — discutez-en et **consignez-la** dans ce document
106
118
  comme une convention supplémentaire.
@@ -148,7 +148,6 @@ out/
148
148
  {"@context":"https://schema.org","@type":"Product","..."}
149
149
  </script>
150
150
  <meta name="bake-revalidate" content="3600" data-mado-head="baked">
151
- <meta name="bake-stamp" content="1234567890" data-mado-head="baked">
152
151
  </head>
153
152
  <body>
154
153
  <div id="app">
@@ -231,7 +230,7 @@ export default page<{ slug: string }>({
231
230
 
232
231
  ## Revalidate / CDN
233
232
 
234
- `bake.revalidate: 3600` écrit `<meta name="bake-revalidate" content="3600">` et `bake-stamp`
233
+ `bake.revalidate: 3600` écrit `<meta name="bake-revalidate" content="3600">`
235
234
  dans le HTML. C'est des **métadonnées** — le framework ne re-bake rien lui-même. Stratégies :
236
235
 
237
236
  1. **Option la plus simple** : cron dans CI — `npm run bake && rsync out/ origin:/var/www/`.
@@ -154,6 +154,12 @@ await save.run(newUser);
154
154
  // automatiquement : user.data() se mettra à jour si le glob correspond
155
155
  ```
156
156
 
157
+ Les clés de `resource()` sont l'identité du cache. Incluez l'endpoint, les query
158
+ params et la forme des données dans la clé : deux `resource()` vivants avec la
159
+ même clé partagent le cache et la requête in-flight. Si la même clé est utilisée
160
+ avec un fetcher différent, Mado avertit, car cela signifie généralement que la
161
+ clé de cache est trop large.
162
+
157
163
  Si une telle abstraction existait dans le monde Go pour les caches côté serveur — on
158
164
  pleurerait tous de joie.
159
165
 
@@ -619,5 +619,7 @@ Plus de détails : [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md).
619
619
  | `@customElement('x')` | `component('x-name', setup)` |
620
620
  | `host.getAttribute('x')` dans render | `ctx.attr('x', default)` (réactif) |
621
621
  | `jsonFetcher()` avec auth | `apiFetcher()` (attache le Bearer token) |
622
+ | `setInterval` dans page view | `onDispose(() => clearInterval(id))` |
623
+ | lecture signal dans async init view() | `untracked(() => cursor())` |
622
624
 
623
625
  Si quelque chose ne rentre pas dans cette liste — ouvrez `src/` et **lisez 500 lignes**. Sérieusement. Mado est intentionnellement petit pour être lisible.
@@ -53,6 +53,11 @@ Cherchez ces éléments après l'implémentation :
53
53
  L'implémentation actuelle de `examples/tickets` n'a pas nécessité de nouvelles API publiques ni
54
54
  de dépendances runtime.
55
55
 
56
+ CI exécute `npm run llm:smoke` comme proxy déterministe pour cette tâche :
57
+ il vérifie que `llms.txt` contient toujours les règles clés, compare l'artefact
58
+ commité `examples/tickets` à la surface API Mado requise et aux failure
59
+ patterns, puis build le projet et lance `test/tickets-smoke.test.mjs`.
60
+
56
61
  Le principal point de pression dans la documentation reste le lifecycle : les anciens exemples
57
62
  peuvent donner l'impression qu'il est acceptable de créer `resource()` directement dans
58
63
  `page.view()`. L'exemple tickets utilise plutôt des composants wrapper au niveau page, de sorte
@@ -0,0 +1,63 @@
1
+ # Carte de gel de l'API
2
+
3
+ > Ce qui est public, ce qui est interne, et ce que SemVer protégera en v1.
4
+
5
+ Le contrat v1 de Mado est volontairement petit. Le code applicatif importe
6
+ depuis la racine du package :
7
+
8
+ ```ts
9
+ import { component, html, resource, routes, signal } from "@madojs/mado";
10
+ ```
11
+
12
+ Le seul subpath public est le module side-effect devtools :
13
+
14
+ ```ts
15
+ import "@madojs/mado/devtools.js";
16
+ ```
17
+
18
+ Tout le reste sous `dist/src/` est un détail d'implémentation, même si le
19
+ fichier est visible dans le dépôt.
20
+
21
+ ## API publique stable
22
+
23
+ Ces noms sont publics et protégés par SemVer une fois v1 publiée :
24
+
25
+ - Réactivité : `signal`, `computed`, `effect`, `untracked`, `batch`,
26
+ `flushSync`.
27
+ - Templates et directives : `html`, `render`, `each`, `list`, `unsafeHTML`,
28
+ `ref`, `classMap`, `styleMap`.
29
+ - Composants et CSS : `component`, `css`, `cssVars`.
30
+ - Routage et pages : `routes`, `router`, `page`, `layout`, `nested`,
31
+ `navigate`, `queryParam`, `prefetchPath`.
32
+ - Data : `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
+ - Formulaires : `useForm`.
34
+ - Head et persistence : `applyHead`, `persisted`.
35
+ - Context : `createContext`, `provide`, `inject`.
36
+ - Helpers lifecycle avancés : `createLifecycle`, `runInLifecycle`,
37
+ `getCurrentLifecycle`.
38
+ - Types TypeScript publics exportés depuis `@madojs/mado`.
39
+
40
+ ## Interne ou instable
41
+
42
+ Ce n'est pas de l'API publique :
43
+
44
+ - Subpaths du package autres que `@madojs/mado` et
45
+ `@madojs/mado/devtools.js`.
46
+ - Internals du parser/binding comme `html/parser.js`, `html/bindings.js`,
47
+ `ChildState` et `EachEntry`.
48
+ - Internals du routeur comme `router/match.js`, `router/navigation.js` et
49
+ `router/manifest.js`.
50
+ - Internals de diagnostics et tous les `_testHooks`.
51
+ - Texte exact du bundle généré, noms des chunks et layout interne des fichiers.
52
+
53
+ Les tests du dépôt peuvent importer des fichiers internes via des chemins
54
+ relatifs `dist/`. Le code applicatif ne doit pas le faire.
55
+
56
+ ## Ce qui peut changer
57
+
58
+ Les patch et minor releases peuvent ajouter des root exports, options,
59
+ diagnostics, docs ou starters. Elles peuvent aussi changer les internals, la
60
+ forme du bundle et les détails d'implémentation tant que l'API stable et le
61
+ comportement documenté restent compatibles.
62
+
63
+ Les changements cassants de l'API stable nécessitent une version majeure.
@@ -0,0 +1,97 @@
1
+ # Ordre de la réactivité
2
+
3
+ > Le petit ensemble de garanties d'ordre que Mado traite comme comportement public.
4
+
5
+ La réactivité de Mado est synchrone pour les lectures et planifiée pour les
6
+ side effects. Le but est une mise à jour UI prévisible plutôt qu'un grand modèle
7
+ de scheduling.
8
+
9
+ ## Signals
10
+
11
+ `signal(value)` renvoie une fonction getter. `set(next)` change la valeur
12
+ immédiatement sauf si `Object.is(previous, next)` vaut `true`.
13
+
14
+ ```ts
15
+ const count = signal(0);
16
+ count.set(1);
17
+ count(); // 1, immédiatement
18
+ ```
19
+
20
+ Les computed values sont marquées avant l'exécution des effects : un effect qui
21
+ lit un computed observe les dépendances courantes, pas un cache obsolète.
22
+
23
+ ## Effects
24
+
25
+ `effect(fn)` s'exécute une première fois immédiatement. Les changements
26
+ ultérieurs de dépendances planifient une seule exécution en microtask. Les tests
27
+ peuvent appeler `flushSync()` pour vider cette file de façon synchrone.
28
+
29
+ Si un effect retourne une fonction de cleanup, Mado l'exécute avant l'exécution
30
+ suivante de l'effect et à nouveau quand le disposer est appelé.
31
+
32
+ ```ts
33
+ const stop = effect(() => {
34
+ const id = setInterval(tick, 1000);
35
+ return () => clearInterval(id);
36
+ });
37
+
38
+ stop();
39
+ ```
40
+
41
+ Dans les composants et pages, préférez `ctx.onDispose()` / page `onDispose()`
42
+ pour le cleanup à l'unmount. Le cleanup d'un effect est un cleanup par run.
43
+
44
+ ## Batch
45
+
46
+ `batch(fn)` regroupe les écritures de signals en un seul passage des
47
+ subscribers. Les effects ne s'exécutent pas avant la sortie du batch le plus
48
+ extérieur, y compris avec des batches imbriqués.
49
+
50
+ ```ts
51
+ batch(() => {
52
+ first.set("Ada");
53
+ batch(() => last.set("Lovelace"));
54
+ });
55
+ // les effects ne voient que la paire finale
56
+ ```
57
+
58
+ Les `computed({ equals })` observés préservent aussi l'atomicité du batch : ils
59
+ se recalculent une fois après le batch extérieur, sur l'état entièrement
60
+ appliqué. Ils ne doivent pas observer un batch à moitié appliqué comme
61
+ `(new x, old y)`.
62
+
63
+ ## Mises à jour DOM
64
+
65
+ `render(result, container)` réutilise l'instance de template existante quand le
66
+ render suivant a les mêmes template strings. Pour les child bindings qui
67
+ retournent un `html``` imbriqué, la même règle s'applique : mêmes strings =
68
+ mise à jour sur place, autres strings = reconstruction de cette branche.
69
+
70
+ Ainsi, des changements de signals sans rapport ne recréent pas un `<input>`
71
+ dans un template imbriqué stable : focus, état DOM et listeners survivent.
72
+
73
+ Les listes doivent utiliser `each(items, key, renderItem)`. Les keys définissent
74
+ l'identité DOM. Les duplicate keys avertissent en development et retombent sur
75
+ un suffixe positionnel pour que chaque item soit rendu, mais les duplicate keys
76
+ sont un bug de données.
77
+
78
+ ## Teardown des composants
79
+
80
+ Les custom elements peuvent recevoir `disconnectedCallback()` puis
81
+ `connectedCallback()` pendant un déplacement dans le même tick. Mado diffère le
82
+ teardown du composant jusqu'à une microtask et l'annule si l'élément est
83
+ reconnecté, donc les reorders keyed préservent l'état du composant. Une vraie
84
+ suppression lance tout de même le cleanup lifecycle à la microtask suivante.
85
+
86
+ ## Non garanti
87
+
88
+ Mado ne garantit pas le nombre exact de microtasks internes du scheduler,
89
+ l'ordre des effects indépendants sans dépendances communes, la forme du bundle
90
+ généré ni le layout interne des modules. Ce sont des détails d'implémentation.
91
+
92
+ Les tests invariants de ce contrat vivent dans :
93
+
94
+ - `test/reactivity-ordering.test.mjs`
95
+ - `test/signal-batch-equals.test.mjs`
96
+ - `test/update-nested-reuse.test.mjs`
97
+ - `test/each-component-state.test.mjs`