@madojs/mado 0.6.1 → 0.8.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.
@@ -1,56 +1,57 @@
1
- # LLM zero-history test
1
+ # Тест LLM с нулевой историей
2
2
 
3
- This document defines a practical validation test for Mado.
3
+ Этот документ определяет практический валидационный тест для Mado.
4
4
 
5
- The question is not "can an LLM generate frontend code?" It can. The question is:
6
- can a fresh LLM write idiomatic Mado without falling back to React-shaped code?
5
+ Вопрос не в том, «может ли LLM генерировать фронтенд-код?» может. Вопрос в том:
6
+ может ли свежая LLM написать идиоматичный Mado-код без скатывания в React-подобные
7
+ паттерны?
7
8
 
8
- ## Allowed context
9
+ ## Допустимый контекст
9
10
 
10
- For the first pass, give the agent only:
11
+ При первом проходе предоставьте агенту только:
11
12
 
12
13
  - `AGENTS.md`
13
14
  - `README.md`
14
- - `docs/ru/07-llm-pitfalls.md`
15
- - `examples/basic/README.md` if a minimal API tour is needed
16
- - specific `examples/showcase/**` files only when the agent asks for a larger app pattern
15
+ - `docs/en/07-llm-pitfalls.md`
16
+ - `examples/basic/README.md` если нужен минимальный обзор API
17
+ - конкретные файлы из `examples/showcase/**` только если агент сам попросит паттерн более крупного приложения
17
18
 
18
- The agent may search targeted APIs in `src/` when blocked, but should not load
19
- the whole framework into context.
19
+ Агент может искать целевые API в `src/` когда заблокирован, но не должен
20
+ загружать весь фреймворк в контекст.
20
21
 
21
- ## Task
22
+ ## Задание
22
23
 
23
- Build `examples/tickets`: a small ticket-admin SPA for a solo/backend developer.
24
+ Построить `examples/tickets`: маленький ticket-admin SPA для соло/бекенд-разработчика.
24
25
 
25
- Required behavior:
26
+ Требуемое поведение:
26
27
 
27
- - routes: `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*`;
28
- - in-memory mock API with realistic async delays;
29
- - list page with `resource()`, `queryParam()` search/status filters, `computed()`,
30
- and keyed `each()` rows;
31
- - create and edit flows with `useForm()` + `mutation()` + `invalidates`;
32
- - local UI state with `signal()`;
33
- - slotted shell, metric, and badge components for a more realistic admin UI;
34
- - smoke test importing the built example.
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-тест, импортирующий собранный пример.
35
36
 
36
- ## Failure checklist
37
+ ## Чеклист ошибок
37
38
 
38
- Look for these after implementation:
39
+ Ищите следующее после реализации:
39
40
 
40
- - JSX, `useState`, `useEffect`, `ref`, `$state`, or class-style components;
41
- - `${signal()}` or `${signal() + 1}` where a reactive child thunk is required;
42
- - `disabled=${...}` instead of `?disabled=${...}`;
43
- - dynamic lists rendered with unkeyed array mapping instead of `each()`;
44
- - browser ESM imports without `.js`;
45
- - `resource()` created outside component setup;
46
- - new runtime dependencies or new public framework APIs.
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 фреймворка.
47
48
 
48
- ## Result notes
49
+ ## Заметки по результатам
49
50
 
50
- The current `examples/tickets` implementation did not require new public APIs or
51
- runtime dependencies.
51
+ Текущая реализация `examples/tickets` не потребовала новых публичных API или
52
+ runtime-зависимостей.
52
53
 
53
- The main documentation pressure point remains lifecycle: older examples can make
54
- it look acceptable to create `resource()` directly in `page.view()`. The tickets
55
- example uses page-level wrapper components instead, so resources are registered
56
- inside component setup and clean up with the component.
54
+ Основная болевая точка в документации остается lifecycle: старые примеры могут
55
+ создать впечатление, что создание `resource()` прямо в `page.view()` допустимо.
56
+ Пример tickets использует page-level wrapper-компоненты вместо этого, поэтому
57
+ ресурсы регистрируются внутри component setup и очищаются вместе с компонентом.
@@ -1,10 +1,9 @@
1
1
  # Shadow DOM vs Light DOM
