@madojs/mado 0.7.0 → 0.9.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 (41) hide show
  1. package/AGENTS.md +29 -3
  2. package/CHANGELOG.md +164 -1
  3. package/README.md +168 -242
  4. package/ROADMAP.md +174 -79
  5. package/TODO.md +8 -5
  6. package/dist/src/component.js +48 -3
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/forms.js +21 -1
  9. package/dist/src/forms.js.map +1 -1
  10. package/dist/src/html/bindings.js +32 -3
  11. package/dist/src/html/bindings.js.map +1 -1
  12. package/dist/src/html/parser.js +60 -3
  13. package/dist/src/html/parser.js.map +1 -1
  14. package/dist/src/lifecycle.js +18 -0
  15. package/dist/src/lifecycle.js.map +1 -1
  16. package/dist/src/page.d.ts +12 -0
  17. package/dist/src/page.js.map +1 -1
  18. package/dist/src/persisted.js +43 -9
  19. package/dist/src/persisted.js.map +1 -1
  20. package/dist/src/resource.d.ts +10 -0
  21. package/dist/src/resource.js +24 -6
  22. package/dist/src/resource.js.map +1 -1
  23. package/dist/src/router/manifest.js +29 -3
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/navigation.js +56 -2
  26. package/dist/src/router/navigation.js.map +1 -1
  27. package/dist/src/signal.js +55 -7
  28. package/dist/src/signal.js.map +1 -1
  29. package/docs/en/00-the-mado-way.md +23 -12
  30. package/docs/en/05-why-mado.md +78 -68
  31. package/docs/en/06-for-backenders.md +75 -55
  32. package/docs/en/07-llm-pitfalls.md +101 -0
  33. package/docs/fr/00-the-mado-way.md +25 -13
  34. package/docs/fr/07-llm-pitfalls.md +2 -0
  35. package/docs/ru/00-the-mado-way.md +24 -11
  36. package/docs/ru/07-llm-pitfalls.md +2 -0
  37. package/docs/uk/00-the-mado-way.md +3 -1
  38. package/docs/uk/07-llm-pitfalls.md +2 -0
  39. package/llms.txt +7 -5
  40. package/package.json +3 -3
  41. package/scripts/bundle.mjs +6 -6
@@ -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.
@@ -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.
@@ -2,7 +2,11 @@
2
2
 
3
3
  > Один правильный путь. Жёсткие контракты. Никакой магии.
4
4
 
5
- Mado — это не просто фреймворк, это **набор соглашений**. Если ты следуешь им, проект остаётся понятным даже когда в нём 200 экранов и 5 разработчиков. Если нарушаешь — типы и линтер скажут об этом сразу.
5
+ Mado — фреймворк для команд, которые строят админки, внутренние инструменты
6
+ и бизнес-SPA — приложения, которые должны быть просты в разработке и скучны
7
+ в поддержке. Для этого он задаёт **набор соглашений**. Если ты следуешь им,
8
+ проект остаётся понятным даже когда в нём 200 экранов и 5 разработчиков. Если
9
+ нарушаешь — типы и линтер скажут об этом сразу.
6
10
 
7
11
  ## Принципы
8
12
 
@@ -32,13 +36,21 @@ src/
32
36
 
33
37
  ```ts
34
38
  // src/components/user-card.ts
35
- import { component, html, css } from '@madojs/mado';
36
-
37
- component('x-user-card', () => {
38
- return () => html`<div class="card"><slot/></div>`;
39
- }, {
40
- styles: css`.card { padding: 1rem; }`,
41
- });
39
+ import { component, html, css } from "@madojs/mado";
40
+
41
+ component(
42
+ "x-user-card",
43
+ () => {
44
+ return () => html`<div class="card"><slot /></div>`;
45
+ },
46
+ {
47
+ styles: css`
48
+ .card {
49
+ padding: 1rem;
50
+ }
51
+ `,
52
+ },
53
+ );
42
54
  ```
43
55
 
44
56
  Импорт `import './components/user-card.js'` **регистрирует** компонент через `customElements.define`. Это side-effect. Где компонент нужен — там и импортируем.
