@rt-tools/agent-kit 0.1.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 +85 -0
- package/assets/laws/access.md +46 -0
- package/assets/laws/admin-lists.md +47 -0
- package/assets/laws/admin-navigation.md +50 -0
- package/assets/laws/code-structure.md +32 -0
- package/assets/laws/delivery.md +34 -0
- package/assets/laws/entity-editing.md +73 -0
- package/assets/laws/entity-models.md +34 -0
- package/assets/laws/frontend-application.md +34 -0
- package/assets/laws/lib-imports.md +31 -0
- package/assets/laws/locales.md +44 -0
- package/assets/laws/project-documentation.md +39 -0
- package/assets/laws/reuse-first.md +35 -0
- package/assets/laws/search-visibility.md +37 -0
- package/assets/laws/shared-code.md +32 -0
- package/assets/laws/verifiability.md +33 -0
- package/assets/templates/pattern.md +29 -0
- package/assets/templates/rule.md +43 -0
- package/bin/agent-kit.d.ts +4 -0
- package/bin/agent-kit.d.ts.map +1 -0
- package/bin/agent-kit.js +56 -0
- package/bin/agent-kit.js.map +1 -0
- package/index.d.ts +9 -0
- package/index.d.ts.map +1 -0
- package/index.js +9 -0
- package/index.js.map +1 -0
- package/lib/assets.d.ts +11 -0
- package/lib/assets.d.ts.map +1 -0
- package/lib/assets.js +35 -0
- package/lib/assets.js.map +1 -0
- package/lib/commands.d.ts +14 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +100 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +25 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +85 -0
- package/lib/config.js.map +1 -0
- package/lib/package-root.d.ts +2 -0
- package/lib/package-root.d.ts.map +1 -0
- package/lib/package-root.js +27 -0
- package/lib/package-root.js.map +1 -0
- package/lib/plan.d.ts +33 -0
- package/lib/plan.d.ts.map +1 -0
- package/lib/plan.js +36 -0
- package/lib/plan.js.map +1 -0
- package/lib/sections.d.ts +38 -0
- package/lib/sections.d.ts.map +1 -0
- package/lib/sections.js +93 -0
- package/lib/sections.js.map +1 -0
- package/lib/stamp.d.ts +22 -0
- package/lib/stamp.d.ts.map +1 -0
- package/lib/stamp.js +57 -0
- package/lib/stamp.js.map +1 -0
- package/lib/sync.d.ts +13 -0
- package/lib/sync.d.ts.map +1 -0
- package/lib/sync.js +59 -0
- package/lib/sync.js.map +1 -0
- package/lib/vars.d.ts +20 -0
- package/lib/vars.d.ts.map +1 -0
- package/lib/vars.js +38 -0
- package/lib/vars.js.map +1 -0
- package/package.json +42 -0
- package/rt-tools-agent-kit-0.1.0.tgz +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @rt-tools/agent-kit
|
|
2
|
+
|
|
3
|
+
Переносимый слой правил для агента: законы, шаблоны правил и паттернов раскладываются в
|
|
4
|
+
репозиторий одной командой и обновляются следующей, не затирая того, что проект дописал сам.
|
|
5
|
+
|
|
6
|
+
## Зачем
|
|
7
|
+
|
|
8
|
+
Правила, по которым агент правит код, копятся в каждом репозитории заново. Скопированные из
|
|
9
|
+
соседнего они устаревают молча: правку в исходнике никто не переносит, а расхождение видно
|
|
10
|
+
только по последствиям.
|
|
11
|
+
|
|
12
|
+
Пакет разделяет текст на два слоя. **Закон** — что должно быть верно; он не знает ни путей, ни
|
|
13
|
+
имён файлов и потому переносится целиком. **Правило** — чем это названо в конкретном дереве;
|
|
14
|
+
оно остаётся в проекте, потому что только там имеет смысл.
|
|
15
|
+
|
|
16
|
+
Везёт пакет законы. Правила, паттерны и привязки к коду пишет проект.
|
|
17
|
+
|
|
18
|
+
## Как пользоваться
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add -D @rt-tools/agent-kit
|
|
22
|
+
|
|
23
|
+
npx agent-kit init # завести .claude/rt-kit.json и каталог надстроек
|
|
24
|
+
npx agent-kit sync # разложить законы в docs/constitution/
|
|
25
|
+
npx agent-kit doctor # что разложено, что отстало, чего не хватает
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
В гейте пуша:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx agent-kit sync --check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Команда ничего не пишет и отказывает, если разложенное отстало от пакета или его правили
|
|
35
|
+
руками.
|
|
36
|
+
|
|
37
|
+
## Конфиг
|
|
38
|
+
|
|
39
|
+
`.claude/rt-kit.json` коммитится: раскладка обязана повторяться на чужой машине без вопросов.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"vars": { "mainBranch": "main" },
|
|
44
|
+
"layout": { "laws": "docs/constitution" },
|
|
45
|
+
"skip": ["laws/admin-lists.md"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **`vars`** — значения дырок `{{имя}}` в тексте пакета. Пути раскладки подставляются сами:
|
|
50
|
+
`{{lawsDir}}`, `{{hooksDir}}`, `{{checksDir}}`, `{{agentsDir}}`, `{{templatesDir}}`.
|
|
51
|
+
Неподставленная дырка — отказ раскладки, а не строка в разложенном тексте: `{{mainBranch}}`
|
|
52
|
+
посреди правила агент прочтёт как имя.
|
|
53
|
+
- **`layout`** — куда класть каждый род ресурса. Умолчания менять без нужды не стоит: правила
|
|
54
|
+
ссылаются на законы теми же путями.
|
|
55
|
+
- **`skip`** — ресурсы, от которых проект отказался, идентификаторами вида `laws/<закон>.md`.
|
|
56
|
+
|
|
57
|
+
## Надстройки
|
|
58
|
+
|
|
59
|
+
Проект дописывает своё не в разложенный файл, а рядом — в
|
|
60
|
+
`.claude/rt-kit/overrides/<идентификатор ресурса>`. Правка разложенного файла теряется на
|
|
61
|
+
следующем `sync`, и пакет на неё отказывает, вместо того чтобы переписать молча.
|
|
62
|
+
|
|
63
|
+
Слияние идёт по разделам `## `:
|
|
64
|
+
|
|
65
|
+
| В надстройке | Что происходит |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| заголовок, который есть у пакета | раздел замещается целиком |
|
|
68
|
+
| заголовок, которого у пакета нет | раздел дописывается в конец |
|
|
69
|
+
| заголовок без тела | раздел пакета снимается |
|
|
70
|
+
|
|
71
|
+
Так правка пакета доезжает во все разделы, которых проект не трогал, а проектная история
|
|
72
|
+
решений остаётся при проекте.
|
|
73
|
+
|
|
74
|
+
## Что разложено
|
|
75
|
+
|
|
76
|
+
Каждый разложенный файл несёт шапку с версией пакета и суммой тела. По ней `sync` отличает три
|
|
77
|
+
случая: файл отстал от пакета, файл правили руками, файл положен вообще не пакетом. Первый
|
|
78
|
+
перекладывается, на двух других раскладка отказывает и называет их поимённо.
|
|
79
|
+
|
|
80
|
+
Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого.
|
|
81
|
+
|
|
82
|
+
## Чего в пакете пока нет
|
|
83
|
+
|
|
84
|
+
Хуков, проверок, агентов и пресетов карты гейта он не везёт — только законы и шаблоны правила и
|
|
85
|
+
паттерна. Генератора черновиков правил по закону тоже нет.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Доступ
|
|
2
|
+
|
|
3
|
+
Кто что может делать: вход владельца, права на действия, поведение публичных путей.
|
|
4
|
+
Правила держат все процедуры контракта и все разделы админки.
|
|
5
|
+
|
|
6
|
+
**Ревизия:** 2026-08-02
|
|
7
|
+
|
|
8
|
+
## Терминология
|
|
9
|
+
|
|
10
|
+
| Термин | Определение |
|
|
11
|
+
| -------------- | ------------------------------------------------------------------------------------------- |
|
|
12
|
+
| Право | Пара «ресурс и действие», например `bookings:manage`. Не роль: роль набирается из прав |
|
|
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
|
+
- **Q-A-1.** Что видит владелец, у которого право отобрали посреди сессии? Токен живёт со
|
|
41
|
+
своими правами до истечения, и раздел останется доступным до перевхода. Заведён
|
|
42
|
+
2026-07-31.
|
|
43
|
+
- **Q-A-2.** Как владелец меняет свой пароль? Сегодня пароль задаётся при заведении учётной
|
|
44
|
+
записи и из интерфейса не меняется: ни экрана смены, ни процедуры, ни восстановления
|
|
45
|
+
забытого. Решение потребует процедуры смены и правила о подтверждении прежним паролем.
|
|
46
|
+
Заведён 2026-07-31, уточнён 2026-08-01 по коду домена `auth`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Список записей в админке
|
|
2
|
+
|
|
3
|
+
Как владелец видит набор записей и что с ними делает, не открывая ни одну: заявки, объекты,
|
|
4
|
+
адреса страниц, промокоды, подписки на календарь. Правила общие для всех разделов — список,
|
|
5
|
+
устроенный иначе остальных, владелец читает заново.
|
|
6
|
+
|
|
7
|
+
Открытая запись, попадание в раздел и объём данных на строку — предмет других законов.
|
|
8
|
+
|
|
9
|
+
**Ревизия:** 2026-08-02
|
|
10
|
+
|
|
11
|
+
## Статьи
|
|
12
|
+
|
|
13
|
+
- **Записи показаны таблицей: строка — запись, столбец — её свойство.**
|
|
14
|
+
- **Клик по строке открывает запись.** Меню строки — для действий над ней, а не для входа.
|
|
15
|
+
- **Пока список читается, на месте строк — скелетоны.** Пустая область неотличима от списка,
|
|
16
|
+
в котором ничего нет.
|
|
17
|
+
- **Пустой список объясняет, почему он пуст.**
|
|
18
|
+
- **На узком экране строка становится карточкой.**
|
|
19
|
+
- **Состав и порядок столбцов задаёт владелец, и выбор сохраняется между заходами.**
|
|
20
|
+
- **Слева в тулбаре — то, что меняет выборку, справа — действия над списком.**
|
|
21
|
+
- **Действие, недоступное записи, в её меню не показано.**
|
|
22
|
+
- **У записи, которой нельзя сделать ничего, кнопки меню нет.**
|
|
23
|
+
- **Действие, которое нельзя отменить, спрашивает подтверждение и называет последствие.**
|
|
24
|
+
- **Отказ загрузки подаётся тостом, а повторить владелец может кнопкой обновления.**
|
|
25
|
+
- **Список показывает тот порядок и тот отбор, которые применены к записям.** Порядок по
|
|
26
|
+
умолчанию виден стрелкой сразу, а условие, которого сервер не принял, не остаётся в тулбаре
|
|
27
|
+
как действующее.
|
|
28
|
+
- **Заголовки столбцов, тексты состояний и подписи действий приходят из словарей.**
|
|
29
|
+
|
|
30
|
+
## Открытые вопросы
|
|
31
|
+
|
|
32
|
+
- **Q-L-4 — вид списка уходит в адрес у трёх списков.** Промокоды, прежние адреса объекта и
|
|
33
|
+
подписки на календари читают из адреса номер страницы, порядок и строку поиска; у остальных
|
|
34
|
+
списков выборка живёт в состоянии экрана: перезагрузка возвращает список к умолчанию, а
|
|
35
|
+
ссылку на отфильтрованный список не передать. Решено класть в адрес весь вид списка — фильтры, сортировку и номер страницы, — чтобы ссылка открывала ту же
|
|
36
|
+
страницу с тем же порядком, а возврат из панели ничего не сбрасывал. Размер страницы и состав
|
|
37
|
+
столбцов остаются за владельцем и в адресе не участвуют. Заведён 2026-08-01.
|
|
38
|
+
- **Q-L-5 — страницу отдают три процедуры.** Промокоды, прежние адреса объекта и подписки на
|
|
39
|
+
календари принимают выборку и отвечают страницей; остальные списки приходят одним ответом
|
|
40
|
+
целиком, и заявки за несколько сезонов по нескольким объектам придут им же. Решено, что
|
|
41
|
+
список запрашивается страницей: номер и размер страницы, сортировка по одному полю
|
|
42
|
+
и несколько фильтров сразу. Форма запроса одна на все списки и объявлена в общей части
|
|
43
|
+
контракта, а не переписывается в каждом домене. Ответ несёт общее число записей — без него не
|
|
44
|
+
показать, сколько страниц. Заведён 2026-08-01.
|
|
45
|
+
- **Q-L-7 — выпадающие списки грузятся целиком.** Выбор объекта, гостя или тега приходит одним
|
|
46
|
+
ответом. Решено брать их той же страницей, что и таблицу, но догружать следующую при
|
|
47
|
+
прокрутке: страниц в таком списке не видно, значит и переключать их нечем. Заведён 2026-08-01.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Навигация админки
|
|
2
|
+
|
|
3
|
+
Как владелец находит раздел и попадает в него. Правила держат все разделы админки сразу —
|
|
4
|
+
заявки, объекты, календарь, переписку, аналитику, цены, организацию, — и разойтись им нельзя:
|
|
5
|
+
раздел, который ведёт себя иначе остальных, выглядит сломанным.
|
|
6
|
+
|
|
7
|
+
**Ревизия:** 2026-08-02
|
|
8
|
+
|
|
9
|
+
## Статьи
|
|
10
|
+
|
|
11
|
+
- **Пункт меню ведёт на свой экран.** Не на вкладку внутри чужого экрана и не на панель: адрес
|
|
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
|
+
- **Q-N-5.** Хлебных крошек нет: на вложенном экране не видно, из какого он раздела и как вернуться
|
|
42
|
+
на уровень выше. Заведён 2026-08-01.
|
|
43
|
+
- **Q-N-6 — маркер непросмотренного не загорается ни у одного пункта.** Точку кит рисует, и у
|
|
44
|
+
раздела с панелью она сводится с его пунктов, но признак не поднимает никто: отметки «владелец
|
|
45
|
+
видел до сюда» нет ни в контракте, ни в базе. Решение заведёт отметку на пару владелец-раздел,
|
|
46
|
+
научит разделы считать записи, появившиеся после последнего захода, и гасить точку при
|
|
47
|
+
открытии. Заведён 2026-08-01, переписан 2026-08-02.
|
|
48
|
+
- **Q-N-8 — «Финансы» показывают одну подсказку вместо счетов и налогов.** Раздел стоит в ряду
|
|
49
|
+
недоступным: спеков на счета организации и расчёт налогов нет, и показывать в нём нечего.
|
|
50
|
+
Решение опишет раздел спеком и заведёт его экраны. Заведён 2026-08-02.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Устройство кода
|
|
2
|
+
|
|
3
|
+
Что должно быть верно про сам код независимо от того, что он делает. Закон нужен потому, что
|
|
4
|
+
код читают чаще, чем пишут: имя, по которому не видно рода объявления, и тип, объявленный
|
|
5
|
+
подходящим вместо проверенного, стоят читателю отдельного похода в соседний файл.
|
|
6
|
+
|
|
7
|
+
**Ревизия:** 2026-08-05
|
|
8
|
+
|
|
9
|
+
## Статьи
|
|
10
|
+
|
|
11
|
+
- **Имя объявления говорит, какого оно рода.** Без этого род объявления выясняется переходом
|
|
12
|
+
к нему, и на каждом чтении заново.
|
|
13
|
+
- **Источник значения, за которым следят, виден по его имени.** Иначе подписка на него
|
|
14
|
+
выглядит как обычное чтение, и её забывают снять.
|
|
15
|
+
- **Тип берётся из того пакета, где объявлен.** Своя копия чужого типа расходится с
|
|
16
|
+
оригиналом молча, а компилируется из них только одна.
|
|
17
|
+
- **Отметка об устаревании — повод убрать, а не повод оставить.** Устаревшее объявление,
|
|
18
|
+
которое молча продолжает работать, переживает того, кто его пометил.
|
|
19
|
+
|
|
20
|
+
## Открытые вопросы
|
|
21
|
+
|
|
22
|
+
- **Q-CS-1 — приведение значения к типу не проверяет ничто.** Объявить значение подходящим
|
|
23
|
+
вместо того, чтобы проверить, можно в любом месте: приведение принимает любую строку и
|
|
24
|
+
компилируется. Решение изменит, появится ли на это проверка. Заведён 2026-08-05.
|
|
25
|
+
- **Q-CS-2 — имя файла не связано с тем, что в нём лежит.** Соответствие держится
|
|
26
|
+
договорённостью, и файл под чужим именем проходит все проверки. Решение изменит, появится
|
|
27
|
+
ли на это проверка. Заведён 2026-08-05.
|
|
28
|
+
|
|
29
|
+
## История изменений
|
|
30
|
+
|
|
31
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правила `typescript-conventions` — в нём
|
|
32
|
+
осталось то, из чего объявление собирается.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Поставка
|
|
2
|
+
|
|
3
|
+
Как правка доезжает до работающего приложения. Закон держит и историю изменений, и то, что
|
|
4
|
+
в этот момент видит пользователь: неудачная выкатка отличается от удачной только тем, что
|
|
5
|
+
приложение перестаёт отвечать, а причина этого выясняется по истории.
|
|
6
|
+
|
|
7
|
+
**Ревизия:** 2026-08-05
|
|
8
|
+
|
|
9
|
+
## Статьи
|
|
10
|
+
|
|
11
|
+
- **Правка попадает в главную ветку только через отдельную ветку.** Прямая запись в главную
|
|
12
|
+
лишает правку и обсуждения, и возможности откатить её одним движением.
|
|
13
|
+
- **Попадание правки в главную ветку означает выкатку.** Всё, от чего правка зависит снаружи
|
|
14
|
+
кода — переменные окружения, секреты, записи имён, — ставится до этого момента, а не после.
|
|
15
|
+
- **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
|
|
16
|
+
главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
|
|
17
|
+
- **Порядок изменений хранилища проверяется с пустого места.** На уже работающем хранилище
|
|
18
|
+
неверный порядок незаметен: он проявляется только при развёртывании с нуля.
|
|
19
|
+
- **Документ едет вместе с правкой, которую он описывает.** Ни сборка, ни проверки текстов
|
|
20
|
+
не читают, поэтому расхождение копится молча и потом выглядит действующей справкой.
|
|
21
|
+
|
|
22
|
+
## Открытые вопросы
|
|
23
|
+
|
|
24
|
+
- **Q-D-1 — что происходит с выкаткой, когда правка задела несколько задач.** Сегодня одна
|
|
25
|
+
ветка закрывает одну задачу, и вторая идёт отдельной строкой связи. Решение изменит, можно
|
|
26
|
+
ли откатить одну задачу, не откатывая соседнюю. Заведён 2026-08-05.
|
|
27
|
+
- **Q-D-2 — связь ветки с задачей не проверяет ничто.** Имя ветки складывается по
|
|
28
|
+
договорённости, и ветка без номера задачи проходит все гарды. Решение изменит, появится ли
|
|
29
|
+
на это проверка или связь остаётся за автором. Заведён 2026-08-05.
|
|
30
|
+
|
|
31
|
+
## История изменений
|
|
32
|
+
|
|
33
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правила `git-workflow` — в нём осталось
|
|
34
|
+
то, из чего поставка собирается.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Правка сущности
|
|
2
|
+
|
|
3
|
+
Как ведёт себя приложение, когда владелец создаёт или меняет запись: объект, бронь,
|
|
4
|
+
подписку на календарь, адрес страницы. Поведение одинаково во всех разделах админки и ни
|
|
5
|
+
одному домену не принадлежит — спек домена описывает только то, что у него своего.
|
|
6
|
+
|
|
7
|
+
Попадание в запись из набора и объём запрашиваемых данных — предмет других законов.
|
|
8
|
+
|
|
9
|
+
**Ревизия:** 2026-08-02
|
|
10
|
+
|
|
11
|
+
## Статьи
|
|
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
|
+
клавишей Esc.**
|
|
47
|
+
- **Закрытая панель нажатий не перехватывает.** Она стоит за краем экрана, ничего не видно, и
|
|
48
|
+
страница под ней перестала бы отвечать на нажатия молча.
|
|
49
|
+
- **Прокручивается только содержимое: шапка и футер видны всегда.**
|
|
50
|
+
- **Поля во всех панелях отбиты от края одинаково и получают одну ширину.** Прокручивается
|
|
51
|
+
при этом всё содержимое целиком, поэтому обводка фокуса у крайнего поля видна полностью.
|
|
52
|
+
- **Пока запись читается, поля показаны скелетонами.**
|
|
53
|
+
- **Запись, которую можно только смотреть, показана данными, а не полями ввода.** Поле ввода,
|
|
54
|
+
которое не принимает ввод, владелец сначала пробует заполнить.
|
|
55
|
+
- **Непонятное объясняется на месте: подсказкой под полем или сообщением над формой.**
|
|
56
|
+
|
|
57
|
+
## Открытые вопросы
|
|
58
|
+
|
|
59
|
+
- **Q-E-4 — дополнительных действий в шапке нет ни у одной панели.** Место для них есть, но
|
|
60
|
+
доменные действия живут только в меню строки списка, поэтому из открытой записи их не
|
|
61
|
+
выполнить. Решено ставить их иконками справа в шапке и прятать под одну кнопку меню, когда их
|
|
62
|
+
больше двух. Заведён 2026-08-01.
|
|
63
|
+
- **Q-E-5 — вкладок в панелях нет.** Разделы идут подряд одной полосой, и до нижнего нужно
|
|
64
|
+
прокручивать всю форму. Решено раскладывать разделы по вкладкам, оставлять вкладки на месте
|
|
65
|
+
при прокрутке и не заводить действий, относящихся к содержимому одной вкладки: с закрытой
|
|
66
|
+
вкладки такое действие не видно, а на открытой спорит с общей кнопкой записи. Заведён
|
|
67
|
+
2026-08-01.
|
|
68
|
+
- **Q-E-8 — ненайденная запись обрабатывается двумя способами.** Панель промокода показывает
|
|
69
|
+
«код не найден» и остаётся открытой, остальные уходят с адреса. Заведён 2026-08-01.
|
|
70
|
+
- **Q-E-12 — правки формы документа пропадают при закрытии молча.** Карточка заявки собирает
|
|
71
|
+
адресата и состав гостей, но записывает их только выдачей документа, поэтому вопрос о
|
|
72
|
+
несохранённых правках ей задать нечем: исход «закрыть с сохранением» отправил бы гостю
|
|
73
|
+
письмо. Заведён 2026-08-02.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Модели сущностей
|
|
2
|
+
|
|
3
|
+
Сколько данных приложение запрашивает и отдаёт на каждом экране: строка списка, открытая
|
|
4
|
+
запись, пункт выпадающего списка. Одна и та же сущность выглядит на них по-разному, и разница
|
|
5
|
+
эта не украшение — от неё зависит, сколько владелец ждёт список.
|
|
6
|
+
|
|
7
|
+
Как список показывает записи и как ведёт себя панель правки — предмет других законов.
|
|
8
|
+
|
|
9
|
+
**Ревизия:** 2026-08-01
|
|
10
|
+
|
|
11
|
+
## Статьи
|
|
12
|
+
|
|
13
|
+
- **У сущности две стороны: то, что пришло из контракта, и то, чем пользуется экран.** Первая
|
|
14
|
+
повторяет контракт дословно, вторая нормализована и от смены контракта не зависит.
|
|
15
|
+
- **Сторона контракта руками не пишется.** Она объявляется псевдонимом сгенерированного типа:
|
|
16
|
+
переписанное вручную расходится с контрактом молча.
|
|
17
|
+
- **Между сторонами стоит перевод, и экраны читают только вторую.**
|
|
18
|
+
- **Пустое значение выражается пустой строкой или нулём, а не отсутствием поля.** Необязательных
|
|
19
|
+
скаляров в контракте нет, и «не задано» у каждого поля значит своё.
|
|
20
|
+
|
|
21
|
+
## Открытые вопросы
|
|
22
|
+
|
|
23
|
+
- **Q-M-1 — уровней у моделей нет.** У сущности одна модель на все случаи: строка списка,
|
|
24
|
+
открытая запись и пункт выпадающего списка получают одинаковый набор полей. Решено завести
|
|
25
|
+
уровни, где каждый следующий расширяет предыдущий, а не повторяет его поля: короткий — для
|
|
26
|
+
строки списка, выпадающего списка и подсказок ввода, полный — для открытой записи. Заведён
|
|
27
|
+
2026-08-01.
|
|
28
|
+
- **Q-M-2 — контракт короткой модели не различает.** Одно сообщение отдаётся и списком, и
|
|
29
|
+
поштучно, поэтому список тянет поля, которые в нём не показаны: фотографии, длинные тексты и
|
|
30
|
+
вложенные наборы. Решено разделить контракт на короткое сообщение списка и полное сообщение
|
|
31
|
+
записи. Заведён 2026-08-01.
|
|
32
|
+
- **Q-M-3 — открытая запись берётся из уже загруженного списка.** Своего чтения по
|
|
33
|
+
идентификатору нет: панель, открытая по ссылке, ждёт, пока подгрузится весь список. Заведён
|
|
34
|
+
2026-08-01.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Фронтовое приложение
|
|
2
|
+
|
|
3
|
+
Как устроено то, что рисует экран. Закон держит и сайт, и админку сразу: экран, устроенный
|
|
4
|
+
иначе соседнего, пользователь читает заново, а разработчик правит вслепую.
|
|
5
|
+
|
|
6
|
+
**Ревизия:** 2026-08-05
|
|
7
|
+
|
|
8
|
+
## Статьи
|
|
9
|
+
|
|
10
|
+
- **Состояние экрана пересчитывается само, а не по команде.** Пересчёт вручную рано или
|
|
11
|
+
поздно пропускают, и экран показывает прежнее значение рядом с новым.
|
|
12
|
+
- **Шаблон показывает готовое, а не вычисляет.** Вычисление в шаблоне повторяется на каждой
|
|
13
|
+
перерисовке, и цена его не видна ни в одном месте кода.
|
|
14
|
+
- **Подписка живёт не дольше того, кто её завёл.** Пережившая владельца подписка продолжает
|
|
15
|
+
работать на уничтоженном экране и держит его в памяти.
|
|
16
|
+
- **Оформление берётся из общего набора значений, а не пишется числом на месте.** Написанное
|
|
17
|
+
числом расходится с остальным при первой же смене оформления, и найти все такие места
|
|
18
|
+
нечем.
|
|
19
|
+
- **У каждого класса в разметке есть своё правило стилей.** Класс без правила выглядит
|
|
20
|
+
рабочим и молча ничего не делает.
|
|
21
|
+
- **Набор записей приходит с сервера страницей, а не целиком.** Список, растущий вместе с
|
|
22
|
+
данными, однажды перестаёт открываться, и заметно это становится уже у пользователя.
|
|
23
|
+
|
|
24
|
+
## Открытые вопросы
|
|
25
|
+
|
|
26
|
+
- **Q-FA-1 — обращение к окружению браузера не проверяет ничто.** Прямое обращение к
|
|
27
|
+
глобальному объекту компилируется и падает только при отдаче страницы сервером. Решение
|
|
28
|
+
изменит, появится ли на это проверка. Заведён 2026-08-05.
|
|
29
|
+
|
|
30
|
+
## История изменений
|
|
31
|
+
|
|
32
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правил `angular-patterns`,
|
|
33
|
+
`component-structure`, `styling-bem`, `platform-access` и `api-layer` — в них осталось то,
|
|
34
|
+
из чего экран собирается.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Импорты между либами
|
|
2
|
+
|
|
3
|
+
Кто кого видит. Приложение нарезано на либы, и у каждой объявлен список тех, от кого ей
|
|
4
|
+
разрешено зависеть: так домен, которому незачем знать про чужой транспорт или чужие экраны,
|
|
5
|
+
и не получает к ним доступа. Список этот работает, только пока символ приходит оттуда, где
|
|
6
|
+
объявлен.
|
|
7
|
+
|
|
8
|
+
Закон отвечает на вопрос, откуда берётся импорт. Раскладка самих либ и слоёв — не его
|
|
9
|
+
предмет.
|
|
10
|
+
|
|
11
|
+
**Ревизия:** 2026-08-02
|
|
12
|
+
|
|
13
|
+
## Статьи
|
|
14
|
+
|
|
15
|
+
- **Либа отдаёт наружу только то, что объявила сама.** Чужой символ через неё не проходит:
|
|
16
|
+
потребитель импортирует его у той либы, где символ объявлен.
|
|
17
|
+
- **Право видеть либу даётся списком её тега, а не соседом.** Не хватает права — оно
|
|
18
|
+
дописывается строкой с объяснением, зачем оно понадобилось.
|
|
19
|
+
- **У либы, общей всем трём приложениям, список зависимостей пуст.** Её видит и бэкенд, и
|
|
20
|
+
любая её зависимость стала бы общей для всех сразу.
|
|
21
|
+
|
|
22
|
+
## Открытые вопросы
|
|
23
|
+
|
|
24
|
+
- **Q-I-2 — куда положен общий символ, не проверяет ничто.** `check:dupes` ловит копию по
|
|
25
|
+
совпавшему имени или набору членов, а та же логика, написанная заново под другим именем,
|
|
26
|
+
проходит. Решение изменит, появится ли на это проверка или выбор места остаётся за ревью.
|
|
27
|
+
Заведён 2026-08-02.
|
|
28
|
+
- **Q-I-3 — доменом ли заведено то, у чего нет ни экранов, ни состояния, ни запросов, не
|
|
29
|
+
проверяет ничто.** Проверка раскладки требует полного набора слоёв у всех, поэтому пустой
|
|
30
|
+
слой механики выглядит так же, как слот домена, который заполнит следующая задача. Решение
|
|
31
|
+
изменит, станет ли признак «непуст один только `util`» отказом проверки. Заведён 2026-08-02.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Локали и переводы
|
|
2
|
+
|
|
3
|
+
Как в системе устроены языки: адреса публичного сайта, подписи интерфейса, переводы
|
|
4
|
+
контента и язык админки. Правила держат сайт, админку, письма и документ-подтверждение
|
|
5
|
+
одновременно.
|
|
6
|
+
|
|
7
|
+
**Ревизия:** 2026-07-31
|
|
8
|
+
|
|
9
|
+
## Терминология
|
|
10
|
+
|
|
11
|
+
| Термин | Определение |
|
|
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
|
+
- **Q-L-1.** Что показывать гостю, чей перевод объекта устарел после правки владельца, но
|
|
40
|
+
ещё не перезаписан из-за отказа провайдера? Сейчас видно прежний текст, признака
|
|
41
|
+
устаревания у страницы нет. Заведён 2026-07-31.
|
|
42
|
+
- **Q-L-2.** Как выбирается локаль гостю, пришедшему на корень сайта: сегодня это адрес, а
|
|
43
|
+
не язык браузера. Решение изменит поведение первой страницы и набор адресов, которые
|
|
44
|
+
попадают в кэш. Заведён 2026-07-31.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Документация проекта
|
|
2
|
+
|
|
3
|
+
Что должно быть верно про тексты, которые проект о себе пишет. Расхождение документа с
|
|
4
|
+
работающим приложением беззвучно: ни сборка, ни проверки текстов не читают, поэтому
|
|
5
|
+
устаревшее описание живёт дальше и выглядит действующей справкой — тем убедительнее, чем оно
|
|
6
|
+
старше.
|
|
7
|
+
|
|
8
|
+
**Ревизия:** 2026-08-05
|
|
9
|
+
|
|
10
|
+
## Статьи
|
|
11
|
+
|
|
12
|
+
- **У документа заранее задан набор разделов, и отсутствие раздела — отказ.** Требования,
|
|
13
|
+
для которых не заведено места, вспоминаются уже после выкатки.
|
|
14
|
+
- **Каждое утверждение документа привязано к месту, где оно исполняется.** Утверждение без
|
|
15
|
+
такого места — намерение, и называть его действующим нельзя.
|
|
16
|
+
- **Привязка не ведёт в код, который никто не зовёт.** Объявленный и никем не используемый
|
|
17
|
+
символ ничего не исполняет, а проверка на нём остаётся зелёной.
|
|
18
|
+
- **Путь, названный в документе, существует.** Ссылка на переехавший файл читается как
|
|
19
|
+
действующее указание, и следующий читатель заводит снятое заново.
|
|
20
|
+
- **Документ, разошедшийся с приложением, правится тогда же, когда замечено расхождение.**
|
|
21
|
+
Отложенная правка не случается: расхождение перестаёт быть заметным на следующий день.
|
|
22
|
+
|
|
23
|
+
## Открытые вопросы
|
|
24
|
+
|
|
25
|
+
- **Q-PD-1 — утверждение о будущем не проверяет ничто.** «Не планируется», «не будет» — это
|
|
26
|
+
намерение, а не свойство приложения, и сверить его не с чем: оно проходит любую проверку.
|
|
27
|
+
Решение изменит, появится ли на это проверка или это остаётся за читателем. Заведён
|
|
28
|
+
2026-08-05.
|
|
29
|
+
- **Q-PD-2 — число в тексте стареет молча.** Пересчёт числа командой в том же изменении
|
|
30
|
+
держится договорённостью; устаревшее число выглядит так же, как свежее. Решение изменит,
|
|
31
|
+
появится ли способ отличить одно от другого. Заведён 2026-08-05.
|
|
32
|
+
- **Q-PD-3 — начало работы с описания не проверяет ничто.** Порядок «сначала описание, потом
|
|
33
|
+
код» держится договорённостью, и обратный порядок виден только по истории. Решение
|
|
34
|
+
изменит, появится ли на это проверка. Заведён 2026-08-05.
|
|
35
|
+
|
|
36
|
+
## История изменений
|
|
37
|
+
|
|
38
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правил `spec-driven` и `doc-style` — в них
|
|
39
|
+
осталось то, из чего документ собирается и как он формулируется.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Единообразие приложения
|
|
2
|
+
|
|
3
|
+
Одинаковые вещи в сайте и в админке выглядят и ведут себя одинаково: поле ввода отвечает на
|
|
4
|
+
ошибку так же, как соседнее, панель правки закрывается так же, как в другом разделе, отказ
|
|
5
|
+
подаётся так же на любом экране. Держится это тем, что части собраны из общего кита и общих
|
|
6
|
+
основ, а не написаны заново каждая под себя.
|
|
7
|
+
|
|
8
|
+
Закон говорит о том, что верно для владельца и гостя. Как эти основы устроены в коде — не
|
|
9
|
+
его предмет.
|
|
10
|
+
|
|
11
|
+
**Ревизия:** 2026-08-04
|
|
12
|
+
|
|
13
|
+
## Статьи
|
|
14
|
+
|
|
15
|
+
- **Готовое из кита и общих основ берётся, а не пишется заново.** Своя копия расходится с
|
|
16
|
+
оригиналом с первой же правки, и одинаковые с виду места начинают вести себя по-разному.
|
|
17
|
+
- **Свой примитив и своя основа заводятся только с ведома владельца.** Отступление от общего
|
|
18
|
+
вида — решение о продукте, а не деталь реализации.
|
|
19
|
+
- **Поле ввода ведёт себя одинаково во всех формах.** Значение, недоступность, тронутость и
|
|
20
|
+
показ ошибки у всех полей одни.
|
|
21
|
+
- **Панель правки записи ведёт себя одинаково во всех разделах.** Открытие адресом, скелетоны
|
|
22
|
+
на чтении, спиннер на записи, вопрос о несохранённых правках и закрытие после успеха.
|
|
23
|
+
- **Сообщение об успехе и об отказе подаётся одинаково на всех экранах.** Разный вид у
|
|
24
|
+
одинаковых по смыслу сообщений читается как разные состояния приложения.
|
|
25
|
+
- **Стили компонента живут в его файле стилей.** Заданные в разметке или в декораторе, они
|
|
26
|
+
видны только на своём экране, и соседний остаётся с прежним видом.
|
|
27
|
+
- **Раскладка экранов, форм, панелей и окон одна на всё приложение.** Отступы страницы,
|
|
28
|
+
заголовок, полоса действий и зона содержимого у всех разделов одни, и меняются они разом.
|
|
29
|
+
|
|
30
|
+
## Открытые вопросы
|
|
31
|
+
|
|
32
|
+
- **Q-R-2 — расхождение вида на уже написанных экранах не измерено.** Гард отбивает новое, а
|
|
33
|
+
сколько мест сегодня показывают отказ и поля не так, как кит, известно только по одному
|
|
34
|
+
признаку — `role="alert"` вне кита. Решение изменит, заводится ли на это проверка по дереву
|
|
35
|
+
или разбор идёт экраном за экраном. Заведён 2026-08-03.
|