@rt-tools/agent-kit 0.3.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 +194 -30
- 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 +0 -0
- package/assets/hooks/browser-guard-device-id.sh +2 -1
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +2 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
- package/assets/hooks/browser-guard-require-select.sh +2 -1
- package/assets/hooks/commit-msg.sh +1 -1
- package/assets/hooks/constitution-index.sh +5 -4
- package/assets/hooks/dev-server-guard.sh +8 -6
- package/assets/hooks/docs-guard.sh +223 -37
- package/assets/hooks/git-guard-delivery.sh +86 -29
- package/assets/hooks/git-guard-main.sh +1 -0
- package/assets/hooks/git-guard-push-tests.sh +34 -13
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +155 -30
- package/assets/hooks/qa-dataid-guard.sh +72 -32
- package/assets/hooks/reuse-first-guard.sh +105 -34
- package/assets/hooks/skill-gate-rearm.sh +1 -0
- package/assets/hooks/skill-gate.sh +75 -15
- package/assets/hooks/skill-loaded.sh +1 -0
- package/assets/hooks/sql-guard.sh +606 -56
- 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 +29 -22
- package/assets/patterns/api-layer-pair.md +40 -30
- package/assets/patterns/browser-verification-measure.md +41 -38
- package/assets/patterns/browser-verification-stand.md +106 -42
- package/assets/patterns/component-structure-new.md +33 -32
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +65 -28
- package/assets/patterns/doc-style-write.md +36 -33
- 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 +42 -25
- package/assets/patterns/git-workflow-migration.md +61 -31
- package/assets/patterns/git-workflow-restart.md +20 -20
- package/assets/patterns/lib-layers-move.md +50 -32
- package/assets/patterns/lib-layers-new.md +41 -29
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +35 -33
- package/assets/patterns/platform-access-di.md +39 -25
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +22 -22
- package/assets/patterns/seo-page.md +52 -40
- package/assets/patterns/seo-verify.md +48 -29
- package/assets/patterns/shared-code-new.md +37 -31
- package/assets/patterns/spec-driven-domain.md +44 -37
- package/assets/patterns/spec-driven-rule.md +55 -40
- package/assets/patterns/styling-bem-component.md +43 -32
- package/assets/patterns/styling-bem-layout.md +30 -24
- 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 +53 -51
- package/assets/patterns/testing-unit.md +70 -46
- package/assets/patterns/translations-key.md +32 -19
- package/assets/patterns/ts-procedure.md +24 -25
- package/assets/rules/angular-patterns.md +46 -27
- package/assets/rules/api-layer.md +46 -28
- package/assets/rules/browser-verification.md +66 -48
- package/assets/rules/component-structure.md +43 -27
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +81 -39
- 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 +56 -30
- 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 +43 -25
- package/assets/rules/platform-access.md +57 -29
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +57 -43
- package/assets/rules/seo.md +51 -30
- package/assets/rules/shared-code.md +51 -26
- package/assets/rules/spec-driven.md +96 -50
- package/assets/rules/styling-bem.md +54 -39
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +78 -47
- package/assets/rules/translations.md +48 -31
- package/assets/rules/typescript-conventions.md +57 -27
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +23 -15
- package/assets/templates/implementation.md +14 -8
- package/assets/templates/pattern.md +1 -1
- package/assets/templates/project.sh +32 -19
- package/assets/templates/rule.md +1 -1
- 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 +8 -3
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +13 -3
- 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 +202 -14
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +5 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +29 -2
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +26 -9
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +41 -15
- 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/sync.d.ts +26 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -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/assets/patterns/git-workflow-commit.md +0 -175
- package/assets/rules/git-workflow.md +0 -106
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ownership-scope-resolve
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: ownership-scope
|
|
5
|
+
description: Паттерн правила ownership-scope. Брать, когда процедура принимает идентификатор владеющей сущности — готовое разрешение одной и всех действующих, коды отказа на каждый случай, условие отбора вместо своего поля запроса. Не брать для объявления доступа к процедуре — это паттерн permissions-procedure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Разрешение владеющей сущности в процедуре
|
|
9
|
+
|
|
10
|
+
Паттерн правила `ownership-scope`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/ownership.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Процедура принимает идентификатор владеющей сущности.
|
|
16
|
+
- Экран или сводка отвечает сразу по всем действующим.
|
|
17
|
+
- Заводится новый домен, записи которого принадлежат владеющей сущности.
|
|
18
|
+
|
|
19
|
+
## Одна сущность
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const ownerId: string = await resolveRequestedOwner(this.#db, request.ownerId);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Обёртка сама разбирает случаи и переводит их в отказ:
|
|
26
|
+
|
|
27
|
+
| Что пришло | Что происходит |
|
|
28
|
+
| ----------------------------------------------------- | ------------------------- |
|
|
29
|
+
| пусто, действующая одна | берётся она |
|
|
30
|
+
| пусто, действующих две и больше | `Code.InvalidArgument` |
|
|
31
|
+
| пусто, действующих нет вовсе | `Code.FailedPrecondition` |
|
|
32
|
+
| названная явно, есть — хоть действующая, хоть скрытая | берётся она |
|
|
33
|
+
| названная явно, такой нет | `Code.NotFound` |
|
|
34
|
+
|
|
35
|
+
Своего разбора не заводить: чистая функция разбирает случай, обёртка ходит в хранилище. Вторая
|
|
36
|
+
реализация того же правила однажды уже жила у обработчика статистики соседнего домена и успела
|
|
37
|
+
разойтись порядком выборки.
|
|
38
|
+
|
|
39
|
+
## Все действующие
|
|
40
|
+
|
|
41
|
+
Там, где показана сводка, пустой идентификатор означает все действующие:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const ownerIds: string[] = await resolveRequestedOwners(this.#db, request.ownerId);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Так отвечают списки, статистика и дашборд.
|
|
48
|
+
|
|
49
|
+
## Отбор вместо своего поля запроса
|
|
50
|
+
|
|
51
|
+
У списка, переведённого на общую выборку, сущность приходит условием отбора, а не отдельным
|
|
52
|
+
полем: прежний номер поля в контракте помечен `reserved`.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
reserved 4;
|
|
56
|
+
reserved "owner_id";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Частые промахи
|
|
60
|
+
|
|
61
|
+
- Свой разбор пустого идентификатора в обработчике: он разойдётся с общим при первой же правке.
|
|
62
|
+
- Один и тот же код отказа на «действующих нет» и «такой сущности нет»: для вызывающего это
|
|
63
|
+
разные случаи.
|
|
64
|
+
- Подстановка скрытой, когда действующих нет: скрытая вместо отсутствующей действующей не
|
|
65
|
+
берётся.
|
|
66
|
+
- Общая настройка, действующая сразу на все: заведение второй не должно менять поведение
|
|
67
|
+
первой.
|
|
68
|
+
- Новое поле идентификатора рядом с выборкой у списка: отбор, живущий отдельно, не виден ни
|
|
69
|
+
стору, ни адресу.
|
|
@@ -2,68 +2,70 @@
|
|
|
2
2
|
name: permissions-procedure
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: permissions
|
|
5
|
-
description: Паттерн правила permissions. Брать при заведении
|
|
5
|
+
description: Паттерн правила permissions. Брать при заведении процедуры Connect и при закрытии раздела админки — готовые декораторы доступа, отбивка без входа и без права, декларация пункта меню с правом и флагом. Не брать для устройства самого меню — это правило navigation.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Объявление доступа
|
|
9
9
|
|
|
10
|
-
Паттерн правила `permissions`. Что при этом должно быть верно — закон
|
|
10
|
+
Паттерн правила `permissions`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/access.md`.
|
|
11
12
|
|
|
12
13
|
## Когда брать
|
|
13
14
|
|
|
14
|
-
- Заводится
|
|
15
|
-
- Раздел
|
|
16
|
-
-
|
|
15
|
+
- Заводится процедура Connect.
|
|
16
|
+
- Раздел админки закрывается правом.
|
|
17
|
+
- Процедура должна отвечать гостю.
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## Декоратор на классе процедуры
|
|
19
20
|
|
|
20
21
|
Объявление ровно одно; без него приложение не поднимается:
|
|
21
22
|
|
|
22
23
|
```typescript
|
|
23
24
|
@Injectable()
|
|
24
25
|
@ConnectProcedure()
|
|
25
|
-
@RequiresPermission('
|
|
26
|
-
export class
|
|
27
|
-
public readonly method: typeof
|
|
26
|
+
@RequiresPermission('chat:manage')
|
|
27
|
+
export class LinkBookingProcedure implements IConnectProcedure<typeof ChatService.method.linkBooking> {
|
|
28
|
+
public readonly method: typeof ChatService.method.linkBooking = ChatService.method.linkBooking;
|
|
28
29
|
}
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
32
|
+
| Декоратор | Кому доступно |
|
|
33
|
+
| -------------------------------------------- | ------------------------------------------------- |
|
|
34
|
+
| `@RequiresPermission('<ресурс>:<действие>')` | вошедшему с этим правом |
|
|
35
|
+
| `@RequiresAuth('<причина>')` | любому вошедшему; так живут профиль и выбор языка |
|
|
36
|
+
| `@PublicProcedure('<причина>')` | гостю без входа |
|
|
37
|
+
| `@OptionalAuthProcedure('<причина>')` | гостю, но токен читается, если он есть |
|
|
37
38
|
|
|
38
|
-
Аргумент — причина для читателя кода. Ни в ответ, ни в
|
|
39
|
+
Аргумент — причина для читателя кода. Ни в ответ, ни в лог она не уходит.
|
|
39
40
|
|
|
40
41
|
## Отбивка
|
|
41
42
|
|
|
42
|
-
Перехватчик отвечает до тела
|
|
43
|
+
Перехватчик отвечает до тела процедуры:
|
|
43
44
|
|
|
44
|
-
- нет входа там, где вход нужен, —
|
|
45
|
-
- вход есть, права нет —
|
|
46
|
-
-
|
|
45
|
+
- нет входа там, где вход нужен, — `Code.Unauthenticated`;
|
|
46
|
+
- вход есть, права нет — `Code.PermissionDenied`;
|
|
47
|
+
- процедура, о которой перехватчик ничего не знает, — тоже отказ в доступе, а не пропуск.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
Обработчик решения о допуске не принимает.
|
|
49
50
|
|
|
50
|
-
## Раздел
|
|
51
|
+
## Раздел админки
|
|
51
52
|
|
|
52
|
-
Пункт меню и адрес закрываются одной
|
|
53
|
-
|
|
53
|
+
Пункт меню и адрес закрываются одной декларацией в
|
|
54
|
+
`libs/admin/common/container/util`: шапка берёт из неё подписи и адреса, гвард — права.
|
|
55
|
+
Второго объявления этой связи не заводится.
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
Гейтинг двухслойный: право пользователя и флаг раздела. Пункт с флагом объявляется без прав и
|
|
58
|
+
без адреса — право открывает экран, а экрана нет. Появится экран — флаг снимается, права
|
|
59
|
+
добавляются.
|
|
58
60
|
|
|
59
61
|
## Частые промахи
|
|
60
62
|
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
63
|
+
- Два объявления доступа на одной процедуре: приложение не поднимется, и увидено это будет
|
|
64
|
+
только при запуске.
|
|
65
|
+
- Проверка права внутри `handle`: право проверяется до тела.
|
|
66
|
+
- Своё объявление прав рядом с маршрутами: оно разойдётся с декларацией меню, и получится
|
|
65
67
|
«пункта не видно, а страница открывается».
|
|
66
|
-
-
|
|
68
|
+
- Гвард, повешенный на защищённую группу целиком: он отрабатывает один раз за загрузку
|
|
67
69
|
страницы и переходов между разделами не видит.
|
|
68
|
-
-
|
|
70
|
+
- Ожидание прав, которое роняется на отказе запроса: с неизвестными правами не закрывается
|
|
69
71
|
ничего, и пустая шапка выхода владельцу не оставляет.
|
|
@@ -2,23 +2,28 @@
|
|
|
2
2
|
name: platform-access-di
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: platform-access
|
|
5
|
-
description: Паттерн правила platform-access. Брать, когда в код заходит окно, документ или проверка среды —
|
|
5
|
+
description: Паттерн правила platform-access. Брать, когда в код заходит окно, документ или проверка среды — готовые инжекты токенов, приведение к Window & typeof globalThis, окно параметром в чистой функции, инициализация DOM после первой отрисовки. Не брать под libs/api и apps/api.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Окно, документ и проверка среды
|
|
9
9
|
|
|
10
10
|
Паттерн правила `platform-access`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- В компонент,
|
|
16
|
-
- Появляется работа с
|
|
15
|
+
- В компонент, сервис или директиву заходит `window`, `document` или проверка среды.
|
|
16
|
+
- Появляется работа с DOM, которой нельзя случиться до первой отрисовки.
|
|
17
17
|
- Чистой функции нужен доступ к окну.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Инжекты
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
+
import { DOCUMENT } from '@angular/common';
|
|
23
|
+
import { inject, Injectable } from '@angular/core';
|
|
24
|
+
|
|
25
|
+
import { PlatformService, WINDOW } from '@rt-tools/core';
|
|
26
|
+
|
|
22
27
|
@Injectable({ providedIn: 'root' })
|
|
23
28
|
export class SomeService {
|
|
24
29
|
readonly #document: Document = inject(DOCUMENT);
|
|
@@ -27,44 +32,53 @@ export class SomeService {
|
|
|
27
32
|
}
|
|
28
33
|
```
|
|
29
34
|
|
|
30
|
-
## Когда нужен
|
|
35
|
+
## Когда нужен `Window & typeof globalThis`
|
|
31
36
|
|
|
32
|
-
Интерфейс
|
|
33
|
-
|
|
34
|
-
уточняется приведением, и рядом ставится комментарий с причиной:
|
|
37
|
+
Интерфейс `Window` не описывает глобальные конструкторы и неймспейсы —
|
|
38
|
+
`IntersectionObserver`, `ResizeObserver`, `google` из `@types/google.maps`. Токен отдаёт тот же
|
|
39
|
+
самый объект, поэтому тип уточняется приведением, и рядом ставится комментарий с причиной:
|
|
35
40
|
|
|
36
41
|
```typescript
|
|
37
|
-
// Конструкторы
|
|
38
|
-
// токен отдаёт тот же объект, тип лишь уточняется.
|
|
42
|
+
// Конструкторы вроде IntersectionObserver объявлены на globalThis, а не на
|
|
43
|
+
// интерфейсе Window — токен отдаёт тот же объект, тип лишь уточняется.
|
|
39
44
|
readonly #window: Window & typeof globalThis = inject(WINDOW) as Window & typeof globalThis;
|
|
40
45
|
```
|
|
41
46
|
|
|
42
47
|
## Чистая функция принимает окно параметром
|
|
43
48
|
|
|
44
|
-
В
|
|
49
|
+
В `*.logic.ts` и `*.util.ts` нет DI, и глобал внутрь не тянется:
|
|
45
50
|
|
|
46
51
|
```typescript
|
|
47
52
|
export function mapsReady(windowRef: Window & typeof globalThis): boolean {
|
|
48
|
-
return typeof windowRef.maps?.importLibrary === 'function';
|
|
53
|
+
return typeof windowRef.google?.maps?.importLibrary === 'function';
|
|
49
54
|
}
|
|
50
55
|
```
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
Инжектит его вызывающий компонент.
|
|
53
58
|
|
|
54
59
|
## Проверка среды и первая отрисовка
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
```typescript
|
|
62
|
+
if (!this.#platform.isPlatformBrowser) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
afterNextRender((): void => {
|
|
69
|
+
// работа с DOM, которой не должно быть до первой отрисовки
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`typeof window !== 'undefined'` не годится: проверка по наличию глобала верна случайно.
|
|
59
74
|
|
|
60
75
|
## Частые промахи
|
|
61
76
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
вне браузера, и
|
|
65
|
-
-
|
|
66
|
-
метода под проверкой среды.
|
|
67
|
-
-
|
|
77
|
+
- `isPlatformBrowser(inject(PLATFORM_ID))` вместо `PlatformService`.
|
|
78
|
+
- Проверка среды вокруг чтения и записи в хранилище: `StorageService` и так уходит в память
|
|
79
|
+
вне браузера, и такой `if` — мёртвый код.
|
|
80
|
+
- `WINDOW` полем класса в сервисе, который обязан работать без DOM вообще: там окно берётся
|
|
81
|
+
внутри метода под проверкой среды.
|
|
82
|
+
- Правка добавила `WINDOW` в сервис, создающийся на подъёме, а проверили одной сборкой: падение
|
|
68
83
|
видно только на поднятом сервере отдачи страниц.
|
|
69
|
-
-
|
|
70
|
-
читается как нарушение.
|
|
84
|
+
- Приведение без комментария: в мапперах приведение запрещено, и строка читается как нарушение.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pricing-quote
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: pricing
|
|
5
|
+
description: Паттерн правила pricing. Брать при правке расчёта цены, механик скидки, работы с курсом валют и сумм в письмах и документах — где округлять, как выбирается одна скидка, что кладётся в бронь. Не брать для доступа к процедурам цен — это паттерн permissions-procedure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Расчёт цены
|
|
9
|
+
|
|
10
|
+
Паттерн правила `pricing`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/money.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Правится расчёт цены или механика скидки.
|
|
16
|
+
- В код заходит сумма в чужой валюте.
|
|
17
|
+
- Сумма попадает в заказ, письмо, документ или сводку.
|
|
18
|
+
|
|
19
|
+
## Всё считается один раз
|
|
20
|
+
|
|
21
|
+
Расчёт живёт в `calculateQuote` и отдаёт готовые числа: ночи, подытог, скидку, итог, признак
|
|
22
|
+
минимального срока. Показ, письмо и запись в базу берут одни и те же числа, а не пересчитывают
|
|
23
|
+
их каждый у себя.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const quote: IQuoteResult = calculateQuote({
|
|
27
|
+
checkIn,
|
|
28
|
+
checkOut,
|
|
29
|
+
basePriceThb,
|
|
30
|
+
defaultMinNights,
|
|
31
|
+
seasons,
|
|
32
|
+
lengthDiscounts,
|
|
33
|
+
promoCode,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Одна скидка — наибольшая
|
|
38
|
+
|
|
39
|
+
Механики считаются по отдельности, а выбор между ними один:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const best: IDiscountCandidate | undefined = bestCandidateOf([
|
|
43
|
+
lengthCandidateOf(nights, input, subtotalThb),
|
|
44
|
+
promoCandidateOf(input.promoCode, subtotalThb),
|
|
45
|
+
]);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
При равных суммах побеждает промокод: владелец выдал его адресно, а скидка за длительность
|
|
49
|
+
досталась бы гостю и без него. Новая механика добавляется третьим кандидатом в тот же вызов, а
|
|
50
|
+
не отдельным вычитанием из итога.
|
|
51
|
+
|
|
52
|
+
## Чужая валюта — на показ
|
|
53
|
+
|
|
54
|
+
В заказе лежат сумма в валюте хранения и код валюты, в которой гость смотрел цену. Само число в чужой
|
|
55
|
+
валюте не хранится нигде:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
export const SUPPORTED_QUOTE_CURRENCIES: readonly string[] = ['USD', 'RUB', 'EUR', 'CNY'];
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Курс тянется по расписанию и кэшируется в хранилище. Курса нет — сумма показывается в валюте хранения без
|
|
62
|
+
пересчёта.
|
|
63
|
+
|
|
64
|
+
## Частые промахи
|
|
65
|
+
|
|
66
|
+
- Округление на показе: сумма гостя, сумма письма и сумма в базе разойдутся на единицы.
|
|
67
|
+
- Сложение двух скидок: владелец таких сумм не закладывал, и объяснить гостю итог нечем.
|
|
68
|
+
- Сохранённое число в чужой валюте: оно устареет вместе с курсом.
|
|
69
|
+
- Справочная сумма, подписанная как сумма к оплате, — оплата идёт в валюте хранения.
|
|
70
|
+
- Письмо в валюте, отличной от той, что гость выбирал на сайте: оно читается как другая цена.
|
|
71
|
+
- Дробная часть в сумме: единица хранения целая.
|
|
@@ -2,39 +2,39 @@
|
|
|
2
2
|
name: reuse-first-extend
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: reuse-first
|
|
5
|
-
description: Паттерн правила reuse-first. Брать, когда готового в
|
|
5
|
+
description: Паттерн правила reuse-first. Брать, когда готового в ките или в базовом классе не хватило — что проверить перед тем, как писать своё, как расширить готовое, как объявить разовое отступление маркером native-ok и когда его снимать.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Готового не хватило
|
|
9
9
|
|
|
10
|
-
Паттерн правила `reuse-first`. Что при этом должно быть верно — закон
|
|
10
|
+
Паттерн правила `reuse-first`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/reuse-first.md`.
|
|
11
12
|
|
|
12
13
|
## Когда брать
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Компонент кита или базовый класс не покрывает случай, и рука тянется написать своё рядом.
|
|
15
16
|
|
|
16
17
|
## Сначала — проверить, что действительно не хватает
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
grep -
|
|
20
|
-
grep -rn "<похожий приём>"
|
|
20
|
+
grep -n "export" @rt-tools/ui-kit-v2/src/index.ts | wc -l
|
|
21
|
+
grep -rn "<похожий приём>" libs/admin libs/site --include='*.html' | head
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
Кит большой, и большинства его компонентов нет ни в одной таблице. Соседний домен читается
|
|
25
|
+
целиком: приём, который кажется новым, обычно уже написан — и переименованный при переносе он
|
|
26
|
+
перестаёт узнаваться.
|
|
26
27
|
|
|
27
|
-
Частые подмены, которые находятся при чтении:
|
|
28
|
-
|
|
28
|
+
Частые подмены, которые находятся при чтении: `<префикс>-skeleton` вместо своего мерцания,
|
|
29
|
+
`<префикс>-dialog` вместо своей вуали, `<префикс>-message` вместо своего `role="alert"`.
|
|
29
30
|
|
|
30
31
|
## Расширять, а не клонировать
|
|
31
32
|
|
|
32
|
-
Недостающий вариант заводится **в
|
|
33
|
-
экраны.
|
|
33
|
+
Недостающий вариант заводится **в ките или в базовом классе**, и его видят остальные экраны.
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
✗
|
|
37
|
-
✓
|
|
36
|
+
✗ libs/admin/<домен>/ui/my-dialog/ клон «почти как китовый»
|
|
37
|
+
✓ @rt-tools/ui-kit-v2/src/lib/components/dialog/ новый вариант у готового
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Клон, написанный рядом, забирает правки на себя и расходится с оригиналом с первой же.
|
|
@@ -42,31 +42,31 @@ grep -rn "<похожий приём>" <каталоги кода> --include='*.
|
|
|
42
42
|
|
|
43
43
|
## Свой примитив — только с одобрения владельца
|
|
44
44
|
|
|
45
|
-
Спрашивается до того, как написан первый файл. То же относится к своей
|
|
46
|
-
|
|
45
|
+
Спрашивается до того, как написан первый файл. То же относится к своей базе и к своему
|
|
46
|
+
инлайновому стилю.
|
|
47
47
|
|
|
48
48
|
## Разовое отступление объявляется маркером
|
|
49
49
|
|
|
50
50
|
```html
|
|
51
|
-
<!--
|
|
51
|
+
<!-- native-ok: в ките нет поля с маской телефона, заведено задачей <КЛЮЧ>-000 -->
|
|
52
52
|
<input type="tel" qa-dataid="phone-input" />
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Маркер ставится в той же строке и объясняет, **чего именно нет в
|
|
56
|
-
здесь раньше» причиной не считается: гард вычёркивает из проверяемого текста то,
|
|
57
|
-
в файле, поэтому отказ означает новый текст.
|
|
55
|
+
Маркер `native-ok` ставится в той же строке и объясняет, **чего именно нет в ките**. «Эти
|
|
56
|
+
строки были здесь раньше» причиной не считается: гард вычёркивает из проверяемого текста то,
|
|
57
|
+
что уже лежит в файле, поэтому отказ означает новый текст.
|
|
58
58
|
|
|
59
59
|
Сверка идёт без отступов — при переезде блок меняет отступ, оставаясь тем же кодом.
|
|
60
60
|
|
|
61
61
|
## Снятие отступления
|
|
62
62
|
|
|
63
|
-
Когда недостающее появилось в
|
|
63
|
+
Когда недостающее появилось в ките, маркер снимается вместе с обходом. Комментарий,
|
|
64
64
|
оправдывающий отклонение, держит это отклонение на себе: пока объяснение выглядит убедительно,
|
|
65
65
|
его не трогают.
|
|
66
66
|
|
|
67
67
|
## Частые промахи
|
|
68
68
|
|
|
69
|
-
- Своё написано до чтения
|
|
69
|
+
- Своё написано до чтения кита и соседнего домена.
|
|
70
70
|
- Клон рядом вместо нового варианта у готового.
|
|
71
71
|
- Маркер поставлен без объяснения, чего не хватает.
|
|
72
72
|
- Маркер оставлен после того, как готовое появилось.
|
|
@@ -2,91 +2,103 @@
|
|
|
2
2
|
name: seo-page
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: seo
|
|
5
|
-
description: Паттерн правила seo. Брать, когда правится разметка
|
|
5
|
+
description: Паттерн правила seo. Брать, когда правится разметка страницы публичного сайта, заводится новый маршрут или новая страница должна попасть в карту сайта — готовый вызов PropertySeoService, ветки локалей в app.routes.ts, строка в sitemap. Не брать для проверки отданной разметки — это паттерн seo-verify.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Разметка
|
|
8
|
+
# Разметка страницы публичного сайта
|
|
9
9
|
|
|
10
|
-
Паттерн правила `seo`. Что при этом должно быть верно — закон
|
|
10
|
+
Паттерн правила `seo`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/search-visibility.md`.
|
|
11
12
|
|
|
12
13
|
## Когда брать
|
|
13
14
|
|
|
14
|
-
- Правится шаблон
|
|
15
|
-
- Заводится новый
|
|
16
|
-
- Появилась страница, которая должна попасть в
|
|
15
|
+
- Правится шаблон страницы сайта, её заголовок, описание или картинка для соцсетей.
|
|
16
|
+
- Заводится новый маршрут сайта.
|
|
17
|
+
- Появилась страница, которая должна попасть в `sitemap.xml`.
|
|
17
18
|
|
|
18
|
-
## Разметку ставит
|
|
19
|
+
## Разметку ставит сервис, а не шаблон
|
|
19
20
|
|
|
20
|
-
Страница собирает данные и одним вызовом отдаёт их
|
|
21
|
-
заводить: они не помечены
|
|
22
|
-
|
|
21
|
+
Страница собирает данные и одним вызовом отдаёт их `PropertySeoService`. Своих `<meta>` в
|
|
22
|
+
шаблоне не заводить: они не помечены `data-<префикс>-seo`, не переписываются при переходе и
|
|
23
|
+
переживут смену страницы.
|
|
24
|
+
|
|
25
|
+
Образец — `property-page.component.ts:#applySeo`:
|
|
23
26
|
|
|
24
27
|
```typescript
|
|
25
|
-
readonly #seo:
|
|
28
|
+
readonly #seo: PropertySeoService = inject(PropertySeoService);
|
|
26
29
|
|
|
27
30
|
constructor() {
|
|
28
31
|
effect((): void => this.#applySeo());
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
#applySeo(): void {
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
35
|
+
const property: IProperty.State | null = this.property();
|
|
36
|
+
if (!property) {
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
const name: string = this.propertyName();
|
|
41
|
+
const cover: IPhotoView | null = this.coverDesktop();
|
|
42
|
+
|
|
37
43
|
this.#seo.apply({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
property,
|
|
45
|
+
name,
|
|
46
|
+
pageTitle: name ? `${name} — ${this.#titleSuffix()}` : this.#titleSuffix(),
|
|
47
|
+
description: property.shortDescription || this.#descriptionFallback(),
|
|
48
|
+
ogImageUrl: cover ? photoOgUrl(cover.baseUrl) : `${SITE_ORIGIN}/${BRAND_OG_IMAGE}`,
|
|
49
|
+
ogImageAlt: cover ? cover.alt : name,
|
|
50
|
+
ogImageWidth: OG_IMAGE_WIDTH,
|
|
51
|
+
ogImageHeight: OG_IMAGE_HEIGHT,
|
|
42
52
|
locale: this.#localeId,
|
|
43
53
|
});
|
|
44
54
|
}
|
|
45
55
|
```
|
|
46
56
|
|
|
47
|
-
Вызов идёт из
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
Вызов идёт из `effect`, а не из конструктора напрямую: объект приходит сигналом, и на первом
|
|
58
|
+
кадре его ещё нет. Ранний выход по пустому объекту обязателен — без него разметка встала бы на
|
|
59
|
+
пустых значениях и второй раз уже не переписалась бы.
|
|
50
60
|
|
|
51
|
-
У картинки есть запасной вариант:
|
|
61
|
+
У картинки есть запасной вариант: объект без обложки отдаёт брендовое изображение того же формата,
|
|
52
62
|
иначе превью в соцсети пустует.
|
|
53
63
|
|
|
54
64
|
## Новый маршрут заводится веткой на каждую локаль
|
|
55
65
|
|
|
56
|
-
Ветки
|
|
57
|
-
корня.
|
|
66
|
+
Ветки собирает `apps/site/src/app/app.routes.ts` из `LOCALE_CODES`. Локаль по умолчанию своей
|
|
67
|
+
ветки не имеет — она отдаётся из корня.
|
|
58
68
|
|
|
59
69
|
```typescript
|
|
60
70
|
export const appRoutes: Route[] = [
|
|
61
71
|
...LOCALE_CODES.filter((code: ELocale): boolean => code !== DEFAULT_LOCALE).map((code: ELocale): Route => ({
|
|
62
72
|
path: code,
|
|
63
|
-
children:
|
|
73
|
+
children: propertyRoutes,
|
|
64
74
|
})),
|
|
65
|
-
...
|
|
75
|
+
...propertyRoutes,
|
|
66
76
|
];
|
|
67
77
|
```
|
|
68
78
|
|
|
69
|
-
Новый путь дописывается в
|
|
70
|
-
перечислены явными кодами, а не
|
|
71
|
-
|
|
79
|
+
Новый путь дописывается в `propertyRoutes` — тогда он появляется во всех восьми ветках сразу.
|
|
80
|
+
Ветки перечислены явными кодами, а не `:locale`: иначе `/<адрес страницы>` был бы принят за
|
|
81
|
+
язык, а не за адрес объекта.
|
|
72
82
|
|
|
73
83
|
## Страница попадает в карту сайта явно
|
|
74
84
|
|
|
75
|
-
Карта строится из
|
|
76
|
-
|
|
85
|
+
Карта строится из объектов, а не из маршрутов, и новая страница сама туда не попадёт. Записи
|
|
86
|
+
собирает `buildSitemap` (слой `util` домена страницы объекта), отдаёт обработчик
|
|
87
|
+
`/sitemap.xml` в `apps/site/src/server.ts`. Правка идёт вместе со спекой в
|
|
88
|
+
`sitemap.util.spec.ts`.
|
|
77
89
|
|
|
78
90
|
## Тексты идут из словарей
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
перевода они уезжают в выдачу
|
|
92
|
+
`pageTitle` и `description` собираются из переведённых значений и заводятся во всех восьми
|
|
93
|
+
локалях. Без перевода они уезжают в выдачу по-английски, и заметно это только в чужой локали.
|
|
82
94
|
|
|
83
95
|
## Частые промахи
|
|
84
96
|
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
97
|
+
- `<meta>` в шаблоне вместо вызова сервиса — тег не помечен `data-<префикс>-seo` и переживёт переход.
|
|
98
|
+
- Вызов без раннего выхода по пустому объекту — разметка встаёт на пустых значениях.
|
|
99
|
+
- Новый путь дописан мимо `propertyRoutes` — работает только в локали по умолчанию,
|
|
100
|
+
`/de/<путь>` отдаёт 404.
|
|
101
|
+
- Свой разбор адреса вместо `splitRequestUrl` — теряется всё после второго `?`, и источник
|
|
102
|
+
заявки считается неверно.
|
|
103
|
+
- Правка разметки без проверки на прод-сборке: в дев-сервере теги ставит не тот путь. Проверка
|
|
104
|
+
описана паттерном `seo-verify`.
|