@rt-tools/agent-kit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -18
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +28 -0
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +18 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
- package/assets/hooks/browser-guard-require-select.sh +54 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +43 -0
- package/assets/hooks/dev-server-guard.sh +115 -0
- package/assets/hooks/docs-guard.sh +282 -0
- package/assets/hooks/git-guard-delivery.sh +167 -0
- package/assets/hooks/git-guard-main.sh +73 -0
- package/assets/hooks/git-guard-push-tests.sh +94 -0
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +219 -0
- package/assets/hooks/qa-dataid-guard.sh +121 -0
- package/assets/hooks/reuse-first-guard.sh +154 -0
- package/assets/hooks/skill-gate-rearm.sh +23 -0
- package/assets/hooks/skill-gate.sh +128 -0
- package/assets/hooks/skill-loaded.sh +21 -0
- package/assets/hooks/sql-guard.sh +679 -0
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +101 -0
- package/assets/patterns/api-layer-pair.md +88 -0
- package/assets/patterns/browser-verification-measure.md +86 -0
- package/assets/patterns/browser-verification-stand.md +143 -0
- package/assets/patterns/component-structure-new.md +99 -0
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +137 -0
- package/assets/patterns/doc-style-write.md +109 -0
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +99 -0
- package/assets/patterns/git-workflow-migration.md +88 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +95 -0
- package/assets/patterns/lib-layers-new.md +82 -0
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +71 -0
- package/assets/patterns/platform-access-di.md +84 -0
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +104 -0
- package/assets/patterns/seo-verify.md +83 -0
- package/assets/patterns/shared-code-new.md +86 -0
- package/assets/patterns/spec-driven-domain.md +107 -0
- package/assets/patterns/spec-driven-rule.md +127 -0
- package/assets/patterns/styling-bem-component.md +88 -0
- package/assets/patterns/styling-bem-layout.md +73 -0
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +92 -0
- package/assets/patterns/testing-unit.md +117 -0
- package/assets/patterns/translations-key.md +64 -0
- package/assets/patterns/ts-procedure.md +65 -0
- package/assets/rules/angular-patterns.md +71 -0
- package/assets/rules/api-layer.md +71 -0
- package/assets/rules/browser-verification.md +87 -0
- package/assets/rules/component-structure.md +64 -0
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +103 -0
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +80 -0
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +70 -0
- package/assets/rules/platform-access.md +77 -0
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +83 -0
- package/assets/rules/seo.md +71 -0
- package/assets/rules/shared-code.md +70 -0
- package/assets/rules/spec-driven.md +135 -0
- package/assets/rules/styling-bem.md +74 -0
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +100 -0
- package/assets/rules/translations.md +69 -0
- package/assets/rules/typescript-conventions.md +76 -0
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +45 -0
- package/assets/templates/implementation.md +44 -0
- package/assets/templates/pattern.md +5 -1
- package/assets/templates/project.sh +54 -0
- package/assets/templates/rule.md +12 -23
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +14 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +23 -2
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +218 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +57 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +60 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +42 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +60 -2
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +29 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +78 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/rt-tools-agent-kit-0.2.0.tgz +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: task-flow-resume
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: task-flow
|
|
5
|
+
description: Паттерн правила task-flow. Брать при возвращении к незаконченной работе новым заходом — что уже пришло в контекст, чего не спрашивать у владельца, как править «Где стоим», как записывать решение по ходу и пересмотр этапа. Не брать для начала работы — это паттерн task-flow-start.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Возвращение к незаконченной работе
|
|
9
|
+
|
|
10
|
+
Паттерн правила `task-flow`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/work-conduct.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Сессия начата на ветке `<КЛЮЧ>-*`, работа в ней уже шла.
|
|
16
|
+
- Работа прерывается — надо оставить её так, чтобы следующий заход поднял без владельца.
|
|
17
|
+
|
|
18
|
+
## Что уже пришло в контекст
|
|
19
|
+
|
|
20
|
+
Хук `task-context-load.sh` на запуске сессии отдал замысел и ход работы целиком, а разбор
|
|
21
|
+
просьбы — путём. Перечитывать их файлами не нужно; `grill.md` читается, когда в ходе работы
|
|
22
|
+
всплыло решение, причина которого неясна.
|
|
23
|
+
|
|
24
|
+
Пришло предупреждение «РАБОТА БЕЗ ПАПКИ ЗАДАЧИ» — работа шла мимо: папка собирается с
|
|
25
|
+
образца, а `progress.md` заполняется по тому, что видно в дереве и в истории ветки, а не по
|
|
26
|
+
расспросам владельца.
|
|
27
|
+
|
|
28
|
+
## Чего не делать
|
|
29
|
+
|
|
30
|
+
- **Не спрашивать владельца о том, что записано.** Ради этого всё и заведено.
|
|
31
|
+
- **Не начинать заново то, что отмечено сделанным.** Отметка стоит в ходе работы; сомнение в
|
|
32
|
+
ней проверяется деревом — сборкой, тестами, чтением файла, — а не вопросом.
|
|
33
|
+
- **Не править замысел.** С ним сверяют результат; пересмотр идёт записью в ходе работы.
|
|
34
|
+
|
|
35
|
+
## Первое действие захода
|
|
36
|
+
|
|
37
|
+
Сверить «Где стоим» с деревом. Запись описывает день, когда её сделали:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git status --short
|
|
41
|
+
git log --oneline origin/main..HEAD
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Разошлось — «Где стоим» правится сразу, до работы: следующий заход поверит записи, а не
|
|
45
|
+
дереву.
|
|
46
|
+
|
|
47
|
+
## Как ведётся ход работы
|
|
48
|
+
|
|
49
|
+
Раздел «Где стоим» **перезаписывается**, а не дописывается — это первое, что читает следующий
|
|
50
|
+
заход, и единственное, что переживает обрезку по объёму:
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
## Где стоим
|
|
54
|
+
|
|
55
|
+
- **Этап:** 3 из 6 — гард и хук запуска
|
|
56
|
+
- **Сделано:** закон заведён, папка задачи и образец написаны
|
|
57
|
+
- **Следующий шаг:** сценарии обоих хуков, затем подключение в настройках
|
|
58
|
+
- **Незакоммиченное:** всё, ветка пока без коммитов
|
|
59
|
+
- **Ждём владельца:** нет
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Решение, принятое по ходу, — вместе с причиной и с тем, что было альтернативой:
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
- **2026-08-07. Гард судит по путям правки, а не по замыслу задачи.** Оценку «меняет ли
|
|
66
|
+
поведение» назначал бы тот, кому она мешает. Альтернатива — строка в замысле — отвергнута.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Пересмотр этапа — туда же, а не правкой замысла:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
- **2026-08-07. Этап 4 заменён: роли заводятся обе, а не одна.** Владелец решил при разборе;
|
|
73
|
+
прежний этап в замысле оставлен видимым.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Запись захода отмечает сделанное и то, чем это подтверждено:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
### 2026-08-07
|
|
80
|
+
|
|
81
|
+
- 28 сценариев в наборе, `bash .claude/hooks/tests/run.sh` — все наборы зелёные.
|
|
82
|
+
- Доэтапное, не этой работы: сверка очереди перечисляет шесть закрытых задач вне борды.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Ловушки
|
|
86
|
+
|
|
87
|
+
- **Заход, кончившийся ничем, тоже записывается.** Иначе следующий пойдёт той же дорогой:
|
|
88
|
+
«пробовали так — не вышло, потому что» стоит одной строки и экономит целый заход.
|
|
89
|
+
- **Незакоммиченное называется явно.** Работа живёт в дереве неделями; строка «что лежит
|
|
90
|
+
несохранённым и почему» — единственное, по чему это видно, пока отчёта нет.
|
|
91
|
+
- **Подтверждение — вывод команды или замер, а не пересказ.** «Проверил, работает» через
|
|
92
|
+
заход неотличимо от «казалось, что работает».
|
|
93
|
+
- **Доэтапное отделяется от своего.** Красное, найденное по дороге и не этой работой
|
|
94
|
+
сделанное, помечается таковым сразу: иначе следующий заход примет его за свою поломку.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: task-flow-start
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: task-flow
|
|
5
|
+
description: Паттерн правила task-flow. Брать в начале работы от владельца — готовый порядок: разведка до первого вопроса, шесть обязательных вопросов, договорённость о продукте, конвейер ролей, заведение задачи и ветки, сборка папки задачи. Не брать для возвращения к незаконченной работе — это паттерн task-flow-resume.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Начало работы
|
|
9
|
+
|
|
10
|
+
Паттерн правила `task-flow`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/work-conduct.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Владелец просит что-то сделать, и работы больше, чем на одну реплику.
|
|
16
|
+
- Замеченный по ходу дефект становится задачей.
|
|
17
|
+
|
|
18
|
+
## Порядок
|
|
19
|
+
|
|
20
|
+
### 1. Разведка — до первого вопроса
|
|
21
|
+
|
|
22
|
+
Вопрос, ответ на который лежит в коде, владельцу не задаётся: он обесценивает и остальные.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Agent(subagent_type: "Explore", prompt: "<тема просьбы>: что по ней уже есть в дереве —
|
|
26
|
+
какие спеки описывают, какие законы и правила задевает, какие либы затронуты,
|
|
27
|
+
есть ли готовый образец рядом. Верни находки, а не пересказ файлов.")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Находки складываются в раздел «Что уже есть в дереве» разбора.
|
|
31
|
+
|
|
32
|
+
### 2. Разбор с владельцем
|
|
33
|
+
|
|
34
|
+
Ведёт главный агент: субагент до владельца не достучится. Команда — `/grill-me`, один вопрос
|
|
35
|
+
за раз, к каждому — свой рекомендуемый ответ с доводом.
|
|
36
|
+
|
|
37
|
+
Шесть вопросов задаются всегда, даже когда задача кажется понятной:
|
|
38
|
+
|
|
39
|
+
| Вопрос | Зачем |
|
|
40
|
+
| -------------------------------------------- | -------------------------------------------------------- |
|
|
41
|
+
| Меняет ли задача поведение приложения | от этого зависит договорённость о продукте |
|
|
42
|
+
| Требует ли правки закона или правила | закон без ведома владельца не правится |
|
|
43
|
+
| Одна задача или несколько | делится то, что откатывается порознь, и делится ДО ветки |
|
|
44
|
+
| Что в задачу не входит | не названная вслух граница не существует |
|
|
45
|
+
| Чем будет видно, что задача закрыта | «работает» признаком не является |
|
|
46
|
+
| Есть ли образец, с которого снимается подход | разведка найдёт похожее, а не то |
|
|
47
|
+
|
|
48
|
+
Спрашивается прозой. Меню вариантов годится, только когда постановка уже подтверждена и
|
|
49
|
+
остался выбор значения из закрытого набора; выбор слова, имени и термина — никогда.
|
|
50
|
+
|
|
51
|
+
Ответы пишутся в `docs/tasks/_draft-<slug>/grill.md` — папка ещё черновик, номера нет.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mkdir -p docs/tasks/_draft-<slug>
|
|
55
|
+
cp docs/tasks/_template/grill.md docs/tasks/_draft-<slug>/grill.md
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Конвейер после разбора
|
|
59
|
+
|
|
60
|
+
Вопросов больше не будет — дальше роли:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Workflow(name: "plan", args: "docs/tasks/_draft-<slug>")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Нужность → договорённость о продукте (`spec-writer`) → её состязательный разбор
|
|
67
|
+
(`spec-critic`) → замысел и разбивка (`project-manager`). Пробелы, которые роли не смогли
|
|
68
|
+
закрыть, возвращаются владельцу — их относит главный агент.
|
|
69
|
+
|
|
70
|
+
### 4. Задача, ветка, папка
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run task:new -- --title '<Что не так>' --slug <slug> --label documentation --label area:tooling < тело.md
|
|
74
|
+
git checkout -b <КЛЮЧ>-<номер>-<slug>
|
|
75
|
+
npm run task:move -- <номер> in-progress
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`task:new` переименовывает `_draft-<slug>` в `<КЛЮЧ>-<номер>-<slug>` и проставляет шапку замысла.
|
|
79
|
+
Ветка заводится вторым вызовом: составную «завести и сразу коммитить» гард главной ветки
|
|
80
|
+
отклоняет целиком.
|
|
81
|
+
|
|
82
|
+
Остальные два файла — с образца:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cp docs/tasks/_template/plan.md docs/tasks/<КЛЮЧ>-<номер>-<slug>/plan.md
|
|
86
|
+
cp docs/tasks/_template/progress.md docs/tasks/<КЛЮЧ>-<номер>-<slug>/progress.md
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 5. Шапка замысла
|
|
90
|
+
|
|
91
|
+
Её читает гард:
|
|
92
|
+
|
|
93
|
+
```markdown
|
|
94
|
+
**Задача:** <КЛЮЧ>-282 · **Ветка:** <КЛЮЧ>-282-task-flow
|
|
95
|
+
**Драфт:** `docs/specs/bookings/proposed/aside-header/`
|
|
96
|
+
**Поведение:** меняется
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Работа, не задевающая `apps/**` и `libs/**`, договорённости не требует:
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
**Поведение:** не меняется — переезд слоя, снаружи не видно. Подтверждено владельцем.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Пустая причина не принимается.
|
|
106
|
+
|
|
107
|
+
## Ловушки
|
|
108
|
+
|
|
109
|
+
- **Номер не бывает первым.** До конца разбора неизвестно даже, сколько задач из него выйдет:
|
|
110
|
+
заведённая заранее задача после разбивки закрывается и остаётся мусором в очереди работ.
|
|
111
|
+
- **Разбор пишется на диск сразу, а не копится в переписке.** Сессия обрывается, и разбор,
|
|
112
|
+
прожитый в разговоре, восстанавливается только пересказом владельца.
|
|
113
|
+
- **Из одного разбора вышло несколько задач — общее уезжает в `docs/plans/<линия>.md`.**
|
|
114
|
+
Папка задачи умирает с мержем, а порядок задач и зависимости между ними должны его
|
|
115
|
+
пережить.
|
|
116
|
+
- **Задача заводится командой, а не четырьмя вызовами подряд.** Борда к репозиторию не
|
|
117
|
+
привязана, и задача попадает на неё только явным добавлением.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-e2e
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: testing
|
|
5
|
+
description: Паттерн правила testing. Брать при правке и прогоне сквозных спек в apps/site-e2e и apps/admin-e2e — что закрывается сквозной спекой, готовые команды прогона, стенд из прод-сборки под настоящим nginx, выключатели спек. Не брать для юнитов — это паттерн testing-unit.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Сквозные спеки
|
|
9
|
+
|
|
10
|
+
Паттерн правила `testing`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Пишется или правится спека в `apps/site-e2e/src` или `apps/admin-e2e/src`.
|
|
16
|
+
- Надо воспроизвести дефект, который виден только на прод-конфигурации.
|
|
17
|
+
- Готовится стенд под прогон против nginx.
|
|
18
|
+
|
|
19
|
+
## Сквозной спекой закрывается накопленное состояние
|
|
20
|
+
|
|
21
|
+
Случай для неё — тот, где состояние копится нажатиями: открытая панель, активный маршрут,
|
|
22
|
+
гард прошлого экрана. Всё, что проверяется одним заходом по адресу, дешевле закрыть юнитом на
|
|
23
|
+
чистой функции или замером экрана.
|
|
24
|
+
|
|
25
|
+
Отсюда и способ: нажатия по тем же элементам, что нажимает владелец, несколько подряд, без
|
|
26
|
+
перезагрузки между ними. Заход по прямому адресу поднимает приложение заново, накопленного
|
|
27
|
+
состояния у него нет, и проход читается как «дефект не подтверждается» — так на прод уехала
|
|
28
|
+
панель, застревавшая в адресе при уходе в соседний раздел.
|
|
29
|
+
|
|
30
|
+
Элементы находятся по `qa-dataid`: классы меняются вместе с вёрсткой, а поиск по роли и тексту
|
|
31
|
+
ломается на переводах.
|
|
32
|
+
|
|
33
|
+
## Прогон
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx nx e2e site-e2e -- --project=chromium
|
|
37
|
+
npx nx e2e admin-e2e -- --project=chromium
|
|
38
|
+
BASE_URL=http://localhost:{{dockerSitePort}} npx nx e2e site-e2e -- --project=chromium # против внешнего стенда
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
По умолчанию конфиг идёт на {{sitePort}} и подхватывает уже поднятый сервер. С `BASE_URL` свой сервер
|
|
42
|
+
не стартует вовсе.
|
|
43
|
+
|
|
44
|
+
Полный набор админки гоняется **одним воркером** (`--workers=1`): тесты с настоящей сессией
|
|
45
|
+
правят одни и те же объекты живой базы и в параллельном прогоне мешают друг другу. Одни и те
|
|
46
|
+
же файлы дали три падения на восьми воркерах и ноль на одном.
|
|
47
|
+
|
|
48
|
+
## Стенд из прод-сборки под настоящим nginx
|
|
49
|
+
|
|
50
|
+
Как его поднять — паттерн `browser-verification-stand`; там же сказано, почему главный конфиг
|
|
51
|
+
монтируется отдельной строкой и почему каталогом, а не файлом. Здесь важно одно: против такого
|
|
52
|
+
стенда прогон идёт с `BASE_URL`, и вместе с ним включаются проверки перенаправлений локалей.
|
|
53
|
+
|
|
54
|
+
## Выключатели
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const ALLOW_RENAME: boolean = process.env['E2E_ALLOW_SLUG_RENAME'] === '1';
|
|
58
|
+
test.skip(!ALLOW_RENAME, 'меняет живой адрес объекта: включается E2E_ALLOW_SLUG_RENAME=1');
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Спека, необратимо меняющая данные стенда, по умолчанию пропускается и включается своей
|
|
62
|
+
переменной.
|
|
63
|
+
- `BEHIND_NGINX` в `home.smoke.spec.ts` — это `!!process.env['BASE_URL']`: вместе с ним
|
|
64
|
+
просыпаются проверки перенаправлений локалей. Голый сервер отдачи страниц их не проходит, и
|
|
65
|
+
два падения выглядят регрессией.
|
|
66
|
+
- `HAS_ADMIN_SESSION` в `sign-in.ts` — пара `E2E_ADMIN_EMAIL` и `E2E_ADMIN_PASSWORD`.
|
|
67
|
+
- `playwright/no-skipped-test` выключен в `eslint.config.mjs`: выключатель теста здесь —
|
|
68
|
+
приём, а не забытый `test.skip`.
|
|
69
|
+
|
|
70
|
+
## Частые промахи
|
|
71
|
+
|
|
72
|
+
- **Порт {{ssrPort}} занимать осторожно:** стенд разработчика на {{sitePort}} ходит по тому же имени
|
|
73
|
+
`ssr:{{ssrPort}}` через `host-gateway`, и пока на нём висит чужой процесс, стенд отдаёт чужую
|
|
74
|
+
сборку.
|
|
75
|
+
- Браузер стоит один — chromium; узкий экран — `--project=mobile-chrome`. Ошибка «Executable
|
|
76
|
+
doesn't exist» разобрана в правиле `testing`: она же приходит после смены версии Playwright.
|
|
77
|
+
- Спеки админки без сессии пропускаются молча — прогон выглядит успешным, а проверено меньше
|
|
78
|
+
половины.
|
|
79
|
+
- Конфиг nginx монтируется каталогом, а не одиночным файлом: редактор пересоздаёт файл, и
|
|
80
|
+
контейнеру остаётся обрезанная копия.
|
|
81
|
+
- **Подменяется не только то, что запрашивает экран, но и то, что запрашивает шапка.** Счётчик
|
|
82
|
+
непрочитанного запрашивается на каждом экране под шапкой. Без подмены на этот запрос
|
|
83
|
+
отвечает настоящий сервер, поддельный вход он отбивает, интерцептор сбрасывает сессию, и
|
|
84
|
+
пятьдесят девять тестов падают на пропавшей шапке — выглядит это дефектом экрана.
|
|
85
|
+
- **Состояние, которое живёт только во время операции, тестом не проверяется.** Список заливки
|
|
86
|
+
под кнопкой виден, пока файлы летят: на подменённых ответах они долетают раньше, чем тест
|
|
87
|
+
успевает его прочитать, и тест краснеет через раз. Проверяют либо конечное состояние
|
|
88
|
+
(`data-state` строки стал `done`), либо то же промежуточное — но на ответе, который тест сам
|
|
89
|
+
задержал и сам отпускает.
|
|
90
|
+
- **Каталог сборки, удалённый под смонтированным томом, оставляет контейнер с пустым
|
|
91
|
+
корнем:** стенд отвечает 403 на всё, и падают сразу все тесты. Контейнер после
|
|
92
|
+
`rm -rf dist/apps/<приложение>` пересоздаётся.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-unit
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: testing
|
|
5
|
+
description: Паттерн правила testing. Брать при заведении или правке *.spec.ts под Vitest — готовая раскладка describe и it, сборщик фикстур, идентификатор сценария в заголовке, спека процедуры Connect с рукописным двойником базы. Не брать для сквозных спек — это паттерн testing-e2e.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Спека на чистую функцию и на процедуру
|
|
9
|
+
|
|
10
|
+
Паттерн правила `testing`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится или правится `*.spec.ts` рядом с исходником.
|
|
16
|
+
- Логику надо вынести из компонента или сервиса, чтобы её стало чем проверить.
|
|
17
|
+
- Пишется спека на процедуру Connect.
|
|
18
|
+
|
|
19
|
+
## Импорты явные
|
|
20
|
+
|
|
21
|
+
`globals: true` в конфиге стоит, но список всё равно пишется:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { describe, expect, it } from 'vitest';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Один `describe` на функцию
|
|
28
|
+
|
|
29
|
+
Имя блока совпадает с именем функции дословно; заголовки `it` — предложения по-русски,
|
|
30
|
+
настоящим временем, о поведении, а не об устройстве:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
describe('applyDayClick', () => {
|
|
34
|
+
it('SC-BK-19 — клик по занятому дню ничего не меняет', () => {
|
|
35
|
+
expect(applyDayClick(day('2026-08-12'), selection)).toEqual(selection);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Идентификатор сценария из `docs/specs/<домен>/scenarios.md` стоит в начале заголовка, через
|
|
41
|
+
тире. Краевые случаи — отдельные `it` в том же блоке, а не один тест с десятком проверок.
|
|
42
|
+
|
|
43
|
+
## Фикстура собирается функцией с `Partial<T>`
|
|
44
|
+
|
|
45
|
+
Не повторяющимся литералом:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
function day(iso: string, overrides: Partial<ICalendarDay> = {}): ICalendarDay {
|
|
49
|
+
return {
|
|
50
|
+
iso,
|
|
51
|
+
dayOfMonth: Number(iso.slice(8)),
|
|
52
|
+
priceThb: 6000,
|
|
53
|
+
busyNight: isNightBusy(iso, BUSY),
|
|
54
|
+
past: false,
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Общие константы (`PRICING`, `BUSY`) лежат наверху файла, в области модуля.
|
|
61
|
+
|
|
62
|
+
## Решение выносится в чистую функцию
|
|
63
|
+
|
|
64
|
+
Господствующая форма в этом дереве: логика уезжает в `*.logic.ts`, `*.util.ts` или
|
|
65
|
+
`*.calculator.ts`, и проверяется вызовом — без `TestBed`, без подмены зависимостей. Образцы —
|
|
66
|
+
`libs/site/common/booking/util/src/lib/availability-calendar.logic.ts` и
|
|
67
|
+
`libs/api/<домен расчёта>/util/src/lib/quote.calculator.ts`.
|
|
68
|
+
|
|
69
|
+
## Процедура зовётся напрямую
|
|
70
|
+
|
|
71
|
+
Обработчик — метод `handle` класса процедуры в слое `feature` своего домена. Двойник базы
|
|
72
|
+
пишется руками; образец — `FakePrismaClient` в
|
|
73
|
+
`libs/api/<домен заявок>/feature/src/lib/link-booking.procedure.spec.ts`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const prisma: FakePrismaClient = new FakePrismaClient();
|
|
77
|
+
await procedureWith(prisma, emitter).handle(request());
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Проверяются порядок действий, откат при отказе половины, идемпотентность повтора и то, что
|
|
81
|
+
именно ушло в базу. Раскладку полей стерегут тесты слоя `api`, и здесь она не повторяется.
|
|
82
|
+
|
|
83
|
+
## Разовый тест-доказательство
|
|
84
|
+
|
|
85
|
+
Дефект, который иначе подтверждается только чтением кода, доказывается тестом, написанным на
|
|
86
|
+
время разбора: он поднимает настоящую процедуру, подменяет её единственный выход наружу и
|
|
87
|
+
считает походы.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
globalThis.fetch = (): Promise<Response> => Promise.resolve(Response.json({ success: false }));
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Число до правки и число после — единственная форма ответа, которую такая проверка даёт: «сто
|
|
94
|
+
походов из ста» и «двадцать из ста» различимы, а «код выглядит правильным» — нет.
|
|
95
|
+
|
|
96
|
+
Второй способ снять «до» — вернуть на место версию файла из точки расхождения ветки и
|
|
97
|
+
прогнать тест ветки: падение теста и есть воспроизведение дефекта. Файл после этого
|
|
98
|
+
восстанавливается копией, и восстановление проверяется прогоном того же теста, а не памятью.
|
|
99
|
+
|
|
100
|
+
Двойники соседей не выдумываются: рабочий набор берётся из теста соседней процедуры того же
|
|
101
|
+
домена. Двойник, собранный по типу, падает не на утверждении, а на вызове метода, которого у
|
|
102
|
+
него нет.
|
|
103
|
+
|
|
104
|
+
**Такой файл не коммитится.** Он живёт до конца разбора и удаляется вместе с ним; проверка,
|
|
105
|
+
которую стоит оставить, переписывается в обычный `*.spec.ts` с идентификатором сценария в
|
|
106
|
+
заголовке и едет в ветке. Признак временного файла — его заголовок не называет ни одного
|
|
107
|
+
сценария.
|
|
108
|
+
|
|
109
|
+
## Частые промахи
|
|
110
|
+
|
|
111
|
+
- Либа без своего `vitest.config.mts`: `nx test <проект>` пройдёт зелёным, не запустив ни
|
|
112
|
+
одного файла. Прежде чем писать первый тест в либе, проверить, что конфиг рядом есть.
|
|
113
|
+
- Подмена модуля (`vi.mock`) скрыла бы то, ради чего тест и заводится, — какой именно вызов
|
|
114
|
+
ушёл в базу и в каком порядке. Двойник пишется руками.
|
|
115
|
+
- Своих помощников для проверок не заводить: `expect(...).toBe(...)` и `.toEqual(...)` прямо.
|
|
116
|
+
- Заголовок с несуществующим идентификатором сценария роняет `npm run check:specs`, а сами
|
|
117
|
+
тесты при этом остаются зелёными.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: translations-key
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: translations
|
|
5
|
+
description: Паттерн правила translations. Брать, когда в интерфейсе появляется видимый текст — куда положить ключ, как подставить его в разметку и в класс, чем дозаполнить остальные семь локалей и чем проверить полноту. Не брать для перевода контента объекта — его заполняет бэкенд.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Ключ перевода
|
|
9
|
+
|
|
10
|
+
Паттерн правила `translations`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/locales.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- В интерфейсе появляется любой текст, который видит человек.
|
|
16
|
+
- Правится подпись, пустое состояние, текст отказа, подпись кнопки подтверждения.
|
|
17
|
+
|
|
18
|
+
## Куда кладётся ключ
|
|
19
|
+
|
|
20
|
+
`libs/common/i18n/src/lib/dictionaries/<локаль>/<раздел>.json`. Разделов четыре, и наборы
|
|
21
|
+
ключей сверяются внутри раздела:
|
|
22
|
+
|
|
23
|
+
| Раздел | Что в нём |
|
|
24
|
+
| ------------- | --------------------------- |
|
|
25
|
+
| `common.json` | общее для сайта и админки |
|
|
26
|
+
| `site.json` | публичный сайт |
|
|
27
|
+
| `admin.json` | админ-панель |
|
|
28
|
+
| `mail.json` | письма и документ-основание |
|
|
29
|
+
|
|
30
|
+
Локалей восемь: `en`, `ru`, `de`, `zh-Hans`, `zh-Hant`, `ko`, `th`, `hi`. Ключ заводится во
|
|
31
|
+
всех — пустое значение считается пропуском, а не переводом.
|
|
32
|
+
|
|
33
|
+
## Подстановка
|
|
34
|
+
|
|
35
|
+
В разметке — пайпом:
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<h1 rtElem="title">{{ 'bookingsTitle' | transloco }}</h1>
|
|
39
|
+
<<префикс>-table [emptyMessage]="'bookingsEmpty' | transloco" [ariaLabel]="'bookingsTableAria' | transloco">
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
В классе — сигналом:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
protected readonly title: Signal<string> = translateSignal('promoCodesTitle');
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Дозаполнить и проверить
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run i18n:fill # дозаполняет недостающее в остальных локалях
|
|
52
|
+
npx nx test common-i18n # роняет сборку на недостающем или пустом ключе
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Частые промахи
|
|
56
|
+
|
|
57
|
+
- Текст строкой прямо в шаблоне: он уедет в интерфейс по-английски во всех локалях перевода.
|
|
58
|
+
- Ключ заведён только в `en` и `ru`: тест полноты падает, но замечают это уже в гейте пуша.
|
|
59
|
+
- Пустая строка вместо перевода: на экране она выглядит как задуманная — кнопка без подписи,
|
|
60
|
+
заголовок без текста.
|
|
61
|
+
- Ключ положен не в свой раздел: наборы сверяются внутри раздела, и расхождение вылезет как
|
|
62
|
+
недостача в другом.
|
|
63
|
+
- Свой ключ успеха у панели правки записи: текст успеха принадлежит мутации, и заводится он во
|
|
64
|
+
всех локалях перевода сразу.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ts-procedure
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: typescript-conventions
|
|
5
|
+
description: Паттерн правила typescript-conventions. Брать при заведении или правке процедуры Connect на бэкенде — готовый класс с полем method и методом handle, зависимости конструктором, имя файла и класса, почему форма именно такая. Не брать для объявления доступа к процедуре — это паттерн permissions-procedure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Процедура Connect
|
|
9
|
+
|
|
10
|
+
Паттерн правила `typescript-conventions`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/code-structure.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится новая процедура бэкенда.
|
|
16
|
+
- Правится тело существующей.
|
|
17
|
+
- Домен переезжает на вертикальную нарезку.
|
|
18
|
+
|
|
19
|
+
## Одна процедура — один класс
|
|
20
|
+
|
|
21
|
+
Файл `<процедура>.procedure.ts` в слое `feature` своего домена, класс `<Процедура>Procedure`,
|
|
22
|
+
публичное поле `method` с дескриптором из контракта и публичный метод `handle` с телом.
|
|
23
|
+
Зависимости приходят конструктором — на бэкенде DI нестовский, `inject()` там нет.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
@Injectable()
|
|
27
|
+
@ConnectProcedure()
|
|
28
|
+
@RequiresPermission('bookings:manage')
|
|
29
|
+
export class PingProcedure implements IConnectProcedure<typeof HealthService.method.ping> {
|
|
30
|
+
readonly #health: HealthCheckService;
|
|
31
|
+
|
|
32
|
+
public readonly method: typeof HealthService.method.ping = HealthService.method.ping;
|
|
33
|
+
|
|
34
|
+
constructor(health: HealthCheckService) {
|
|
35
|
+
this.#health = health;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async handle(): Promise<{ status: EHealthStatus }> {
|
|
39
|
+
return { status: (await this.#health.check()).status };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Объявление доступа обязательно, и оно ровно одно — паттерн `permissions-procedure`.
|
|
45
|
+
|
|
46
|
+
## Почему форма такая
|
|
47
|
+
|
|
48
|
+
Прежняя — `register(router)` с телами в замыканиях внутри `router.service(...)` — делала
|
|
49
|
+
обработчик недостижимым для спеки: наружу торчал только класс с методом `register`. А
|
|
50
|
+
`router.service` заглушает каждый непереданный метод ответом `Unimplemented`, поэтому один
|
|
51
|
+
proto-сервис не мог обслуживаться двумя доменами.
|
|
52
|
+
|
|
53
|
+
Класс решает и то, и другое: `handle` зовётся спекой напрямую, а реестр кладёт процедуры
|
|
54
|
+
поштучно через `router.rpc`.
|
|
55
|
+
|
|
56
|
+
## Частые промахи
|
|
57
|
+
|
|
58
|
+
- Третий суффикс: `*.rpc.ts` и `*.connect.ts` — прежние имена, они уходят вместе с последним
|
|
59
|
+
переехавшим доменом, и новых таких файлов не заводится.
|
|
60
|
+
- `inject()` в классе процедуры: на бэкенде зависимости идут конструктором.
|
|
61
|
+
- Тело в замыкании внутри регистрации роутера: спека до него не дотянется.
|
|
62
|
+
- Процедура без объявления доступа: приложение не поднимется.
|
|
63
|
+
- Приведение `as` в переводе моделей: на бэкенде оно запрещено так же, как в маппере фронта.
|
|
64
|
+
- Свой тип у результата `groupBy` Prisma: он условный, собирается из аргументов вызова и с
|
|
65
|
+
выписанным руками не сходится. Там, где ключей единицы, идёт `count` на ключ в `Promise.all`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-patterns
|
|
3
|
+
kind: rule
|
|
4
|
+
law: frontend-application
|
|
5
|
+
description: Правило под «Закон о фронтовом приложении». Брать при правке любого класса Angular — компонента, стора админки, сервиса, директивы, пайпа, гарда, интерцептора. Называет сигнальный API входов, OnPush, zoneless, inject и место, где живёт подписка. Не действует под libs/api и apps/api. Готовый код — в паттерне angular-patterns-state.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Реактивность экрана — как это устроено здесь
|
|
9
|
+
|
|
10
|
+
Правило под закон `docs/constitution/frontend-application.md`. Закон говорит, что должно быть
|
|
11
|
+
верно; здесь — на чём это стоит в этом дереве. Раскладка файла компонента —
|
|
12
|
+
`component-structure`, стили — `styling-bem`, окружение браузера — `platform-access`, слой
|
|
13
|
+
обращения к серверу — `api-layer`. Все пять под одним законом.
|
|
14
|
+
|
|
15
|
+
Правило про фронт: `libs/api/**` и `apps/api/**` — это NestJS, там своя среда, и ничего из
|
|
16
|
+
перечисленного не применяется.
|
|
17
|
+
|
|
18
|
+
## Как это называется здесь
|
|
19
|
+
|
|
20
|
+
| В законе | Здесь |
|
|
21
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| состояние, которое пересчитывается само | `signal()` и `computed()`; Angular без Zone.js (`provideZonelessChangeDetection()`) |
|
|
23
|
+
| вход и выход компонента | `input()`, `input.required()`, `output()`; `viewChild()`, `contentChild()` и их множественные пары |
|
|
24
|
+
| перерисовка по требованию | `ChangeDetectionStrategy.OnPush` — на каждом компоненте |
|
|
25
|
+
| владелец подписки | `takeUntilDestroyed(this.#destroyRef)` |
|
|
26
|
+
| источник действия | `Subject` с суффиксом `Source` в имени поля |
|
|
27
|
+
|
|
28
|
+
## Где это лежит
|
|
29
|
+
|
|
30
|
+
В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
|
|
31
|
+
переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
|
|
32
|
+
же дереве, которое держит код иначе.
|
|
33
|
+
|
|
34
|
+
## Как закон применяется здесь
|
|
35
|
+
|
|
36
|
+
- **Подписка объявляется один раз, а не в методе действия.** Метод толкает значение в
|
|
37
|
+
источник, а долгоживущая подписка со `switchMap`, `exhaustMap` или `concatMap` объявляется в
|
|
38
|
+
конструкторе, в `ngOnInit` или в инициализаторе поля.
|
|
39
|
+
- **Подписка гасится вместе с владельцем.** `takeUntilDestroyed` ставится в тот же поток, где
|
|
40
|
+
объявлена подписка.
|
|
41
|
+
- **Источник действия носит суффикс `Source` в имени.** Иначе поток и значение в коде
|
|
42
|
+
неотличимы, и `next` уходит не туда.
|
|
43
|
+
- **Списочный стор наследует общую основу.** Записи, страница, порядок, условия отбора, строка
|
|
44
|
+
поиска и конфиг выборки уже там, и наследнику остаются четыре строки.
|
|
45
|
+
|
|
46
|
+
## Чего из закона здесь нет
|
|
47
|
+
|
|
48
|
+
Ни `OnPush`, ни сигнальный API входов, ни отсутствие геттеров в компонентах не проверяет
|
|
49
|
+
ничто: `@Input()` и геттер компилируются и работают, а расхождение видно только чтением.
|
|
50
|
+
Обращение к окружению браузера тоже не проверяется — это `Q-FA-1` в законе.
|
|
51
|
+
|
|
52
|
+
## Паттерны
|
|
53
|
+
|
|
54
|
+
- `angular-patterns-state` — сигналы, производные значения, состояние сервиса, подписка.
|
|
55
|
+
|
|
56
|
+
## Ловушки
|
|
57
|
+
|
|
58
|
+
- **Производное значение считается `computed`, а не эффектом.** `effect`, кладущий значение в
|
|
59
|
+
сигнал, — это ручной пересчёт, и он рано или поздно отстаёт от источника.
|
|
60
|
+
- **Геттеров в компонентах нет.** Геттер пересчитывается на каждой перерисовке, и цена его не
|
|
61
|
+
видна ни в одном месте кода.
|
|
62
|
+
- **Подписка на каждый вызов метода не даёт выбрать, что делать с предыдущим запросом.**
|
|
63
|
+
Быстрые нажатия дают гонку ответов, и побеждает тот, что вернулся последним, а не тот, что
|
|
64
|
+
нажали последним.
|
|
65
|
+
- Запрет подписки в методе идёт по имени `subscribe`, а не по типу: вызов с таким именем у
|
|
66
|
+
чего угодно считается подпиской, а `const fn = stream$.subscribe` без вызова — нет.
|
|
67
|
+
Разрешены конструктор, `ngOnInit`, инициализатор поля и всё, что объявлено вне класса;
|
|
68
|
+
запрещены остальные методы, включая приватные с `#`, геттеры и `ngAfterViewInit`.
|
|
69
|
+
- Инициализация DOM после первой отрисовки — `afterNextRender()`, а не `ngAfterViewInit`:
|
|
70
|
+
сайт отдаётся сервером, и DOM там появляется позже.
|
|
71
|
+
- `viewChild` на поле с `#` Angular не принимает — поле объявляется `protected`.
|