2
2
 
3
- Mado components use Shadow DOM by default. This is a good default for
4
- self-contained widgets, but it is not the right default for every component in
5
- an application.
3
+ Компоненты Mado по умолчанию используют Shadow DOM. Это хороший дефолт для
4
+ самодостаточных виджетов, но не для каждого компонента в приложении.
6
5
 
7
- ## Rule of Thumb
6
+ ## Правило большого пальца
8
7
 
9
8
  В Mado layout — это тоже component. Если файл описывает видимую переиспользуемую
10
9
  часть UI-дерева — app shell, sidebar, modal, table, page section — по умолчанию
@@ -17,25 +16,25 @@ const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
17
16
  ```
18
17
 
19
18
  Не стоит делать app shell функцией в публичных примерах. Это работает, но
20
- прячет browser model вместо того, чтобы ее объяснять.
19
+ прячет browser model вместо того, чтобы её объяснять.
21
20
 
22
- Use **Shadow DOM** for leaf widgets:
21
+ Используйте **Shadow DOM** для leaf-виджетов:
23
22
 
24
- - buttons, badges, cards, metrics;
25
- - modals, toasts, small visual components;
26
- - embed widgets that should not inherit app CSS accidentally;
27
- - components whose styling should be owned by the component itself.
23
+ - кнопки, бейджи, карточки, метрики;
24
+ - модалы, тосты, маленькие визуальные компоненты;
25
+ - embed-виджеты, которые не должны наследовать CSS приложения случайно;
26
+ - компоненты, стили которых принадлежат самому компоненту.
28
27
 
29
- Use **Light DOM** (`{ shadow: false }`) for app structure that wants to share
30
- global CSS utilities:
28
+ Используйте **Light DOM** (`{ shadow: false }`) для структуры приложения,
29
+ которая хочет разделять глобальные CSS-утилиты:
31
30
 
32
- - route/page components;
33
- - admin screens with dense table/form layouts;
34
- - data-heavy screens with tables and forms;
35
- - components that intentionally share global layout, form and table utilities;
36
- - places where children should simply remain normal document DOM.
31
+ - route/page компоненты;
32
+ - admin-экраны с плотными таблицами/формами;
33
+ - data-heavy экраны с таблицами и формами;
34
+ - компоненты, которые намеренно используют глобальные layout/form/table утилиты;
35
+ - места, где children должны оставаться обычным document DOM.
37
36
 
38
- Use **Shadow DOM** для slot-based layouts:
37
+ Используйте **Shadow DOM** для slot-based layouts:
39
38
 
40
39
  - app shells с `<slot>`;
41
40
  - sidebar/content wrappers;
@@ -44,73 +43,89 @@ Use **Shadow DOM** для slot-based layouts:
44
43
  `<slot>` — это feature Shadow DOM. В компоненте с `shadow: false` тег `<slot>`
45
44
  становится обычным DOM-элементом и не переносит children в это место layout.
46
45
 
47
- ## The Footgun
46
+ ## Подвох
48
47
 
49
- Global CSS does not cross a Shadow DOM boundary.
48
+ Глобальный CSS не пересекает границу Shadow DOM.
50
49
 
51
50
  ```ts
52
51
  // global.ts
53
52
  export const globalStyles = css`
54
- .page-head { display: flex; justify-content: space-between; }
55
- .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
53
+ .page-head {
54
+ display: flex;
55
+ justify-content: space-between;
56
+ }
57
+ .metric-grid {
58
+ display: grid;
59
+ grid-template-columns: repeat(4, 1fr);
60
+ }
56
61
  `;
57
62
 
