@madojs/mado 0.11.0 → 0.12.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 +102 -25
- package/CHANGELOG.md +185 -1
- package/README.md +137 -172
- package/dist/src/component.d.ts +3 -2
- package/dist/src/component.js +98 -6
- package/dist/src/component.js.map +1 -1
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/head.d.ts +6 -6
- package/dist/src/head.js +6 -6
- package/dist/src/html/bindings.js +3 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +1 -1
- package/dist/src/html/parser.js +1 -1
- package/dist/src/html/template.js +24 -10
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +8 -4
- package/dist/src/index.js +13 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +62 -28
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/resource.js +2 -1
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +65 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.js +75 -31
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +16 -2
- package/dist/src/router/match.js +41 -1
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/static-runtime.d.ts +81 -0
- package/dist/src/static-runtime.js +209 -0
- package/dist/src/static-runtime.js.map +1 -0
- package/dist/src/vite/index.d.ts +31 -3
- package/dist/src/vite/index.js +49 -18
- package/dist/src/vite/index.js.map +1 -1
- package/docs/README.md +5 -8
- package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
- package/docs/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +271 -0
- package/docs/en/11-templates-and-signals.md +211 -0
- package/docs/en/12-routing.md +229 -0
- package/docs/en/13-data.md +225 -0
- package/docs/en/14-forms.md +244 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +53 -2
- package/docs/en/{13-deployment.md → 20-deployment.md} +59 -10
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +12 -3
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +3 -3
- package/docs/en/40-llm-guide.md +776 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +15 -16
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +1 -1
- package/docs/en/README.md +97 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +246 -211
- package/package.json +17 -10
- package/scripts/bake.mjs +4 -568
- package/scripts/cli/generate.mjs +55 -5
- package/scripts/cli/help.mjs +11 -5
- package/scripts/cli/index.mjs +20 -4
- package/scripts/cli/init.mjs +7 -2
- package/scripts/cli/release.mjs +20 -68
- package/scripts/docs-lint.mjs +170 -0
- package/scripts/package-smoke.mjs +67 -9
- package/scripts/preview.mjs +95 -6
- package/scripts/size-budget.mjs +5 -1
- package/scripts/static/browser.mjs +654 -0
- package/scripts/static/discover.mjs +281 -0
- package/scripts/static/output.mjs +141 -0
- package/scripts/static/serialize.mjs +212 -0
- package/scripts/static/server.mjs +167 -0
- package/scripts/static.mjs +191 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +3 -9
- package/starters/default/src/app.routes.ts +20 -33
- package/starters/default/src/components/feature-card.component.ts +40 -0
- package/starters/default/src/components/live-counter.component.ts +57 -0
- package/starters/default/src/content/guides.ts +55 -0
- package/starters/default/src/main.ts +9 -13
- package/starters/default/src/pages/app.page.ts +41 -0
- package/starters/default/src/pages/guide.page.ts +40 -0
- package/starters/default/src/pages/home.page.ts +49 -0
- package/starters/default/src/pages/not-found.page.ts +18 -0
- package/starters/default/src/styles/document.css +39 -0
- package/starters/default/src/styles/reset.css +37 -0
- package/starters/default/src/styles/tokens.css +30 -0
- package/starters/default/src/styles.d.ts +3 -1
- package/starters/default/src/vite-env.d.ts +1 -1
- package/starters/default/vite.config.ts +14 -1
- package/starters/modular/README.md +142 -0
- package/starters/modular/index.html +13 -0
- package/starters/modular/package.json +30 -0
- package/starters/modular/src/app.routes.ts +39 -0
- package/starters/{default → modular}/src/layouts/app-shell.layout.ts +8 -5
- package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
- package/starters/modular/src/main.ts +16 -0
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +3 -3
- package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
- package/starters/modular/src/modules/home/not-found.page.ts +14 -0
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +24 -0
- package/starters/modular/vite.config.ts +131 -0
- package/TODO.md +0 -79
- package/docs/en/01-routing.md +0 -152
- package/docs/en/02-project-layout.md +0 -124
- package/docs/en/03-static-bake.md +0 -249
- package/docs/en/04-ide-setup.md +0 -162
- package/docs/en/07-llm-pitfalls.md +0 -724
- package/docs/en/08-llm-zero-history-test.md +0 -56
- package/docs/en/09-shadow-vs-light-dom.md +0 -174
- package/docs/en/11-layouts.md +0 -113
- package/docs/en/12-auth-and-api.md +0 -124
- package/docs/en/16-bake-cookbook.md +0 -100
- package/docs/en/17-shadow-dom-forms.md +0 -192
- package/docs/fr/00-the-mado-way.md +0 -85
- package/docs/fr/01-routing.md +0 -120
- package/docs/fr/02-project-layout.md +0 -84
- package/docs/fr/03-static-bake.md +0 -289
- package/docs/fr/04-ide-setup.md +0 -162
- package/docs/fr/05-why-mado.md +0 -193
- package/docs/fr/06-for-backenders.md +0 -438
- package/docs/fr/07-llm-pitfalls.md +0 -625
- package/docs/fr/08-llm-zero-history-test.md +0 -38
- package/docs/fr/09-shadow-vs-light-dom.md +0 -65
- package/docs/fr/10-app-architecture.md +0 -138
- package/docs/fr/11-layouts.md +0 -47
- package/docs/fr/12-auth-and-api.md +0 -76
- package/docs/fr/13-deployment.md +0 -57
- package/docs/fr/14-testing.md +0 -41
- package/docs/fr/15-error-handling.md +0 -50
- package/docs/fr/16-bake-cookbook.md +0 -88
- package/docs/fr/17-shadow-dom-forms.md +0 -196
- package/docs/fr/18-api-freeze-map.md +0 -63
- package/docs/fr/19-reactivity-ordering.md +0 -97
- package/docs/fr/20-v1-stability.md +0 -88
- package/docs/fr/README.md +0 -27
- package/docs/ru/00-the-mado-way.md +0 -84
- package/docs/ru/01-routing.md +0 -119
- package/docs/ru/02-project-layout.md +0 -84
- package/docs/ru/03-static-bake.md +0 -250
- package/docs/ru/04-ide-setup.md +0 -144
- package/docs/ru/05-why-mado.md +0 -193
- package/docs/ru/06-for-backenders.md +0 -428
- package/docs/ru/07-llm-pitfalls.md +0 -624
- package/docs/ru/08-llm-zero-history-test.md +0 -57
- package/docs/ru/09-shadow-vs-light-dom.md +0 -63
- package/docs/ru/10-app-architecture.md +0 -152
- package/docs/ru/11-layouts.md +0 -47
- package/docs/ru/12-auth-and-api.md +0 -75
- package/docs/ru/13-deployment.md +0 -66
- package/docs/ru/14-testing.md +0 -50
- package/docs/ru/15-error-handling.md +0 -56
- package/docs/ru/16-bake-cookbook.md +0 -95
- package/docs/ru/17-shadow-dom-forms.md +0 -193
- package/docs/ru/18-api-freeze-map.md +0 -64
- package/docs/ru/19-reactivity-ordering.md +0 -95
- package/docs/ru/20-v1-stability.md +0 -82
- package/docs/ru/README.md +0 -25
- package/docs/uk/00-the-mado-way.md +0 -82
- package/docs/uk/01-routing.md +0 -76
- package/docs/uk/02-project-layout.md +0 -73
- package/docs/uk/03-static-bake.md +0 -48
- package/docs/uk/04-ide-setup.md +0 -26
- package/docs/uk/05-why-mado.md +0 -34
- package/docs/uk/06-for-backenders.md +0 -55
- package/docs/uk/07-llm-pitfalls.md +0 -145
- package/docs/uk/08-llm-zero-history-test.md +0 -34
- package/docs/uk/09-shadow-vs-light-dom.md +0 -58
- package/docs/uk/10-app-architecture.md +0 -97
- package/docs/uk/11-layouts.md +0 -47
- package/docs/uk/12-auth-and-api.md +0 -70
- package/docs/uk/13-deployment.md +0 -40
- package/docs/uk/14-testing.md +0 -34
- package/docs/uk/15-error-handling.md +0 -32
- package/docs/uk/16-bake-cookbook.md +0 -36
- package/docs/uk/17-shadow-dom-forms.md +0 -193
- package/docs/uk/18-api-freeze-map.md +0 -61
- package/docs/uk/19-reactivity-ordering.md +0 -95
- package/docs/uk/20-v1-stability.md +0 -83
- package/docs/uk/README.md +0 -27
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- /package/docs/en/{15-error-handling.md → 21-error-handling.md} +0 -0
- /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
- /package/starters/{default → modular}/.editorconfig +0 -0
- /package/starters/{default → modular}/eslint.config.mjs +0 -0
- /package/starters/{default → modular}/public/favicon.svg +0 -0
- /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/login.page.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
- /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
- /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/reset.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-button.component.ts +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
# Mado · LLM pitfalls
|
|
2
|
-
|
|
3
|
-
> Типичные ошибки, которые AI-ассистенты (Copilot, Claude, ChatGPT, Cursor)
|
|
4
|
-
> делают при генерации Mado-кода. И как их исправлять.
|
|
5
|
-
|
|
6
|
-
Этот документ — для **двух аудиторий**:
|
|
7
|
-
|
|
8
|
-
1. **AI-агентов в IDE**, которые читают `AGENTS.md` / `.cursorrules` / `.github/copilot-instructions.md`. Здесь больше деталей по типичным граблям.
|
|
9
|
-
2. **Людей**, которые получили от AI код с этими ошибками и не понимают, что не так.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Pitfall #1: `${signal()}` вместо `${() => signal()}`
|
|
14
|
-
|
|
15
|
-
**Симптом:** значение в шаблоне отображается, но не обновляется при изменении сигнала.
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
const count = signal(0);
|
|
19
|
-
|
|
20
|
-
// ❌ AI генерирует это часто
|
|
21
|
-
html`<div>Count: ${count() * 2}</div>`;
|
|
22
|
-
// → Отрисует "Count: 0", и больше никогда не обновится.
|
|
23
|
-
// count() прочитан один раз в момент создания TemplateResult.
|
|
24
|
-
|
|
25
|
-
// ✅ Правильно — функция-геттер
|
|
26
|
-
html`<div>Count: ${() => count() * 2}</div>`;
|
|
27
|
-
// → Mado создаст effect() на эту функцию, при изменении count перерисует.
|
|
28
|
-
|
|
29
|
-
// ✅ Тоже правильно — сам сигнал является функцией
|
|
30
|
-
html`<div>Count: ${count}</div>`;
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Правило:**
|
|
34
|
-
|
|
35
|
-
- Если в `${...}` есть **выражение** (что-то делает с сигналом) — оборачивай в `() => ...`.
|
|
36
|
-
- Если в `${...}` **сам сигнал** — можно как есть.
|
|
37
|
-
|
|
38
|
-
Это работает для **child-биндингов** (текст внутри тегов) и для **value-атрибутов** (`@click`, `.prop`, `?attr`, обычных атрибутов).
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Pitfall #2: `<button disabled=${loading}>` вместо `?disabled`
|
|
43
|
-
|
|
44
|
-
**Симптом:** кнопка не disable'ится либо disable'ится всегда.
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
const loading = signal(false);
|
|
48
|
-
|
|
49
|
-
// ❌ Это setAttribute("disabled", "false") — DOM воспринимает это как disabled
|
|
50
|
-
html`<button disabled=${loading()}>Save</button>`;
|
|
51
|
-
|
|
52
|
-
// ✅ Правильно — boolean-биндинг (toggle attribute)
|
|
53
|
-
html`<button ?disabled=${loading}>Save</button>`;
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Правило для атрибутов:**
|
|
57
|
-
| Префикс | Что делает | Когда использовать |
|
|
58
|
-
|---|---|---|
|
|
59
|
-
| `attr=` | `setAttribute("attr", value)` | строки, числа, URL |
|
|
60
|
-
| `.attr=` | `el.attr = value` (DOM property) | объекты, массивы, `.value` инпута |
|
|
61
|
-
| `?attr=` | toggle attribute (по truthy) | `disabled`, `hidden`, `checked`, etc |
|
|
62
|
-
| `@evt=` | `addEventListener("evt", fn)` | обработчики событий |
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Pitfall #3: useState / useEffect-стиль
|
|
67
|
-
|
|
68
|
-
**Симптом:** AI генерирует React-подобный код, который не работает в Mado.
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
// ❌ AI часто это пишет
|
|
72
|
-
function Counter() {
|
|
73
|
-
const [count, setCount] = useState(0);
|
|
74
|
-
useEffect(() => { console.log(count); }, [count]);
|
|
75
|
-
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ✅ Правильно в Mado
|
|
79
|
-
import { component, signal, effect, html } from "@madojs/mado";
|
|
80
|
-
|
|
81
|
-
component("x-counter", () => {
|
|
82
|
-
const count = signal(0);
|
|
83
|
-
effect(() => console.log(count())); // auto-subscribe, dispose автоматически
|
|
84
|
-
return () => html`
|
|
85
|
-
<button @click=${() => count.update(c => c + 1)}>${count}</button>
|
|
86
|
-
`;
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
**Ключевые отличия:**
|
|
91
|
-
|
|
92
|
-
- Нет хуков, нет правил хуков.
|
|
93
|
-
- `signal()` можно создавать где угодно — в setup, в effect, в обработчике.
|
|
94
|
-
- `effect()` сам видит, что прочитал — не нужен dependency array.
|
|
95
|
-
- Компонент = `component("x-name", setup)`, не JSX-функция.
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Pitfall #4: `useEffect(() => { ... return cleanup })`
|
|
100
|
-
|
|
101
|
-
**Симптом:** AI пишет `return cleanup` в effect, ожидая что это сработает как в React.
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
// ❌ AI пытается это написать
|
|
105
|
-
component("x-timer", () => {
|
|
106
|
-
effect(() => {
|
|
107
|
-
const id = setInterval(..., 1000);
|
|
108
|
-
return () => clearInterval(id); // НЕ сработает, нужно через onDispose
|
|
109
|
-
});
|
|
110
|
-
return () => html`...`;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// ✅ Правильно: cleanup через ctx.onDispose
|
|
114
|
-
component("x-timer", (ctx) => {
|
|
115
|
-
const id = setInterval(..., 1000);
|
|
116
|
-
ctx.onDispose(() => clearInterval(id));
|
|
117
|
-
return () => html`...`;
|
|
118
|
-
});
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**Примечание:** `effect()` действительно поддерживает `return cleanup`, но это **per-run cleanup** (выполнится при следующем прогоне effect'а), а не при unmount. Для unmount-cleanup используй `ctx.onDispose`.
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Pitfall #5: Компонент как класс или с декоратором
|
|
126
|
-
|
|
127
|
-
**Симптом:** AI генерирует Lit-style или vanilla WebComponent класс.
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
// ❌ AI: "сделаем как в Lit"
|
|
131
|
-
import { LitElement, html } from "lit";
|
|
132
|
-
import { customElement, property } from "lit/decorators.js";
|
|
133
|
-
|
|
134
|
-
@customElement('x-counter')
|
|
135
|
-
class XCounter extends LitElement { ... }
|
|
136
|
-
|
|
137
|
-
// ❌ AI: "сделаем как vanilla"
|
|
138
|
-
class XCounter extends HTMLElement {
|
|
139
|
-
connectedCallback() { ... }
|
|
140
|
-
}
|
|
141
|
-
customElements.define("x-counter", XCounter);
|
|
142
|
-
|
|
143
|
-
// ✅ Правильно: функциональный component()
|
|
144
|
-
import { component, html, signal } from "@madojs/mado";
|
|
145
|
-
|
|
146
|
-
component("x-counter", () => {
|
|
147
|
-
const count = signal(0);
|
|
148
|
-
return () => html`<button @click=${() => count.update(n => n + 1)}>${count}</button>`;
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Pitfall #6: импорт без расширения `.js`
|
|
155
|
-
|
|
156
|
-
**Симптом:** TypeScript компилирует, но в браузере 404.
|
|
157
|
-
|
|
158
|
-
```ts
|
|
159
|
-
// ❌ AI часто опускает расширение
|
|
160
|
-
import { foo } from "./bar";
|
|
161
|
-
import { Home } from "./pages/home";
|
|
162
|
-
|
|
163
|
-
// ✅ Правильно: ES-модули в браузере требуют расширение
|
|
164
|
-
import { foo } from "./bar.js";
|
|
165
|
-
import { Home } from "./pages/home.js";
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
**Почему `.js`, а не `.ts`:** в браузер уходит уже скомпилированный JS. TypeScript достаточно умён, чтобы понимать `./bar.js` как ссылку на `./bar.ts` при компиляции.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Pitfall #7: списки через `.map()` без ключей
|
|
173
|
-
|
|
174
|
-
**Симптом:** при перестановке элементов теряется фокус инпутов / ломаются CSS-анимации / тормозит на больших списках.
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
// ❌ Работает, но не keyed: пересоздаёт DOM на каждое изменение
|
|
178
|
-
html`<ul>
|
|
179
|
-
${() => items().map((t) => html`<li>${t.name}</li>`)}
|
|
180
|
-
</ul>`;
|
|
181
|
-
|
|
182
|
-
// ✅ Правильно: each() с key-функцией
|
|
183
|
-
import { each } from "@madojs/mado";
|
|
184
|
-
html`<ul>
|
|
185
|
-
${() =>
|
|
186
|
-
each(
|
|
187
|
-
items(),
|
|
188
|
-
(t) => t.id,
|
|
189
|
-
(t) => html`<li>${t.name}</li>`,
|
|
190
|
-
)}
|
|
191
|
-
</ul>`;
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
**Правило:** всегда используй `each()` для списков из массивов с устойчивыми ID. `.map()` оставь только для статичных списков.
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Pitfall #8: `signal.value` или `count.get()`
|
|
199
|
-
|
|
200
|
-
**Симптом:** AI пишет API в стиле Vue или Solid pre-v1.
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
const count = signal(0);
|
|
204
|
-
|
|
205
|
-
// ❌ Нет такого API
|
|
206
|
-
count.value;
|
|
207
|
-
count.value = 5;
|
|
208
|
-
count.get();
|
|
209
|
-
|
|
210
|
-
// ✅ Правильно
|
|
211
|
-
count(); // прочитать
|
|
212
|
-
count.set(5); // записать
|
|
213
|
-
count.update((n) => n + 1);
|
|
214
|
-
count.peek(); // прочитать без подписки
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Pitfall #9: `provide(ApiCtx, value)` без host
|
|
220
|
-
|
|
221
|
-
**Симптом:** TypeError при попытке поднять контекст.
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
// ❌ AI забывает host
|
|
225
|
-
provide(ApiCtx, myApi);
|
|
226
|
-
inject(ApiCtx);
|
|
227
|
-
|
|
228
|
-
// ✅ Правильно: первый аргумент — host (текущий компонент)
|
|
229
|
-
component("x-app", ({ host }) => {
|
|
230
|
-
provide(host, ApiCtx, myApi);
|
|
231
|
-
return () => html`...`;
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
component("x-child", ({ host }) => {
|
|
235
|
-
const api = inject(host, ApiCtx); // signal<value>
|
|
236
|
-
return () => html`...`;
|
|
237
|
-
});
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Pitfall #10: ожидание SSR
|
|
243
|
-
|
|
244
|
-
**Симптом:** AI пишет код, предполагая, что страница пререндерится на сервере.
|
|
245
|
-
|
|
246
|
-
```ts
|
|
247
|
-
// ❌ Это работает только в браузере
|
|
248
|
-
const userId = location.pathname.split("/")[2];
|
|
249
|
-
|
|
250
|
-
// ❌ Это тоже только в браузере
|
|
251
|
-
if (typeof window !== "undefined") { ... } // в Mado window есть ВСЕГДА
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
Mado **не делает SSR с гидрацией**. На сервере код не выполняется — есть только `bake` (статический prerender на build) и edge-prerender. Оба заменяют user code на linkedom-окружение, но это **только** для генерации HTML с meta-тегами, не для выполнения логики страницы.
|
|
255
|
-
|
|
256
|
-
Это значит:
|
|
257
|
-
|
|
258
|
-
- ✅ `window`, `document`, `location`, `fetch` — доступны без проверок.
|
|
259
|
-
- ❌ Не пиши код, который пытается «универсально работать на сервере и клиенте».
|
|
260
|
-
- ❌ Не используй паттерны Next.js (`getServerSideProps`, `headers()`).
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## Pitfall #11: `useForm()` с zod/yup-резолвером
|
|
265
|
-
|
|
266
|
-
**Симптом:** AI хочет подключить валидатор.
|
|
267
|
-
|
|
268
|
-
```ts
|
|
269
|
-
// ❌ Нет такого API
|
|
270
|
-
const f = useForm({ resolver: zodResolver(schema) });
|
|
271
|
-
|
|
272
|
-
// ✅ Правильно: HTML5-валидация атрибутами
|
|
273
|
-
const f = useForm({
|
|
274
|
-
email: { required: true, type: "email" },
|
|
275
|
-
age: { required: true, type: "number", min: 18 },
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// ✅ Или кастомная функция, если HTML5 не хватает
|
|
279
|
-
const f = useForm(
|
|
280
|
-
{ name: { required: true } },
|
|
281
|
-
{
|
|
282
|
-
validate: (values) => {
|
|
283
|
-
const errors: Record<string, string> = {};
|
|
284
|
-
if (values.name && /\d/.test(values.name as string)) {
|
|
285
|
-
errors.name = "Имя не должно содержать цифры";
|
|
286
|
-
}
|
|
287
|
-
return Object.keys(errors).length ? errors : null;
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
);
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## Pitfall #12: Tailwind / styled-components / CSS Modules
|
|
296
|
-
|
|
297
|
-
**Симптом:** AI предлагает стандартные React-CSS-решения.
|
|
298
|
-
|
|
299
|
-
Mado использует **Shadow DOM + `css\`\`` + CSS variables**. Глобальные UI-фреймворки (Tailwind, Bootstrap-через-классы) **работают только в light DOM** (`shadow: false`):
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
// Light-DOM page/screen компонент, Tailwind-классы работают
|
|
303
|
-
component(
|
|
304
|
-
"x-admin-page",
|
|
305
|
-
() => () => html`
|
|
306
|
-
<section class="bg-white shadow-lg rounded-lg p-4">...</section>
|
|
307
|
-
`,
|
|
308
|
-
{ shadow: false },
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// Shadow-DOM компонент (default) — Tailwind НЕ работает.
|
|
312
|
-
// Используй css`` или ::part() для внешней стилизации.
|
|
313
|
-
component("x-button", () => () => html`<button><slot></slot></button>`, {
|
|
314
|
-
styles: css`
|
|
315
|
-
button {
|
|
316
|
-
background: var(--button-bg, #2563eb);
|
|
317
|
-
color: white;
|
|
318
|
-
padding: 0.5rem 1rem;
|
|
319
|
-
border-radius: 6px;
|
|
320
|
-
}
|
|
321
|
-
`,
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**Темы и кастомизация — через CSS variables**, а не классы.
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
## Pitfall #13: `import * as Mado from "@madojs/mado"`
|
|
330
|
-
|
|
331
|
-
**Симптом:** AI хочет namespace-import.
|
|
332
|
-
|
|
333
|
-
Это работает, но дублирует имена и плохо tree-shake'ится. Лучше named-import:
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
// ✅ Канонично
|
|
337
|
-
import { signal, html, component, css, page } from "@madojs/mado";
|
|
338
|
-
|
|
339
|
-
// ⚠️ Работает, но избыточно
|
|
340
|
-
import * as Mado from "@madojs/mado";
|
|
341
|
-
Mado.signal(0);
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
## Pitfall #14: попытка добавить runtime-зависимость
|
|
347
|
-
|
|
348
|
-
**Симптом:** AI предлагает `npm install lodash` / `npm install date-fns` / etc.
|
|
349
|
-
|
|
350
|
-
Mado — **zero runtime deps** by design. Если AI хочет добавить:
|
|
351
|
-
|
|
352
|
-
- **lodash** → используй нативный JS (`Object.entries`, `Array.prototype`, `structuredClone`);
|
|
353
|
-
- **date-fns** → используй `Intl.DateTimeFormat` и `Intl.RelativeTimeFormat`;
|
|
354
|
-
- **uuid** → `crypto.randomUUID()`;
|
|
355
|
-
- **axios** → нативный `fetch` + `jsonFetcher()` из Mado;
|
|
356
|
-
- **classnames** → нативный template literal или объект-mапа.
|
|
357
|
-
|
|
358
|
-
Любая runtime-зависимость — **нарушение принципа фреймворка**. Если без неё реально нельзя — добавляй в пользовательский проект, не в Mado core.
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## Pitfall #15: inline `<style>` внутри page-шаблонов
|
|
363
|
-
|
|
364
|
-
**Симптом:** AI кладёт большой `<style>` прямо в `html\`\`` страницы.
|
|
365
|
-
|
|
366
|
-
```ts
|
|
367
|
-
// ❌ Работает, но плохо масштабируется и усложняет cleanup
|
|
368
|
-
page({
|
|
369
|
-
view: () => html`
|
|
370
|
-
<style>
|
|
371
|
-
.panel {
|
|
372
|
-
padding: 1rem;
|
|
373
|
-
}
|
|
374
|
-
</style>
|
|
375
|
-
<section class="panel">...</section>
|
|
376
|
-
`,
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// ✅ Правильно: стили компонента через css``
|
|
380
|
-
component(
|
|
381
|
-
"x-admin-panel",
|
|
382
|
-
() => () => html` <section class="panel">...</section> `,
|
|
383
|
-
{
|
|
384
|
-
styles: css`
|
|
385
|
-
.panel {
|
|
386
|
-
padding: 1rem;
|
|
387
|
-
}
|
|
388
|
-
`,
|
|
389
|
-
},
|
|
390
|
-
);
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
Для backend-admin route/page экранов часто уместен `shadow: false`, чтобы
|
|
394
|
-
глобальные layout/form/table utilities работали как обычная админка. Но если
|
|
395
|
-
layout использует `<slot>` для проекции страницы внутрь shell, оставь layout в
|
|
396
|
-
Shadow DOM и держи shell-стили в `styles: css\`\``.
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## Pitfall #16: Shadow DOM links без `data-link`
|
|
401
|
-
|
|
402
|
-
**Симптом:** ссылка внутри Web Component перезагружает страницу или не
|
|
403
|
-
prefetch'ится.
|
|
404
|
-
|
|
405
|
-
```ts
|
|
406
|
-
// ❌ Обычная ссылка: браузер сделает full reload
|
|
407
|
-
html`<a href="/tickets/42">Open</a>`;
|
|
408
|
-
|
|
409
|
-
// ✅ SPA-навигация: router() перехватит click даже через Shadow DOM
|
|
410
|
-
html`<a href="/tickets/42" data-link>Open</a>`;
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
Mado ищет ссылку через `event.composedPath()`, поэтому `data-link` работает
|
|
414
|
-
и внутри Shadow DOM. Hover-prefetch использует тот же путь; `data-no-prefetch`
|
|
415
|
-
отключает prefetch для конкретной ссылки.
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
## Pitfall #17: `resource()` вне component setup
|
|
420
|
-
|
|
421
|
-
**Симптом:** AI создаёт resource в module scope, чтобы "переиспользовать"
|
|
422
|
-
данные между страницами.
|
|
423
|
-
|
|
424
|
-
```ts
|
|
425
|
-
// ❌ Нет lifecycle cleanup, будет dev-warning
|
|
426
|
-
const tickets = resource(
|
|
427
|
-
() => "tickets",
|
|
428
|
-
() => api.listTickets(),
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
component("x-tickets", () => {
|
|
432
|
-
return () => html`${() => tickets.data()?.length ?? 0}`;
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// ✅ Создавай resource внутри setup компонента
|
|
436
|
-
component("x-tickets", () => {
|
|
437
|
-
const tickets = resource(
|
|
438
|
-
() => "tickets",
|
|
439
|
-
() => api.listTickets(),
|
|
440
|
-
);
|
|
441
|
-
return () => html`${() => tickets.data()?.length ?? 0}`;
|
|
442
|
-
});
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Так подписки на invalidation, abort controller и effects будут очищены при
|
|
446
|
-
disconnect компонента.
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
## Pitfall #18: предположение, что nested templates не требуют cleanup
|
|
451
|
-
|
|
452
|
-
**Симптом:** AI собирает route outlet или conditional UI из вложенных
|
|
453
|
-
`TemplateResult`, а потом старые элементы продолжают жить ниже новой страницы.
|
|
454
|
-
|
|
455
|
-
```ts
|
|
456
|
-
const view = signal(html`<x-home></x-home>`);
|
|
457
|
-
|
|
458
|
-
// ✅ Нормальный паттерн: вложенный TemplateResult можно возвращать из child-binding
|
|
459
|
-
html`${view}`;
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
Начиная с v0.3 это закреплено регрессиями: при замене child-binding Mado
|
|
463
|
-
dispose'ит вложенные template instances/effects рекурсивно. Если видишь
|
|
464
|
-
накопление страниц в `#app`, это баг ядра, а не "нужно руками чистить DOM".
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## Pitfall #19: global CSS utilities внутри Shadow DOM
|
|
469
|
-
|
|
470
|
-
**Симптом:** страница выглядит “без стилей”: `.page-head`, `.btn`,
|
|
471
|
-
`.form-grid`, `.metric-grid` не применяются.
|
|
472
|
-
|
|
473
|
-
```ts
|
|
474
|
-
// ❌ .page-head объявлен глобально, но x-dashboard по умолчанию Shadow DOM
|
|
475
|
-
component(
|
|
476
|
-
"x-dashboard",
|
|
477
|
-
() => () => html`
|
|
478
|
-
<header class="page-head">...</header>
|
|
479
|
-
<div class="metric-grid">...</div>
|
|
480
|
-
`,
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
// ✅ Page/layout/admin-shell компоненты часто должны быть Light DOM
|
|
484
|
-
component(
|
|
485
|
-
"x-dashboard",
|
|
486
|
-
() => () => html`
|
|
487
|
-
<header class="page-head">...</header>
|
|
488
|
-
<div class="metric-grid">...</div>
|
|
489
|
-
`,
|
|
490
|
-
{ shadow: false },
|
|
491
|
-
);
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
Правило: Shadow DOM — для leaf widgets и slot-based layouts, Light DOM — для
|
|
495
|
-
route/page/admin-screen компонентов, которые намеренно используют общие
|
|
496
|
-
layout/form/table utilities. Не забывай: `<slot>` проецирует детей только в
|
|
497
|
-
Shadow DOM; при `shadow: false` это обычный элемент.
|
|
498
|
-
Подробнее: [`09-shadow-vs-light-dom.md`](./09-shadow-vs-light-dom.md).
|
|
499
|
-
|
|
500
|
-
---
|
|
501
|
-
|
|
502
|
-
## Pitfall #20: `host.getAttribute()` в render = не реактивно
|
|
503
|
-
|
|
504
|
-
**Симптом:** внешний вид компонента не обновляется при изменении атрибута родителем.
|
|
505
|
-
|
|
506
|
-
```ts
|
|
507
|
-
// ❌ host.getAttribute() в render-функции читается один раз, но
|
|
508
|
-
// render перезапускается только при изменении его собственных сигналов.
|
|
509
|
-
// Внешние изменения атрибута не триггерят перерисовку.
|
|
510
|
-
component("x-badge", ({ host }) => () => {
|
|
511
|
-
const variant = host.getAttribute("variant") ?? "default";
|
|
512
|
-
return html`<span class=${variant}>...</span>`;
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// ✅ Правильно: ctx.attr() — возвращает реактивный Signal<string>
|
|
516
|
-
component("x-badge", ({ attr }) => {
|
|
517
|
-
const variant = attr("variant", "default");
|
|
518
|
-
return () => html`<span class=${() => `badge-${variant()}`}>...</span>`;
|
|
519
|
-
});
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
**Правило:** никогда не читайте `host.getAttribute()` или `host.hasAttribute()` внутри
|
|
523
|
-
render-функции для значений, которые могут измениться снаружи. Используйте `ctx.attr()` —
|
|
524
|
-
он возвращает Signal, который автоматически обновляется через `attributeChangedCallback`.
|
|
525
|
-
|
|
526
|
-
---
|
|
527
|
-
|
|
528
|
-
## Pitfall #21: Shadow DOM `<button>` не сабмитит формы
|
|
529
|
-
|
|
530
|
-
**Симптом:** клик по `<x-button type="submit">` внутри `<form>` ничего не делает.
|
|
531
|
-
|
|
532
|
-
`<button>` внутри Shadow DOM не участвует в алгоритме form-owner для
|
|
533
|
-
`<form>` в Light DOM — это ограничение спецификации, не баг Mado.
|
|
534
|
-
|
|
535
|
-
```ts
|
|
536
|
-
// ❌ Внутренняя <button type="submit"> не может триггерить родительскую <form>
|
|
537
|
-
component("x-button", ({ host }) => {
|
|
538
|
-
return () => html`<button type="submit"><slot></slot></button>`;
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// ✅ Мост через requestSubmit()
|
|
542
|
-
component("x-button", ({ host, attr }) => {
|
|
543
|
-
const disabled = attr("disabled");
|
|
544
|
-
|
|
545
|
-
const handleClick = () => {
|
|
546
|
-
const typeAttr = host.getAttribute("type");
|
|
547
|
-
if (typeAttr === "button" || typeAttr === "reset") return;
|
|
548
|
-
const form = host.closest("form");
|
|
549
|
-
if (form && !host.hasAttribute("disabled")) form.requestSubmit();
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
return () => html`
|
|
553
|
-
<button ?disabled=${() => disabled() !== ""} @click=${handleClick}>
|
|
554
|
-
<slot></slot>
|
|
555
|
-
</button>
|
|
556
|
-
`;
|
|
557
|
-
});
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
Подробнее: [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md).
|
|
561
|
-
|
|
562
|
-
---
|
|
563
|
-
|
|
564
|
-
## Pitfall #22: `useForm()` с Shadow DOM кастомными input
|
|
565
|
-
|
|
566
|
-
**Симптом:** `form.onInput` получает `undefined` для name/value от `<x-input>`.
|
|
567
|
-
|
|
568
|
-
Когда Shadow DOM input диспатчит `input` событие, браузер ретаргетирует
|
|
569
|
-
`e.target` с внутреннего `<input>` на host `<x-input>`. Но `<x-input>`
|
|
570
|
-
(HTMLElement) не имеет `.name` или `.value` — поэтому `useForm` ничего не получает.
|
|
571
|
-
|
|
572
|
-
```ts
|
|
573
|
-
// ❌ Нет proxy-свойств — useForm тихо игнорирует события
|
|
574
|
-
component("x-input", ({ host, attr }) => {
|
|
575
|
-
const name = attr("name", "");
|
|
576
|
-
return () => html`<input name=${name} />`;
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// ✅ Добавить proxy-свойства для совместимости с useForm
|
|
580
|
-
component("x-input", ({ host, attr }) => {
|
|
581
|
-
const name = attr("name", "");
|
|
582
|
-
|
|
583
|
-
Object.defineProperty(host, "name", {
|
|
584
|
-
get: () => host.getAttribute("name") ?? "",
|
|
585
|
-
configurable: true,
|
|
586
|
-
});
|
|
587
|
-
Object.defineProperty(host, "value", {
|
|
588
|
-
get: () => host.shadowRoot?.querySelector("input")?.value ?? "",
|
|
589
|
-
configurable: true,
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
return () => html`<input name=${name} />`;
|
|
593
|
-
});
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
Подробнее: [`17-shadow-dom-forms.md`](./17-shadow-dom-forms.md).
|
|
597
|
-
|
|
598
|
-
---
|
|
599
|
-
|
|
600
|
-
## Cheat-sheet для AI
|
|
601
|
-
|
|
602
|
-
| Если хочешь сделать… | Правильно в Mado |
|
|
603
|
-
| ------------------------------------- | ------------------------------------------- |
|
|
604
|
-
| `useState(0)` | `signal(0)` |
|
|
605
|
-
| `useEffect(() => {...}, [a, b])` | `effect(() => {...})` (auto-deps) |
|
|
606
|
-
| `useEffect(() => return cleanup, [])` | `ctx.onDispose(cleanup)` |
|
|
607
|
-
| `useMemo(() => x, [a])` | `computed(() => x)` |
|
|
608
|
-
| `useCallback(fn, [])` | обычная функция |
|
|
609
|
-
| `useContext(Ctx)` | `inject(host, Ctx)` |
|
|
610
|
-
| `useQuery(['key'], fn)` | `resource(() => 'key', fn)` |
|
|
611
|
-
| `useMutation(fn)` | `mutation(fn, { invalidates: [...] })` |
|
|
612
|
-
| `useRouter().push('/')` | `navigate('/')` |
|
|
613
|
-
| `useRouter().query.q` | `queryParam('q')` |
|
|
614
|
-
| `<input value={v} onChange={...}>` | `<input .value=${v} @input=${...}>` |
|
|
615
|
-
| `{items.map(x => ...)}` | `${() => each(items, x => x.id, x => ...)}` |
|
|
616
|
-
| `useForm({ resolver: zodResolver })` | `useForm({...}, { validate: (v) => ... })` |
|
|
617
|
-
| `class extends HTMLElement` | `component('x-name', setup)` |
|
|
618
|
-
| `@customElement('x')` | `component('x-name', setup)` |
|
|
619
|
-
| `host.getAttribute('x')` в render | `ctx.attr('x', default)` (реактивно) |
|
|
620
|
-
| `jsonFetcher()` с авторизацией | `apiFetcher()` (прикрепляет Bearer токен) |
|
|
621
|
-
| `setInterval` в page view | `onDispose(() => clearInterval(id))` |
|
|
622
|
-
| чтение сигнала в async init view() | `untracked(() => cursor())` |
|
|
623
|
-
|
|
624
|
-
Если что-то не подходит из этого списка — открой `src/` и **прочитай 500 строк**. Это серьёзно. Mado специально маленький, чтобы быть читаемым.
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Тест LLM с нулевой историей
|
|
2
|
-
|
|
3
|
-
Этот документ определяет практический валидационный тест для Mado.
|
|
4
|
-
|
|
5
|
-
Вопрос не в том, «может ли LLM генерировать фронтенд-код?» — может. Вопрос в том:
|
|
6
|
-
может ли свежая LLM написать идиоматичный Mado-код без скатывания в React-подобные
|
|
7
|
-
паттерны?
|
|
8
|
-
|
|
9
|
-
## Допустимый контекст
|
|
10
|
-
|
|
11
|
-
При первом проходе предоставьте агенту только:
|
|
12
|
-
|
|
13
|
-
- `AGENTS.md`
|
|
14
|
-
- `README.md`
|
|
15
|
-
- `docs/en/07-llm-pitfalls.md`
|
|
16
|
-
- файлы из внешнего workspace `madojs-examples` только если агент сам попросит
|
|
17
|
-
паттерн более крупного приложения
|
|
18
|
-
|
|
19
|
-
Агент может искать целевые API в `src/` когда заблокирован, но не должен
|
|
20
|
-
загружать весь фреймворк в контекст.
|
|
21
|
-
|
|
22
|
-
## Задание
|
|
23
|
-
|
|
24
|
-
Построить маленький ticket-admin SPA для соло/бекенд-разработчика.
|
|
25
|
-
|
|
26
|
-
Требуемое поведение:
|
|
27
|
-
|
|
28
|
-
- маршруты: `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*`;
|
|
29
|
-
- in-memory mock API с реалистичными асинхронными задержками;
|
|
30
|
-
- страница списка с `resource()`, `queryParam()` фильтрами поиска/статуса,
|
|
31
|
-
`computed()` и `each()` с ключами по строкам;
|
|
32
|
-
- сценарии создания и редактирования с `useForm()` + `mutation()` + `invalidates`;
|
|
33
|
-
- локальный UI-стейт через `signal()`;
|
|
34
|
-
- slotted shell, metric и badge компоненты для более реалистичного admin UI;
|
|
35
|
-
- smoke-тест, импортирующий собранный пример.
|
|
36
|
-
|
|
37
|
-
## Чеклист ошибок
|
|
38
|
-
|
|
39
|
-
Ищите следующее после реализации:
|
|
40
|
-
|
|
41
|
-
- JSX, `useState`, `useEffect`, `ref`, `$state` или class-style компоненты;
|
|
42
|
-
- `${signal()}` или `${signal() + 1}` где требуется реактивный child thunk;
|
|
43
|
-
- `disabled=${...}` вместо `?disabled=${...}`;
|
|
44
|
-
- динамические списки, отрендеренные через unkeyed `.map()` вместо `each()`;
|
|
45
|
-
- ESM-импорты в браузере без `.js`;
|
|
46
|
-
- `resource()`, созданный вне component setup;
|
|
47
|
-
- новые runtime-зависимости или новые публичные API фреймворка.
|
|
48
|
-
|
|
49
|
-
## Заметки по результатам
|
|
50
|
-
|
|
51
|
-
Историческая реализация tickets живет во внешнем workspace `madojs-examples`.
|
|
52
|
-
Core-репозиторий больше не поставляет этот артефакт и отдельную smoke-команду;
|
|
53
|
-
используйте этот документ как ручной сценарий оценки при обновлении LLM-guidance.
|
|
54
|
-
|
|
55
|
-
Основная болевая точка в документации остается lifecycle: примеры не должны
|
|
56
|
-
создавать впечатление, что long-lived `resource()` можно случайно держать на
|
|
57
|
-
module scope или в route-коде без cleanup.
|