@rt-tools/agent-kit 0.1.0 → 0.3.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 +88 -9
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +17 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +78 -0
- package/assets/hooks/browser-guard-require-select.sh +53 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +42 -0
- package/assets/hooks/dev-server-guard.sh +113 -0
- package/assets/hooks/docs-guard.sh +96 -0
- package/assets/hooks/git-guard-delivery.sh +110 -0
- package/assets/hooks/git-guard-main.sh +72 -0
- package/assets/hooks/git-guard-push-tests.sh +73 -0
- package/assets/hooks/lint-after-edit.sh +94 -0
- package/assets/hooks/qa-dataid-guard.sh +81 -0
- package/assets/hooks/reuse-first-guard.sh +83 -0
- package/assets/hooks/skill-gate-rearm.sh +22 -0
- package/assets/hooks/skill-gate.sh +68 -0
- package/assets/hooks/skill-loaded.sh +20 -0
- package/assets/hooks/sql-guard.sh +129 -0
- package/assets/laws/access.md +3 -12
- package/assets/laws/admin-lists.md +9 -21
- package/assets/laws/admin-navigation.md +3 -15
- package/assets/laws/code-structure.md +5 -16
- package/assets/laws/delivery.md +2 -16
- package/assets/laws/entity-editing.md +7 -20
- package/assets/laws/entity-models.md +10 -17
- package/assets/laws/frontend-application.md +3 -14
- package/assets/laws/lib-imports.md +0 -13
- package/assets/laws/locales.md +2 -11
- package/assets/laws/project-documentation.md +7 -20
- package/assets/laws/reuse-first.md +0 -9
- package/assets/laws/search-visibility.md +0 -13
- package/assets/laws/shared-code.md +0 -13
- package/assets/laws/verifiability.md +0 -13
- package/assets/patterns/angular-patterns-state.md +94 -0
- package/assets/patterns/api-layer-pair.md +78 -0
- package/assets/patterns/browser-verification-measure.md +83 -0
- package/assets/patterns/browser-verification-stand.md +79 -0
- package/assets/patterns/component-structure-new.md +98 -0
- package/assets/patterns/doc-style-sweep.md +100 -0
- package/assets/patterns/doc-style-write.md +106 -0
- package/assets/patterns/git-workflow-commit.md +175 -0
- package/assets/patterns/git-workflow-merge.md +82 -0
- package/assets/patterns/git-workflow-migration.md +58 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +77 -0
- package/assets/patterns/lib-layers-new.md +70 -0
- package/assets/patterns/permissions-procedure.md +69 -0
- package/assets/patterns/platform-access-di.md +70 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +92 -0
- package/assets/patterns/seo-verify.md +64 -0
- package/assets/patterns/shared-code-new.md +80 -0
- package/assets/patterns/spec-driven-domain.md +100 -0
- package/assets/patterns/spec-driven-rule.md +112 -0
- package/assets/patterns/styling-bem-component.md +77 -0
- package/assets/patterns/styling-bem-layout.md +67 -0
- package/assets/patterns/testing-e2e.md +90 -0
- package/assets/patterns/testing-unit.md +93 -0
- package/assets/patterns/translations-key.md +51 -0
- package/assets/patterns/ts-procedure.md +66 -0
- package/assets/rules/angular-patterns.md +52 -0
- package/assets/rules/api-layer.md +53 -0
- package/assets/rules/browser-verification.md +69 -0
- package/assets/rules/component-structure.md +48 -0
- package/assets/rules/doc-style.md +61 -0
- package/assets/rules/git-workflow.md +106 -0
- package/assets/rules/lib-layers.md +54 -0
- package/assets/rules/permissions.md +52 -0
- package/assets/rules/platform-access.md +49 -0
- package/assets/rules/reuse-first.md +69 -0
- package/assets/rules/seo.md +50 -0
- package/assets/rules/shared-code.md +45 -0
- package/assets/rules/spec-driven.md +89 -0
- package/assets/rules/styling-bem.md +59 -0
- package/assets/rules/testing.md +69 -0
- package/assets/rules/translations.md +52 -0
- package/assets/rules/typescript-conventions.md +46 -0
- package/assets/templates/gate-map.sh +37 -0
- package/assets/templates/implementation.md +38 -0
- package/assets/templates/pattern.md +4 -0
- package/assets/templates/project.sh +41 -0
- package/assets/templates/rule.md +12 -23
- package/bin/agent-kit.d.ts +1 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +63 -7
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +9 -0
- package/bin/prompt.d.ts.map +1 -0
- package/bin/prompt.js +57 -0
- package/bin/prompt.js.map +1 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +10 -2
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +24 -28
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +44 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +86 -0
- package/lib/catalog.js.map +1 -0
- package/lib/commands.d.ts +13 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +106 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +53 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +33 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +29 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +43 -6
- package/lib/config.js.map +1 -1
- package/lib/picker.d.ts +47 -0
- package/lib/picker.d.ts.map +1 -0
- package/lib/picker.js +112 -0
- package/lib/picker.js.map +1 -0
- 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 +3 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +20 -1
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
- package/rt-tools-agent-kit-0.1.0.tgz +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-patterns-state
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: angular-patterns
|
|
5
|
+
description: Паттерн правила angular-patterns. Брать при объявлении состояния и потоков в классе фронтового каркаса — реактивные входы и выходы, производные значения, состояние службы, долгоживущая подписка с источником действия. Не брать для раскладки файла компонента — это паттерн component-structure-new.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Состояние и потоки
|
|
9
|
+
|
|
10
|
+
Паттерн правила `angular-patterns`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Объявляется состояние компонента или службы.
|
|
16
|
+
- Появляется поток, на который надо подписаться.
|
|
17
|
+
- Значение считается из другого значения.
|
|
18
|
+
|
|
19
|
+
## Реактивные входы и выходы
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
public readonly data: InputSignal<Item[]> = input.required<Item[]>();
|
|
23
|
+
public readonly isNarrow: InputSignal<boolean | undefined> = input<boolean>();
|
|
24
|
+
public readonly save: OutputEmitterRef<void> = output<void>();
|
|
25
|
+
|
|
26
|
+
protected readonly myButton: Signal<ElementRef | undefined> = viewChild<ElementRef>('button');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Декораторной формы входов, выходов и запросов к разметке в дереве нет: у неё нет типа, который
|
|
30
|
+
видно в месте использования, и нет реактивности, на которую можно подписаться.
|
|
31
|
+
|
|
32
|
+
## Производное значение — вычисляемое, а не эффект
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
✗ effect((): void => { this.count.set(this.items().length); });
|
|
36
|
+
✓ protected readonly count: Signal<number> = computed((): number => this.items().length);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Эффект, кладущий значение в реактивное поле, — это ручной пересчёт, и он рано или поздно
|
|
40
|
+
отстаёт от источника. Геттера в компоненте не заводить: он пересчитывается на каждой
|
|
41
|
+
перерисовке, и цена его не видна ни в одном месте кода.
|
|
42
|
+
|
|
43
|
+
## Состояние службы
|
|
44
|
+
|
|
45
|
+
Наружу — только чтение:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
@Injectable({ providedIn: 'root' })
|
|
49
|
+
export class DomainStateService {
|
|
50
|
+
readonly #items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
51
|
+
|
|
52
|
+
public readonly items: Signal<Item[]> = this.#items.asReadonly();
|
|
53
|
+
public readonly itemCount: Signal<number> = computed((): number => this.#items().length);
|
|
54
|
+
|
|
55
|
+
public addItem(item: Item): void {
|
|
56
|
+
this.#items.update((items: Item[]): Item[] => [...items, item]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Подписка объявляется один раз
|
|
62
|
+
|
|
63
|
+
Метод действия толкает значение в источник, подписка живёт при создании владельца:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
readonly #loadSource: Subject<void> = new Subject<void>();
|
|
67
|
+
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
this.#loadSource
|
|
71
|
+
.pipe(
|
|
72
|
+
switchMap((): Observable<IResult> => this.#api.getList(this.#query())),
|
|
73
|
+
takeUntilDestroyed(this.#destroyRef)
|
|
74
|
+
)
|
|
75
|
+
.subscribe();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected reload(): void {
|
|
79
|
+
this.#loadSource.next();
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Оператор выбирается по тому, что делать с предыдущим запросом: список берёт последний ответ,
|
|
84
|
+
кнопка не плодит дублей, независимые строки идут параллельно.
|
|
85
|
+
|
|
86
|
+
## Частые промахи
|
|
87
|
+
|
|
88
|
+
- **Подписка внутри метода:** правило линтера отбивает, а вместе с ним отбивается и гонка
|
|
89
|
+
ответов на быстрых нажатиях.
|
|
90
|
+
- **Подписка без гашения:** она переживает владельца и держит уничтоженный экран в памяти.
|
|
91
|
+
- **Поле-поток без суффикса источника:** поток и значение в коде становятся неотличимы.
|
|
92
|
+
- **Параметры конструктора вместо функции внедрения** — везде, включая базовые классы.
|
|
93
|
+
- **Эффект без снятия слежения там, где зависимость не нужна:** он просыпается на каждое чужое
|
|
94
|
+
изменение.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-layer-pair
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: api-layer
|
|
5
|
+
description: Паттерн правила api-layer. Брать при заведении или правке слоя обращения к серверу во фронтовом домене — готовые фасад и служба, единственный вход выборки, общий конвертер страницы, типы порядка и отбора при сущности.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Фасад и служба домена
|
|
9
|
+
|
|
10
|
+
Паттерн правила `api-layer`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится слой обращения к серверу у нового домена.
|
|
16
|
+
- Список переводится на общую выборку.
|
|
17
|
+
- Появляется новый обработчик, за которым ходит экран.
|
|
18
|
+
|
|
19
|
+
## Фасад
|
|
20
|
+
|
|
21
|
+
Принимает запрос контракта, отдаёт ответ контракта, ожидание заворачивает в поток. Ни выборки,
|
|
22
|
+
ни перевода моделей в нём нет:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
@Injectable({ providedIn: 'root' })
|
|
26
|
+
export class EntityApiFacade implements IListApiFacade<TListRequest, TListResponse, TItemResponse> {
|
|
27
|
+
readonly #client: Client<typeof DomainService> = injectClient(DomainService);
|
|
28
|
+
|
|
29
|
+
public getList(request: TListRequest): Observable<TListResponse> {
|
|
30
|
+
return from(this.#client.listItems(request));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Метод, которого у домена нет, не объявляется: список читают все, правят не все.
|
|
36
|
+
|
|
37
|
+
## Служба
|
|
38
|
+
|
|
39
|
+
Принимает доменные модели, отдаёт их же. Тип контракта до стора и шаблона не доходит:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
export class EntityApiService implements IListApiService<IEntity.State, ESortProperty, EFilterProperty, IEntity.Draft> {
|
|
43
|
+
public getList(query: IEntity.Query): Observable<IEntity.ListResult> {
|
|
44
|
+
return this.#facade
|
|
45
|
+
.getList({ query: this.#queryMapper.mapTo(query) })
|
|
46
|
+
.pipe(
|
|
47
|
+
map((response: TListResponse): IEntity.ListResult =>
|
|
48
|
+
convertPaginationApiModelToStateModel((item: TItem): IEntity.State => this.#mapper.mapFrom(item), response)
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Выборка — единственный вход: объект, к которому привязан список, род ленты, состояние подписки
|
|
56
|
+
— это условия отбора, и лежат они в её условиях.
|
|
57
|
+
|
|
58
|
+
## Типы выборки при сущности
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
export type Query = IList.Query.State<ESortProperty, EFilterProperty>;
|
|
62
|
+
export type ListResult = IList.Result.State<IEntity.State, ESortProperty, EFilterProperty>;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Перечисления порядка и отбора объявляются рядом с сущностью и повторяют набор имён, по которым
|
|
66
|
+
сортирует и отбирает сервер именно этого домена.
|
|
67
|
+
|
|
68
|
+
## Частые промахи
|
|
69
|
+
|
|
70
|
+
- **Промежуточный объект между ответом и моделью:** ответ ложится в конвертер целиком.
|
|
71
|
+
- **Выборка из своего запроса вместо применённой из ответа:** умолчание сервера и отброшенное
|
|
72
|
+
им условие экран иначе не увидит.
|
|
73
|
+
- **Второй вход рядом с выборкой:** отбор, живущий отдельно, не виден ни стору, ни адресу.
|
|
74
|
+
- **Голая строка в поле порядка:** имя, по которому сервер не сортирует, компилируется и падает
|
|
75
|
+
запросом.
|
|
76
|
+
- **Один класс на две сущности:** подмена источника одной потянет за собой правку другой.
|
|
77
|
+
- **Служба на обещаниях в новом сторе:** основа списочного стора работает потоками.
|
|
78
|
+
- **Своя копия общих переводчиков страницы, порядка и отбора** — её ловит проверка повторов.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-verification-measure
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: browser-verification
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда вывод о вёрстке надо подкрепить числом — готовые замеры, разбивка вычисленного значения по всем узлам, узкий экран вложенной рамкой, ловушки инструмента снимка экрана. Не брать для подъёма стенда — это паттерн browser-verification-stand.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Замер вместо взгляда
|
|
9
|
+
|
|
10
|
+
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Правилась вёрстка, и надо подтвердить результат.
|
|
16
|
+
- Экран выглядит не так, а искать нечего: поиск по коду отвечает «чисто».
|
|
17
|
+
- Проверяется узкий экран.
|
|
18
|
+
|
|
19
|
+
## Вывод подкрепляется числом
|
|
20
|
+
|
|
21
|
+
Вычисленный стиль, прямоугольник элемента, контраст, совпадение центров, попадание в видимую
|
|
22
|
+
область. «Выглядит нормально» результатом проверки не является.
|
|
23
|
+
|
|
24
|
+
Замер отвечает только на тот вопрос, который задали. Совпадение перечисленных свойств ничего не
|
|
25
|
+
говорит о правиле, которого в списке замера нет. Если исходники образца доступны, расхождение
|
|
26
|
+
ищется чтением, а замер остаётся проверкой результата.
|
|
27
|
+
|
|
28
|
+
Вид элемента, которого сегодня не видно ни на одном экране, замером не подтверждается, и
|
|
29
|
+
правило о нём остаётся гипотезой.
|
|
30
|
+
|
|
31
|
+
## Значение, которого в коде нет
|
|
32
|
+
|
|
33
|
+
Гарнитуру, межстрочный интервал, цвет и оформление элементам формы задаёт браузер, и в дереве
|
|
34
|
+
этих значений нет. Поиск по коду на такой дефект отвечает «чисто», линтер и сборка молчат.
|
|
35
|
+
Ищется разбивкой вычисленного значения по всем узлам страницы, а не замером у пары элементов:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
[...document.querySelectorAll('*')].reduce((acc, el) => {
|
|
39
|
+
const key = getComputedStyle(el).fontFamily;
|
|
40
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
41
|
+
return acc;
|
|
42
|
+
}, {});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Счёт годится любому наследуемому свойству: смотрится не одно значение, а число узлов с
|
|
46
|
+
неожиданным. Так находятся десятки элементов, набранных не той гарнитурой, — по одному их не
|
|
47
|
+
заметил бы никто.
|
|
48
|
+
|
|
49
|
+
## Узкий экран
|
|
50
|
+
|
|
51
|
+
Изменение размера окна не работает, когда браузер в полноэкранном режиме: инструмент рапортует
|
|
52
|
+
успех, ширина не меняется, медиазапросы остаются широкими. Узкие ширины проверяются во
|
|
53
|
+
вложенной рамке нужной ширины — внутри неё запрос ширины считается от рамки, и рамка своей
|
|
54
|
+
толщиной уменьшает внутреннюю ширину.
|
|
55
|
+
|
|
56
|
+
Правка числа элементов в контейнере — это правка раскладки: она проверяется на узкой ширине, а
|
|
57
|
+
не только кодами ответа.
|
|
58
|
+
|
|
59
|
+
## Ловушки инструмента снимка экрана
|
|
60
|
+
|
|
61
|
+
- **Координаты нажатия — координаты снимка, а не страницы.** При широком окне снимок приходит
|
|
62
|
+
уже, и нажатие по «увиденной» координате уходит мимо, давая ложный сигнал: пересчитывать по
|
|
63
|
+
фактическому масштабу либо целиться поиском элемента.
|
|
64
|
+
- **Область приближения должна целиком лежать внутри видимой области.**
|
|
65
|
+
- **Между нажатиями обязательно ожидание:** цикл «нажал — прочитал разметку» без него читает
|
|
66
|
+
состояние до перерисовки и возвращает устаревшие значения.
|
|
67
|
+
- **Выбор браузера протухает.** На длинной проверке это срабатывает посреди работы — не сбой
|
|
68
|
+
стенда, а повод повторить вызов и продолжить.
|
|
69
|
+
|
|
70
|
+
## Поведение маршрутизации воспроизводится нажатиями
|
|
71
|
+
|
|
72
|
+
Подстановка адреса, программный переход и заход по прямой ссылке поднимают приложение заново, и
|
|
73
|
+
накопленного состояния — открытой панели, стража прошлого экрана — у него нет. Чистый проход по
|
|
74
|
+
адресам читается как «дефект не подтверждается».
|
|
75
|
+
|
|
76
|
+
## Частые промахи
|
|
77
|
+
|
|
78
|
+
- **Комментарий в конфиге — гипотеза наравне с прочими.** Утверждение о поведении кэша
|
|
79
|
+
переживает несколько кругов разбора кода и опровергается одним запросом.
|
|
80
|
+
- **Вывод «дефекта нет, это кэш» закрывает разбор**, поэтому принимается только после проверки
|
|
81
|
+
на чистой сборке.
|
|
82
|
+
- **Дефект в клиентском куске от компиляции до правки выглядит как дефект кода** — признак
|
|
83
|
+
сборки для разработки — имена файлов без хеша.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-verification-stand
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: browser-verification
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда нужен честный стенд — прод-сборка, стенд под настоящим прокси, вход в приложение, разбор того, что висит на порту, стенд серверной стороны с переменными окружения. Не брать для замеров вёрстки — это паттерн browser-verification-measure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Честный стенд
|
|
9
|
+
|
|
10
|
+
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Проверяется то, чего на сервере разработки не видно: разметка от сервера, локали, кэш,
|
|
16
|
+
перенаправления, заголовки, размер сборки.
|
|
17
|
+
- Порт отвечает не тем, чего ждали.
|
|
18
|
+
- Нужен вход в приложение.
|
|
19
|
+
|
|
20
|
+
## Сначала — что отвечает на порту
|
|
21
|
+
|
|
22
|
+
До первого запроса, а не после непонятного ответа:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
lsof -nP -iTCP:<порт> -sTCP:LISTEN
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
На порту регулярно висит собранный артефакт из прошлой сессии: он отвечает успехом на старом
|
|
29
|
+
коде, а заведённого в ветке обработчика у него нет вовсе. Таких процессов бывает несколько, и
|
|
30
|
+
снимать надо все — по идентификатору из вывода, каждый: завершение по шаблону команды не
|
|
31
|
+
попадает ни в один.
|
|
32
|
+
|
|
33
|
+
## Прод-сборка
|
|
34
|
+
|
|
35
|
+
Собранный сервер поднимается прямо, а не через сервер разработки: гард ловит запуск сервера
|
|
36
|
+
разработки, пакетные раннеры и статические серверы, а запуск собранного сервера пропускает.
|
|
37
|
+
|
|
38
|
+
Приложению, отдающему статику, нужен явный базовый адрес — без него стенд отдаёт пустую
|
|
39
|
+
страницу без единой ошибки в консоли. Стенду, на котором нужны отладочные инструменты каркаса,
|
|
40
|
+
нужна сборка для разработки: прод-сборка их не публикует. Выводы о размере сборки и минификации
|
|
41
|
+
на такой сборке делать нельзя.
|
|
42
|
+
|
|
43
|
+
## Вход в приложение
|
|
44
|
+
|
|
45
|
+
Сессия кладётся в хранилище браузера ровно в той форме, в какой её читает приложение: значение,
|
|
46
|
+
записанное иначе, приложение молча не увидит. Токен не подписывается руками, а берётся у живого
|
|
47
|
+
сервера входом.
|
|
48
|
+
|
|
49
|
+
Взять уже открытую сессию нельзя: чтение хранилища чужого профиля блокируется. Оба пути к
|
|
50
|
+
своему стенду упираются в пароль, поэтому остаётся третий — смотреть там, где вход уже сделан.
|
|
51
|
+
Свой стенд нужен, только когда проверяют прод-сборку, базовый адрес или конфиг прокси; чтобы
|
|
52
|
+
просто посмотреть экраны, он не нужен.
|
|
53
|
+
|
|
54
|
+
## Стенд серверной стороны
|
|
55
|
+
|
|
56
|
+
Собранный артефакт поднимается на свободном порту, а не на рабочем: на рабочем отвечает сервер
|
|
57
|
+
владельца, и окружение у него не то, которое проверяется.
|
|
58
|
+
|
|
59
|
+
Переменные окружения задаются в самой команде, по одной на проверяемый случай. Отказ на старте
|
|
60
|
+
— такой же результат проверки, как успешный ответ: приложение, упавшее при сборке зависимостей,
|
|
61
|
+
порт не слушает вовсе, и это видно по списку слушателей, а не по тексту в консоли.
|
|
62
|
+
|
|
63
|
+
Обработчик зовётся полным именем, как его объявляет контракт. Токен подписывается руками только
|
|
64
|
+
там, где проверяется сама подпись; во всех остальных случаях он берётся у живого сервера входом
|
|
65
|
+
— рукописный скрывает расхождение состава притязаний.
|
|
66
|
+
|
|
67
|
+
## Стенд под настоящим прокси
|
|
68
|
+
|
|
69
|
+
Кэш, перенаправления и заголовки живут в конфиге прокси, а не в приложении, и любой вывод о них
|
|
70
|
+
с голого сервера отдачи страниц неверен. Устройство такого стенда — паттерн `testing-e2e`.
|
|
71
|
+
|
|
72
|
+
## Частые промахи
|
|
73
|
+
|
|
74
|
+
- **Порт занят чужой сборкой, а ответ читается как дефект ветки.** Сначала список слушателей,
|
|
75
|
+
потом запрос.
|
|
76
|
+
- **Стенд без явного базового адреса отдаёт пустую страницу** и ни одной ошибки при этом не
|
|
77
|
+
печатает.
|
|
78
|
+
- **Токен, подписанный руками, скрывает расхождение состава притязаний** — вход берётся у
|
|
79
|
+
живого сервера.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-structure-new
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: component-structure
|
|
5
|
+
description: Паттерн правила component-structure. Брать при заведении или правке файла компонента — порядок свойств декоратора, группировка импортов, раскладка полей класса, договорённости шаблона и якорь для спек. Не брать для состояния и потоков — это паттерн angular-patterns-state.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Файл компонента
|
|
9
|
+
|
|
10
|
+
Паттерн правила `component-structure`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится новый компонент.
|
|
16
|
+
- Правится декоратор, список импортов или шаблон существующего.
|
|
17
|
+
|
|
18
|
+
## Декоратор: порядок свойств
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@Component({
|
|
22
|
+
selector: '<префикс>-component-name', // 1. селектор
|
|
23
|
+
templateUrl: './component-name.component.html', // 2. шаблон
|
|
24
|
+
styleUrl: './component-name.component.scss', // 3. стиль, в единственном числе
|
|
25
|
+
changeDetection: ChangeDetectionStrategy.OnPush, // 4. стратегия перерисовки
|
|
26
|
+
imports: [
|
|
27
|
+
// 5. импорты, группами
|
|
28
|
+
// каркас
|
|
29
|
+
FormsModule,
|
|
30
|
+
|
|
31
|
+
// директивы разметки
|
|
32
|
+
BlockDirective,
|
|
33
|
+
ElemDirective,
|
|
34
|
+
|
|
35
|
+
// компоненты
|
|
36
|
+
SomeChildComponent,
|
|
37
|
+
],
|
|
38
|
+
providers: [], // 6. провайдеры
|
|
39
|
+
host: { class: '<префикс>-component-name' }, // 7. привязки хоста
|
|
40
|
+
})
|
|
41
|
+
export class ComponentNameComponent {
|
|
42
|
+
readonly #someService: SomeService = inject(SomeService);
|
|
43
|
+
|
|
44
|
+
public readonly data: InputSignal<Item[]> = input.required<Item[]>();
|
|
45
|
+
public readonly save: OutputEmitterRef<void> = output<void>();
|
|
46
|
+
|
|
47
|
+
protected readonly items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
48
|
+
protected readonly itemCount: Signal<number> = computed((): number => this.items().length);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Группирующие комментарии в импортах обязательны: без них список растёт вперемешку, и первое,
|
|
53
|
+
что в нём теряется, — свои компоненты среди чужих.
|
|
54
|
+
|
|
55
|
+
## Шаблон
|
|
56
|
+
|
|
57
|
+
- Самозакрывающиеся теги у компонентов без содержимого.
|
|
58
|
+
- Лишних обёрток нет — корнем работает хост, класс блока приходит его привязкой.
|
|
59
|
+
- Сложный шаблон объявляет блок в корне отдельной директивой.
|
|
60
|
+
- Один компонент в обеих ветках условия — это условная привязка:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<!-- ✗ -->
|
|
64
|
+
@if (isRangeMode()) {
|
|
65
|
+
<calendar [rangeMode]="true" />
|
|
66
|
+
} @else {
|
|
67
|
+
<calendar [rangeMode]="false" />
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
<!-- ✓ -->
|
|
71
|
+
<calendar [rangeMode]="isRangeMode()" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- Прокрутка по документу — средствами маршрутизатора, а не ссылкой на фрагмент: при объявленном
|
|
75
|
+
базовом адресе браузер разрешает фрагмент относительно него и уходит в полную навигацию.
|
|
76
|
+
|
|
77
|
+
## Якорь для спек — на каждый интерактивный элемент
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<button qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
|
|
81
|
+
<div rtElem="grid" qa-dataid="admin-calendar-grid"></div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Значение — через дефис, по смыслу элемента, без имени компонента-обёртки.
|
|
85
|
+
- Уникальность — в пределах экрана; повторяющиеся элементы списка носят один якорь и
|
|
86
|
+
различаются атрибутами данных.
|
|
87
|
+
- Декоративный элемент помечается признаком пропуска на самом теге.
|
|
88
|
+
|
|
89
|
+
## Частые промахи
|
|
90
|
+
|
|
91
|
+
- **Чужой префикс селектора** — компонент перестаёт узнаваться как свой.
|
|
92
|
+
- **Множественная форма свойства стилей вместо единственной** — стиль молча не подключается.
|
|
93
|
+
- **Вызов метода в привязке** — отбивается линтером; замена — вычисляемое значение, а там, где
|
|
94
|
+
оно зависит от контекста шаблона, — чистый преобразователь.
|
|
95
|
+
- **Обёртка, которая существует только чтобы быть контейнером раскладки вокруг всех детей:** её
|
|
96
|
+
раскладка уезжает на хост.
|
|
97
|
+
- **Глубокий относительный импорт между либами** вместо алиаса.
|
|
98
|
+
- **Своя разметка вместо готового компонента** — правило `reuse-first`.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doc-style-sweep
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: doc-style
|
|
5
|
+
description: Паттерн правила doc-style. Брать, когда документ накопил список работ и его надо разобрать на действующее и закрытое — признак отбора, обход по утверждениям, сверка с очередью работ, измерение потерь, куда девать доводы за отсрочку и судьба самого файла. Не брать для написания нового текста — это паттерн doc-style-write.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Разбор документа, накопившего список работ
|
|
9
|
+
|
|
10
|
+
Паттерн правила `doc-style`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/project-documentation.md`, статья о том, что предстоящая работа перечислена в одном
|
|
12
|
+
месте.
|
|
13
|
+
|
|
14
|
+
## Когда брать
|
|
15
|
+
|
|
16
|
+
- Документ перечисляет, что осталось сделать, а очередь работ перечисляет то же самое.
|
|
17
|
+
- Файл вырос настолько, что его перестали читать целиком.
|
|
18
|
+
- Планы лежат вперемешку: часть исполнена, часть нет, и по тексту это не различить.
|
|
19
|
+
|
|
20
|
+
## Признак отбора — один, и он решается до первой правки
|
|
21
|
+
|
|
22
|
+
**Утверждение остаётся в документе, если править его никто не собирается.** Всё остальное —
|
|
23
|
+
работа, и её место в очереди работ.
|
|
24
|
+
|
|
25
|
+
| Что нашлось | Куда |
|
|
26
|
+
| ------------------------------------------------------- | --------------------------------------------------------------- |
|
|
27
|
+
| дыра, которую будут чинить | задача; строка из документа уходит |
|
|
28
|
+
| дыра, которую решено не чинить | остаётся, вместе с причиной |
|
|
29
|
+
| условный отказ («заведём, если случай окажется частым») | остаётся |
|
|
30
|
+
| вывод разбора, не ставший правилом | остаётся |
|
|
31
|
+
| описание сделанного | удаляется без переноса: о нём говорят закрытые задачи и история |
|
|
32
|
+
| утверждение, разошедшееся с деревом | удаляется как протухшее, а не переносится в задачу |
|
|
33
|
+
|
|
34
|
+
Спрашивать владельца об этом наборе не нужно — он записан здесь. Спрашивать стоит одно: судьбу
|
|
35
|
+
самого файла, когда в нём не осталось ничего.
|
|
36
|
+
|
|
37
|
+
## Обход идёт по утверждениям, а не по пунктам
|
|
38
|
+
|
|
39
|
+
Работа лежит и в прозе: раздел без единого маркера обход по списку не увидит вовсе. Абзац
|
|
40
|
+
судится тем же признаком, что и пункт.
|
|
41
|
+
|
|
42
|
+
## Номер задачи стоит в нескольких местах
|
|
43
|
+
|
|
44
|
+
Прежде чем считать «пункты без задачи», надо знать все формы записи — в заголовке, отдельной
|
|
45
|
+
строкой у раздела и в конце пункта. Разбор, знающий одну форму, ошибается молча. **Число,
|
|
46
|
+
полученное разбором текста, сверяется на выборке руками до того, как его называют.**
|
|
47
|
+
|
|
48
|
+
## Сверка с очередью работ
|
|
49
|
+
|
|
50
|
+
У пункта есть задача — это ещё не значит, что задача несёт его содержание. Перед удалением
|
|
51
|
+
меряется покрытие: сколько значимых слов пункта встречается в теле его задачи. Пункт, покрытый
|
|
52
|
+
наполовину и меньше, читается глазами и разводится на три исхода:
|
|
53
|
+
|
|
54
|
+
- живое уточнение, которого в задаче нет, — дописать в тело;
|
|
55
|
+
- другой дефект — завести своей задачей;
|
|
56
|
+
- протухшее — удалить, **не** перенося. Иначе разбор занесёт в задачу ложь.
|
|
57
|
+
|
|
58
|
+
**Наследование номера от заголовка — предположение, а не факт.** Пункт под заголовком с
|
|
59
|
+
несколькими номерами не принадлежит ни одному из них: привязка проверяется чтением.
|
|
60
|
+
|
|
61
|
+
## Сделанность читается по дереву
|
|
62
|
+
|
|
63
|
+
Утверждение о том, что задача не сделана, стареет так же, как любое другое. Проверяется то, о
|
|
64
|
+
чём собираешься сказать «не сделано», — поиском по дереву, а не памятью.
|
|
65
|
+
|
|
66
|
+
## Доводы за отсрочку — это «чего это стоит»
|
|
67
|
+
|
|
68
|
+
Раздел «что стоит отложить» переезжает не в архив, а **в тела тех задач, которых он касается**:
|
|
69
|
+
там он и есть оценка цены. В файле он остаётся, только если отсрочка — решение владельца, а не
|
|
70
|
+
предложение разбора. Признак — записанный ответ владельца с датой.
|
|
71
|
+
|
|
72
|
+
## Ссылки на снятые разделы
|
|
73
|
+
|
|
74
|
+
Задача, чьё тело ссылается на раздел документа, после разбора ведёт в пустоту, и проверка путей
|
|
75
|
+
этого не видит: она читает файлы репозитория, а не тела задач. Строка снимается тем же заходом.
|
|
76
|
+
|
|
77
|
+
## Судьба файла
|
|
78
|
+
|
|
79
|
+
- Осталось незадачное — файл живёт, и его вступление объявляет новый признак отбора.
|
|
80
|
+
- Не осталось ничего, а документ описывал состоявшуюся работу — уезжает в архив.
|
|
81
|
+
- Не осталось ничего, и это был список работ — удаляется.
|
|
82
|
+
|
|
83
|
+
Разбор целиком — одна задача и одна ветка: делится то, что придётся откатывать порознь, а здесь
|
|
84
|
+
откат общий. Правка кода, найденная по дороге, в эту ветку не идёт — иначе откат разбора унесёт
|
|
85
|
+
починку.
|
|
86
|
+
|
|
87
|
+
## Что чинится в дереве следом
|
|
88
|
+
|
|
89
|
+
Документ — не единственное место, обещающее, что работа живёт в нём. Снятое имя вычищается одним
|
|
90
|
+
проходом по всему дереву, включая описания ролей, скилы, README и комментарии в коде.
|
|
91
|
+
|
|
92
|
+
## Частые промахи
|
|
93
|
+
|
|
94
|
+
- Обход по маркированным пунктам: половина работы лежит прозой и переживает разбор.
|
|
95
|
+
- «На это есть задача» без чтения её тела: пункт удалён, содержание потеряно.
|
|
96
|
+
- Число пунктов названо владельцу до сверки на выборке.
|
|
97
|
+
- Протухшее утверждение перенесено в задачу и стало действующим указанием.
|
|
98
|
+
- Доводы за отсрочку оставлены в документе: он снова становится вторым списком работ.
|
|
99
|
+
- Разбор поделён на несколько задач «по объёму» — признак деления не объём, а раздельный откат.
|
|
100
|
+
- Архив тронут: он описывает состояние на момент написания и под новые термины не правится.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doc-style-write
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: doc-style
|
|
5
|
+
description: Паттерн правила doc-style. Брать при написании любой прозы проекта — правил, спеков, README, комментариев в коде, тел коммитов, описаний PR. Примеры «так» и «не так» на каждую договорённость о формулировке. Не брать для устройства спека — это правило spec-driven.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Как формулировать
|
|
9
|
+
|
|
10
|
+
Паттерн правила `doc-style`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/project-documentation.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Пишется правило, статья закона, пункт спека или README.
|
|
16
|
+
- Пишется комментарий в коде, тело коммита, описание PR.
|
|
17
|
+
|
|
18
|
+
## Статья — одна фраза
|
|
19
|
+
|
|
20
|
+
Сама статья укладывается в одно предложение. Следом — не больше двух предложений о том, что
|
|
21
|
+
сломается иначе, и только если из самой фразы это не видно. Обоснование, у которого была
|
|
22
|
+
альтернатива, идёт в «Ловушки» правила, а не в закон: разросшийся пункт читают по диагонали, а
|
|
23
|
+
закон говорит, что верно, — не почему когда-то выбрали так.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
✗ **Взрослых в записи хотя бы один — на любом пути записи, без исключений.** Нижнюю
|
|
27
|
+
границу держит хранилище: путей записи три, и проверка в одном из них закрывает не все.
|
|
28
|
+
Верхнюю ограничением хранилища не выразить — она лежит в самой записи и меняется
|
|
29
|
+
владельцем.
|
|
30
|
+
|
|
31
|
+
✓ **Взрослых в записи хотя бы один.** Исключений нет.
|
|
32
|
+
✓ **Взрослые и дети не превышают вместимость.**
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Что верно, а не где это держится
|
|
36
|
+
|
|
37
|
+
«Держит хранилище, а не служба», «проверяется в транзакции», «умолчание колонки», «на трёх
|
|
38
|
+
путях записи из четырёх», имена функций и колонок внутри фразы — это устройство кода. Читателю
|
|
39
|
+
нужно знать, **что** верно.
|
|
40
|
+
|
|
41
|
+
Место исполнения меняется при первом же переносе, и документ, который его называет, устаревает
|
|
42
|
+
молча. Для него заведён отдельный файл — `implementation.md` рядом с правилом.
|
|
43
|
+
|
|
44
|
+
Исключение — «Ловушки» правила: там устройство кода называется прямо, потому что ловушка и есть
|
|
45
|
+
место, где на него наступают.
|
|
46
|
+
|
|
47
|
+
## Без утверждений о будущем
|
|
48
|
+
|
|
49
|
+
«Не планируется», «не будет», «отдельная задача по запросу» — это намерение владельца, а не
|
|
50
|
+
свойство системы. Что не сделано — да, почему не сделано — да, что не будет сделано никогда —
|
|
51
|
+
нет.
|
|
52
|
+
|
|
53
|
+
Ошибка беззвучная: сверять утверждение о будущем не с чем, оно проходит любую проверку.
|
|
54
|
+
|
|
55
|
+
Отсюда же: того, чего нет в документе о продукте, пересказ не утверждает. Домысел при пересказе
|
|
56
|
+
звучит убедительнее исходника — он короче и категоричнее.
|
|
57
|
+
|
|
58
|
+
## Простыми словами
|
|
59
|
+
|
|
60
|
+
Афоризмы, метафоры и инверсии затрудняют чтение и ничего не добавляют.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
✗ запись о прошлом не должна упираться в правило, появившееся позже
|
|
64
|
+
✗ пустой список без текста и отказ без кнопки выглядят одинаково — как сломанная страница
|
|
65
|
+
|
|
66
|
+
✓ запись, внесённую владельцем, пропажа события из ленты не снимает
|
|
67
|
+
✓ у пустого списка должен быть текст, у отказа — кнопка повтора
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Проверка: прочитать фразу вслух. Если так не говорят — переписать.
|
|
71
|
+
|
|
72
|
+
## Факт проверяется, а не вспоминается
|
|
73
|
+
|
|
74
|
+
Перед тем как написать, что код делает то-то, — открыть код и посмотреть. Пересказ по памяти
|
|
75
|
+
выглядит так же уверенно, как проверенное утверждение, и отличить их потом нечем.
|
|
76
|
+
|
|
77
|
+
Особенно это касается отказов: недостижимый отказ, названный в документе, живёт там годами —
|
|
78
|
+
проверить его читателю нечем.
|
|
79
|
+
|
|
80
|
+
## Не пересказывать то, у чего есть источник
|
|
81
|
+
|
|
82
|
+
- типы и поля контракта — ссылкой на сам контракт;
|
|
83
|
+
- колонки и индексы — ссылкой на схему хранилища;
|
|
84
|
+
- числа, которые правит владелец, — только как они применяются;
|
|
85
|
+
- слои и имена классов — правило `lib-layers` и сам код.
|
|
86
|
+
|
|
87
|
+
## Комментарии в коде
|
|
88
|
+
|
|
89
|
+
Те же правила. Комментарий отвечает на вопрос «почему так, а не иначе» — если ответ неочевиден.
|
|
90
|
+
Что делает строка, видно из строки.
|
|
91
|
+
|
|
92
|
+
Многострочный комментарий над каждой функцией — признак того, что объяснение подменило имя.
|
|
93
|
+
Сначала переименовать, потом писать комментарий.
|
|
94
|
+
|
|
95
|
+
Комментарий, оправдывающий отклонение от правила, держит это отклонение на себе: пока
|
|
96
|
+
объяснение выглядит убедительно, отклонение не трогают. Факт в нём проверяется, когда
|
|
97
|
+
комментарий пишут, и перепроверяется, когда отклонение снимают.
|
|
98
|
+
|
|
99
|
+
## Частые промахи
|
|
100
|
+
|
|
101
|
+
- Чужой проект назван в коммите, PR, комментарии или документе. Ни имени репозитория, ни
|
|
102
|
+
«портировано из», ни ссылок на его файлы — нигде.
|
|
103
|
+
- Число написано по памяти, а не пересчитано командой в том же коммите.
|
|
104
|
+
- Пункт плана вычеркнут по памяти о работе, а не по дереву.
|
|
105
|
+
- Формулировка звучит как заголовок, а не как статья: «работа со скидками» нарушить нельзя,
|
|
106
|
+
«применяется одна максимальная скидка» — можно.
|