58
- // ❌ .page-head and .metric-grid will not apply inside x-dashboard shadowRoot
59
- component("x-dashboard", () => () => html`
60
- <header class="page-head">...</header>
61
- <div class="metric-grid">...</div>
62
- `);
63
+ // ❌ .page-head и .metric-grid НЕ применятся внутри shadowRoot x-dashboard
64
+ component(
65
+ "x-dashboard",
66
+ () => () => html`
67
+ <header class="page-head">...</header>
68
+ <div class="metric-grid">...</div>
69
+ `,
70
+ );
63
71
  ```
64
72
 
65
- Fix it by making the route/page component Light DOM:
73
+ Решение сделать route/page компонент Light DOM:
66
74
 
67
75
  ```ts
68
- component("x-dashboard", () => () => html`
69
- <header class="page-head">...</header>
70
- <div class="metric-grid">...</div>
71
- `, {
72
- shadow: false,
73
- styles: css`
74
- x-dashboard { display: block; }
75
- x-dashboard .panel { padding: 1rem; }
76
+ component(
77
+ "x-dashboard",
78
+ () => () => html`
79
+ <header class="page-head">...</header>
80
+ <div class="metric-grid">...</div>
76
81
  `,
77
- });
82
+ {
83
+ shadow: false,
84
+ styles: css`
85
+ x-dashboard {
86
+ display: block;
87
+ }
88
+ x-dashboard .panel {
89
+ padding: 1rem;
90
+ }
91
+ `,
92
+ },
93
+ );
78
94
  ```
79
95
 
80
- Now global utilities and local scoped styles both work.
96
+ Теперь глобальные утилиты и локальные scoped-стили работают вместе.
81
97
 
82
- ## How Styles Behave
98
+ ## Как ведут себя стили
83
99
 
84
- - `styles: css\`\`` in Shadow DOM is adopted into the component shadowRoot.
85
- - `styles: css\`\`` with `shadow: false` is scoped to the tag name and adopted
86
- globally.
87
- - CSS custom properties (`--accent`, `--bg`, etc.) cross Shadow DOM boundaries.
88
- - Class selectors like `.btn`, `.form-grid`, `.page-head` do **not** cross
89
- Shadow DOM boundaries.
90
- - Slotted children keep their own document styles; the shadow component can only
91
- target them through `::slotted(...)`.
92
- - `<slot>` projects children only in Shadow DOM. In a `shadow: false` component
93
- it is just a normal `<slot>` element and will not move children into that
94
- place in your layout.
100
+ - `styles: css\`\`` в Shadow DOM адоптируется в shadowRoot компонента.
101
+ - `styles: css\`\``с`shadow: false` скоупится по имени тега и адоптируется
102
+ глобально.
103
+ - CSS custom properties (`--accent`, `--bg` и т.д.) пересекают границу Shadow DOM.
104
+ - Селекторы по классу (`.btn`, `.form-grid`, `.page-head`) **не** пересекают
105
+ границу Shadow DOM.
106
+ - Slotted-children сохраняют свои стили из документа; shadow-компонент может
107
+ таргетировать их только через `::slotted(...)`.
108
+ - `<slot>` проецирует children только в Shadow DOM. В компоненте с `shadow: false`
109
+ это обычный `<slot>` элемент, который не перемещает children в своё место.
95
110
 
96
- ## Recommended App Shape
111
+ ## Рекомендованная архитектура
97
112
 
98
113
  ```ts
99
- // root and pages: Light DOM
114
+ // root и pages: Light DOM
100
115
  component("x-app", setup, { shadow: false });
101
116
  component("x-users-page", setup, { shadow: false });
102
117
 
103
- // slot-based layout: Shadow DOM default, because it owns the shell grid
118
+ // slot-based layout: Shadow DOM по умолчанию, потому что владеет shell grid
104
119
  component("x-app-layout", setup);
105
120
 
106
- // leaf widgets: Shadow DOM default
121
+ // leaf widgets: Shadow DOM по умолчанию
107
122
  component("x-status-badge", setup);
108
123
  component("x-stat-card", setup);
109
124
  component("x-toast-stack", setup);
110
125
  ```
111
126
 
112
- This gives backend-admin screens predictable CSS while preserving encapsulation
113
- for reusable widgets and slot-based shells.
127
+ Это даёт backend-admin экранам предсказуемый CSS, сохраняя инкапсуляцию
128
+ для переиспользуемых виджетов и slot-based shells.
114
129
 
115
130
  Import model специально browser-native:
116
131
 
@@ -121,30 +136,31 @@ render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
121
136
  ```
122
137
 
123
138
  Import регистрирует custom element через `customElements.define()`. Template
124
- создает `<x-app-layout>` element. Дальше браузер сам связывает тег с классом
125
- компонента. Тут нет React-style component value, который передается как функция.
139
+ создаёт `<x-app-layout>` элемент. Дальше браузер сам связывает тег с классом
140
+ компонента. Тут нет React-style component value, который передаётся как функция.
126
141
 
127
- If a layout does not need slot projection and should be styled entirely by
128
- global CSS, `shadow: false` can still be a good choice. If it contains
129
- `<slot>`, keep Shadow DOM and put the shell styles in that component.
142
+ Если layout не нуждается в slot projection и должен стилизоваться полностью
143
+ глобальным CSS, `shadow: false` хороший выбор. Если он содержит `<slot>`,
144
+ оставьте Shadow DOM и поместите стили shell в этот компонент.
130
145
 
131
- ## Routing and Links
146
+ ## Маршрутизация и ссылки
132
147
 
133
- `data-link` works inside Shadow DOM. The router uses `event.composedPath()`, so
134
- click interception and hover-prefetch can see links from open shadow roots.
148
+ `data-link` работает внутри Shadow DOM. Роутер использует `event.composedPath()`,
149
+ поэтому перехват кликов и hover-prefetch видят ссылки из open shadow roots.
135
150
 
136
151
  ```ts
137
- component("x-card-link", () => () => html`
138
- <a href="/app/accounts" data-link>Accounts</a>
139
- `);
152
+ component(
153
+ "x-card-link",
154
+ () => () => html` <a href="/app/accounts" data-link>Accounts</a> `,
155
+ );
140
156
  ```
141
157
 
142
- The link can be in Shadow DOM; navigation still stays SPA.
158
+ Ссылка может быть внутри Shadow DOM навигация всё равно остаётся SPA.
143
159
 
144
160
  ## Где импортировать компоненты
145
161
 
146
- Custom elements становятся глобальными после регистрации, но регистрация все
147
- равно остается явным JavaScript import.
162
+ Custom elements становятся глобальными после регистрации, но регистрация всё
163
+ равно остаётся явным JavaScript import.
148
164
 
149
165
  ```ts
150
166
  // main.ts: global app frame
@@ -158,7 +174,7 @@ import "../components/ticket-list.js";
158
174
  `<ticket-list>`. Файл должен быть где-то импортирован. После import он вызывает
159
175
  `customElements.define(...)`, и тег становится известен текущему document.
160
176
 
161
- Не стоит bulk-import всех компонентов в `main.ts` "just in case". Для маленьких
177
+ Не стоит bulk-import всех компонентов в `main.ts` «на всякий случай». Для маленьких
162
178
  demo это работает, но прячет ownership и ломает lazy route loading. Лучше:
163
179
 
164
180
  - global app shell/providers импортировать в `main.ts`;
@@ -167,16 +183,16 @@ demo это работает, но прячет ownership и ломает lazy r
167
183
  - truly global leaf components импортировать в `main.ts` только если они реально
168
184
  используются везде.
169
185
 
170
- ## Showcase Lesson
186
+ ## Урок из showcase
171
187
 
172
- `examples/showcase` uses this split deliberately:
188
+ `examples/showcase` использует это разделение намеренно:
173
189
 
174
- - `x-app` and CRM route pages are Light DOM;
175
- - `x-app-layout` keeps Shadow DOM because it owns a slot-based sidebar/content
176
- shell;
177
- - table/form/page utilities live in `styles/global.ts`;
178
- - leaf components such as `x-stat-card`, `x-status-badge`, `x-modal`, and
179
- `x-toast-stack` keep Shadow DOM.
190
+ - `x-app` и CRM route pages Light DOM;
191
+ - `x-app-layout` остаётся в Shadow DOM, потому что владеет slot-based
192
+ sidebar/content shell;
193
+ - table/form/page утилиты живут в `styles/global.ts`;
194
+ - leaf-компоненты типа `x-stat-card`, `x-status-badge`, `x-modal` и
195
+ `x-toast-stack` остаются в Shadow DOM.
180
196
 
181
- If a page suddenly looks unstyled, check whether it uses global classes inside a
182
- Shadow DOM component. That is usually the issue.
197
+ Если страница внезапно выглядит без стилей, проверьте не используете ли вы
198
+ глобальные классы внутри Shadow DOM компонента. Обычно проблема именно в этом.
@@ -0,0 +1,193 @@
1
+ # Shadow DOM + формы
2
+
3
+ Использование `useForm()` с кастомными input-компонентами в Shadow DOM требует
4
+ знания двух поведений на уровне браузера:
5
+
6
+ 1. **Ретаргетинг событий** — события, всплывающие из Shadow DOM, получают
7
+ `e.target`, перенаправленный на host-элемент. `useForm().onInput` читает
8
+ `e.target.name` и `e.target.value`, но host-элемент `<x-input>`
9
+ не имеет этих свойств нативно.
10
+
11
+ 2. **Ассоциация с формой** — `<button type="submit">` внутри Shadow Root
12
+ НЕ участвует в алгоритме form-owner для `<form>` в Light DOM. Клик по ней
13
+ не триггерит submit формы.
14
+
15
+ Оба ограничения — на уровне спецификации, не баги Mado. Но фреймворк предоставляет
16
+ паттерны, которые делают их безболезненными.
17
+
18
+ ## Паттерн: Proxy-свойства на input-компонентах
19
+
20
+ Оборачивая `<input>` в Shadow DOM компонент, экспонируйте `name` и `value`
21
+ как DOM-свойства на host, чтобы `useForm().onInput` работал после ретаргетинга:
22
+
23
+ ```ts
24
+ import { component, css, html } from "@madojs/mado";
25
+
26
+ component("x-input", ({ host, attr }) => {
27
+ const name = attr("name", "");
28
+ const type = attr("type", "text");
29
+ const value = attr("value", "");
30
+
31
+ // Proxy-свойства для совместимости с useForm().
32
+ // После ретаргетинга Shadow DOM e.target из <input> → <x-input>,
33
+ // useForm читает e.target.name / e.target.value — эти геттеры наводят мост.
34
+ Object.defineProperty(host, "name", {
35
+ get: () => host.getAttribute("name") ?? "",
36
+ configurable: true,
37
+ });
38
+ Object.defineProperty(host, "value", {
39
+ get: () => host.shadowRoot?.querySelector("input")?.value ?? "",
40
+ set: (v: string) => {
41
+ const input = host.shadowRoot?.querySelector("input");
42
+ if (input) input.value = v;
43
+ },
44
+ configurable: true,
45
+ });
46
+
47
+ return () => html`<input name=${name} type=${type} .value=${value} />`;
48
+ });
49
+ ```
50
+
51
+ Событие `input` от внутреннего `<input>` имеет `composed: true` по умолчанию,
52
+ поэтому оно всплывает через границу shadow. После ретаргетинга `e.target` —
53
+ это `<x-input>`, но теперь у него есть геттеры `.name` и `.value` → `useForm`
54
+ работает.
55
+
56
+ ## Паттерн: Submit формы из Shadow DOM кнопок
57
+
58
+ `<button type="submit">` внутри Shadow DOM не может триггерить submit `<form>`
59
+ в Light DOM. Мост через `requestSubmit()`:
60
+
61
+ ```ts
62
+ import { component, css, html } from "@madojs/mado";
63
+
64
+ component("x-button", ({ host, attr }) => {
65
+ const disabled = attr("disabled");
66
+
67
+ const handleClick = () => {
68
+ const typeAttr = host.getAttribute("type");
69
+ if (typeAttr === "button" || typeAttr === "reset") return;
70
+ const form = host.closest("form");
71
+ if (form && !host.hasAttribute("disabled")) form.requestSubmit();
72
+ };
73
+
74
+ return () => html`
75
+ <button ?disabled=${() => disabled() !== ""} @click=${handleClick}>
76
+ <slot></slot>
77
+ </button>
78
+ `;
79
+ });
80
+ ```
81
+
82
+ `host.closest("form")` работает, потому что сам host-элемент живёт в Light DOM
83
+ (только его внутренности в тени). `requestSubmit()` триггерит валидацию и
84
+ событие `submit` точно так, как если бы пользователь кликнул нативную submit-кнопку
85
+ внутри формы.
86
+
87
+ ## Паттерн: Реактивные атрибуты через ctx.attr()
88
+
89
+ С версии 0.7 `ctx.attr(name, defaultValue?)` возвращает `Signal<string>`,
90
+ который автоматически обновляется при изменении атрибута на host. Никакого
91
+ `MutationObserver` бойлерплейта:
92
+
93
+ ```ts
94
+ component("x-badge", ({ attr }) => {
95
+ const variant = attr("variant", "default"); // Signal<string>
96
+
97
+ return () =>
98
+ html`<span class=${() => `badge badge-${variant()}`}>
99
+ <slot></slot>
100
+ </span>`;
101
+ });
102
+ ```
103
+
104
+ Родитель может использовать `?disabled=${() => !form.isValid()}` (boolean атрибут)
105
+ или `.variant=${"danger"}` — компонент перерендеривается реактивно в любом случае.
106
+
107
+ ## Полный пример формы
108
+
109
+ ```ts
110
+ import { page, html, useForm, navigate } from "@madojs/mado";
111
+ import "../components/x-input.js";
112
+ import "../components/x-button.js";
113
+
114
+ export default page({
115
+ title: "Вход",
116
+ view: () => {
117
+ const form = useForm({
118
+ email: { required: true, type: "email" },
119
+ password: { required: true, minLength: 6 },
120
+ });
121
+
122
+ const handleLogin = async (values) => {
123
+ await api("/auth/login", { method: "POST", json: values });
124
+ navigate("/admin");
125
+ };
126
+
127
+ return html`
128
+ <form @submit=${form.onSubmit(handleLogin)}>
129
+ <x-input
130
+ name="email"
131
+ type="email"
132
+ label="Email"
133
+ required
134
+ @input=${form.onInput}
135
+ @blur=${form.onBlur}
136
+ ></x-input>
137
+ ${() =>
138
+ form.errors().email
139
+ ? html`<small class="err">${form.errors().email}</small>`
140
+ : null}
141
+
142
+ <x-input
143
+ name="password"
144
+ type="password"
145
+ label="Пароль"
146
+ required
147
+ @input=${form.onInput}
148
+ @blur=${form.onBlur}
149
+ ></x-input>
150
+
151
+ <x-button type="submit" ?disabled=${() => !form.isValid()}>
152
+ Войти
153
+ </x-button>
154
+ </form>
155
+ `;
156
+ },
157
+ });
158
+ ```
159
+
160
+ ## Когда использовать Light DOM
161
+
162
+ Если ваш input-компонент — это просто стилизованная обёртка без нужды в
163
+ инкапсуляции, `shadow: false` избегает обеих проблем (ретаргетинг и
164
+ form-association):
165
+
166
+ ```ts
167
+ component(
168
+ "x-field",
169
+ ({ attr }) => {
170
+ const label = attr("label", "");
171
+ return () => html`
172
+ <label>
173
+ <span>${label}</span>
174
+ <slot></slot>
175
+ </label>
176
+ `;
177
+ },
178
+ { shadow: false },
179
+ );
180
+ ```
181
+
182
+ С Light DOM нативный `<input>` — часть дерева документа, события не
183
+ ретаргетируются, и submit формы работает нативно. Компромисс: стили не
184
+ инкапсулированы (нужно скоупить самостоятельно).
185
+
186
+ ## Итого
187
+
188
+ | Задача | Решение Shadow DOM | Альтернатива Light DOM |
189
+ | --------------------------- | -------------------------------------- | --------------------------- |
190
+ | `useForm` + кастомный input | Proxy `name`/`value` на host | Нативный `<input>` в slot |
191
+ | Submit формы | `form.requestSubmit()` в click handler | Нативная кнопка работает |
192
+ | Реактивные атрибуты | `ctx.attr()` → авто-сигнал | `ctx.attr()` работает везде |
193
+ | Инкапсуляция стилей | Да (автоматически) | Ручной `@scope` или BEM |
package/docs/ru/README.md CHANGED
@@ -1,21 +1,22 @@
1
1
  # Mado docs — Русский
2
2
 
3
- | Раздел | Файл |
4
- |---|---|
5
- | Принципы | [00-the-mado-way.md](./00-the-mado-way.md) |
6
- | Routing | [01-routing.md](./01-routing.md) |
7
- | Project layout | [02-project-layout.md](./02-project-layout.md) |
8
- | Static bake & SEO | [03-static-bake.md](./03-static-bake.md) |
9
- | IDE setup | [04-ide-setup.md](./04-ide-setup.md) |
10
- | Why Mado | [05-why-mado.md](./05-why-mado.md) |
11
- | For backenders | [06-for-backenders.md](./06-for-backenders.md) |
12
- | LLM pitfalls | [07-llm-pitfalls.md](./07-llm-pitfalls.md) |
13
- | LLM zero-history test | [08-llm-zero-history-test.md](./08-llm-zero-history-test.md) |
14
- | Shadow DOM vs Light DOM | [09-shadow-vs-light-dom.md](./09-shadow-vs-light-dom.md) |
15
- | Архитектура приложения | [10-app-architecture.md](./10-app-architecture.md) |
16
- | Layouts | [11-layouts.md](./11-layouts.md) |
17
- | Auth and API | [12-auth-and-api.md](./12-auth-and-api.md) |
18
- | Deployment | [13-deployment.md](./13-deployment.md) |
19
- | Тестирование | [14-testing.md](./14-testing.md) |
20
- | Обработка ошибок | [15-error-handling.md](./15-error-handling.md) |
21
- | Bake cookbook | [16-bake-cookbook.md](./16-bake-cookbook.md) |
3
+ | Раздел | Файл |
4
+ | --------------------------- | ------------------------------------------------------------ |
5
+ | Принципы | [00-the-mado-way.md](./00-the-mado-way.md) |
6
+ | Маршрутизация | [01-routing.md](./01-routing.md) |
7
+ | Структура проекта | [02-project-layout.md](./02-project-layout.md) |
8
+ | Статический bake и SEO | [03-static-bake.md](./03-static-bake.md) |
9
+ | Настройка IDE | [04-ide-setup.md](./04-ide-setup.md) |
10
+ | Почему Mado | [05-why-mado.md](./05-why-mado.md) |
11
+ | Для бекендеров | [06-for-backenders.md](./06-for-backenders.md) |
12
+ | Типичные ошибки LLM | [07-llm-pitfalls.md](./07-llm-pitfalls.md) |
13
+ | Тест LLM с нулевой историей | [08-llm-zero-history-test.md](./08-llm-zero-history-test.md) |
14
+ | Shadow DOM vs Light DOM | [09-shadow-vs-light-dom.md](./09-shadow-vs-light-dom.md) |
15
+ | Архитектура приложения | [10-app-architecture.md](./10-app-architecture.md) |
16
+ | Макеты (layouts) | [11-layouts.md](./11-layouts.md) |
17
+ | Авторизация и API | [12-auth-and-api.md](./12-auth-and-api.md) |
18
+ | Развёртывание | [13-deployment.md](./13-deployment.md) |
19
+ | Тестирование | [14-testing.md](./14-testing.md) |
20
+ | Обработка ошибок | [15-error-handling.md](./15-error-handling.md) |
21
+ | Рецепты bake | [16-bake-cookbook.md](./16-bake-cookbook.md) |
22
+ | Shadow DOM + формы | [17-shadow-dom-forms.md](./17-shadow-dom-forms.md) |