mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
121 lines
9.7 KiB
Markdown
121 lines
9.7 KiB
Markdown
# ТЗ #425 — Ядра фронтенда растут только осознанно
|
||
|
||
- Issue: https://github.com/Matysh/houseplan-card/issues/425
|
||
- Приоритет: P2, tech-debt; полный трек — не потому, что задача сложная, а
|
||
потому, что лёгкий трек в этом репозитории не обслуживается конвейером
|
||
(«метка small конвейер не запускает»), и ревью ТЗ на нём пришлось бы просить
|
||
у человека. Для гейта, который будет стоять в CI годами, дешевле пройти
|
||
обычную процедуру
|
||
- Ревизия: 1 (2026-09-03)
|
||
- Заменяет #34 ([HP-ARCH-01]), который закрывается вместе с этой задачей
|
||
|
||
## Сценарий
|
||
|
||
Продуктового сценария у задачи нет — она инженерная, и это сказано явно, а не
|
||
подразумевается. Разработчик добавляет функцию, кладёт её в ядро, потому что
|
||
там уже всё, и ядро прибавляет ещё пятьсот строк. Никто не против: каждый шаг
|
||
разумен, а счёт никто не ведёт. Через год 42% кода снова в двух файлах, и
|
||
следующая функция стоит дороже предыдущей.
|
||
|
||
## Что человек увидит до и после
|
||
|
||
Пользователь — ничего: продуктовый код задача не трогает.
|
||
|
||
**До**: ядра растут молча, и узнать об этом можно только из аудита, если кто-то
|
||
удосужится посчитать.
|
||
**После**: рост выше потолка роняет `npm test` и называет цену числом, а
|
||
уменьшение требует опустить потолок — то есть выигрыш фиксируется, а не
|
||
испаряется.
|
||
|
||
## Почему не проект, а ограничитель
|
||
|
||
#34 заведён 09.08, за 25 дней не сдвинулся и шестой аудит подряд отмечается как «без движения». При этом декомпозиция идёт — но не как проект, а попутно: файлов в `src/` было 18 на v1.58, сейчас больше сотни; `houseplan-editor-runtime.ts` выделен из ядра между v1.66 и v1.69; за последние недели отдельными модулями вышли `support-feedback.ts`, `command-stack.ts`, `device-area-relocation.ts`, `danger-confirm.ts`.
|
||
|
||
Целевая карта каталогов из ТЗ #34 (`app/`, `editors/`, `render/`, `dialogs/`) не реализована и, судя по темпу, не будет. Комментарий от 30.08 в самом #34 это уже признал: «исходная метрика и целевая карта устарели».
|
||
|
||
**Но долг никуда не делся**, и вот он в числах:
|
||
|
||
| | card | runtime |
|
||
|---|---|---|
|
||
| v1.66.0 | 20 287 | — |
|
||
| v1.69.0 | 12 824 | 13 290 ← разделение ядра |
|
||
| v1.70.0 | 13 603 | 14 287 |
|
||
| dev | 13658 | 14322 |
|
||
|
||
Два файла держат **42%** всего TypeScript в `src/`, и оба растут примерно на 500–1000 строк за релиз: новое уезжает в новые модули, а ядра всё равно прибавляют. Закрыть #34 молча — значит перестать это считать.
|
||
|
||
## Контракт
|
||
|
||
Размер двух ядер зафиксирован потолком, и потолок работает **храповиком**:
|
||
|
||
- ядро выросло выше потолка → отказ с числом: «выросло на N строк, вынесите столько же»;
|
||
- ядро уменьшилось заметно ниже потолка → тоже отказ: «опустите потолок на N». Без этой половины выигрыш от любого выноса испарится молча, и через год потолок станет фикцией.
|
||
|
||
Люфт между «уменьшилось» и «пора опускать потолок» — 250 строк, чтобы обычная правка не требовала трогать константу.
|
||
|
||
Правило не запрещает расти: оно требует называть цену. Хочешь добавить в ядро — вынеси столько же, и это видно в дифе одной строкой.
|
||
|
||
## AC
|
||
|
||
- **AC1**. Рост любого из двух ядер выше потолка роняет `npm test` с сообщением, называющим файл, потолок и превышение в строках. Доказательство: мутант, добавляющий строки в `houseplan-card.ts`, прогнанный штатным раннером.
|
||
- **AC2**. Уменьшение ядра больше чем на 250 строк ниже потолка тоже роняет тест — с требованием опустить потолок. Доказательство: юнит на чистой функции.
|
||
- **AC3**. Изменение в пределах люфта тест не трогает. Доказательство: тот же юнит.
|
||
- **AC4**. Потолки записаны числами в самом тесте: любое их изменение видно в
|
||
дифе и проходит ревью как решение, а не как побочный эффект. Доказательство:
|
||
ревью кода — в тесте нет чтения потолков из внешнего источника (файла,
|
||
переменной окружения, вычисления от текущего размера), и юнит на то, что
|
||
функция принимает потолки аргументом, а не добывает их сама.
|
||
- **AC5**. Гейт судит только два ядра. Остальные файлы `src/` не ограничиваются: цель — не заморозить размер кода, а сделать рост ядер осознанным.
|
||
|
||
## Скоуп / не-скоуп
|
||
|
||
**В скоупе**: один тест с потолками и храповиком, мутант, строка в
|
||
`docs/TESTING.md`, закрытие #34 с объяснением.
|
||
|
||
**Не в скоупе**: сама декомпозиция (она идёт попутно и продолжит идти), другие
|
||
файлы `src/**`, стилевой слой, размер бандла (у него свой бюджет — `#367`).
|
||
|
||
## UX, модель данных, i18n
|
||
|
||
Не применимо: продуктового кода задача не касается.
|
||
|
||
## План автотестов
|
||
|
||
Логика выносится в чистую функцию `coreBudgetViolations(sizes, caps, slack)` —
|
||
тест проверяет её на всех ветках без чтения файлов, и она же вызывается на
|
||
реальных ядрах.
|
||
|
||
1. рост выше потолка → нарушение с числом превышения (AC1);
|
||
2. падение ниже потолка больше чем на люфт → нарушение с требованием опустить
|
||
потолок (AC2);
|
||
3. изменение в пределах люфта → пусто (AC3);
|
||
4. ровно на потолке и ровно на границе люфта → пусто (границы включительно);
|
||
5. потолки заданы для обоих ядер и ни для чего больше (AC5).
|
||
|
||
**Мутант** (`scripts/mutation-gate.mjs`): `core-budget-ignores-growth` —
|
||
добавить в `src/houseplan-card.ts` заведомо больше строк, чем люфт, → тест
|
||
красный. Мутант правит ЯДРО, а не тест: проверяется, что гейт видит рост
|
||
настоящего файла, а не что арифметика сходится сама с собой.
|
||
|
||
## Риски
|
||
|
||
- **Потолок будут поднимать не глядя.** Это главный риск любого храповика.
|
||
Смягчение: число живёт в тесте, его изменение попадает в дифф и проходит
|
||
ревью как решение; сообщение об отказе прямо называет альтернативу — вынести
|
||
столько же строк.
|
||
- **Строки — грубая мера.** Файл можно «уменьшить», сжав форматирование.
|
||
Смягчение: мера и не претендует на точность, она нужна как сигнал; ревью
|
||
видит дифф и отличает вынос от переупаковки.
|
||
- **Люфт 250 строк может оказаться мал или велик.** Смягчение: он записан
|
||
константой рядом с потолками, меняется тем же порядком и с тем же ревью.
|
||
|
||
## Откат
|
||
|
||
Удаление одного тестового файла и одного мутанта.
|
||
|
||
## Release-артефакты
|
||
|
||
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md`: **не требуется**
|
||
(User-Visible: no).
|
||
- `docs/TESTING.md`: правило и что делать, когда гейт покраснел.
|