@@ -52,7 +64,7 @@ component('x-user-card', () => {
52
64
  const user = resource(() => `/api/users/${id()}`, jsonFetcher());
53
65
 
54
66
  // запись → mutation
55
- const save = mutation(api.save, { invalidates: ['/api/users*'] });
67
+ const save = mutation(api.save, { invalidates: ["/api/users*"] });
56
68
  ```
57
69
 
58
70
  Это даёт кеш, отмену, обработку ошибок, авто-инвалидацию.
@@ -61,11 +73,11 @@ const save = mutation(api.save, { invalidates: ['/api/users*'] });
61
73
 
62
74
  ```ts
63
75
  // src/pages/user-profile.ts
64
- import { page, html, resource, jsonFetcher } from '@madojs/mado';
76
+ import { page, html, resource, jsonFetcher } from "@madojs/mado";
65
77
 
66
78
  export default page({
67
79
  title: ({ id }) => `User #${id}`,
68
- view: ({ params }) => html`...`,
80
+ view: ({ params }) => html`...`,
69
81
  });
70
82
  ```
71
83
 
@@ -87,6 +99,7 @@ export default page({
87
99
  ## Когда сомневаешься
88
100
 
89
101
  Если ты задаёшься вопросом "а как тут лучше?" — это сигнал, что:
102
+
90
103
  1. Либо есть встроенный хелпер, который ты не знаешь (загляни в `docs/`).
91
104
  2. Либо это новая ситуация — её надо обсудить и **зафиксировать** в этом документе как ещё одно соглашение.
92
105
 
@@ -618,5 +618,7 @@ component("x-input", ({ host, attr }) => {
618
618
  | `@customElement('x')` | `component('x-name', setup)` |
619
619
  | `host.getAttribute('x')` в render | `ctx.attr('x', default)` (реактивно) |
620
620
  | `jsonFetcher()` с авторизацией | `apiFetcher()` (прикрепляет Bearer токен) |
621
+ | `setInterval` в page view | `onDispose(() => clearInterval(id))` |
622
+ | чтение сигнала в async init view() | `untracked(() => cursor())` |
621
623
 
622
624
  Если что-то не подходит из этого списка — открой `src/` и **прочитай 500 строк**. Это серьёзно. Mado специально маленький, чтобы быть читаемым.
@@ -2,7 +2,9 @@
2
2
 
3
3
  > Один зрозумілий шлях. Жорсткі контракти. Мінімум магії.
4
4
 
5
- Mado — це не лише набір API, а набір домовленостей. Якщо їх дотримуватись,
5
+ Mado — фреймворк для команд, що будують адмін-панелі, внутрішні інструменти
6
+ та бізнес-SPA — застосунки, які мають бути простими у розробці та нудними в
7
+ підтримці. Для цього він задає **набір домовленостей**. Якщо їх дотримуватись,
6
8
  проєкт залишається читабельним навіть тоді, коли в ньому десятки сторінок і
7
9
  кілька розробників.
8
10
 
@@ -141,3 +141,5 @@ Object.defineProperty(host, "value", {
141
141
  | `class extends HTMLElement` | `component('x-name', setup)` |
142
142
  | `host.getAttribute('x')` | `ctx.attr('x', default)` |
143
143
  | `jsonFetcher()` з auth | `apiFetcher()` |
144
+ | `setInterval` в page view | `onDispose(() => clearInterval(id))` |
145
+ | читання сигналу в async init | `untracked(() => cursor())` |
package/llms.txt CHANGED
@@ -1,9 +1,9 @@
1
1
  # Mado
2
2
 
3
- > Small native-web SPA framework on the platform (Web Components + signals + tagged-template `html`).
4
- > No build step (only `tsc`), no runtime dependencies, readable in an evening.
3
+ > A calm browser-native SPA framework for internal tools, admin panels and business apps.
4
+ > Routing, forms, state, data fetching and prerendering. TypeScript-only build (`tsc`), zero runtime dependencies.
5
5
 
6
- Mado is a narrowly-focused frontend framework that deliberately avoids React patterns (no JSX, no hooks, no VDOM, no Vite). Target audience: backend developers and senior frontend engineers who are tired of the infrastructure complexity of React/Next.
6
+ Mado is a focused frontend framework for admin panels, internal tools and CRUD-heavy SPA. It deliberately avoids React patterns (no JSX, no hooks, no VDOM, no Vite). Target audience: backend developers and small teams who want a complete app stack without frontend infrastructure overhead.
7
7
 
8
8
  ## Key things an AI assistant needs to know
9
9
 
@@ -13,7 +13,9 @@ Mado is a narrowly-focused frontend framework that deliberately avoids React pat
13
13
  - **Components are Web Components.** Registered via `component('x-name', setupFn, options)`. Names must include a hyphen (`x-foo`, `my-button`).
14
14
  - **Component files register tags as side effects.** The browser does not auto-import files by tag name. If `<x-card>` works, some imported module already ran `customElements.define("x-card", ...)`.
15
15
  - **Cleanup via `ctx.onDispose(fn)`** in setup, not via return from effect.
16
- - **Reactive attributes via `ctx.attr(name, default?)`** returns a Signal<string> that auto-updates when the attribute changes. No MutationObserver needed.
16
+ - **Page cleanup via `onDispose`** in view: `view: ({ onDispose }) => { ... onDispose(() => cleanup()); }`. Only needed for raw APIs (setInterval, WebSocket). `resource()`/`effect()` auto-cleanup.
17
+ - **`untracked()` in page view async** — functions called synchronously in `view()` that read signals must wrap reads in `untracked()` to avoid effect cycles with the router.
18
+ - **Reactive attributes via `ctx.attr(name, default?)`** — returns a Signal<string> that auto-updates when the attribute changes. Uses `observedAttributes` when available, falls back to a per-instance `MutationObserver` for attrs registered during setup. No boilerplate needed.
17
19
 
18
20
  ## Critical template rules
19
21
 
@@ -219,7 +221,7 @@ export default page({
219
221
 
220
222
  ## Version
221
223
 
222
- `0.7.0` — pre-1.0 product-surface release. API may still change before 1.0.
224
+ `0.8.0` — pre-1.0 product-surface release. API may still change before 1.0.
223
225
  Semver is not guaranteed on minor versions before 1.0.
224
226
 
225
227
  ## License
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@madojs/mado",
3
- "version": "0.7.0",
4
- "description": "Mado — a small native-web SPA framework with Web Components, signals, tagged-template html, router, resources, and forms. TypeScript-only build, zero runtime dependencies.",
3
+ "version": "0.9.0",
4
+ "description": "Mado — a calm browser-native SPA framework for internal tools, admin panels and business apps. Routing, forms, state and data fetching without frontend infrastructure overhead.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -80,4 +80,4 @@
80
80
  "engines": {
81
81
  "node": ">=20"
82
82
  }
83
- }
83
+ }
@@ -120,14 +120,14 @@ const result = await build({
120
120
  legalComments: "none",
121
121
  });
122
122
 
123
- const entryOutput = Object.entries(result.metafile.outputs).find(
124
- ([name, info]) => info.entryPoint && name.endsWith(".js"),
125
- );
126
- if (!entryOutput) {
127
- console.error("[bundle] entry not found in outputs");
123
+ // With splitting: true, esbuild marks all dynamic-import chunks as having
124
+ // entryPoint. We identify the real app entry by the `entryNames` prefix "main-".
125
+ const mainBundle = (await readdir(ASSETS_DIR))
126
+ .find((f) => f.startsWith("main-") && f.endsWith(".js") && !f.endsWith(".js.map"));
127
+ if (!mainBundle) {
128
+ console.error("[bundle] entry not found in outputs (no main-*.js in assets dir)");
128
129
  process.exit(1);
129
130
  }
130
- const mainBundle = basename(entryOutput[0]);
131
131
 
132
132
  // Collect all js chunks in the assets dir.
133
133
  const allJs = (await readdir(ASSETS_DIR)).filter((f) => f.endsWith(".js"));