@madojs/mado 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/AGENTS.md +58 -7
  2. package/CHANGELOG.md +121 -1
  3. package/README.md +21 -5
  4. package/dist/src/component.d.ts +2 -12
  5. package/dist/src/component.js +2 -29
  6. package/dist/src/component.js.map +1 -1
  7. package/dist/src/diagnostics.d.ts +0 -4
  8. package/dist/src/diagnostics.js +1 -0
  9. package/dist/src/diagnostics.js.map +1 -1
  10. package/dist/src/html/bindings.js +3 -0
  11. package/dist/src/html/bindings.js.map +1 -1
  12. package/dist/src/html/template.js +10 -0
  13. package/dist/src/html/template.js.map +1 -1
  14. package/dist/src/resource.d.ts +3 -6
  15. package/dist/src/resource.js +59 -10
  16. package/dist/src/resource.js.map +1 -1
  17. package/dist/src/router/manifest.d.ts +0 -3
  18. package/dist/src/router/manifest.js +1 -0
  19. package/dist/src/router/manifest.js.map +1 -1
  20. package/dist/src/router.d.ts +1 -1
  21. package/dist/src/router.js +1 -1
  22. package/dist/src/router.js.map +1 -1
  23. package/dist/src/signal.d.ts +0 -4
  24. package/dist/src/signal.js +1 -0
  25. package/dist/src/signal.js.map +1 -1
  26. package/docs/en/02-project-layout.md +3 -2
  27. package/docs/en/03-static-bake.md +1 -2
  28. package/docs/en/06-for-backenders.md +5 -0
  29. package/docs/en/08-llm-zero-history-test.md +5 -0
  30. package/docs/en/13-deployment.md +10 -7
  31. package/docs/en/16-bake-cookbook.md +10 -2
  32. package/docs/en/18-api-freeze-map.md +63 -0
  33. package/docs/en/19-reactivity-ordering.md +93 -0
  34. package/docs/en/20-v1-stability.md +83 -0
  35. package/docs/en/README.md +3 -0
  36. package/docs/fr/02-project-layout.md +20 -13
  37. package/docs/fr/03-static-bake.md +1 -2
  38. package/docs/fr/06-for-backenders.md +6 -0
  39. package/docs/fr/08-llm-zero-history-test.md +5 -0
  40. package/docs/fr/13-deployment.md +33 -12
  41. package/docs/fr/16-bake-cookbook.md +57 -4
  42. package/docs/fr/18-api-freeze-map.md +63 -0
  43. package/docs/fr/19-reactivity-ordering.md +97 -0
  44. package/docs/fr/20-v1-stability.md +88 -0
  45. package/docs/fr/README.md +3 -0
  46. package/docs/ru/02-project-layout.md +22 -15
  47. package/docs/ru/03-static-bake.md +2 -3
  48. package/docs/ru/06-for-backenders.md +6 -0
  49. package/docs/ru/08-llm-zero-history-test.md +5 -0
  50. package/docs/ru/13-deployment.md +23 -13
  51. package/docs/ru/16-bake-cookbook.md +42 -8
  52. package/docs/ru/18-api-freeze-map.md +62 -0
  53. package/docs/ru/19-reactivity-ordering.md +95 -0
  54. package/docs/ru/20-v1-stability.md +82 -0
  55. package/docs/ru/README.md +3 -0
  56. package/docs/uk/06-for-backenders.md +5 -0
  57. package/docs/uk/08-llm-zero-history-test.md +5 -0
  58. package/docs/uk/18-api-freeze-map.md +61 -0
  59. package/docs/uk/19-reactivity-ordering.md +95 -0
  60. package/docs/uk/20-v1-stability.md +83 -0
  61. package/docs/uk/README.md +3 -0
  62. package/llms.txt +59 -5
  63. package/package.json +8 -3
  64. package/scripts/bake.mjs +4 -2
  65. package/scripts/cli.mjs +83 -5
  66. package/scripts/llm-zero-history-smoke.mjs +93 -0
  67. package/scripts/new.mjs +1 -1
  68. package/scripts/package-smoke.mjs +74 -0
  69. package/scripts/preview.mjs +7 -27
  70. package/scripts/size-budget.mjs +88 -0
  71. package/starters/admin/README.md +2 -2
  72. package/starters/admin/package.json +2 -2
  73. package/starters/crud/package.json +2 -2
  74. package/starters/minimal/package.json +2 -2
