@rt-tools/agent-kit 0.3.0 → 0.5.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 +329 -0
- package/assets/checks/check-board.github.mjs +181 -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 +1086 -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 +106 -0
- package/assets/defaults/project.sh +204 -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 +171 -31
- 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/grill-gate.sh +96 -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 +118 -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 +27 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +30 -1
- package/assets/laws/work-conduct.md +59 -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 +337 -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 +60 -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 +154 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +129 -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 +50 -27
- package/assets/rules/api-layer.md +46 -28
- package/assets/rules/browser-verification.md +67 -48
- package/assets/rules/component-structure.md +43 -27
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +95 -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 +107 -51
- package/assets/rules/styling-bem.md +54 -39
- package/assets/rules/task-flow.md +150 -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 +85 -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 +2 -2
- 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 +27 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +77 -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.5.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
|
@@ -2,45 +2,50 @@
|
|
|
2
2
|
name: angular-patterns-state
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: angular-patterns
|
|
5
|
-
description: Паттерн правила angular-patterns. Брать при объявлении состояния и потоков в классе
|
|
5
|
+
description: Паттерн правила angular-patterns. Брать при объявлении состояния и потоков в классе Angular — готовые сигналы, производные значения, состояние сервиса, долгоживущая подписка с источником действия. Не брать для раскладки файла компонента — это правило component-structure.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Состояние и потоки
|
|
9
9
|
|
|
10
10
|
Паттерн правила `angular-patterns`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Объявляется состояние компонента или
|
|
15
|
+
- Объявляется состояние компонента или сервиса.
|
|
16
16
|
- Появляется поток, на который надо подписаться.
|
|
17
17
|
- Значение считается из другого значения.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Сигнальный API входов и выходов
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
public readonly data: InputSignal<Item[]> = input.required<Item[]>();
|
|
23
|
-
public readonly
|
|
23
|
+
public readonly isMobile: InputSignal<boolean | undefined> = input<boolean>();
|
|
24
24
|
public readonly save: OutputEmitterRef<void> = output<void>();
|
|
25
25
|
|
|
26
26
|
protected readonly myButton: Signal<ElementRef | undefined> = viewChild<ElementRef>('button');
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
Декораторов `@Input()`, `@Output()`, `@ViewChild()`, `@ContentChild()` и их множественных пар
|
|
30
|
+
в дереве нет.
|
|
31
31
|
|
|
32
|
-
## Производное значение —
|
|
32
|
+
## Производное значение — `computed`, а не эффект
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
protected readonly items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
36
|
+
protected readonly itemCount: Signal<number> = computed((): number => this.items().length);
|
|
37
|
+
protected readonly hasItems: Signal<boolean> = computed((): boolean => this.itemCount() > 0);
|
|
38
|
+
```
|
|
33
39
|
|
|
34
40
|
```typescript
|
|
35
41
|
✗ effect((): void => { this.count.set(this.items().length); });
|
|
36
42
|
✓ protected readonly count: Signal<number> = computed((): number => this.items().length);
|
|
37
43
|
```
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
перерисовке, и цена его не видна ни в одном месте кода.
|
|
45
|
+
Геттера в компоненте не заводить: он пересчитывается на каждой перерисовке, и цена его не
|
|
46
|
+
видна ни в одном месте кода.
|
|
42
47
|
|
|
43
|
-
## Состояние
|
|
48
|
+
## Состояние сервиса
|
|
44
49
|
|
|
45
50
|
Наружу — только чтение:
|
|
46
51
|
|
|
@@ -60,7 +65,7 @@ export class DomainStateService {
|
|
|
60
65
|
|
|
61
66
|
## Подписка объявляется один раз
|
|
62
67
|
|
|
63
|
-
Метод действия толкает значение в источник, подписка живёт
|
|
68
|
+
Метод действия толкает значение в источник, подписка живёт в конструкторе:
|
|
64
69
|
|
|
65
70
|
```typescript
|
|
66
71
|
readonly #loadSource: Subject<void> = new Subject<void>();
|
|
@@ -69,7 +74,7 @@ readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
|
|
69
74
|
constructor() {
|
|
70
75
|
this.#loadSource
|
|
71
76
|
.pipe(
|
|
72
|
-
switchMap((): Observable<
|
|
77
|
+
switchMap((): Observable<IPromoCode.ListResult> => this.#api.getList(this.#query())),
|
|
73
78
|
takeUntilDestroyed(this.#destroyRef)
|
|
74
79
|
)
|
|
75
80
|
.subscribe();
|
|
@@ -80,15 +85,17 @@ protected reload(): void {
|
|
|
80
85
|
}
|
|
81
86
|
```
|
|
82
87
|
|
|
83
|
-
Оператор выбирается по тому, что делать с предыдущим запросом: список берёт последний
|
|
84
|
-
кнопка не плодит
|
|
88
|
+
Оператор выбирается по тому, что делать с предыдущим запросом: список берёт последний ответ
|
|
89
|
+
(`switchMap`), кнопка не плодит дублей (`exhaustMap`), соседние строки идут независимо
|
|
90
|
+
(`mergeMap`).
|
|
85
91
|
|
|
86
92
|
## Частые промахи
|
|
87
93
|
|
|
88
|
-
-
|
|
94
|
+
- `.subscribe()` внутри метода: правило линтера отбивает, а вместе с ним отбивается и гонка
|
|
89
95
|
ответов на быстрых нажатиях.
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
|
|
96
|
+
- Подписка без `takeUntilDestroyed`: она переживает владельца и держит уничтоженный экран в
|
|
97
|
+
памяти.
|
|
98
|
+
- Поле-поток без суффикса `Source`: поток и значение в коде становятся неотличимы.
|
|
99
|
+
- `inject()` вместо параметров конструктора — везде, включая базовые классы.
|
|
100
|
+
- `untracked()` там, где эффекту не нужна зависимость от сигнала: без него эффект просыпается
|
|
101
|
+
на каждое чужое изменение.
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
name: api-layer-pair
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: api-layer
|
|
5
|
-
description: Паттерн правила api-layer. Брать при заведении или правке слоя
|
|
5
|
+
description: Паттерн правила api-layer. Брать при заведении или правке слоя api фронтового домена — готовые фасад и сервис, вход выборки, конвертер страницы, типы порядка и отбора в неймспейсе сущности. Не брать для модели и её маппера — это паттерн entity-models-new.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Фасад и
|
|
8
|
+
# Фасад и сервис домена
|
|
9
9
|
|
|
10
10
|
Паттерн правила `api-layer`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Заводится слой
|
|
15
|
+
- Заводится слой `api` нового домена.
|
|
16
16
|
- Список переводится на общую выборку.
|
|
17
|
-
- Появляется
|
|
17
|
+
- Появляется новая процедура, за которой ходит экран.
|
|
18
18
|
|
|
19
19
|
## Фасад
|
|
20
20
|
|
|
@@ -23,56 +23,66 @@ description: Паттерн правила api-layer. Брать при заве
|
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
25
|
@Injectable({ providedIn: 'root' })
|
|
26
|
-
export class
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
export class PromoCodeApiFacade implements IListApiFacade<
|
|
27
|
+
MessageInitShape<typeof ListPromoCodesRequestSchema>,
|
|
28
|
+
ListPromoCodesResponse,
|
|
29
|
+
GetPromoCodeResponse
|
|
30
|
+
> {
|
|
31
|
+
readonly #client: Client<typeof PricingService> = injectConnectClient(PricingService);
|
|
32
|
+
|
|
33
|
+
public getList(request: MessageInitShape<typeof ListPromoCodesRequestSchema>): Observable<ListPromoCodesResponse> {
|
|
34
|
+
return from(this.#client.listPromoCodes(request));
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
Метод, которого у домена нет, не объявляется: список читают все, правят не все.
|
|
36
40
|
|
|
37
|
-
##
|
|
41
|
+
## Сервис
|
|
38
42
|
|
|
39
43
|
Принимает доменные модели, отдаёт их же. Тип контракта до стора и шаблона не доходит:
|
|
40
44
|
|
|
41
45
|
```typescript
|
|
42
|
-
export class
|
|
43
|
-
|
|
46
|
+
export class PromoCodeApiService implements IListApiService<
|
|
47
|
+
IPromoCode.State,
|
|
48
|
+
EPromoCodeSortProperty,
|
|
49
|
+
EPromoCodeFilterProperty,
|
|
50
|
+
IPromoCode.Draft
|
|
51
|
+
> {
|
|
52
|
+
public getList(query: IPromoCode.Query): Observable<IPromoCode.ListResult> {
|
|
44
53
|
return this.#facade
|
|
45
54
|
.getList({ query: this.#queryMapper.mapTo(query) })
|
|
46
55
|
.pipe(
|
|
47
|
-
map((response:
|
|
48
|
-
convertPaginationApiModelToStateModel((item:
|
|
56
|
+
map((response: ListPromoCodesResponse): IPromoCode.ListResult =>
|
|
57
|
+
convertPaginationApiModelToStateModel((item: PromoCodeInfo): IPromoCode.State => this.#mapper.mapFrom(item), response)
|
|
49
58
|
)
|
|
50
59
|
);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
```
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
— это условия отбора, и лежат они в
|
|
64
|
+
`getList` принимает выборку и больше ничего: объект, к которому привязан список, тип фида,
|
|
65
|
+
состояние подписки — это условия отбора, и лежат они в `filterModel`.
|
|
57
66
|
|
|
58
|
-
## Типы выборки
|
|
67
|
+
## Типы выборки в неймспейсе сущности
|
|
59
68
|
|
|
60
69
|
```typescript
|
|
61
|
-
export type Query = IList.Query.State<
|
|
62
|
-
export type ListResult = IList.Result.State<
|
|
70
|
+
export type Query = IList.Query.State<EPromoCodeSortProperty, EPromoCodeFilterProperty>;
|
|
71
|
+
export type ListResult = IList.Result.State<IPromoCode.State, EPromoCodeSortProperty, EPromoCodeFilterProperty>;
|
|
63
72
|
```
|
|
64
73
|
|
|
65
|
-
Перечисления
|
|
66
|
-
сортирует и отбирает сервер
|
|
74
|
+
Перечисления `EPromoCodeSortProperty` и `EPromoCodeFilterProperty` объявляются в модели рядом с
|
|
75
|
+
сущностью и повторяют набор имён, по которым сортирует и отбирает сервер этого домена.
|
|
67
76
|
|
|
68
77
|
## Частые промахи
|
|
69
78
|
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
79
|
+
- Промежуточный объект между ответом и моделью: ответ ложится в конвертер целиком.
|
|
80
|
+
- Выборка из своего запроса вместо применённой из ответа: умолчание сервера и отброшенное им
|
|
81
|
+
условие экран иначе не увидит.
|
|
82
|
+
- Второй вход рядом с выборкой (`propertyId`, `feedType`): отбор, живущий отдельно, не виден ни
|
|
83
|
+
стору, ни адресу.
|
|
84
|
+
- Голая `string` в поле порядка: имя, по которому сервер не сортирует, компилируется и падает
|
|
75
85
|
запросом.
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
86
|
+
- Один класс на две сущности: подмена источника одной потянет за собой правку другой.
|
|
87
|
+
- Промисный сервис в новом сторе: основа списочного стора работает потоками.
|
|
88
|
+
- Своя копия общих мапперов страницы, порядка и отбора — её ловит `npm run check:dupes`.
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
name: browser-verification-measure
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: browser-verification
|
|
5
|
-
description: Паттерн правила browser-verification. Брать, когда вывод о вёрстке надо подкрепить числом — готовые замеры, разбивка вычисленного значения по всем узлам, узкий экран
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда вывод о вёрстке надо подкрепить числом — готовые замеры, разбивка вычисленного значения по всем узлам, узкий экран через iframe, ловушки инструмента computer. Не брать для подъёма стенда — это паттерн browser-verification-stand.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Замер вместо взгляда
|
|
9
9
|
|
|
10
10
|
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
@@ -18,21 +18,23 @@ description: Паттерн правила browser-verification. Брать, к
|
|
|
18
18
|
|
|
19
19
|
## Вывод подкрепляется числом
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
`getComputedStyle`, `getBoundingClientRect`, контраст, совпадение центров, попадание во
|
|
22
|
+
вьюпорт. «Выглядит нормально» результатом проверки не является.
|
|
23
23
|
|
|
24
|
-
Замер отвечает только на тот вопрос, который задали. Совпадение перечисленных свойств ничего
|
|
25
|
-
говорит о правиле, которого в списке замера
|
|
26
|
-
|
|
24
|
+
Замер отвечает только на тот вопрос, который задали. Совпадение перечисленных свойств ничего
|
|
25
|
+
не говорит о правиле, которого в списке замера нет: строки попапа профиля сошлись с образцом
|
|
26
|
+
по отступам, кеглю и скруглению, а фон на наведении образец в этом месте не красит вовсе —
|
|
27
|
+
полноширинная подсветка держалась два круга при верных числах. Если исходники образца
|
|
28
|
+
доступны, расхождение ищется чтением, а замер остаётся проверкой результата.
|
|
27
29
|
|
|
28
30
|
Вид элемента, которого сегодня не видно ни на одном экране, замером не подтверждается, и
|
|
29
31
|
правило о нём остаётся гипотезой.
|
|
30
32
|
|
|
31
33
|
## Значение, которого в коде нет
|
|
32
34
|
|
|
33
|
-
Гарнитуру,
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
Гарнитуру, `line-height`, цвет и `appearance` элементам формы задаёт браузер, и в дереве этих
|
|
36
|
+
значений нет. Поиск по коду на такой дефект отвечает «чисто», линт и сборка молчат. Ищется
|
|
37
|
+
разбивкой вычисленного значения по всем узлам страницы, а не замером у пары элементов:
|
|
36
38
|
|
|
37
39
|
```javascript
|
|
38
40
|
[...document.querySelectorAll('*')].reduce((acc, el) => {
|
|
@@ -42,42 +44,43 @@ description: Паттерн правила browser-verification. Брать, к
|
|
|
42
44
|
}, {});
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
Так на дашборде админки нашёлся 91 контрол из 106, набранный не той гарнитурой, и 13 из 87 на
|
|
48
|
+
главной сайта. Счёт годится любому наследуемому свойству: смотрится не одно значение, а число
|
|
49
|
+
узлов с неожиданным.
|
|
48
50
|
|
|
49
51
|
## Узкий экран
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
`resize_window` не работает, когда Chrome в полноэкранном режиме: инструмент рапортует успех,
|
|
54
|
+
`innerWidth` не меняется, медиазапросы остаются десктопными. Узкие ширины проверять во
|
|
55
|
+
вложенном iframe нужной ширины — внутри него `matchMedia` считается от рамки; при
|
|
56
|
+
`width: 375px` и `border: 2px` внутренний `innerWidth` равен 371.
|
|
55
57
|
|
|
56
|
-
Правка числа элементов в контейнере — это правка раскладки: она проверяется
|
|
57
|
-
|
|
58
|
+
Правка числа элементов в контейнере — это правка раскладки: она проверяется при 375, а не
|
|
59
|
+
только кодами ответа.
|
|
58
60
|
|
|
59
|
-
## Ловушки инструмента
|
|
61
|
+
## Ловушки инструмента `computer`
|
|
60
62
|
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
фактическому масштабу либо целиться
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
63
|
+
- Координаты клика — координаты **скриншота**, а не CSS-пиксели: при вьюпорте 2560 скриншот
|
|
64
|
+
приходит шириной 1568, и клик по «увиденной» координате уходит мимо, давая ложный сигнал.
|
|
65
|
+
Пересчитывать по фактическому масштабу либо целиться через `find`.
|
|
66
|
+
- Область `zoom` должна целиком лежать внутри вьюпорта.
|
|
67
|
+
- Между кликами обязателен `await`: синхронный цикл «кликнул — прочитал DOM» читает состояние
|
|
68
|
+
до перерисовки и возвращает устаревшие значения.
|
|
69
|
+
- `select_browser` протухает через 300 секунд. На длинной проверке это срабатывает посреди
|
|
70
|
+
работы — это не сбой стенда, повторить вызов и продолжить. Закреплённый профиль — тот, что записан в дереве, идентификатор `062b17db-ae82-4264-927c-e9904d0dd5be`.
|
|
69
71
|
|
|
70
|
-
## Поведение
|
|
72
|
+
## Поведение роутера воспроизводится нажатиями
|
|
71
73
|
|
|
72
|
-
Подстановка адреса,
|
|
73
|
-
накопленного состояния —
|
|
74
|
-
адресам читается как «дефект не
|
|
74
|
+
Подстановка адреса, `history.pushState` с `popstate` и заход по прямой ссылке поднимают
|
|
75
|
+
приложение заново, и накопленного состояния — открытого аутлета, гарда прошлой панели — у него
|
|
76
|
+
нет. Чистый проход по адресам читается как «дефект не подтверждается»: панель ленты событий
|
|
77
|
+
застревала на первом же нажатии из меню и трижды прошла проверку адресами.
|
|
75
78
|
|
|
76
79
|
## Частые промахи
|
|
77
80
|
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
|
|
81
|
+
- Комментарий в конфиге — гипотеза наравне с прочими. Утверждение о вытеснении записи кэша
|
|
82
|
+
продержалось три круга состязательного разбора кода и было опровергнуто одним `curl`.
|
|
83
|
+
- Вывод «дефекта нет, это кэш» закрывает разбор, поэтому принимается только после проверки на
|
|
84
|
+
чистой сборке.
|
|
85
|
+
- Дефект в клиентском чанке от компиляции до правки выглядит как дефект кода: признак
|
|
86
|
+
дев-сборки — имена бандла без хеша.
|
|
@@ -2,78 +2,142 @@
|
|
|
2
2
|
name: browser-verification-stand
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: browser-verification
|
|
5
|
-
description: Паттерн правила browser-verification. Брать, когда нужен честный стенд —
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда нужен честный стенд — прод-сборка сайта, стенд админки, стенд под настоящим nginx, вход в админку, разбор того, что висит на порту. Не брать для замеров вёрстки — это паттерн browser-verification-measure.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Честный стенд
|
|
9
9
|
|
|
10
10
|
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Проверяется то, чего на
|
|
16
|
-
перенаправления, заголовки, размер
|
|
15
|
+
- Проверяется то, чего на дев-сервере не видно: разметка от сервера, локали, кэш,
|
|
16
|
+
перенаправления, заголовки, размер бандла.
|
|
17
17
|
- Порт отвечает не тем, чего ждали.
|
|
18
|
-
- Нужен вход в
|
|
18
|
+
- Нужен вход в админку.
|
|
19
19
|
|
|
20
20
|
## Сначала — что отвечает на порту
|
|
21
21
|
|
|
22
22
|
До первого запроса, а не после непонятного ответа:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
lsof -nP -iTCP
|
|
25
|
+
lsof -nP -iTCP:{{apiPort}} -sTCP:LISTEN
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
На
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
попадает ни в один.
|
|
28
|
+
На {{apiPort}} регулярно висит собранный артефакт из прошлой сессии
|
|
29
|
+
(`node -r dotenv/config dist/apps/api/main.js`): он отвечает 200 старым кодом, а процедуры,
|
|
30
|
+
заведённой в ветке, у него нет вовсе. Таких процессов бывает несколько, и снимать надо все —
|
|
31
|
+
по PID из `lsof`, каждый: `pkill` по шаблону `nx serve api` не попадает ни в один.
|
|
32
32
|
|
|
33
|
-
## Прод-сборка
|
|
33
|
+
## Прод-сборка сайта
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
```bash
|
|
36
|
+
npx nx build site
|
|
37
|
+
PORT={{prodSitePort}} node dist/apps/site/server/server.mjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Это не дев-сервер: гард ловит `nx|ng serve`, пакетные раннеры и статические серверы, а запуск
|
|
41
|
+
собранного сервера пропускает.
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
## Стенд админки
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx nx build admin --base-href=/
|
|
47
|
+
```
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Без `--base-href` стенд отдаёт пустую страницу без ошибок в консоли. Стенду, на котором нужен
|
|
50
|
+
Angular DevTools, нужна ещё и dev-конфигурация (`--configuration=development`): прод-сборка не
|
|
51
|
+
публикует `window.ng`. Выводы о размере бандла и минификации на такой сборке делать нельзя.
|
|
44
52
|
|
|
45
|
-
Сессия кладётся в
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
Сессия кладётся в `localStorage['vm.admin.token']` **строкой JSON** (`JSON.stringify(token)`),
|
|
54
|
+
иначе приложение её не прочитает. Токен не подписывается руками, а берётся у живого API:
|
|
55
|
+
`POST /<область>.v1.AuthService/Login`. Команду с паролем классификатор блокирует — обходить не
|
|
56
|
+
надо, спрашивать разрешение у владельца.
|
|
48
57
|
|
|
49
|
-
Взять уже открытую сессию нельзя: чтение
|
|
50
|
-
своему стенду упираются в пароль, поэтому остаётся третий — смотреть
|
|
51
|
-
|
|
52
|
-
просто посмотреть экраны, он не нужен.
|
|
58
|
+
Взять уже открытую сессию нельзя: чтение `localStorage['vm.admin.token']` из браузера
|
|
59
|
+
блокируется. Оба пути к своему стенду упираются в пароль, поэтому остаётся третий — смотреть
|
|
60
|
+
на админке владельца, где вход уже сделан. Свой стенд нужен, только когда проверяют
|
|
61
|
+
прод-сборку, `--base-href` или конфиг nginx; чтобы просто посмотреть экраны, он не нужен.
|
|
53
62
|
|
|
54
|
-
## Стенд
|
|
63
|
+
## Стенд API
|
|
55
64
|
|
|
56
|
-
Собранный артефакт поднимается на свободном порту, а не на
|
|
65
|
+
Собранный артефакт поднимается на свободном порту, а не на {{apiPort}}: на {{apiPort}} отвечает API
|
|
57
66
|
владельца, и окружение у него не то, которое проверяется.
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
```bash
|
|
69
|
+
npx nx build api
|
|
70
|
+
env -u JWT_SECRET NODE_ENV=production API_PORT={{prodApiPort}} DATABASE_URL=… node dist/apps/api/main.js
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Переменные окружения задаются в самой команде, по одной на проверяемый случай. Отказ на
|
|
74
|
+
старте — такой же результат проверки, как ответ 200: приложение, упавшее в фабрике
|
|
75
|
+
провайдера, порт не слушает вовсе, и это видно по `lsof`, а не по тексту в консоли.
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
Процедура зовётся полным именем, как её объявляет контракт:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
curl -sS -X POST http://localhost:{{prodApiPort}}/<область>.v1.AuthService/GetMe \
|
|
81
|
+
-H 'content-type: application/json' -H "authorization: Bearer $TOKEN" -d '{}'
|
|
82
|
+
```
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
Токен подписывается руками только там, где проверяется сама подпись: доказать, что отладочный
|
|
85
|
+
ключ принимается, другого пути не имеет. Во всех остальных случаях токен берётся у живого API
|
|
86
|
+
входом — рукописный скрывает расхождение состава притязаний.
|
|
87
|
+
|
|
88
|
+
## Стенд под настоящим nginx
|
|
89
|
+
|
|
90
|
+
Кэш, перенаправления и заголовки живут в `deploy/nginx.conf`, а не в приложении. Любой вывод
|
|
91
|
+
про `301`, `Cache-Control` и `X-Cache-Status` делается только здесь:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
docker run -d --name <префикс>-stand-nginx \
|
|
95
|
+
--add-host api:host-gateway --add-host ssr:host-gateway \
|
|
96
|
+
-p {{dockerSitePort}}:80 -p {{dockerAdminPort}}:8081 \
|
|
97
|
+
-v "$PWD/deploy/nginx/main.conf:/etc/nginx/nginx.conf:ro" \
|
|
98
|
+
-v "$PWD/deploy:/etc/nginx/conf.d:ro" \
|
|
99
|
+
-v "$PWD/dist/apps/admin/browser:/usr/share/nginx/html/admin:ro" \
|
|
100
|
+
nginx:1.27-alpine
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Конфиг монтируется **каталогом**, а не одиночным файлом: редактор пересоздаёт файл,
|
|
104
|
+
контейнеру остаётся обрезанная копия, и `nginx -t` внутри падает на «unexpected end of file»
|
|
105
|
+
при целом файле снаружи. Лечится пересозданием контейнера, а не правкой конфига.
|
|
106
|
+
- Главный конфиг подставляется отдельной строкой: он лежит в `deploy/nginx/`, а не рядом с
|
|
107
|
+
`nginx.conf`, — файл с расширением `.conf` в смонтированном каталоге попал бы в `include`
|
|
108
|
+
и уронил бы nginx на директиве `user`. Без него стенд поднимается на конфиге образа, и
|
|
109
|
+
предел соединений на воркер там свой.
|
|
110
|
+
- Сервер отдачи страниц отвечает `400` на чужой `Host`: все запросы идут с
|
|
111
|
+
`-H "Host: localhost"`.
|
|
112
|
+
- Переменные окружения стенда обязаны смотреть на процессы стенда. `CACHE_REFRESH_URL`,
|
|
113
|
+
направленный на {{sitePort}}, сбрасывает кэш мимо того процесса, который держит справочник
|
|
114
|
+
перенаправлений в памяти, — исправный механизм при этом выглядит сломанным.
|
|
115
|
+
|
|
116
|
+
## Дерево для сравнения
|
|
117
|
+
|
|
118
|
+
Сказать «это сломала правка» можно, только если видно, что до правки было иначе. Проверяют это
|
|
119
|
+
вторым деревом, а не по памяти:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git worktree add ../<префикс>-base <коммит> # ветка задачи не трогается
|
|
123
|
+
pnpm install --frozen-lockfile # из ../<префикс>-base: node_modules у него свои
|
|
124
|
+
```
|
|
68
125
|
|
|
69
|
-
|
|
70
|
-
|
|
126
|
+
- Для сравнения берут не главную ветку, а последний коммит, на котором дерево собирается:
|
|
127
|
+
главная бывает сломана, и тогда «до» и «после» различаются не из-за правки. Собирается ли
|
|
128
|
+
коммит — проверяют сборкой, а не тем, что он в главной ветке.
|
|
129
|
+
- Стенд второго дерева поднимают на своих портах: {{sitePort}}, {{adminPort}} и {{apiPort}} заняты владельцем, а {{ssrPort}}
|
|
130
|
+
занимать нельзя — стенд разработчика ходит по имени `ssr:{{ssrPort}}`.
|
|
131
|
+
- Оба стенда держат поднятыми одновременно: если сравнивать по памяти между двумя запусками,
|
|
132
|
+
заметишь только то, что успел запомнить.
|
|
71
133
|
|
|
72
134
|
## Частые промахи
|
|
73
135
|
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
136
|
+
- Свой дев-сервер не поднимать: сайт на {{sitePort}}, админка на {{adminPort}}, API на {{apiPort}} уже подняты
|
|
137
|
+
владельцем, и второй экземпляр отбивается гардом.
|
|
138
|
+
- **Общая сборка глушит все три дев-сервера владельца, а не только API.** После
|
|
139
|
+
`nx run-many -t build` ложатся и сайт на {{sitePort}}, и админка на {{adminPort}}. Собирать надо то, что
|
|
140
|
+
проверяешь (`npx nx build site`), а не всё дерево. Если серверы легли, поднять их обратно
|
|
141
|
+
агент не может — мешает гард, поэтому владельцу говорят об этом сразу, а не в конце сессии.
|
|
142
|
+
- Порт {{ssrPort}} занимать осторожно: стенд разработчика на {{sitePort}} ходит по тому же имени `ssr:{{ssrPort}}`
|
|
143
|
+
через `host-gateway`, и пока на нём висит чужой процесс, стенд отдаёт чужую сборку.
|