@helfy/helfy 0.0.7 → 0.0.9

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 (40) hide show
  1. package/README.md +477 -323
  2. package/babel-preset.js +2 -1
  3. package/dist/Helfy/Render/DOM.d.ts +2 -0
  4. package/dist/app/createApp.d.ts +3 -1
  5. package/dist/app/index.d.ts +1 -1
  6. package/dist/app/types.d.ts +10 -0
  7. package/dist/compiler/babel.js +20 -13
  8. package/dist/decorators/Context.decorator.d.ts +22 -5
  9. package/dist/decorators/Effect.decorator.d.ts +1 -1
  10. package/dist/decorators/Inject.decorator.d.ts +8 -3
  11. package/dist/decorators/InjectContainer.decorator.d.ts +16 -0
  12. package/dist/decorators/Store.decorator.d.ts +12 -3
  13. package/dist/decorators/UseCase.decorator.d.ts +21 -0
  14. package/dist/di/InjectParam.decorator.d.ts +0 -11
  15. package/dist/di/Injectable.decorator.d.ts +2 -0
  16. package/dist/di/createViewInstance.d.ts +4 -5
  17. package/dist/di/index.d.ts +3 -2
  18. package/dist/http/ApiClient.decorator.d.ts +19 -0
  19. package/dist/http/FetchHttpClient.d.ts +17 -0
  20. package/dist/http/HttpClient.types.d.ts +19 -0
  21. package/dist/http/Mutation.types.d.ts +22 -0
  22. package/dist/http/MutationBuilder.d.ts +17 -0
  23. package/dist/http/Query.types.d.ts +22 -0
  24. package/dist/http/QueryBuilder.d.ts +16 -0
  25. package/dist/http/QueryCache.d.ts +13 -0
  26. package/dist/http/index.d.ts +18 -0
  27. package/dist/http/mutationConfig.decorator.d.ts +19 -0
  28. package/dist/http/queryConfig.decorator.d.ts +9 -0
  29. package/dist/http/useInfiniteQuery.decorator.d.ts +45 -0
  30. package/dist/http/useMutation.decorator.d.ts +21 -0
  31. package/dist/http/useQuery.decorator.d.ts +25 -0
  32. package/dist/index.d.ts +6 -6
  33. package/dist/index.js +2 -2
  34. package/dist/router/core.d.ts +2 -2
  35. package/dist/router/decorators.d.ts +4 -4
  36. package/dist/router.js +1 -1
  37. package/dist/signals.d.ts +7 -1
  38. package/package.json +1 -1
  39. package/dist/decorators/Observe.decorator.d.ts +0 -1
  40. package/dist/decorators/Provide.decorator.d.ts +0 -22
package/README.md CHANGED
@@ -1,50 +1,54 @@
1
1
  # Helfy
2
2
 
3
- Helfy -- TypeScript UI-фреймворк с декоратор-ориентированным API, кастомным JSX и fine-grained reactivity.
3
+ Helfy a TypeScript UI framework with a decorator-oriented API, custom JSX, and fine-grained reactivity.
4
4
 
5
- Компоненты описываются классами с декоратором `@View`, состояние управляется через `@state` и `@observable`, а шаблоны используют расширенный JSX с директивами `@if`, `@for`, `@else`. Реактивность построена на signals (аналог SolidJS): `render()` вызывается один раз, а последующие обновления DOM происходят точечно только для тех узлов, которые читают изменившийся signal.
5
+ Components are defined as classes with the `@View` decorator, state is managed via `@state` and `@observable`, and templates use extended JSX with `@if`, `@for`, and `@else` directives. Reactivity is built on signals (similar to SolidJS): `render()` runs once, and subsequent DOM updates are granularonly nodes that read the changed signal are updated.
6
6
 
7
- ## Содержание
7
+ ## Table of contents
8
8
 