@@ -129,7 +129,6 @@ out/
129
129
  {"@context":"https://schema.org","@type":"Product","..."}
130
130
  </script>
131
131
  <meta name="bake-revalidate" content="3600" data-mado-head="baked">
132
- <meta name="bake-stamp" content="1234567890" data-mado-head="baked">
133
132
  </head>
134
133
  <body>
135
134
  <div id="app">
@@ -212,7 +211,7 @@ export default page<{ slug: string }>({
212
211
 
213
212
  ## Revalidate / CDN
214
213
 
215
- `bake.revalidate: 3600` пишет в HTML `<meta name="bake-revalidate" content="3600">` и `bake-stamp`. Это **метаданные** — фреймворк сам ничего не перевыпекает. Стратегии:
214
+ `bake.revalidate: 3600` пишет в HTML `<meta name="bake-revalidate" content="3600">`. Это **метаданные** — фреймворк сам ничего не перевыпекает. Стратегии:
216
215
 
217
216
  1. **Простейший вариант**: cron в CI — `npm run bake && rsync out/ origin:/var/www/`.
218
217
  2. **Через CDN** (Cloudflare/Fastly): кладёте HTML с `Cache-Control: max-age=3600`. CDN сам инвалидирует.
@@ -248,4 +247,4 @@ export default page<{ slug: string }>({
248
247
 
249
248
  Если страница **общая для всех пользователей**, имеет **относительно стабильный набор URL'ов** и важен **SEO + первый paint** — добавьте `bake: { paths, data }` и получите статический HTML с meta/JSON-LD/sitemap за миллисекунды. Без node-сервера, без Chrome, без магии.
250
249
 
251
- Если страница персонализирована, или URL'ов миллион, или контент меняется в реальном времени — `bake` не ваш инструмент. Оставляйте SPA или подключайте отдельный SSR-фреймворк.
250
+ Если страница персонализирована, или URL'ов миллион, или контент меняется в реальном времени — `bake` не ваш инструмент. Оставляйте SPA или подключайте отдельный SSR-фреймворк.
@@ -150,6 +150,12 @@ await save.run(newUser);
150
150
  // автоматически: user.data() обновится, если совпал glob
151
151
  ```
152
152
 
153
+ Ключи `resource()` — это identity кеша. Включайте endpoint, query params и
154
+ форму данных в ключ: два живых `resource()` с одинаковым ключом разделяют кеш
155
+ и in-flight request. Если один и тот же ключ используется с другим fetcher,
156
+ Mado предупреждает, потому что обычно это значит, что ключ кеша слишком
157
+ широкий.
158
+
153
159
  Если бы такая абстракция была в Go-мире для серверных кешей — мы бы все плакали от счастья.
154
160
 
155
161
  ---
@@ -51,6 +51,11 @@
51
51
  Текущая реализация `examples/tickets` не потребовала новых публичных API или
52
52
  runtime-зависимостей.
53
53
 
54
+ CI запускает `npm run llm:smoke` как детерминированный proxy для этой задачи:
55
+ проверяет, что `llms.txt` всё ещё содержит ключевые правила, сверяет
56
+ зафиксированный артефакт `examples/tickets` с нужной Mado API surface и
57
+ failure patterns, затем собирает проект и запускает `test/tickets-smoke.test.mjs`.
58
+
54
59
  Основная болевая точка в документации остается lifecycle: старые примеры могут
55
60
  создать впечатление, что создание `resource()` прямо в `page.view()` допустимо.
56
61
  Пример tickets использует page-level wrapper-компоненты вместо этого, поэтому
@@ -1,6 +1,6 @@
1
1
  # Deployment
2
2
 
3
- Один command, один artifact:
3
+ Одна команда, один deploy artifact:
4
4
 
5
5
  ```bash
6
6
  mado release
@@ -10,15 +10,21 @@ mado release
10
10
 
11
11
  ```txt
12
12
  out/
13
- ├── index.html
14
- ├── assets/
15
- ├── baked/
16
- ├── _redirects
17
- └── _headers
13
+ ├── index.html ← SPA shell или promoted baked HTML для /
14
+ ├── assets/ ← hashed bundles (main-ABC.js, chunk-XYZ.js, ...)
15
+ ├── *.gz ← precompressed gzip
16
+ │ └── *.br ← precompressed brotli
17
+ ├── baked/ ← копия результата bake для inspection/debugging
18
+ │ ├── <route>/index.html
19
+ │ └── sitemap.xml
20
+ ├── <route>/index.html ← promoted baked HTML для static hosts
21
+ ├── sitemap.xml ← sitemap в root сайта
22
+ ├── _redirects ← Cloudflare Pages / Netlify SPA fallback
23
+ └── _headers ← cache rules
18
24
  ```
19
25
 
20
26
  `out/` можно деплоить на nginx, Cloudflare Pages, Netlify, S3/CloudFront или
21
- GitHub Pages.
27
+ GitHub Pages. Не деплой `dist/`: это внутренний output для dev/build.
22
28
 
23
29
  ## Preview
24
30
 
@@ -27,8 +33,10 @@ mado release
27
33
  mado preview
28
34
  ```
29
35
 
30
- `mado preview` сервит `out/` как статический хост: baked HTML имеет приоритет,
31
- а неизвестные пути падают в SPA fallback.
36
+ `mado preview` сервит финальный `out/` как обычный static host: сначала реальные
37
+ файлы (`/<route>/index.html`, если route был baked), потом SPA fallback в
38
+ `index.html`. Preview больше не делает отдельную виртуальную подстановку из
39
+ `out/baked/`, поэтому он проверяет ровно то, что будет загружено на хостинг.
32
40
 
33
41
  ## VPS + nginx
34
42
 
@@ -37,8 +45,8 @@ mado release
37
45
  rsync -avz --delete out/ user@server:/var/www/myapp/
38
46
  ```
39
47
 
40
- В репозитории есть production `nginx.conf`: hashed bundles кешируются
41
- immutably, HTML идет с `no-cache`, deep links работают через SPA fallback.
48
+ В репозитории есть production `nginx.conf`: hashed bundles кешируются immutable,
49
+ HTML идет с `no-cache`, deep links работают через SPA fallback.
42
50
 
43
51
  ## Cloudflare / Netlify
44
52
 
@@ -47,9 +55,11 @@ mado release
47
55
  npx wrangler pages deploy out --project-name=myapp
48
56
  ```
49
57
 
50
- `_redirects` и `_headers` генерируются автоматически.
58
+ `_redirects` и `_headers` генерируются автоматически, если ты не положил свои.
59
+ Baked routes промотируются в реальные файлы (`out/<route>/index.html`), поэтому
60
+ static host отдаст их до SPA fallback.
51
61
 
52
- ## Cache rules
62
+ ## Cache Rules
53
63
 
54
64
  | Path | Cache-Control |
55
65
  |---|---|
@@ -1,9 +1,9 @@
1
- # Bake cookbook
1
+ # Bake Cookbook
2
2
 
3
3
  `mado bake` рендерит выбранные роуты в статический HTML. Это для SEO и быстрого
4
4
  первого ответа, не SSR с hydration.
5
5
 
6
- ## Минимальная страница
6
+ ## Минимальная Страница
7
7
 
8
8
  ```ts
9
9
  export default page({
@@ -23,9 +23,9 @@ export default page({
23
23
  ```
24
24
 
25
25
  В baked views лучше использовать обычные массивы (`items.map(...)`). Runtime
26
- директивы вроде `each()` нужны браузеру.
26
+ директивы вроде keyed `each()` нужны браузеру.
27
27
 
28
- ## Dynamic routes
28
+ ## Dynamic Routes
29
29
 
30
30
  ```ts
31
31
  export default page<{ slug: string }>({
@@ -40,16 +40,50 @@ export default page<{ slug: string }>({
40
40
 
41
41
  `unsafeHTML()` используй только для доверенного или заранее очищенного HTML.
42
42
 
43
- ## Manifest и output
43
+ ## Route Manifest
44
+
45
+ `mado bake` нужен source manifest:
44
46
 
45
47
  ```ts
46
48
  export const manifest = {
47
49
  "/": () => import("./pages/home.js"),
48
50
  "/blog/:slug": () => import("./pages/blog-post.js"),
49
51
  };
52
+
50
53
  export default routes(manifest);
51
54
  ```
52
55
 
53
- `mado release` создает deploy artifact в `out/`. Если bake ругается на
54
- unsupported value, значит в статическую страницу попало runtime-only значение:
55
- замени `each()` на `items.map(...)` или вынеси интерактивный кусок в клиент.
56
+ ## Output
57
+
58
+ Standalone `mado bake` по умолчанию пишет baked pages в `out/baked/`.
59
+ `mado release` использует bundled production shell, оставляет копию в
60
+ `out/baked/` для inspection, промотирует HTML в реальные route paths внутри
61
+ `out/` и копирует sitemap в `out/sitemap.xml`.
62
+
63
+ ```bash
64
+ mado release
65
+ tree out
66
+ ```
67
+
68
+ Deployable folder — `out/`, не `dist/`.
69
+
70
+ ## Client Boot
71
+
72
+ Baked HTML помечает `#app` атрибутом `data-mado-baked`. Это не hydration:
73
+ клиентский `render()` заменяет baked DOM живыми bindings при старте приложения.
74
+ Так первый ответ содержит SEO/first-paint HTML, а SPA после загрузки работает
75
+ как обычно.
76
+
77
+ ## Unsupported Values
78
+
79
+ Bake намеренно падает громко вместо записи `[object Object]`. Если baked view
80
+ ругается на unsupported directive:
81
+
82
+ - замени `each()` на `items.map(...)` в baked markup;
83
+ - интерактивные виджеты оставь в client-only routes;
84
+ - убедись, что все значения сериализуются в статический HTML.
85
+
86
+ ## Canonical Links
87
+
88
+ Передай `--base-url` или задай `bake.baseUrl` в `mado.config.json`, чтобы
89
+ canonical links и sitemap указывали на production.
@@ -0,0 +1,62 @@
1
+ # Карта заморозки API
2
+
3
+ > Что публично, что внутреннее, и что SemVer будет защищать в v1.
4
+
5
+ Контракт Mado v1 намеренно небольшой. Код приложения импортирует API из корня
6
+ пакета:
7
+
8
+ ```ts
9
+ import { component, html, resource, routes, signal } from "@madojs/mado";
10
+ ```
11
+
12
+ Единственный публичный subpath — side-effect модуль devtools:
13
+
14
+ ```ts
15
+ import "@madojs/mado/devtools.js";
16
+ ```
17
+
18
+ Все остальное под `dist/src/` — деталь реализации, даже если файл виден в
19
+ репозитории.
20
+
21
+ ## Стабильный публичный API
22
+
23
+ Эти имена публичны и будут защищены SemVer после v1:
24
+
25
+ - Reactivity: `signal`, `computed`, `effect`, `untracked`, `batch`,
26
+ `flushSync`.
27
+ - Templates и directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
28
+ `ref`, `classMap`, `styleMap`.
29
+ - Components и CSS: `component`, `css`, `cssVars`.
30
+ - Routing и pages: `routes`, `router`, `page`, `layout`, `nested`,
31
+ `navigate`, `queryParam`, `prefetchPath`.
32
+ - Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
+ - Forms: `useForm`.
34
+ - Head и persistence: `applyHead`, `persisted`.
35
+ - Context: `createContext`, `provide`, `inject`.
36
+ - Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
37
+ `getCurrentLifecycle`.
38
+ - Публичные TypeScript-типы, экспортируемые из `@madojs/mado`.
39
+
40
+ ## Внутреннее или нестабильное
41
+
42
+ Это не публичный API:
43
+
44
+ - Package subpaths кроме `@madojs/mado` и `@madojs/mado/devtools.js`.
45
+ - Internals парсера/биндингов: `html/parser.js`, `html/bindings.js`,
46
+ `ChildState`, `EachEntry`.
47
+ - Internals роутера: `router/match.js`, `router/navigation.js`,
48
+ `router/manifest.js`.
49
+ - Diagnostics internals и все `_testHooks`.
50
+ - Точный текст bundle, имена chunks и внутренняя структура файлов.
51
+
52
+ Тесты репозитория могут импортировать внутренние файлы через относительные
53
+ пути `dist/`. Код приложений так делать не должен.
54
+
55
+ ## Что может меняться
56
+
57
+ Patch и minor релизы могут добавлять root exports, опции, diagnostics, docs или
58
+ starter files. Они также могут менять internals, форму bundle и детали
59
+ реализации, если стабильный API и задокументированное поведение остаются
60
+ совместимыми.
61
+
62
+ Ломающие изменения стабильного API требуют major version.
@@ -0,0 +1,95 @@
1
+ # Порядок reactivity
2
+
3
+ > Небольшой набор ordering-гарантий, которые Mado считает публичным поведением.
4
+
5
+ Reactivity в Mado синхронна для чтения и планирует side effects. Цель —
6
+ предсказуемые UI-обновления без большой scheduling-модели.
7
+
8
+ ## Signals
9
+
10
+ `signal(value)` возвращает getter-функцию. `set(next)` меняет значение сразу,
11
+ если `Object.is(previous, next)` не вернул `true`.
12
+
13
+ ```ts
14
+ const count = signal(0);
15
+ count.set(1);
16
+ count(); // 1, сразу
17
+ ```
18
+
19
+ `computed` помечается до запуска effects, поэтому effect, читающий computed,
20
+ видит актуальные dependencies, а не старый кеш.
21
+
22
+ ## Effects
23
+
24
+ `effect(fn)` запускается один раз сразу. Последующие изменения dependencies
25
+ планируют один запуск effect в microtask. В тестах можно вызвать `flushSync()`,
26
+ чтобы синхронно очистить очередь.
27
+
28
+ Если effect возвращает cleanup-функцию, Mado запускает ее перед следующим
29
+ запуском effect и еще раз при вызове disposer.
30
+
31
+ ```ts
32
+ const stop = effect(() => {
33
+ const id = setInterval(tick, 1000);
34
+ return () => clearInterval(id);
35
+ });
36
+
37
+ stop();
38
+ ```
39
+
40
+ В компонентах и pages для unmount cleanup предпочитайте `ctx.onDispose()` /
41
+ page `onDispose()`. Cleanup effect — это cleanup между запусками.
42
+
43
+ ## Batch
44
+
45
+ `batch(fn)` группирует записи signals в один subscriber pass. Effects не
46
+ запускаются до выхода из внешнего batch, включая вложенные batches.
47
+
48
+ ```ts
49
+ batch(() => {
50
+ first.set("Ada");
51
+ batch(() => last.set("Lovelace"));
52
+ });
53
+ // effects увидят только финальную пару
54
+ ```
55
+
56
+ Наблюдаемые `computed({ equals })` также сохраняют batch atomicity: они
57
+ пересчитываются один раз после внешнего batch на полностью примененном state.
58
+ Они не должны видеть половинчатый batch вроде `(new x, old y)`.
59
+
60
+ ## DOM updates
61
+
62
+ `render(result, container)` переиспользует существующий template instance, когда
63
+ следующий render имеет те же template strings. Для child bindings, возвращающих
64
+ вложенный `html```, действует то же правило: те же strings обновляются на
65
+ месте, другие strings пересобирают ветку.
66
+
67
+ Это значит, что несвязанные изменения signals не пересоздают `<input>` внутри
68
+ стабильного вложенного template, поэтому focus, DOM state и listeners
69
+ сохраняются.
70
+
71
+ Списки должны использовать `each(items, key, renderItem)`. Keys задают DOM
72
+ identity. Duplicate keys предупреждают в development и получают positional
73
+ suffix, чтобы все элементы все равно отрендерились, но duplicate keys — это
74
+ ошибка данных.
75
+
76
+ ## Teardown компонентов
77
+
78
+ Custom elements могут получить `disconnectedCallback()`, а затем
79
+ `connectedCallback()` во время same-tick move. Mado откладывает teardown
80
+ компонента до microtask и отменяет его при reconnect, поэтому keyed reorders
81
+ сохраняют state компонента. Настоящее удаление все равно запускает cleanup на
82
+ следующей microtask.
83
+
84
+ ## Не гарантируется
85
+
86
+ Mado не гарантирует точное число внутренних scheduler microtasks, порядок
87
+ независимых effects без общих dependencies, форму generated bundle или
88
+ внутреннюю структуру modules. Это детали реализации.
89
+
90
+ Invariant tests для этого контракта:
91
+
92
+ - `test/reactivity-ordering.test.mjs`
93
+ - `test/signal-batch-equals.test.mjs`
94
+ - `test/update-nested-reuse.test.mjs`
95
+ - `test/each-component-state.test.mjs`
@@ -0,0 +1,82 @@
1
+ # Стабильность v1
2
+
3
+ > Что Mado обещает после v1, и что остается свободным для развития.
4
+
5
+ Mado v1 означает, что публичный app-facing contract достаточно стабилен для
6
+ реальных business apps. Это не значит, что каждый внутренний файл, generated
7
+ byte, starter copy или diagnostic string заморожены навсегда.
8
+
9
+ Читайте вместе с:
10
+
11
+ - [Карта заморозки API](./18-api-freeze-map.md)
12
+ - [Порядок reactivity](./19-reactivity-ordering.md)
13
+
14
+ ## Стабильно под SemVer
15
+
16
+ После v1 Mado считает SemVer-protected:
17
+
18
+ - Public exports из `@madojs/mado`.
19
+ - Public TypeScript types из `@madojs/mado`.
20
+ - Side-effect subpath `@madojs/mado/devtools.js`.
21
+ - Template binding syntax: child `${}`, `@event`, `.prop`, `?boolean`,
22
+ attribute bindings, directives и `each()`.
23
+ - Signal semantics, описанные в reactivity ordering guide.
24
+ - Component lifecycle semantics: setup один раз за connection lifetime,
25
+ deferred teardown для same-tick moves, cleanup через `ctx.onDispose`.
26
+ - Router/page/resource/form contracts, описанные в English docs.
27
+ - Имена CLI commands и широкий смысл команд (`build`, `dev`, `release`,
28
+ `bake`, `bundle`, `preview`, `init`, `new`).
29
+
30
+ Ломать это можно только в major version.
31
+
32
+ ## Разрешено в minor releases
33
+
34
+ Minor releases могут добавлять:
35
+
36
+ - New root exports.
37
+ - New options на существующих API.
38
+ - New diagnostics и warnings.
39
+ - New starters, examples, docs и CLI flags.
40
+ - Performance improvements и внутренние rewrites.
41
+
42
+ Minor release не должен требовать изменений в уже корректных apps.
43
+
44
+ ## Разрешено в patch releases
45
+
46
+ Patch releases могут исправлять bugs, ужесточать diagnostics, улучшать docs и
47
+ делать совместимые implementation changes. Patch может изменить timing только
48
+ когда старый timing был незадокументированным bug и новое поведение сохраняет
49
+ reactivity ordering contract.
50
+
51
+ ## Нестабильно
52
+
53
+ Это намеренно не защищено SemVer:
54
+
55
+ - Internal package subpaths кроме `@madojs/mado/devtools.js`.
56
+ - Файлы под `src/`, `dist/src/` и implementation module boundaries.
57
+ - `_testHooks`, diagnostics internals и warning codes.
58
+ - Точный JavaScript output, chunk names, sourcemap content и bundle byte layout.
59
+ - Internal parser, binding, router и resource cache data structures.
60
+ - Visual copy и demo data в starters.
61
+
62
+ Apps не должны импортировать internal files или проверять точный bundle output.
63
+
64
+ ## Bundle и release output
65
+
66
+ Mado держит size budget и deterministic release tests, но v1 stability не
67
+ замораживает byte-for-byte bundler output. Hashes, chunk boundaries и asset
68
+ names могут меняться, если задокументированный deployment contract продолжает
69
+ работать.
70
+
71
+ ## Если релиз вас сломал
72
+
73
+ Если update ломает код, который использует только public exports и
74
+ задокументированное поведение, считайте это bug. Откройте issue и укажите:
75
+
76
+ - версию Mado до и после;
77
+ - задействованный public API;
78
+ - минимальную репродукцию;
79
+ - это runtime behaviour, TypeScript types, CLI output или docs.
80
+
81
+ Если поломка зависит от internal subpath или точного generated output, ее все
82
+ равно можно зарепортить, но это не считается SemVer break.
package/docs/ru/README.md CHANGED
@@ -20,3 +20,6 @@
20
20
  | Обработка ошибок | [15-error-handling.md](./15-error-handling.md) |
21
21
  | Рецепты bake | [16-bake-cookbook.md](./16-bake-cookbook.md) |
22
22
  | Shadow DOM + формы | [17-shadow-dom-forms.md](./17-shadow-dom-forms.md) |
23
+ | Карта заморозки API | [18-api-freeze-map.md](./18-api-freeze-map.md) |
24
+ | Порядок reactivity | [19-reactivity-ordering.md](./19-reactivity-ordering.md) |
25
+ | Стабильность v1 | [20-v1-stability.md](./20-v1-stability.md) |
@@ -26,6 +26,11 @@ const save = mutation(api.saveUser, {
26
26
  });
27
27
  ```
28
28
 
29
+ Ключі `resource()` — це identity кешу. Додавайте endpoint, query params і форму
30
+ даних у ключ: два живі `resource()` з однаковим ключем ділять cache та
31
+ in-flight request. Якщо той самий ключ використано з іншим fetcher, Mado
32
+ попереджає, бо зазвичай це означає, що ключ кешу занадто широкий.
33
+
29
34
  ## Форми
30
35
 
31
36
  ```ts
@@ -27,5 +27,10 @@ Mado CRUD, не перетворюючи його на React у tagged templates
27
27
  resources, mutations, invalidation, `queryParam`, `computed`, `signal` і
28
28
  keyed lists.
29
29
 
30
+ CI запускає `npm run llm:smoke` як детермінований proxy для цієї задачі:
31
+ перевіряє, що `llms.txt` містить ключові правила, звіряє закомічений артефакт
32
+ `examples/tickets` з потрібною Mado API surface та failure patterns, потім
33
+ збирає проєкт і запускає `test/tickets-smoke.test.mjs`.
34
+
30
35
  Критерій успіху: код виглядає як Mado, а не як React/Vue, переодягнений у
31
36
  template strings.
@@ -0,0 +1,61 @@
1
+ # Карта замороження API
2
+
3
+ > Що є публічним, що внутрішнім, і що SemVer захищатиме у v1.
4
+
5
+ Контракт Mado v1 навмисно невеликий. Код застосунку імпортує API з кореня
6
+ пакета:
7
+
8
+ ```ts
9
+ import { component, html, resource, routes, signal } from "@madojs/mado";
10
+ ```
11
+
12
+ Єдиний публічний subpath — side-effect модуль devtools:
13
+
14
+ ```ts
15
+ import "@madojs/mado/devtools.js";
16
+ ```
17
+
18
+ Усе інше під `dist/src/` — деталь реалізації, навіть якщо файл видно в
19
+ репозиторії.
20
+
21
+ ## Стабільний публічний API
22
+
23
+ Ці імена публічні й захищаються SemVer після v1:
24
+
25
+ - Reactivity: `signal`, `computed`, `effect`, `untracked`, `batch`,
26
+ `flushSync`.
27
+ - Templates і directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
28
+ `ref`, `classMap`, `styleMap`.
29
+ - Components і CSS: `component`, `css`, `cssVars`.
30
+ - Routing і pages: `routes`, `router`, `page`, `layout`, `nested`,
31
+ `navigate`, `queryParam`, `prefetchPath`.
32
+ - Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
+ - Forms: `useForm`.
34
+ - Head і persistence: `applyHead`, `persisted`.
35
+ - Context: `createContext`, `provide`, `inject`.
36
+ - Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
37
+ `getCurrentLifecycle`.
38
+ - Публічні TypeScript-типи, експортовані з `@madojs/mado`.
39
+
40
+ ## Внутрішнє або нестабільне
41
+
42
+ Це не публічний API:
43
+
44
+ - Package subpaths крім `@madojs/mado` і `@madojs/mado/devtools.js`.
45
+ - Internals parser/binding: `html/parser.js`, `html/bindings.js`,
46
+ `ChildState`, `EachEntry`.
47
+ - Internals router: `router/match.js`, `router/navigation.js`,
48
+ `router/manifest.js`.
49
+ - Diagnostics internals і всі `_testHooks`.
50
+ - Точний текст bundle, назви chunks і внутрішня структура файлів.
51
+
52
+ Тести репозиторію можуть імпортувати internal files через відносні шляхи
53
+ `dist/`. Код застосунків не повинен цього робити.
54
+
55
+ ## Що може змінюватися
56
+
57
+ Patch і minor releases можуть додавати root exports, options, diagnostics, docs
58
+ або starter files. Вони також можуть змінювати internals, форму bundle і деталі
59
+ реалізації, якщо stable API та задокументована поведінка лишаються сумісними.
60
+
61
+ Breaking changes стабільного API потребують major version.
@@ -0,0 +1,95 @@
1
+ # Порядок reactivity
2
+
3
+ > Малий набір ordering-гарантій, які Mado вважає публічною поведінкою.
4
+
5
+ Reactivity у Mado синхронна для читання і планована для side effects. Мета —
6
+ передбачувані UI updates без великої scheduling-моделі.
7
+
8
+ ## Signals
9
+
10
+ `signal(value)` повертає getter-функцію. `set(next)` змінює значення одразу,
11
+ якщо `Object.is(previous, next)` не дорівнює `true`.
12
+
13
+ ```ts
14
+ const count = signal(0);
15
+ count.set(1);
16
+ count(); // 1, одразу
17
+ ```
18
+
19
+ Computed values позначаються до запуску effects, тому effect, який читає
20
+ computed, бачить актуальні dependencies, а не застарілий cache.
21
+
22
+ ## Effects
23
+
24
+ `effect(fn)` запускається один раз одразу. Подальші зміни dependencies планують
25
+ один запуск effect у microtask. Тести можуть викликати `flushSync()`, щоб
26
+ синхронно очистити чергу.
27
+
28
+ Якщо effect повертає cleanup-функцію, Mado запускає її перед наступним запуском
29
+ effect і ще раз при виклику disposer.
30
+
31
+ ```ts
32
+ const stop = effect(() => {
33
+ const id = setInterval(tick, 1000);
34
+ return () => clearInterval(id);
35
+ });
36
+
37
+ stop();
38
+ ```
39
+
40
+ У компонентах і pages для unmount cleanup надавайте перевагу
41
+ `ctx.onDispose()` / page `onDispose()`. Cleanup effect — це cleanup між runs.
42
+
43
+ ## Batch
44
+
45
+ `batch(fn)` групує записи signals в один subscriber pass. Effects не
46
+ запускаються до виходу з найзовнішнього batch, включно з вкладеними batches.
47
+
48
+ ```ts
49
+ batch(() => {
50
+ first.set("Ada");
51
+ batch(() => last.set("Lovelace"));
52
+ });
53
+ // effects бачать тільки фінальну пару
54
+ ```
55
+
56
+ Спостережувані `computed({ equals })` також зберігають batch atomicity: вони
57
+ перераховуються один раз після зовнішнього batch на повністю застосованому
58
+ state. Вони не повинні бачити напівзастосований batch на кшталт
59
+ `(new x, old y)`.
60
+
61
+ ## DOM updates
62
+
63
+ `render(result, container)` перевикористовує наявний template instance, коли
64
+ наступний render має ті самі template strings. Для child bindings, що
65
+ повертають вкладений `html```, діє те саме правило: ті самі strings оновлюються
66
+ на місці, інші strings перебудовують гілку.
67
+
68
+ Це означає, що непов'язані зміни signals не пересоздають `<input>` у
69
+ стабільному вкладеному template, тому focus, DOM state і listeners
70
+ зберігаються.
71
+
72
+ Списки повинні використовувати `each(items, key, renderItem)`. Keys задають DOM
73
+ identity. Duplicate keys попереджають у development і отримують positional
74
+ suffix, щоб кожен item все одно рендерився, але duplicate keys — це data bug.
75
+
76
+ ## Teardown компонентів
77
+
78
+ Custom elements можуть отримати `disconnectedCallback()`, а потім
79
+ `connectedCallback()` під час same-tick move. Mado відкладає teardown компонента
80
+ до microtask і скасовує його при reconnect, тому keyed reorders зберігають
81
+ state компонента. Справжнє видалення все одно запускає lifecycle cleanup на
82
+ наступній microtask.
83
+
84
+ ## Не гарантується
85
+
86
+ Mado не гарантує точну кількість internal scheduler microtasks, порядок
87
+ незалежних effects без спільних dependencies, форму generated bundle або
88
+ внутрішній module layout. Це implementation details.
89
+
90
+ Invariant tests для цього контракту:
91
+
92
+ - `test/reactivity-ordering.test.mjs`
93
+ - `test/signal-batch-equals.test.mjs`
94
+ - `test/update-nested-reuse.test.mjs`
95
+ - `test/each-component-state.test.mjs`