9
- - [Установка](#установка)
10
- - [Компоненты](#компоненты)
11
- - [Базовый компонент](#базовый-компонент)
9
+ - [Installation](#installation)
10
+ - [Components](#components)
11
+ - [Basic component](#basic-component)
12
12
  - [Props](#props)
13
- - [Локальное состояние (@state)](#локальное-состояние-state)
14
- - [Вложенные компоненты](#вложенные-компоненты)
15
- - [Монтирование в DOM](#монтирование-в-dom)
16
- - [Привязка к DOM и компонентам (@ref)](#привязка-к-dom-и-компонентам-ref)
17
- - [Двустороннее связывание (@bind)](#двустороннее-связывание-bind)
18
- - [Формы (@Form, @field, @field в JSX)](#формы-form-field-field-в-jsx)
19
- - [Шаблонные директивы](#шаблонные-директивы)
13
+ - [Local state (@state)](#local-state-state)
14
+ - [Nested components](#nested-components)
15
+ - [DOM mounting](#dom-mounting)
16
+ - [DOM and component refs (@ref)](#dom-and-component-refs-ref)
17
+ - [Two-way binding (@bind)](#two-way-binding-bind)
18
+ - [Forms (@Form, @field, @field in JSX)](#forms-form-field-field-in-jsx)
19
+ - [Template directives](#template-directives)
20
20
  - [@if / @elseif / @else](#if--elseif--else)
21
21
  - [@for](#for)
22
22
  - [@empty](#empty)
23
23
  - [State Management](#state-management)
24
- - [Создание Store](#создание-store)
25
- - [Подписка через @observe](#подписка-через-observe)
26
- - [Контекст хранилищ](#контекст-хранилищ)
27
- - [Логирование (@logger)](#логирование-logger)
24
+ - [Architecture layers (Store, Service, UseCase)](#state-management)
25
+ - [Creating a Store](#creating-a-store-store)
26
+ - [Infrastructure services](#infrastructure-services-service)
27
+ - [Business use cases](#business-use-cases-usecase)
28
+ - [Accessing Store and UseCase from components](#accessing-store-and-usecase-from-components)
29
+ - [Store context (optional)](#store-context-optional)
30
+ - [Logging (@logger)](#logging-logger)
28
31
  - [Context & DI](#context--di)
29
- - [Провайдер (@Context, @provide)](#провайдер-context-provide)
30
- - [Потребитель (@ctx)](#потребитель-ctx)
31
- - [Реактивные поля](#реактивные-поля)
32
- - [Опциональная инжекция](#опциональная-инжекция)
33
- - [DI Container](#di-container-конструкторный-инжект)
32
+ - [Provider (@Context)](#provider-context)
33
+ - [Consumer (@useCtx)](#consumer-usectx)
34
+ - [Reactive fields](#reactive-fields)
35
+ - [Optional injection](#optional-injection)
36
+ - [DI: props vs dependencies](#di-props-vs-dependencies)
37
+ - [Global services (@inject)](#global-services-inject)
34
38
  - [JSX](#jsx)
35
- - [Атрибуты](#атрибуты)
36
- - [События](#события)
37
- - [Стили](#стили)
38
- - [CSS-классы](#css-классы)
39
+ - [Attributes](#attributes)
40
+ - [Events](#events)
41
+ - [Styles](#styles)
42
+ - [CSS classes](#css-classes)
39
43
  - [API](#api)
40
44
 
41
45
  ---
42
46
 
43
- ## Установка
47
+ ## Installation
44
48
 
45
- ### Создание нового проекта (helfy-create)
49
+ ### Creating a new project (helfy-create)
46
50
 
47
- Быстрый стартсоздать проект одной командой:
51
+ Quick startcreate a project with a single command:
48
52
 
49
53
  ```bash
50
54
  npx helfy-create my-app
@@ -52,21 +56,21 @@ cd my-app
52
56
  npm run dev
53
57
  ```
54
58
 
55
- Без имени (создаст папку `helfy-app`):
59
+ Without a project name (creates `helfy-app`):
56
60
 
57
61
  ```bash
58
62
  npx helfy-create
59
63
  ```
60
64
 
61
- Опция `--skip-install` — создать без `npm install` (для оффлайна или своего registry).
65
+ The `--skip-install` option create without running `npm install` (e.g. for offline use or custom registry).
62
66
 
63
- ### Добавление в существующий проект
67
+ ### Adding to an existing project
64
68
 
65
69
  ```bash
66
70
  npm install @helfy/helfy
67
71
  ```
68
72
 
69
- или в `package.json`:
73
+ Or in `package.json`:
70
74
 
71
75
  ```json
72
76
  {
@@ -78,11 +82,11 @@ npm install @helfy/helfy
78
82
 
79
83
  ### Build setup
80
84
 
81
- Babel-плагин автоматически запускает DI-сканер перед трансформацией (pre-скрипты в `package.json` не нужны). Сканер генерирует `.helfy/di-tokens.ts`, `.helfy/di-registry.ts`. Добавьте `.helfy` в `.gitignore`.
85
+ The Babel plugin runs the DI scanner and context scanner before transformation (no pre-scripts in `package.json` needed). The scanners generate `.helfy/di-tokens.ts`, `.helfy/di-registry.ts`, and `.helfy/ctx-tokens.ts`. Add `.helfy` to `.gitignore`.
82
86
 
83
87
  ### Babel
84
88
 
85
- В `.babelrc` достаточно одного пресета:
89
+ A single preset in `.babelrc` is enough:
86
90
 
87
91
  ```json
88
92
  {
@@ -90,11 +94,11 @@ Babel-плагин автоматически запускает DI-сканер
90
94
  }
91
95
  ```
92
96
 
93
- Пресет включает: JSX runtime, TypeScript, legacy-декораторы, class properties, babel-plugin-transform-typescript-metadata, **helfy-di** (compile-time DI для `@Injectable<IX>()`, автоинъекция регистрации при `createApp`, трансформация `@logger()` → `@logger("<ClassName>")`).
97
+ The preset includes: JSX runtime, TypeScript, legacy decorators, class properties, babel-plugin-transform-typescript-metadata, **helfy-di** (compile-time DI for `@Injectable<IX>()`, `@Service<IX>()`, `@UseCase<IX>()`, `@Store`, `@inject<IX>()`, `@useCtx<IX>()`, auto-registration at `createApp`, `@logger()` → `@logger("<ClassName>")` transformation).
94
98
 
95
- ### Webpack
99
+ ### Webpack / Rspack
96
100
 
97
- Для обработки директив (`@if`, `@for`, `@ref`, `@bind`, `@field`) в `.tsx` файлах подключить loader:
101
+ To process directives (`@if`, `@for`, `@ref`, `@bind`, `@field`) in `.tsx` files, add the loader:
98
102
 
99
103
  ```javascript
100
104
  {
@@ -106,9 +110,11 @@ Babel-плагин автоматически запускает DI-сканер
106
110
  }
107
111
  ```
108
112
 
113
+ The same configuration works with both Webpack and Rspack. Rspack is a faster, Rust-based bundler with webpack-compatible API.
114
+
109
115
  ### TypeScript
110
116
 
111
- В `tsconfig.json` указать:
117
+ In `tsconfig.json` set:
112
118
 
113
119
  ```json
114
120
  {
@@ -121,15 +127,15 @@ Babel-плагин автоматически запускает DI-сканер
121
127
  }
122
128
  ```
123
129
 
124
- Плагин `@helfy/helfy-ts-plugin` даёт поддержку директив `@if`, `@for`, `@ref`, `@bind`, `@field` в IDE (автодополнение, типизация, переход к определению). Устанавливается отдельно: `npm install @helfy/helfy-ts-plugin`.
130
+ The `@helfy/helfy-ts-plugin` plugin provides IDE support for `@if`, `@for`, `@ref`, `@bind`, `@field` directives (autocomplete, typing, go-to-definition). Install separately: `npm install @helfy/helfy-ts-plugin`.
125
131
 
126
132
  ---
127
133
 
128
- ## Компоненты
134
+ ## Components
129
135
 
130
- ### Базовый компонент
136
+ ### Basic component
131
137
 
132
- Компонент -- это класс с декоратором `@View` и методом `render()`, возвращающим JSX:
138
+ A component is a class with the `@View` decorator and a `render()` method that returns JSX:
133
139
 
134
140
  ```tsx
135
141
  import { View } from "@helfy/helfy";
@@ -146,19 +152,19 @@ class Hello {
146
152
  }
147
153
  ```
148
154
 
149
- Декоратор `@View` автоматически:
150
- - вызывает `render()` **один раз** и монтирует результат в DOM-фрагмент
151
- - добавляет свойство `view` (ссылка на корневой DOM-элемент)
152
- - оборачивает `this.props` в реактивный Proxy (каждый prop signal)
153
- - настраивает fine-grained эффекты: при изменении signal обновляется только конкретный DOM-узел, а не весь компонент
155
+ The `@View` decorator automatically:
156
+ - calls `render()` **once** and mounts the result into a DOM fragment
157
+ - adds a `view` property (reference to the root DOM element)
158
+ - wraps `this.props` in a reactive Proxy (each prop is a signal)
159
+ - configures fine-grained effects: when a signal changes, only the specific DOM node that reads it is updated, not the entire component
154
160
 
155
- > **Важно:** При наследовании `@View` нужно указывать и на дочернем классе. Без этого не будут работать `@ctx`, `scheduleUpdate` и обновление DOM.
161
+ > **Important:** When inheriting from `@View`, apply the decorator on the child class as well. Otherwise `@useCtx`, `scheduleUpdate`, and DOM updates will not work.
156
162
 
157
163
  ### Props
158
164
 
159
- Props передаются через конструктор. Декоратор `@View` оборачивает `this.props` в реактивный Proxy — каждый prop становится signal. Это означает, что при изменении props родителем обновляются только те DOM-узлы, которые их читают, без полного перерендера компонента.
165
+ Props are passed via the constructor. The `@View` decorator wraps `this.props` in a reactive Proxy — each prop becomes a signal. This means that when the parent updates props, only the DOM nodes that read them are updated, without a full re-render.
160
166
 
161
- > **Важно:** Не деструктуризируйте `this.props` в `render()`. Обращайтесь к полям напрямую через `this.props.field` — это обеспечивает корректную подписку на signals.
167
+ > **Important:** Do not destructure `this.props` in `render()`. Access fields directly via `this.props.field` — this ensures correct signal subscription.
162
168
 
163
169
  ```tsx
164
170
  import { View } from "@helfy/helfy";
@@ -186,18 +192,17 @@ class Button {
186
192
  }
187
193
  ```
188
194
 
189
- Использование:
195
+ Usage:
190
196
 
191
197
  ```tsx
192
- <Button label="Нажми" onClick={() => console.log('clicked')} />
198
+ <Button label="Click" onClick={() => console.log('clicked')} />
193
199
  ```
194
200
 
195
- При изменении props родителем обновляются только те DOM-узлы, которые читают изменившиеся props.
201
+ When the parent updates props, only the DOM nodes that read the changed props are updated.
196
202
 
197
- ### Локальное состояние (@state)
203
+ ### Local state (@state)
198
204
 
199
- Декоратор `@state` делает поле реактивным.
200
- При изменении значения обновляются только те DOM-узлы и эффекты, которые читают это поле (fine-grained). Полный перерендер компонента не происходит:
205
+ The `@state` decorator makes a field reactive. When its value changes, only the DOM nodes and effects that read it are updated (fine-grained). The component is not fully re-rendered:
201
206
 
202
207
  ```tsx
203
208
  import { View, state } from "@helfy/helfy";
@@ -207,7 +212,7 @@ class Counter {
207
212
  @state private count = 0;
208
213
 
209
214
  increment() {
210
- this.count++; // обновит только узлы, читающие count
215
+ this.count++; // updates only nodes that read count
211
216
  }
212
217
 
213
218
  decrement() {
@@ -226,13 +231,13 @@ class Counter {
226
231
  }
227
232
  ```
228
233
 
229
- Можно объявить несколько `@state`-полей. Каждое из них независимо триггерит обновление только тех узлов DOM, которые его читают.
234
+ You can declare multiple `@state` fields. Each independently triggers updates only in the DOM nodes that read it.
230
235
 
231
- Signals‑примитивы (`createSignal`, `createEffect`, `createComputed`, `batch`, `onCleanup`) также экспортируются из `helfy` и могут использоваться напрямую, но в большинстве случаев достаточно `@state`, `@computed` и `@effect`.
236
+ Signal primitives (`createSignal`, `createEffect`, `createComputed`, `batch`, `onCleanup`) are also exported from `helfy` and can be used directly, but in most cases `@state`, `@computed`, and `@effect` are sufficient.
232
237
 
233
- ### Вложенные компоненты
238
+ ### Nested components
234
239
 
235
- Компоненты используются в JSX как теги. Props передаются как атрибуты:
240
+ Components are used in JSX as tags. Props are passed as attributes:
236
241
 
237
242
  ```tsx
238
243
  import { View } from "@helfy/helfy";
@@ -250,9 +255,9 @@ class App {
250
255
  }
251
256
  ```
252
257
 
253
- ### Монтирование в DOM
258
+ ### DOM mounting
254
259
 
255
- Типичный bootstrap через `createApp()`: создаётся экземпляр, вызывается `attach(root)` внутри, хук `onAttached()` срабатывает после вставки в document.
260
+ Typical bootstrap is via `createApp()`: an instance is created, `attach(root)` is called internally, and the `onAttached()` hook runs after insertion into the document.
256
261
 
257
262
  ```typescript
258
263
  import { createApp } from "@helfy/helfy";
@@ -262,19 +267,19 @@ createApp({ root: document.getElementById("root")! })
262
267
  .mount(App);
263
268
  ```
264
269
 
265
- Для сценариев без роутера:
270
+ For scenarios without routing:
266
271
 
267
272
  ```typescript
268
273
  createApp({ root: document.getElementById("root")! }).mount(App);
269
274
  ```
270
275
 
271
- Ручное монтирование (без `createApp`): `const app = new App(); app.attach(root)`.
276
+ Manual mounting (without `createApp`): `const app = new App(); app.attach(root)`.
272
277
 
273
- ### Привязка к DOM и компонентам (@ref)
278
+ ### DOM and component refs (@ref)
274
279
 
275
- Декоратор `@ref` помечает поле для получения ссылки на DOM-элемент или экземпляр компонента. В JSX используется директива `@ref(this.имяПоля)`. Доступ к ссылке возможен после `onMount()`. Для операций вроде `focus()`, требующих элемент в document, используйте `onAttached()`.
280
+ The `@ref` decorator marks a field to receive a reference to a DOM element or component instance. In JSX use the `@ref(this.fieldName)` directive. The reference is available after `onMount()`. For operations like `focus()` that require the element in the document, use `onAttached()`.
276
281
 
277
- **Для компонентов**родитель получает прокси с доступом только к методам, помеченным `@expose`. Это позволяет явно определить публичный API компонента.
282
+ **For components**the parent receives a proxy with access only to methods marked with `@expose`. This lets you explicitly define the component's public API.
278
283
 
279
284
  ```tsx
280
285
  import { View, ref, expose } from "@helfy/helfy";
@@ -297,7 +302,7 @@ class Form {
297
302
  @ref private input!: Input;
298
303
 
299
304
  onAttached() {
300
- this.input.focus(); // доступен только помеченный метод
305
+ this.input.focus(); // only the exposed method is available
301
306
  }
302
307
 
303
308
  render() {
@@ -306,15 +311,15 @@ class Form {
306
311
  }
307
312
  ```
308
313
 
309
- - DOM-элементы по `@ref` передаются как есть
310
- - Компоненты с `@expose` — родитель видит только помеченные методы
311
- - Компоненты без `@expose` — передаётся экземпляр целиком (обратная совместимость)
314
+ - DOM elements via `@ref` are passed as-is
315
+ - Components with `@expose` — parent sees only the exposed methods
316
+ - Components without `@expose` — the full instance is passed (backward compatibility)
312
317
 
313
- При условном рендере (`@if`) ссылка обнуляется при удалении элемента. Запрещено использовать `@ref` и `@state` на одном поле.
318
+ With conditional rendering (`@if`), the ref is cleared when the element is removed. Do not use `@ref` and `@state` on the same field.
314
319
 
315
- ### Двустороннее связывание (@bind)
320
+ ### Two-way binding (@bind)
316
321
 
317
- Директива `@bind` синтаксический сахар для двусторонней привязки значения поля `@state` к элементу формы. Синтаксис: `@bind(expr)` — круглые скобки, как у `@ref`. Компилятор автоматически генерирует пару `value`/`checked` и обработчик события.
322
+ The `@bind` directive is syntactic sugar for two-way binding of a `@state` field to a form element. Syntax: `@bind(expr)` — parentheses, like `@ref`. The compiler generates the `value`/`checked` pair and event handler.
318
323
 
319
324
  ```tsx
320
325
  import { View, state } from "@helfy/helfy";
@@ -332,8 +337,8 @@ class LoginForm {
332
337
  <input @bind(this.email) type="email" />
333
338
  <input @bind(this.isActive) type="checkbox" />
334
339
  <select @bind(this.priority)>
335
- <option value="low">Низкий</option>
336
- <option value="high">Высокий</option>
340
+ <option value="low">Low</option>
341
+ <option value="high">High</option>
337
342
  </select>
338
343
  </form>
339
344
  );
@@ -341,24 +346,24 @@ class LoginForm {
341
346
  }
342
347
  ```
343
348
 
344
- Трансформация по типу элемента:
349
+ Transformation by element type:
345
350
 
346
- | Элемент | Атрибут | Событие |
347
- |---------|---------|---------|
351
+ | Element | Attribute | Event |
352
+ |---------|-----------|-------|
348
353
  | `input[text|email|password|...]` | `value` | `oninput` |
349
354
  | `input[checkbox|radio]` | `checked` | `onchange` |
350
355
  | `select` | `value` | `onchange` |
351
356
  | `textarea` | `value` | `oninput` |
352
357
 
353
- Поле должно быть помечено `@state` для реактивного обновления UI. Для union-типов (например, `TodoPriority`) используется приведение `as typeof expr` — типобезопасность сохраняется.
358
+ The field must be marked with `@state` for reactive UI updates. For union types (e.g. `TodoPriority`), use `as typeof expr` — type safety is preserved.
354
359
 
355
- **Кастомные компоненты:** для привязки к компоненту с пропом `value` используется именованный биндинг `@bind:value(expr)`:
360
+ **Custom components:** for binding to a component with a `value` prop, use named binding `@bind:value(expr)`:
356
361
 
357
362
  ```tsx
358
- // Родитель
359
- <Input @bind:value(this.title) placeholder="Заголовок" />
363
+ // Parent
364
+ <Input @bind:value(this.title) placeholder="Title" />
360
365
 
361
- // Компонент Input с @binded("value")
366
+ // Input component with @binded("value")
362
367
  @View
363
368
  class Input {
364
369
  @binded("value") private bindedVal!: string;
@@ -369,11 +374,11 @@ class Input {
369
374
  }
370
375
  ```
371
376
 
372
- ### Формы (@Form, @field, @field в JSX)
377
+ ### Forms (@Form, @field, @field in JSX)
373
378
 
374
- Централизованное управление формами с валидацией через контекст: `@Form` — класс контекста формы, `@field` — декоратор полей (создаёт `FieldState`), `@useForm` — инжект формы в компонент. JSX-директива `@field(expr)` подключает инпут к `FieldState` одной строкой.
379
+ Centralized form handling with validation via context: `@Form` — form context class, `@field` — field decorator (creates `FieldState`), `@useForm` — injects the form into a component. The JSX directive `@field(expr)` connects an input to `FieldState` in one line.
375
380
 
376
- **FormContext** — класс с `@Form` и полями `@field`:
381
+ **FormContext** — a class with `@Form` and `@field` fields:
377
382
 
378
383
  ```tsx
379
384
  import { Form, field, logger, type FieldState, type ILogger } from "@helfy/helfy";
@@ -392,7 +397,7 @@ export class LoginFormContext {
392
397
  rememberMe!: FieldState<boolean>;
393
398
 
394
399
  validateAll(): boolean {
395
- // проверка полей, установка field.error
400
+ // validate fields, set field.error
396
401
  return true;
397
402
  }
398
403
 
@@ -406,9 +411,9 @@ export class LoginFormContext {
406
411
  }
407
412
  ```
408
413
 
409
- **FieldState** содержит `value`, `isDirty`, `isTouched`, `error`, `isValid`. При изменении `value`/`error`/`isTouched` компоненты с `@useForm` перерендериваются.
414
+ **FieldState** has `value`, `isDirty`, `isTouched`, `error`, `isValid`. When `value`/`error`/`isTouched` changes, components with `@useForm` re-render.
410
415
 
411
- **Провайдер формы**родительский компонент оборачивает форму в контекст:
416
+ **Form provider**the parent component wraps the form in context:
412
417
 
413
418
  ```tsx
414
419
  // LoginPage.tsx
@@ -424,7 +429,7 @@ export class LoginPage {
424
429
  }
425
430
  ```
426
431
 
427
- **Компонент формы**инжект через `@useForm`, доступ к полям через `this.form`:
432
+ **Form component**inject via `@useForm`, access fields via `this.form`:
428
433
 
429
434
  ```tsx
430
435
  import { View, useForm } from "@helfy/helfy";
@@ -443,72 +448,72 @@ export class LoginForm {
443
448
  )}
444
449
  <input @field(this.form.password) type="password" class="input" />
445
450
  <input @field(this.form.rememberMe) type="checkbox" id="remember" />
446
- <label for="remember">Запомнить меня</label>
447
- <button type="submit">Войти</button>
451
+ <label for="remember">Remember me</label>
452
+ <button type="submit">Sign in</button>
448
453
  </form>
449
454
  );
450
455
  }
451
456
  }
452
457
  ```
453
458
 
454
- Компоненты-обёртки (TextField, CheckboxField и т.п.) принимают `field` как prop: `<TextField field={this.form.email} label="Email" />` и используют внутри `<input @field(this.props.field) />`.
459
+ Wrapper components (TextField, CheckboxField, etc.) accept `$field` as a writable prop: `<TextField $field={this.form.email} label="Email" />` and use `<input @field(this.props.$field) />` internally. The `$` prefix marks a writable prop — mutable objects like FieldState bypass the default signal-based (readonly) props so form inputs work correctly.
455
460
 
456
- **JSX-директива `@field(expr)`** — одна директива заменяет `@bind` + `onblur` + `class` для ошибок + `aria-invalid`. Компилятор генерирует:
461
+ **JSX directive `@field(expr)`** — one directive replaces `@bind` + `onblur` + error `class` + `aria-invalid`. The compiler generates:
457
462
 
458
- - `value`/`checked` и `oninput`/`onchange`
463
+ - `value`/`checked` and `oninput`/`onchange`
459
464
  - `onblur` → `expr.isTouched = true`
460
- - `class` — объединение с существующим; при `expr.isTouched && expr.error` добавляется `input-error`
465
+ - `class` — merged with existing; when `expr.isTouched && expr.error` adds `input-error`
461
466
  - `aria-invalid={expr.isTouched && expr.error ? "true" : "false"}`
462
467
 
463
- Поддерживаются `input` (text, email, password, checkbox, radio), `select`, `textarea`. Класс `.input-error` можно задать в глобальных стилях (например, `@apply border-red-500` в Tailwind).
468
+ Supports `input` (text, email, password, checkbox, radio), `select`, `textarea`. The `.input-error` class can be defined in global styles (e.g. `@apply border-red-500` in Tailwind).
464
469
 
465
470
  ---
466
471
 
467
- ## Шаблонные директивы
472
+ ## Template directives
468
473
 
469
- Helfy расширяет JSX директивами `@if`, `@elseif`, `@else`, `@for`, `@empty`.
474
+ Helfy extends JSX with `@if`, `@elseif`, `@else`, `@for`, and `@empty` directives.
470
475
 
471
476
  ### @if / @elseif / @else
472
477
 
473
- Условный рендеринг:
478
+ Conditional rendering:
474
479
 
475
480
  ```tsx
476
481
  render() {
477
482
  return (
478
483
  <div>
479
484
  @if (this.count > 0) {
480
- <span>Положительное: {this.count}</span>
485
+ <span>Positive: {this.count}</span>
481
486
  }
482
487
  </div>
483
488
  );
484
489
  }
485
490
  ```
486
491
 
487
- Цепочка условий:
492
+ Condition chains:
488
493
 
489
494
  ```tsx
490
495
  render() {
491
496
  return (
492
497
  <div>
493
498
  @if (this.status === 'loading') {
494
- <span>Загрузка...</span>
499
+ <span>Loading...</span>
495
500
  } @elseif (this.status === 'error') {
496
- <span>Ошибка!</span>
501
+ <span>Error!</span>
497
502
  } @else {
498
- <span>Данные загружены</span>
503
+ <span>Data loaded</span>
499
504
  }
500
505
  </div>
501
506
  );
502
507
  }
503
508
  ```
504
509
 
505
- Вложенные условия:
510
+ Nested conditions:
506
511
 
507
512
  ```tsx
508
513
  @if (this.isVisible) {
509
514
  <div>
510
515
  @if (this.count > 10) {
511
- <span>Больше десяти</span>
516
+ <span>More than ten</span>
512
517
  }
513
518
  </div>
514
519
  }
@@ -516,7 +521,7 @@ render() {
516
521
 
517
522
  ### @for
518
523
 
519
- Итерация по массиву. Синтаксис: `@for (item of array)` или `@for (item, index of array)`.
524
+ Array iteration. Syntax: `@for (item of array)` or `@for (item, index of array)`.
520
525
 
521
526
  ```tsx
522
527
  @state private items = ['apple', 'banana', 'cherry'];
@@ -532,7 +537,7 @@ render() {
532
537
  }
533
538
  ```
534
539
 
535
- `track` задает ключ для оптимизации DOM-диффинга (аналог `key` в React):
540
+ `track` sets the key for DOM diffing optimization (like `key` in React):
536
541
 
537
542
  ```tsx
538
543
  @for (user of this.users; track user.id) {
@@ -542,13 +547,13 @@ render() {
542
547
 
543
548
  ### @empty
544
549
 
545
- Блок `@empty` после `@for` рендерится, когда массив пуст:
550
+ The `@empty` block after `@for` renders when the array is empty:
546
551
 
547
552
  ```tsx
548
553
  @for (item of this.items; track item) {
549
554
  <div>{item}</div>
550
555
  } @empty {
551
- <span>Список пуст</span>
556
+ <span>List is empty</span>
552
557
  }
553
558
  ```
554
559
 
@@ -556,107 +561,214 @@ render() {
556
561
 
557
562
  ## State Management
558
563
 
559
- ### Создание Store
564
+ Helfy uses a three-layer architecture: **@Store** (pure state), **@Service** (infrastructure), **@UseCase** (business logic).
565
+
566
+ ### Dependency flow
567
+
568
+ ```
569
+ @UseCase ──→ @Store ←── @Service
570
+ │ ↑
571
+ └─────────────────┘
572
+ ```
573
+
574
+ - **@Store** — pure reactive state; no dependencies, no side effects. Store does not know about anyone.
575
+ - **@Service** — infrastructure (HTTP, validation, storage); can inject Store and other Services.
576
+ - **@UseCase** — orchestrates business scenarios; injects Store + Services.
577
+
578
+ ### Creating a Store (@Store)
560
579
 
561
- Store -- глобальное реактивное хранилище. Создается классом с декоратором `@Store`, реактивные поля помечаются `@observable`:
580
+ A Store is a global reactive state container. **Requirements:**
581
+ - Empty constructor (no parameters)
582
+ - Only `@state` on fields and `@computed` on getters
583
+ - Mutation methods that only change own state (synchronous; no `await` — async work belongs in @Service)
584
+ - **Forbidden:** `@inject`, `@useCtx`, `@effect`, direct access to HTTP/storage/timers
562
585
 
563
586
  ```typescript
564
- import { Store, observable } from "@helfy/helfy";
587
+ import { Store, state, computed } from "@helfy/helfy";
565
588
 
566
589
  @Store
567
- class UserStore {
568
- @observable name = "Guest";
569
- @observable isLoggedIn = false;
570
-
571
- login(name: string) {
572
- this.name = name;
573
- this.isLoggedIn = true; // подписчики name и isLoggedIn уведомлены
590
+ class TodoStore {
591
+ @state todos: Todo[] = [];
592
+ @state filter: "all" | "active" | "completed" = "all";
593
+
594
+ @computed get filteredTodos() {
595
+ switch (this.filter) {
596
+ case "active": return this.todos.filter(t => !t.completed);
597
+ case "completed": return this.todos.filter(t => t.completed);
598
+ default: return this.todos;
574
599
  }
600
+ }
575
601
 
576
- logout() {
577
- this.name = "Guest";
578
- this.isLoggedIn = false;
579
- }
580
- }
602
+ add(title: string) {
603
+ this.todos = [...this.todos, { id: crypto.randomUUID(), title, completed: false }];
604
+ }
581
605
 
582
- export default UserStore;
606
+ toggle(id: string) {
607
+ this.todos = this.todos.map(t =>
608
+ t.id === id ? { ...t, completed: !t.completed } : t
609
+ );
610
+ }
611
+
612
+ setFilter(f: typeof this.filter) {
613
+ this.filter = f;
614
+ }
615
+ }
583
616
  ```
584
617
 
585
- Декоратор `@Store` добавляет классу:
586
- - `subscribe(field, callback)` -- подписка на изменение поля, возвращает функцию отписки
587
- - `unsubscribe(field, callback)` -- отписка
618
+ Fields use `@state` or `@observable` — both create signals.
588
619
 
589
- Декоратор `@observable` превращает поле в реактивное свойство с getter/setter. При записи нового значения все подписчики уведомляются.
620
+ ### Infrastructure services (@Service)
590
621
 
591
- ### Подписка через @observe
622
+ Use `@Service` for infrastructure: HTTP clients, validators, repositories. **Requirements:**
623
+ - Constructor with DI dependencies (other Services, Store)
624
+ - Provides atomic technical capabilities
625
+ - Can directly mutate Store (e.g. after fetching data)
626
+ - **Forbidden:** `@state`, `@computed`, `@effect` on fields
592
627
 
593
- Декоратор `@observe` связывает поле компонента с полем store. При изменении значения в store компонент автоматически перерендерится:
628
+ ```typescript
629
+ import { Service } from "@helfy/helfy";
594
630
 
595
- ```tsx
596
- import { View, observe } from "@helfy/helfy";
597
- import Stores from "./StoreContext";
598
- import UserStore from "./UserStore";
631
+ export interface ITodoValidateService {
632
+ validateTitle(title: string): ValidationResult;
633
+ }
599
634
 
600
- @View
601
- class Header {
602
- @observe(Stores.userStore, 'name')
603
- private userName: UserStore['name'];
635
+ @Service<ITodoValidateService>()
636
+ export class TodoValidateService implements ITodoValidateService {
637
+ validateTitle(title: string) {
638
+ // ...
639
+ }
640
+ }
641
+ ```
604
642
 
605
- @observe(Stores.userStore, 'isLoggedIn')
606
- private isLoggedIn: UserStore['isLoggedIn'];
643
+ ### Business use cases (@UseCase)
607
644
 
608
- render() {
609
- return (
610
- <header>
611
- @if (this.isLoggedIn) {
612
- <span>Привет, {this.userName}!</span>
613
- } @else {
614
- <span>Войдите в систему</span>
615
- }
616
- </header>
617
- );
618
- }
645
+ Use `@UseCase` to orchestrate business scenarios. **Requirements:**
646
+ - Constructor with DI (Store, Services)
647
+ - Main public method: `execute`, `perform`, or `handle`
648
+ - Typical flow: validation → call services → update Store → side effects (notifications, analytics)
649
+ - **Forbidden:** `@state`, `@computed`, `@effect` on fields
650
+
651
+ ```typescript
652
+ import { UseCase } from "@helfy/helfy";
653
+
654
+ @UseCase<ICreateTodoUseCase>()
655
+ export class CreateTodoUseCase implements ICreateTodoUseCase {
656
+ constructor(
657
+ private store: TodoStore,
658
+ private validateService: ITodoValidateService
659
+ ) {}
660
+
661
+ async execute(dto: CreateTodoDto): Promise<Result<Todo, AppError>> {
662
+ const validation = this.validateService.validateTitle(dto.title);
663
+ if (!validation.valid) return Err(validation.error);
664
+
665
+ const todo = { id: crypto.randomUUID(), ...dto, completed: false };
666
+ this.store.add(todo);
667
+ return Ok(todo);
668
+ }
619
669
  }
620
670
  ```
621
671
 
622
- Тип поля берется из store через lookup-тип (`UserStore['name']`), что дает полную типобезопасность.
672
+ ### HttpClient and ApiClient
673
+
674
+ Helfy provides an HTTP client and a Query/Mutation layer (similar to TanStack Query) for API data fetching.
623
675
 
624
- ### Контекст хранилищ
676
+ **Configure HttpClient** in `createApp`:
677
+
678
+ ```typescript
679
+ createApp({ root: document.getElementById("root")! })
680
+ .http({
681
+ baseUrl: "/api",
682
+ timeout: 10000,
683
+ headers: { "X-App-Version": "1.0" },
684
+ queryCacheMaxSize: 100,
685
+ })
686
+ .router({ routes })
687
+ .mount(App);
688
+ ```
625
689
 
626
- Рекомендуется создать единый контекст для всех store:
690
+ **Define API interface and implementation** with `@ApiClient` and `@queryConfig`:
627
691
 
628
692
  ```typescript
629
- import UserStore from "./UserStore";
630
- import AppStore from "./AppStore";
693
+ import { ApiClient, queryConfig, QueryBuilder, type Query, type HttpClient } from "@helfy/helfy";
631
694
 
632
- class Stores {
633
- static readonly userStore = new UserStore();
634
- static readonly appStore = new AppStore();
695
+ export interface TodoApi {
696
+ todos(): Query<Todo[]>;
635
697
  }
636
698
 
637
- export default Stores;
699
+ @ApiClient<TodoApi>()
700
+ export class TodoApiImpl implements TodoApi {
701
+ constructor(private readonly http: HttpClient) {}
702
+
703
+ @queryConfig("todos")
704
+ todos() {
705
+ return new QueryBuilder<Todo[]>(["todo"])
706
+ .fn(() => this.http.get<Todo[]>("/todos"))
707
+ .staleTime(5 * 60 * 1000)
708
+ .refetchOnWindowFocus(true);
709
+ }
710
+ }
711
+ ```
712
+
713
+ **Use `@useQuery` in View** for automatic fetch on mount:
714
+
715
+ ```tsx
716
+ @View
717
+ class TodoList {
718
+ @useQuery<TodoApi>("todos") private todosQuery!: Query<Todo[]>;
719
+
720
+ render() {
721
+ const q = this.todosQuery;
722
+ if (q.isLoading) return <Spinner />;
723
+ if (q.isError) return <ErrorMessage error={q.error} />;
724
+ return <ul>{q.data?.map((t) => <li>{t.text}</li>)}</ul>;
725
+ }
726
+ }
727
+ ```
728
+
729
+ **Use `@useMutation`** for imperative mutations:
730
+
731
+ ```tsx
732
+ import type { Mutation } from "@helfy/helfy";
733
+
734
+ @useMutation<TodoApi>("create") private createTodo!: Mutation<TodoItem, AddTodoDto>;
735
+
736
+ async handleSubmit() {
737
+ await this.createTodo.mutateAsync({ title: this.title });
738
+ }
638
739
  ```
639
740
 
640
- Использование в компонентах:
741
+ ### Accessing Store and UseCase from components
742
+
743
+ Inject Store and UseCase via `@inject` in View/Context. Prefer UseCase for mutations, Store for reads:
641
744
 
642
745
  ```tsx
643
- import Stores from "./StoreContext";
746
+ import { View, inject } from "@helfy/helfy";
644
747
 
645
- // подписка через декоратор
646
- @observe(Stores.userStore, 'name')
647
- private userName: string;
748
+ @View
749
+ class TodoList {
750
+ @inject<ITodoStore>() private store!: ITodoStore;
751
+ @inject<ICreateTodoUseCase>() private createTodo!: ICreateTodoUseCase;
648
752
 
649
- // прямой вызов метода store
650
- Stores.userStore.login("Alice");
753
+ render() {
754
+ return (
755
+ <ul>
756
+ @for (todo of this.store.filteredTodos; track todo.id) {
757
+ <li>{todo.title}</li>
758
+ }
759
+ </ul>
760
+ );
761
+ }
762
+ }
651
763
  ```
652
764
 
653
765
  ---
654
766
 
655
- ## Логирование (@logger)
767
+ ## Logging (@logger)
656
768
 
657
- Декоратор `@logger` инжектирует логгер в классы View, Context, Store и Injectable. Имя класса подставляется на этапе компиляции (Babel-плагин), что сохраняет читаемые имена даже при минификации.
769
+ The `@logger` decorator injects a logger into View, Context, Store, Service, and UseCase classes. The class name is set at compile time (Babel plugin), keeping readable names even after minification.
658
770
 
659
- ### Использование
771
+ ### Usage
660
772
 
661
773
  ```tsx
662
774
  import { View, logger, type ILogger } from "@helfy/helfy";
@@ -673,22 +785,23 @@ class TodoInput {
673
785
  }
674
786
  ```
675
787
 
676
- **Варианты:**
677
- - `@logger()` — имя класса подставляется на compile-time как `<ClassName>`, формат и цвет зависят от типа класса
678
- - `@logger("my-tag")` — кастомный тег, серый цвет
788
+ **Variants:**
789
+ - `@logger()` — class name is set at compile-time as `<ClassName>`, format and color depend on class type
790
+ - `@logger("my-tag")` — custom tag, gray color
679
791
 
680
- ### Формат и цвет тегов по типу класса
792
+ ### Tag format and color by class type
681
793
 
682
- | Тип | Формат | Цвет |
683
- |-----|--------|------|
684
- | View (компоненты) | `<TodoInput>` | skyblue |
685
- | Injectable (сервисы) | `TodoValidateService()` | pink |
794
+ | Type | Format | Color |
795
+ |------|--------|-------|
796
+ | View (components) | `<TodoInput>` | skyblue |
797
+ | Service / Injectable | `TodoValidateService()` | pink |
798
+ | UseCase | `CreateTodoUseCase()` | pink |
686
799
  | Context | `{TodoContext}` | khaki |
687
800
  | Form | `[LoginFormContext]` | bright blue |
688
801
  | Store | `TodoStore[]` | lime green |
689
- | Кастомный `@logger("...")` | как указано | gray |
802
+ | Custom `@logger("...")` | as specified | gray |
690
803
 
691
- ### API логгера
804
+ ### Logger API
692
805
 
693
806
  ```typescript
694
807
  interface ILogger {
@@ -696,13 +809,13 @@ interface ILogger {
696
809
  info(message: string, meta?: Record<string, unknown>): void;
697
810
  warn(message: string, meta?: Record<string, unknown>): void;
698
811
  error(messageOrError: string | Error, meta?: Record<string, unknown>): void;
699
- withContext(ctx: string): ILogger; // дочерний логгер с доп. префиксом
812
+ withContext(ctx: string): ILogger; // child logger with extra prefix
700
813
  }
701
814
  ```
702
815
 
703
- ### DI и транспорты
816
+ ### DI and transports
704
817
 
705
- `LoggerService` регистрируется под `ILoggerToken` при `registerAllServices`. Можно подключить кастомные транспорты (Sentry, файл и т.д.), реализующие `ILogTransport`:
818
+ `LoggerService` is registered under `ILoggerToken` via `registerAllServices`. Custom transports (Sentry, file, etc.) implementing `ILogTransport` can be wired up:
706
819
 
707
820
  ```typescript
708
821
  import { LoggerService, ConsoleTransport, ILoggerToken } from "@helfy/helfy";
@@ -715,36 +828,37 @@ createApp({ root })
715
828
  .mount(App);
716
829
  ```
717
830
 
718
- Без зарегистрированного логгера в контейнере используется fallback с выводом в `console`.
831
+ Without a registered logger in the container, a fallback that logs to `console` is used.
719
832
 
720
833
  ---
721
834
 
722
835
  ## Context & DI
723
836
 
724
- Helfy поддерживает иерархический Context и Dependency Injection: провайдер оборачивает поддерево в JSX, а дочерние компоненты получают значения через `@ctx`. Это удобно для темы, форм, роутинга и других общих зависимостей.
837
+ Helfy supports hierarchical Context and Dependency Injection: a provider wraps a subtree in JSX, and child components receive values via `@useCtx`. Useful for theme, forms, routing, and other shared dependencies.
838
+
839
+ ### Provider (@Context)
725
840
 
726
- ### Провайдер (@Context, @provide)
841
+ A class with the `@Context` decorator is a non-rendering provider: it has no `render()` and only renders its children. Use `@state` for reactive fields, `@computed` for derived values; public methods are exposed automatically.
727
842
 
728
- Класс с декоратором `@Context` не-рендерящий провайдер: он не имеет `render()` и рендерит только своих детей. Поля с `@provide` становятся доступны потребителям ниже по дереву.
843
+ **Constructor:** receives only props from JSX. Define your own props interface (same as View). Use `@inject` for container dependencies.
729
844
 
730
845
  ```tsx
731
- import { Context, provide } from "@helfy/helfy";
846
+ import { Context, state } from "@helfy/helfy";
732
847
 
733
848
  export type TThemeMode = "light" | "dark";
734
849
 
735
850
  @Context
736
851
  export class ThemeContext {
737
- @provide({ reactive: true })
852
+ @state
738
853
  mode: TThemeMode = "dark";
739
854
 
740
- @provide()
741
855
  toggle = () => {
742
856
  this.mode = this.mode === "dark" ? "light" : "dark";
743
857
  };
744
858
  }
745
859
  ```
746
860
 
747
- Использование в JSX — обёртка вокруг поддерева:
861
+ Usage in JSX — wrap a subtree:
748
862
 
749
863
  ```tsx
750
864
  <ThemeContext>
@@ -753,55 +867,79 @@ export class ThemeContext {
753
867
  </ThemeContext>
754
868
  ```
755
869
 
756
- Фреймворк рендерит только детей `ThemeContext`; сам провайдер не создаёт DOM-узлов.
870
+ The framework only renders the children of `ThemeContext`; the provider itself does not create DOM nodes.
871
+
872
+ **Context with typed props** — define an interface and use it in the constructor (same pattern as View):
873
+
874
+ ```tsx
875
+ interface ApiContextProps {
876
+ baseUrl: string;
877
+ }
878
+
879
+ @Context
880
+ export class ApiContext {
881
+ constructor(readonly props: ApiContextProps) {}
882
+ // ...
883
+ }
884
+ ```
757
885
 
758
- ### Потребитель (@ctx)
886
+ ### Consumer (@useCtx)
759
887
 
760
- Компонент с `@View` может получать значение контекста через `@ctx`:
888
+ A component with `@View` can receive context values via `@useCtx`:
761
889
 
762
890
  ```tsx
763
- import { View, ctx } from "@helfy/helfy";
891
+ import { View, useCtx } from "@helfy/helfy";
764
892
  import { ThemeContext } from "./ThemeContext";
765
893
 
766
894
  @View
767
895
  class ThemeToggle {
768
- @ctx(ThemeContext)
896
+ @useCtx(ThemeContext)
769
897
  private theme!: ThemeContext;
770
898
 
771
899
  render() {
772
900
  return (
773
901
  <button onclick={this.theme.toggle}>
774
- {this.theme.mode === "dark" ? "Светлая тема" : "Тёмная тема"}
902
+ {this.theme.mode === "dark" ? "Light theme" : "Dark theme"}
775
903
  </button>
776
904
  );
777
905
  }
778
906
  }
779
907
  ```
780
908
 
781
- Можно инжектировать только поле контекста:
909
+ You can inject only a context field:
782
910
 
783
911
  ```tsx
784
912
  @View
785
913
  class ModeDisplay {
786
- @ctx(ThemeContext, "mode")
914
+ @useCtx(ThemeContext, "mode")
787
915
  private mode!: TThemeMode;
788
916
 
789
917
  render() {
790
- return <span>Текущая тема: {this.mode}</span>;
918
+ return <span>Current theme: {this.mode}</span>;
791
919
  }
792
920
  }
793
921
  ```
794
922
 
795
- Поиск провайдера идёт вверх по дереву (`_parentView`); используется ближайший провайдер с нужным ключом.
923
+ **Interface-based injection** for contexts with `@Context<IX>()`, use `@useCtx<ITodoContext>()`:
924
+
925
+ ```tsx
926
+ @useCtx<ITodoContext>()
927
+ private ctx!: ITodoContext;
928
+
929
+ @useCtx<ITodoContext>("filteredTodos")
930
+ private filteredTodos!: ITodoContext["filteredTodos"];
931
+ ```
932
+
933
+ Provider lookup goes up the tree (`_parentView`); the nearest provider with the matching key is used.
796
934
 
797
- ### Реактивные поля
935
+ ### Reactive fields
798
936
 
799
- `@provide({ reactive: true })` делает поле реактивным: при изменении все потребители перерендериваются. Для методов достаточно `@provide()` без `reactive`.
937
+ `@state` makes a field reactive: when it changes, all consumers re-render. Public methods are exposed automatically.
800
938
 
801
- **Вычисляемые поля**геттеры с `@provide({ computed: true, deps: ["field1", "field2"] })` пересчитываются при изменении зависимостей и триггерят перерендер потребителей:
939
+ **Computed fields**getters with `@computed` recompute when dependencies change and trigger consumer re-renders:
802
940
 
803
941
  ```tsx
804
- @provide({ computed: true, deps: ["todos", "filter"] })
942
+ @computed
805
943
  get filteredTodos(): Todo[] {
806
944
  return this.filter === "active"
807
945
  ? this.todos.filter(t => !t.completed)
@@ -809,52 +947,62 @@ get filteredTodos(): Todo[] {
809
947
  }
810
948
  ```
811
949
 
812
- ### Опциональная инжекция
950
+ ### Optional injection
813
951
 
814
- Третий аргумент `@ctx` — опции `{ optional?, defaultValue? }`:
952
+ The third argument of `@useCtx` — options `{ optional?, defaultValue? }`:
815
953
 
816
954
  ```tsx
817
- @ctx(ThemeContext, { optional: true })
955
+ @useCtx(ThemeContext, { optional: true })
818
956
  private theme?: ThemeContext;
819
957
 
820
- @ctx(ThemeContext, "mode", { defaultValue: "light" })
958
+ @useCtx(ThemeContext, "mode", { defaultValue: "light" })
821
959
  private mode = "light";
822
960
  ```
823
961
 
824
- Без провайдера в дереве `optional: true` даёт `undefined`; `defaultValue` используется, когда провайдера нет.
962
+ With no provider in the tree, `optional: true` yields `undefined`; `defaultValue` is used when the provider is absent.
963
+
964
+ ### DI: props vs dependencies
825
965
 
826
- ### DI Container (конструкторный инжект)
966
+ **View and Context:** constructor receives only props from JSX. Dependencies are injected via field decorators:
967
+ - `@inject<IX>()` — from global container (Store, Service, UseCase)
968
+ - `@useCtx<IX>()` — from tree @Context / @Form providers
827
969
 
828
- Helfy поддерживает DI-контейнер для глобальных сервисов с **compile-time** магией: сервисы помечаются `@Injectable<IX>()`, потребители указывают только тип в конструкторе — без `@inject` и явных токенов.
970
+ **@Service and @UseCase:** constructor DI via `@diParams` (added by Babel plugin) for injected dependencies.
829
971
 
830
- **Конвенция:** последний параметр конструктора — props от родителя; предшествующие параметры резолвятся из контейнера или из @Context по дереву.
972
+ ### Global services (@inject)
831
973
 
832
- **Сервис** — обязательно `@Injectable<IX>()` (интерфейс в generic):
974
+ Use `@inject<IX>()` in View/Context to access Store, Service, and UseCase from the container:
833
975
 
834
976
  ```typescript
977
+ // Define interface and implement with @Service
835
978
  export interface ITodoValidateService {
836
979
  validate(title: string): ValidationResult;
837
980
  }
838
981
 
839
- @Injectable<ITodoValidateService>()
982
+ @Service<ITodoValidateService>()
840
983
  export class TodoValidateService implements ITodoValidateService {
841
984
  validate(title: string) { ... }
842
985
  }
843
986
  ```
844
987
 
845
- **Потребитель**только тип в конструкторе (плагин подставляет токен):
988
+ **Consumer (View)** inject Store or UseCase:
846
989
 
847
990
  ```tsx
848
991
  @View
849
992
  class TodoInput {
850
- constructor(
851
- private readonly validateService: ITodoValidateService,
852
- readonly props: Props
853
- ) {}
993
+ @inject<ITodoValidateService>() private validateService!: ITodoValidateService;
994
+ @inject<ICreateTodoUseCase>() private createTodo!: ICreateTodoUseCase;
995
+
996
+ constructor(readonly props: Props) {}
997
+
998
+ render() {
999
+ // Call UseCase for mutations, Service for validation
1000
+ return <form onsubmit={() => this.createTodo.execute(this.form.getPayload())}>...</form>;
1001
+ }
854
1002
  }
855
1003
  ```
856
1004
 
857
- **Bootstrap** — регистрация DI подключается автоматически при `createApp` (плагин подставляет `.useDI(registerAllServices)` в цепочку):
1005
+ **Bootstrap** — DI registration is automatic at `createApp` (plugin injects `.useDI(registerAllServices)`):
858
1006
 
859
1007
  ```typescript
860
1008
  createApp({ root: document.getElementById("root")! })
@@ -862,9 +1010,9 @@ createApp({ root: document.getElementById("root")! })
862
1010
  .mount(App);
863
1011
  ```
864
1012
 
865
- Сканер вызывается Babel-плагином перед трансформацией, находит все `@Injectable<IX>()`, генерирует `.helfy/di-tokens.ts` и `.helfy/di-registry.ts`.
1013
+ The DI scanner finds `@Store`, `@Service<IX>()`, `@UseCase<IX>()` and generates `.helfy/di-tokens.ts`, `.helfy/di-registry.ts`.
866
1014
 
867
- **Fallback** — для ручной настройки доступны `.configureContainer()`, `@inject(token)`, `@Injectable(token)`:
1015
+ **Fallback** — for manual setup: `.configureContainer()`, `@Injectable(token)`:
868
1016
 
869
1017
  ```typescript
870
1018
  createApp({ root })
@@ -876,38 +1024,38 @@ createApp({ root })
876
1024
  .mount(App);
877
1025
  ```
878
1026
 
879
- Опционально: передать свой контейнер через `.container(myContainer)` или `createApp({ root, container })`.
1027
+ Optionally: pass a custom container via `.container(myContainer)` or `createApp({ root, container })`.
880
1028
 
881
- `@Context` и контейнер сосуществуют: Context для tree-scoped значений (тема, роутер), Container для глобальных сервисов.
1029
+ `@Context` and the container coexist: Context is for tree-scoped values (theme, router), Container for global services.
882
1030
 
883
1031
  ---
884
1032
 
885
1033
  ## JSX
886
1034
 
887
- Helfy использует кастомный JSX runtime (`@helfy/helfy/jsx-runtime`). JSX транслируется в вызовы `jsx()` / `jsxs()`, которые строят виртуальное представление DOM.
1035
+ Helfy uses a custom JSX runtime (`@helfy/helfy/jsx-runtime`). JSX is translated to `jsx()` / `jsxs()` calls that build a virtual DOM representation.
888
1036
 
889
- ### Атрибуты
1037
+ ### Attributes
890
1038
 
891
- Стандартные HTML-атрибуты передаются напрямую:
1039
+ Standard HTML attributes are passed through:
892
1040
 
893
1041
  ```tsx
894
- <input type="text" placeholder="Введите имя" />
1042
+ <input type="text" placeholder="Enter name" />
895
1043
  <img src="/logo.png" alt="Logo" />
896
1044
  <div id="container" class="wrapper"></div>
897
1045
  ```
898
1046
 
899
- ### События
1047
+ ### Events
900
1048
 
901
- Обработчики событий передаются через атрибуты в **нижнем регистре** (`onclick`, `oninput`, а не `onClick`):
1049
+ Event handlers use **lowercase** attributes (`onclick`, `oninput`, not `onClick`):
902
1050
 
903
1051
  ```tsx
904
1052
  <button onclick={() => this.increment()}>+</button>
905
1053
  <input oninput={(e) => this.handleInput(e)} />
906
1054
  ```
907
1055
 
908
- ### Стили
1056
+ ### Styles
909
1057
 
910
- Инлайн-стили передаются объектом с camelCase-ключами:
1058
+ Inline styles use an object with camelCase keys:
911
1059
 
912
1060
  ```tsx
913
1061
  <div style={{
@@ -916,26 +1064,26 @@ Helfy использует кастомный JSX runtime (`@helfy/helfy/jsx-run
916
1064
  padding: '8px 16px',
917
1065
  borderRadius: '4px'
918
1066
  }}>
919
- Стилизованный блок
1067
+ Styled block
920
1068
  </div>
921
1069
  ```
922
1070
 
923
- ### CSS-классы
1071
+ ### CSS classes
924
1072
 
925
- Атрибут `class` принимает строку или массив с условными классами:
1073
+ The `class` attribute accepts a string or an array with conditional classes:
926
1074
 
927
1075
  ```tsx
928
- // строка
1076
+ // string
929
1077
  <div class="container">...</div>
930
1078
 
931
1079
  // CSS Modules
932
1080
  import styles from './App.module.css';
933
1081
  <div class={styles.wrapper}>...</div>
934
1082
 
935
- // условные классы через массив
1083
+ // conditional classes via array
936
1084
  <div class={[
937
1085
  styles.cell,
938
- [styles.active, this.isActive], // применяется если this.isActive === true
1086
+ [styles.active, this.isActive], // applied when this.isActive === true
939
1087
  [styles.disabled, this.isDisabled],
940
1088
  ]}>...</div>
941
1089
  ```
@@ -944,33 +1092,39 @@ import styles from './App.module.css';
944
1092
 
945
1093
  ## API
946
1094
 
947
- ### Декораторы
948
-
949
- | Декоратор | Область | Описание |
950
- |-----------|---------|----------|
951
- | `@View` | Класс | Превращает класс в компонент с `render()`, `view`, `update()` |
952
- | `@state` | Поле | Локальное состояние компонента на базе signals (запись обновляет только этот компонент) |
953
- | `@Store` | Класс | Добавляет `subscribe`/`unsubscribe` и реактивность `@observable`-полей |
954
- | `@observable` | Поле | Делает поле store реактивным -- запись уведомляет подписчиков |
955
- | `@observe(store, field)` | Поле | Связывает поле компонента с полем store |
956
- | `@Context` | Класс | Не-рендерящий провайдер контекста; рендерит только детей |
957
- | `@provide()` / `@provide({ reactive: true })` / `@provide({ computed: true, deps })` | Поле | Помечает поле как доступное для `@ctx`. `reactive` — потребители перерендериваются при изменении; `computed` + `deps` — для геттеров |
958
- | `@ctx(ContextClass)` / `@ctx(ContextClass, field)` | Поле | Инжектирует контекст или поле контекста из ближайшего провайдера вверх по дереву |
959
- | `@Injectable<IX>()` / `@Injectable<IX>('singleton' \| 'transient' \| 'scoped')` / `@Injectable(token)` | Класс | Помечает класс как injectable. Scope в (): `'singleton'` (по умолчанию), `'transient'`, `'scoped'`. Fallback: `@Injectable(token)` с Symbol/строкой |
960
- | `@inject(token)` | Параметр конструктора | Задаёт токен для параметра (fallback; при `@Injectable<IX>()` плагин подставляет токен автоматически) |
961
- | `@logger()` / `@logger("tag")` | Поле | Инжектирует ILogger. Без аргумента compile-time имя класса и цвет по типу (View/Context/Store/Injectable). С аргументом — кастомный тег (серый) |
962
- | `@ref` | Поле | Помечает поле для получения ссылки на DOM или компонент (использовать с `@ref(this.имяПоля)` в JSX) |
963
- | `@expose` | Метод | Делает метод доступным родителю при `@ref` на компонент (без `@expose` родитель получает экземпляр целиком) |
964
- | `@binded(name)` | Поле | Связывает поле с биндингом `@bind:name` от родителя (для кастомных компонентов) |
965
- | `@bind(expr)` / `@bind:name(expr)` | JSX | Двустороннее связывание с `@state` полем (value/checked + oninput/onchange). Для компонентов: `@bind:value(expr)` |
966
- | `@Form` | Класс | Контекст формы с полями `@field` |
967
- | `@field(options)` | Поле FormContext | Создаёт FieldState для поля формы (value, isTouched, error, isDirty) |
968
- | `@useForm(FormContext)` | Поле | Инжектирует форму в компонент с подпиской на изменения полей |
969
- | `@field(expr)` | JSX | Подключает инпут к FieldState: value/checked + onblur + error class + aria-invalid |
970
-
971
- ### Роутинг (SPA)
972
-
973
- Helfy включает лёгкий SPA‑роутер. При вызове `createApp().router({ routes }).mount(App)` фреймворк автоматически оборачивает приложение в `RouterContext`, поэтому свой App не нужно оборачивать вручную:
1095
+ ### Decorators
1096
+
1097
+ | Decorator | Scope | Description |
1098
+ |-----------|-------|-------------|
1099
+ | `@View` | Class | Turns a class into a component with `render()`, `view`, `update()` |
1100
+ | `@state` | Field | Component local state on signals (write updates only this component) |
1101
+ | `@Store` | Class | Global reactive state. Empty constructor; only `@state`/`@computed`. No `@inject`, `@useCtx`, `@effect`, or external I/O |
1102
+ | `@observable` | Field | Alias for `@state` in Store; makes field reactive |
1103
+ | `@Service<IX>()` | Class | Infrastructure (HTTP, validation, repos). Constructor DI. No `@state`/`@computed`. Can mutate Store |
1104
+ | `@UseCase<IX>()` | Class | Business scenarios. Injects Store + Service. Main method: `execute`/`perform`/`handle`. No `@state`/`@computed` |
1105
+ | `@Context` | Class | Non-rendering context provider; renders only children |
1106
+ | `@state` / `@computed` | Context field | In `@Context`: `@state` for reactive fields, `@computed` for derived getters. Public methods exposed automatically |
1107
+ | `@inject<IX>()` | Field | Injects Store, Service, or UseCase from container. Use in View/Context |
1108
+ | `@useCtx(ContextClass)` / `@useCtx<IX>()` / `@useCtx(ContextClass, field)` | Field | Injects context or a context field from the nearest provider up the tree |
1109
+ | `@Injectable<IX>()` | Class | Generic injectable. Prefer `@Service` for infrastructure, `@UseCase` for business logic |
1110
+ | `@logger()` / `@logger("tag")` | Field | Injects ILogger. No arg compile-time class name and color by type (View/Context/Store/Injectable). With arg — custom tag (gray) |
1111
+ | `@ref` | Field | Marks a field to receive a DOM or component reference (use with `@ref(this.fieldName)` in JSX) |
1112
+ | `@expose` | Method | Makes a method available to the parent when using `@ref` on a component (without `@expose` the parent gets the full instance) |
1113
+ | `@binded(name)` | Field | Binds a field to `@bind:name` from the parent (for custom components) |
1114
+ | `@bind(expr)` / `@bind:name(expr)` | JSX | Two-way binding with `@state` field (value/checked + oninput/onchange). For components: `@bind:value(expr)` |
1115
+ | `@ApiClient<IX>()` | Class | API client with `@queryConfig` / `@mutationConfig` methods. Registers as Service by interface |
1116
+ | `@queryConfig(keyTemplate)` | Method | Marks method as Query; returns QueryBuilder chain, decorator calls `.build()` |
1117
+ | `@mutationConfig(options?)` | Method | Marks method as Mutation; merges invalidateQueries, optimisticFn into Mutation |
1118
+ | `@useQuery<ApiInterface>(keyOrGetter)` | Field | Injects Query from ApiClient, refetch on mount, reactive data/isLoading/isError |
1119
+ | `@useMutation<ApiInterface>(methodName)` | Field | Injects Mutation from ApiClient by method name |
1120
+ | `@Form` | Class | Form context with `@field` fields |
1121
+ | `@field(options)` | FormContext field | Creates FieldState for a form field (value, isTouched, error, isDirty) |
1122
+ | `@useForm(FormContext)` | Field | Injects the form into a component with subscription to field changes |
1123
+ | `@field(expr)` | JSX | Connects an input to FieldState: value/checked + onblur + error class + aria-invalid |
1124
+
1125
+ ### Routing (SPA)
1126
+
1127
+ Helfy includes a lightweight SPA router. When you call `createApp().router({ routes }).mount(App)`, the framework automatically wraps the app in `RouterContext`, so you don't need to wrap your App manually:
974
1128
 
975
1129
  ```tsx
976
1130
  // index.ts
@@ -1012,15 +1166,15 @@ class Sidebar {
1012
1166
 
1013
1167
  return (
1014
1168
  <nav>
1015
- <Link to="/" label="Главная" class={isHome ? "font-bold" : ""} />
1016
- <Link to="/analytics" label="Аналитика" class={isAnalytics ? "font-bold" : ""} />
1169
+ <Link to="/" label="Home" class={isHome ? "font-bold" : ""} />
1170
+ <Link to="/analytics" label="Analytics" class={isAnalytics ? "font-bold" : ""} />
1017
1171
  </nav>
1018
1172
  );
1019
1173
  }
1020
1174
  }
1021
1175
  ```
1022
1176
 
1023
- Типичный компонент‑страница может использовать утилитарные декораторы роутера:
1177
+ A typical page component can use router decorators:
1024
1178
 
1025
1179
  ```tsx
1026
1180
  import { View, path, search, params, router, type RouterAPI } from "@helfy/helfy";
@@ -1047,7 +1201,7 @@ class DebugPage {
1047
1201
  <pre>params: {JSON.stringify(this.routeParams)}</pre>
1048
1202
  <pre>query: {JSON.stringify(this.query)}</pre>
1049
1203
  <button onclick={() => this.rtr.push("/analytics")}>
1050
- Перейти в аналитику
1204
+ Go to analytics
1051
1205
  </button>
1052
1206
  </section>
1053
1207
  );
@@ -1055,7 +1209,7 @@ class DebugPage {
1055
1209
  }
1056
1210
  ```
1057
1211
 
1058
- **Кастомная страница 404.** Оберните `RouterView` и переопределите слот `notFound`:
1212
+ **Custom 404 page.** Wrap `RouterView` and override the `notFound` slot:
1059
1213
 
1060
1214
  ```tsx
1061
1215
  @View
@@ -1070,35 +1224,35 @@ class AppRouter {
1070
1224
  );
1071
1225
  }
1072
1226
  }
1073
- ```
1227
+ ```
1074
1228
 
1075
- ### Жизненный цикл компонента
1229
+ ### Component lifecycle
1076
1230
 
1077
- 1. `constructor(props)` -- создание экземпляра, `this.props` оборачивается в реактивный Proxy
1078
- 2. `render()` -- возврат JSX (**вызывается один раз** при монтировании; Babel-плагин оборачивает выражения в `createReactiveChild` для fine-grained обновлений)
1079
- 3. `mount()` -- первичный рендер JSX в DOM-фрагмент
1080
- 4. `onMount()` -- хук после первого монтирования (опциональный)
1081
- 5. `onAttached()` -- хук после вставки в document (опциональный). Вызывается при `attach(parent)`.
1082
- 6. `update()` -- структурное обновление (при смене корневого компонента, например при роутинге). Для одного и того же дочернего компонента обновляются только signals через `updateProps`
1083
- 7. `updateProps(newProps)` -- обновление prop-signals через `batch()` (fine-grained, без полного перерендера)
1231
+ 1. `constructor(props)` instance creation, `this.props` wrapped in reactive Proxy
1232
+ 2. `render()` returns JSX (**called once** on mount)
1233
+ 3. `mount()` initial JSX render to DOM fragment
1234
+ 4. `onMount()` hook after first mount (optional)
1235
+ 5. `onAttached()` hook after insertion into document (optional). Called on `attach(parent)`.
1236
+ 6. `update()` structural update (e.g. on route change). For the same child component, only signals are updated via `updateProps`
1237
+ 7. `updateProps(newProps)` updates prop signals (fine-grained, no full re-render)
1084
1238
 
1085
1239
  #### onMount vs onAttached
1086
1240
 
1087
1241
  | | `onMount()` | `onAttached()` |
1088
1242
  |---|---|---|
1089
- | **Когда** | Сразу после `mount()`, дерево построено, рефы назначены | После `attach(parent)`, элемент в document |
1090
- | **Элемент в document** | Может быть ещё нет (корень в fragment) | Да |
1091
- | **Рефы** | Доступны | Доступны |
1243
+ | **When** | Right after `mount()`, tree built, refs assigned | After `attach(parent)`, element in document |
1244
+ | **Element in document** | May not be yet (root in fragment) | Yes |
1245
+ | **Refs** | Available | Available |
1092
1246
 
1093
- **`onMount()`** — для инициализации, не требующей document:
1094
- - Подписки на store/observable/сервисы
1095
- - Настройка внутреннего состояния
1096
- - Добавление обработчиков (работают и на detached-узлах)
1247
+ **`onMount()`** — for initialization that doesn't need the document:
1248
+ - Subscriptions to store/observable/services
1249
+ - Internal state setup
1250
+ - Adding handlers (works on detached nodes too)
1097
1251
 
1098
- **`onAttached()`** — для операций, требующих элемент в document:
1252
+ **`onAttached()`** — for operations that require the element in the document:
1099
1253
  - `focus()`, `scrollIntoView()`
1100
- - `getBoundingClientRect()`, замер layout
1101
- - Любые DOM API, работающие только с подключённым узлом
1254
+ - `getBoundingClientRect()`, layout measurement
1255
+ - Any DOM API that only works on attached nodes
1102
1256
 
1103
1257
  ```tsx
1104
1258
  @View
@@ -1106,22 +1260,22 @@ class SearchInput {
1106
1260
  @ref private input!: HTMLInputElement;
1107
1261
 
1108
1262
  onMount() {
1109
- this.store.subscribe(this.handleChange); // подписка — document не нужен
1263
+ this.store.subscribe(this.handleChange); // subscription — document not needed
1110
1264
  }
1111
1265
 
1112
1266
  onAttached() {
1113
- this.input.focus(); // фокуснужен document
1267
+ this.input.focus(); // focusneeds document
1114
1268
  }
1115
1269
  }
1116
1270
  ```
1117
1271
 
1118
- ### Слоты (content projection)
1272
+ ### Slots (content projection)
1119
1273
 
1120
- Helfy поддерживает именованные слоты с fallback‑содержимым и переопределением в дочерних компонентах.
1274
+ Helfy supports named slots with fallback content and override in child components.
1121
1275
 
1122
- #### Провайдер слота (`@View`‑компонент)
1276
+ #### Slot provider (`@View` component)
1123
1277
 
1124
- Слоты объявляются прямо в JSX через директиву `@slot:имя(...)` внутри `render()`:
1278
+ Slots are declared in JSX via the `@slot:name(...)` directive inside `render()`:
1125
1279
 
1126
1280
  ```tsx
1127
1281
  import { View } from "@helfy/helfy";
@@ -1131,24 +1285,24 @@ class AppLayout {
1131
1285
  render() {
1132
1286
  return (
1133
1287
  <section class="layout">
1134
- {/* Именованный слот header с fallback-разметкой */}
1135
- @slot:header({ title: "Список задач" }) fallback {
1288
+ {/* Named slot header with fallback markup */}
1289
+ @slot:header({ title: "Task list" }) fallback {
1136
1290
  <header class="mb-4">
1137
1291
  <h1 class="text-2xl font-bold text-gray-900">
1138
- Список задач
1292
+ Task list
1139
1293
  </h1>
1140
1294
  </header>
1141
1295
  }
1142
1296
 
1143
- {/* Именованный слот content с fallback и директивой @if внутри */}
1297
+ {/* Named slot content with fallback and @if inside */}
1144
1298
  @slot:content({ store: this.props.store, filtered: this.props.filtered, hasTodos: this.props.hasTodos }) fallback {
1145
1299
  @if (this.props.hasTodos) {
1146
1300
  <section class="pt-3 border-t border-gray-200 text-sm text-gray-600">
1147
1301
  <p class="mb-1">
1148
- Всего задач: {this.props.store.todos.length}, активных: {this.props.store.activeCount},
1149
- завершённых: {this.props.store.completedCount}
1302
+ Total: {this.props.store.todos.length}, active: {this.props.store.activeCount},
1303
+ completed: {this.props.store.completedCount}
1150
1304
  </p>
1151
- <p>Фильтрованных к показу: {this.props.filtered.length}</p>
1305
+ <p>Filtered: {this.props.filtered.length}</p>
1152
1306
  </section>
1153
1307
  }
1154
1308
  }
@@ -1158,15 +1312,15 @@ class AppLayout {
1158
1312
  }
1159
1313
  ```
1160
1314
 
1161
- Правила для провайдера:
1315
+ Provider rules:
1162
1316
 
1163
- - `@slot:header({ ... })` — объявляет именованный слот `header` и вызывает его.
1164
- - Блок `fallback { ... }` (опциональный) задаёт разметку по умолчанию, если слот не переопределён.
1165
- - Внутри `fallback` можно использовать директивы `@if`, `@for` и обычный JSX.
1317
+ - `@slot:header({ ... })` — declares the named slot `header` and invokes it.
1318
+ - The `fallback { ... }` block (optional) defines default markup when the slot is not overridden.
1319
+ - Inside `fallback` you can use `@if`, `@for`, and regular JSX.
1166
1320
 
1167
- #### Потребитель слота (override в JSX)
1321
+ #### Slot consumer (override in JSX)
1168
1322
 
1169
- Переопределение слота в дочернем компоненте делается через `@slot.имя(...) { ... }` внутри JSX‑детей:
1323
+ Override a slot in a child component via `@slot.name(...) { ... }` inside JSX children:
1170
1324
 
1171
1325
  ```tsx
1172
1326
  @View
@@ -1182,20 +1336,20 @@ class TodoApp {
1182
1336
  filtered={filtered}
1183
1337
  hasTodos={this.hasTodos}
1184
1338
  >
1185
- {/* Переопределяем слот header */}
1339
+ {/* Override header slot */}
1186
1340
  @slot.header({ title }) {
1187
1341
  <header class="mb-5">
1188
1342
  <h1 class="mb-4 text-2xl font-bold text-gray-900">
1189
- Задачи ({title})
1343
+ Tasks ({title})
1190
1344
  </h1>
1191
1345
  <TodoInput
1192
- placeholder="Добавить задачу…"
1346
+ placeholder="Add task…"
1193
1347
  onSubmit={(text) => store.add(text)}
1194
1348
  />
1195
1349
  </header>
1196
1350
  }
1197
1351
 
1198
- {/* Переопределяем слот content, внутри можно использовать @if/@for */}
1352
+ {/* Override content slot, @if/@for allowed inside */}
1199
1353
  @slot.content({ store, filtered, hasTodos }) {
1200
1354
  @if (hasTodos) {
1201
1355
  <section class="pt-3 border-t border-gray-200">
@@ -1221,8 +1375,8 @@ class TodoApp {
1221
1375
  }
1222
1376
  ```
1223
1377
 
1224
- Кратко по синтаксису:
1378
+ Syntax summary:
1225
1379
 
1226
- - `@slot:имя({ props }) fallback { FallbackJSX }` — объявление и вызов слота в провайдере.
1227
- - `@slot.имя({ ctx }) { OverrideJSX }` — переопределение слота в потребителе.
1228
- - Внутри `FallbackJSX` и `OverrideJSX` поддерживаются все директивы (`@if`, `@for`, `@empty`) и обычный JSX.
1380
+ - `@slot:name({ props }) fallback { FallbackJSX }` — declare and invoke the slot in the provider.
1381
+ - `@slot.name({ ctx }) { OverrideJSX }` — override the slot in the consumer.
1382
+ - All directives (`@if`, `@for`, `@empty`) and regular JSX work inside `FallbackJSX` and `OverrideJSX`.