Files
houseplan-card/docs/reviews/SPEC-REVIEW-176-r1.md
T
2026-08-19 13:09:19 +00:00

260 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-176-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/176
- **ТЗ под ревью:** тело issue #176 (лёгкий трек, файл `docs/specs/` не
создаётся — метка `small` подтверждена аналитиком и не оспорена владельцем)
- **Роль:** ревьюер ТЗ (не автор), этап `S4-spec-review`
- **Трек:** лёгкий (`small`), лимит циклов ревью ТЗ — 2 (§4 PROCESS.md)
- **Цикл:** r1/2
## Скоуп ревью
ТЗ #176 удаляет мёртвый внутренний tool-state `'partition'` из
`src/houseplan-card.ts` (значение `MarkupTool`, `MARKUP_TOOLS`, метод
`_partitionClick`, ~15 условных веток `this._tool === 'partition'`) и три
осиротевших i18n-ключа, оставленных после #173 (замена one-shot Partition
единым Walls-инструментом). Persisted-модель независимых стен
(`space.partitions`, `PartitionCfg`, `kind: 'partition'` у selection/opening
host, весь UI работы с уже созданными стенами) не меняется.
`User-Visible: no`; changelog не требуется.
Находка изначально заведена самим ревьюером кода на #173
(`docs/reviews/CODE-REVIEW-173-r1.md`, Medium-1) и подтверждена владельцем как
issue — проверка «issue создан явным решением владельца» выполнена (первая
запись в issue — команда владельца на вход в `S2-analysis`).
Не в скоупе ревью: продуктовый код не написан (issue в `S4-spec-review`;
ветки `issue/176-*` в репозитории нет — проверено `git branch -a`), поэтому
реализация, тесты и гейты — предмет будущего код-ревью (PROCESS.md §2.7).
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком (актуальная
редакция, включая §2.4/§2.5/§5/§7.1/§8) и тело issue #176 со всеми
комментариями (аналитика владельца, передача на ревью, отметка о сбое
автоматического прогона).
2. Построчно сверены технические утверждения ТЗ с текущим `dev`
(`src/houseplan-card.ts`, `grep -in partition`, полный список ~140
вхождений):
- `type MarkupTool = ... | 'partition' | ...` — строка 516, подтверждено;
- `'partition'` в `MARKUP_TOOLS` — строка 547, подтверждено;
- `private _partitionClick(...)` — строка 7143, реализует «два клика → одна
перегородка» через `_recordGeometry('history.partition_add', …)`;
подтверждено;
- диспетчер клика: `if (this._tool === 'partition') { this._partitionClick(...); }`
— строка 6834-6835, единственная точка вызова метода — подтверждено, что
`_partitionClick` не достижим никаким иным путём;
- ветки `this._tool === 'draw' || this._tool === 'partition'` (лимиты,
snap, hints, Undo/Escape, рендер превью) — найдены на строках 2241,
5362, 5429, 5450, 5487, 6364, 6601, 17202, 17923, 18039, 18041, 18091,
9597; одиночные `this._tool === 'partition'` — строки 2270, 2323, 6834,
9559, 9598, 11908 — количество и характер веток соответствуют заявленным
в issue «порядка 15».
3. Проверена причина недостижимости `this._tool === 'partition'` — ключевое
техническое утверждение issue, а не декларация:
- `normalizeMarkupTool` (`houseplan-card.ts:551-562`) — единственное место,
присваивающее `this._tool` из внешнего/сохранённого значения
(`this._tool = normalizeMarkupTool(vp.tool)` при восстановлении warm
viewport, строка 2656);
- `normalizeMarkupTool` безусловно вызывает
`value = normalizeUnifiedWallTool(value)` **до** проверки
`MARKUP_TOOLS.has(...)` (строка 555);
- `normalizeUnifiedWallTool` (`src/wall-face-graph.ts:53-55`):
`return value === 'partition' ? 'draw' : value;` — литерал `'partition'`
маппится в `'draw'` безусловно.
- Других обработчиков, присваивающих `this._tool = 'partition'` из
пользовательского ввода, не найдено (единственное текстовое совпадение
— сравнения `===`, не присваивания). Вывод issue «код недостижим, а не
редко используем» подтверждён чтением, не декларацией автора.
4. Проверено, что тип `MarkupTool` и `MARKUP_TOOLS` не используются больше
нигде в `src/` (`grep -rn "MarkupTool\|MARKUP_TOOLS" src/`) — оба
контейнера частные для `houseplan-card.ts`; удаление литерала не пробивает
границу модуля.
5. Проверена граница «мёртвый код инструмента» vs «живая persisted-модель»
построчно: `kind: 'partition'` в типах selection/drag/dialog (строки 1225,
1228, 1234, 1243, 7196, 7257, 7283, 7374, 7401, 7415, 7434, 7462, 7505,
11485, 12005, 17840, 17863, 17881, 17981), `space.partitions`/`PartitionCfg`,
`_partitionDeleteDialog`/`_confirmPartitionDelete`,
`_partitionOpeningCuts`/`resolvePartitionOpeningCompat` и весь модуль
`partition-openings.ts` — ни один из них не завязан на `this._tool`, все
работают с уже существующими объектами независимо от активного
инструмента. Граница, которую ТЗ объявляет неприкосновенной (контракт п.3),
реально проходит там, где заявлено.
6. Проверены i18n-ключи (`src/i18n/en.json`, `src/i18n/ru.json`):
`title.markup_partition`, `markup.hint_partition`, `physical.partition_size_title`
— все три существуют в обоих locale и (`grep -rn` по `src/`) используются
только через `this._tool === 'partition'` (строки 9559, 9598) либо нигде
не используются вне собственного определения (`title.markup_partition`) —
подтверждено, что это ровно осиротевший набор, ни ключом больше, ни
меньше. Ключи `markup.partition`, `physical.partition_properties`,
`history.partition_add`, `opening.host_partition`,
`confirm.delete_partition_openings_*`, `opening.rebind_partition` — заняты
persisted-объектом и диалогами, ТЗ верно не включает их в список удаления.
7. Проверен `test/golden-matrix.test.mjs:72`:
`assert.equal(['draw', 'partition'].includes(scenario.planSnap.tool), true, …)`
— сейчас допускает оба значения. Проверены реальные сценарии
`demo/golden/matrix.mjs` (`planSnap: {...}`, строки 73-80) — оба
существующих `planSnap`-сценария уже используют `tool: 'draw'`; ни один
golden-сценарий не задаёт `'partition'`. Сужение assert до одного `'draw'`
не меняет ни одного изображения — заявление ТЗ «визуальные эталоны не
меняются» подтверждено чтением фикстур, а не предположением.
8. Проверены названные в плане автотестов файлы:
`demo/smoke_free_walls.mjs` (существует; строки 57-60, 177-181 напрямую
ставят `c._tool = 'partition'` и зовут `c._partitionClick(...)` в обход
публичного dispatch — ровно то приватное использование, которое ТЗ обязуется
перевести на публичный flow), `demo/smoke_plan_snap_overlay.mjs`
(существует; строка 185 `card._tool = 'partition'` перед `_markupClick`,
что действительно проходит через диспетчер строки 6834-6835), и
`demo/smoke_unified_wall_tool.mjs` (существует; не использует приватный
tool-state — уже написан против публичного `_tool = 'draw'` +
`_keepClosedAsPartitions()`, строка 128 — служит рабочим образцом того,
как должны выглядеть переписанные сценарии).
9. Проверено существование паттерна «source-contract test», на который
ссылается AC1 как способ доказательства: `test/isometric-contract.test.mjs`,
`open-passage-contract.test.mjs`, `optional-space-model-contract.test.mjs`,
`release-contract.test.mjs` и другие — это существующая практика репозитория
(в т.ч. для внутренних инвариантов), а не изобретённый под это ТЗ механизм.
10. Проверено `docs/USER-GUIDE.ru.md:343` — таблица инструментов Плана уже
фиксирует «отдельного инструмента «Перегородка» нет» на пользовательском
уровне; терминология ТЗ («persisted-объект остаётся «Перегородка»,
внутренний tool-token удаляется») не расходится и не переизобретает то,
что задокументировано.
11. Проверено `docs/TESTING.md:486-497` («Room markup editor») и
`docs/STATUS.md` (`grep -in partition` — без совпадений) — ни один
текущий пункт не описывает приватный `_tool='partition'`; изменение кода
не требует правки формулировок этих файлов по существу, перечисление их
в «Затрагиваемые поверхности» — корректная страховка на случай появления
новых заметок в реализации, не декларация обязательной правки.
12. Проверено, что issue #176 не оспорил `small`: сложность/риск 3/10, одна
поверхность (`src/houseplan-card.ts` + производные тесты/i18n), нет
миграции, нового UX-контракта, влияния на perf/touch — все критерии §5
выполнены одновременно; `trivial` корректно не применён (тип `tech-debt`,
не `bug`).
13. Проверено отсутствие ветки `issue/176-*` (`git branch -a | grep 176` —
пусто) и отсутствие более раннего `docs/reviews/SPEC-REVIEW-176-*.md`
(`ls docs/reviews | grep 176` — пусто) — это первый цикл, r1/2.
Гейты (`typecheck`/`test`/`build`) не прогонялись — на этапе ревью ТЗ
продуктового кода нет; прогон гейтов не относится к этому этапу (PROCESS.md
§2.7/§8).
## Обязательные разделы (§7.1 PROCESS.md, лёгкий трек — тело issue)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | N/A, обоснованно | задача не создаёт наблюдаемого изменения продукта (`User-Visible: no`, контракт п.6); §7.1 формулирует эти два раздела как продуктовые именно для отделения «работы» от «изменения продукта» — здесь по существу нет стороны-наблюдателя, это подтверждено чтением кода (см. п.3), а не заявлено голословно |
| Что человек увидит до/после | N/A, обоснованно | то же: «до» и «после» с точки зрения пользователя идентичны — задача про внутреннюю модель, не про интерфейс |
| Проблема | ✅ | подтверждена чтением `houseplan-card.ts` и `wall-face-graph.ts` (см. «Как проверялось» п.2-3), а не декларацией автора |
| Контракт (скоуп/не-скоуп внутри пунктов 1-3) | ✅ | граница мёртвый-код/persisted-модель проверена построчно (п.5) и проходит ровно там, где заявлено |
| Модель данных и миграция | ✅ | «persisted-модель не меняется» — подтверждено (п.5); миграции нет, потому что нет изменения хранимой схемы |
| i18n | ✅ | ровно три ключа, ни больше ни меньше — подтверждено построчной сверкой (п.6) |
| AC1…ACn с доказательством | ✅ | 3 штуки, у каждого назван реалистичный и проверяемый способ доказательства (typecheck+source-contract test / unit+targeted smokes / locale unit+build diff+golden:verify) |
| План автотестов | ✅ | ссылается на реально существующие файлы (п.7-8), включая точную строку смока, которая сейчас использует приватный API и должна быть переписана |
| Риски | частично | явного раздела «Риски» нет, но риск («будущий контрибьютор вернёт мёртвую ветку по аналогии») назван в теле issue и закрывается самим фактом удаления кода — для лёгкого трека §5 отдельного раздела «Риски» не требует (шаблон: проблема · контракт · AC · откат) |
| Откат | ✅ | один revert, без миграции данных — обоснованно, так как persisted-схема не тронута |
| Release-артефакты | ✅ | явно и корректно: `User-Visible: no` → changelog не требуется; три bundle snapshot синхронизируются по стандартному процессу сборки |
Раздел «Принятые предположения» присутствует и корректно отделяет то, что
уже прочно установлено чтением кода (`partition` в persisted-типах — не
мёртвый код; legacy-нормализатор остаётся намеренно), от того, что не
является продуктовым вопросом вовсе (нет UI, нет changelog) — ни одно
утверждение раздела не выдаёт техническую догадку за факт.
## Находки
Отсутствуют. Ниже — то, что специально проверено на предмет типичных для
этого класса дефектов (пропущенный call site, недосказанная граница
скоупа, домысленное поведение) и не подтвердилось.
- **Домыслы вместо решения не найдены.** Единственное потенциально спорное
техническое утверждение — «код недостижим, а не редко используем» — не
принято на веру, а прослежено до кода нормализации (`normalizeUnifiedWallTool`,
«Как проверялось» п.3): это не догадка автора, а проверяемый факт.
- **Скоуп i18n не занижен и не завышен.** Отдельно проверено, что среди трёх
удаляемых ключей есть `physical.partition_size_title`, которого не было в
исходной находке код-ревью #173 (там названы только `title.markup_partition`
и `markup.hint_partition`) — авторская аналитика нашла его самостоятельно и
подтвердила чтением (`this._tool === 'partition' ? 'physical.partition_size_title'`,
строка 9559); ключ действительно используется только приватным
tool-state и нигде в persisted-UI.
- **Golden-эталоны не сузятся ошибочно.** Проверено, что сужение
`test/golden-matrix.test.mjs:72` до одного `'draw'` не затрагивает ни один
реальный сценарий `demo/golden/matrix.mjs` — оба `planSnap`-сценария уже
на `'draw'`.
## Что проверено и корректно
- **Соответствие `docs/SCOPE.md`:** задача не создаёт нового Core user job и
не расширяет ни один из них напрямую — это ожидаемо для `tech-debt`.
Косвенная связь с J4/J6 (поддержание единственного тестируемого пути Plan
editor, без альтернативного нетестируемого API) не притянута: старый
`_partitionClick` действительно обходит crash-safe/finish/limit-контракт,
установленный #173 для «unified Walls chain» (J6 — «keep the plan true as
the home evolves»).
- **Легитимность лёгкого трека:** сложность 3/10, одна поверхность, нет
миграции/нового UX-контракта/влияния на perf или touch — критерии §5
выполнены все одновременно; аналитик верно не предложил `trivial` (тип
`tech-debt`, критерий §5.1 требует `bug`).
- **Продуктовых вопросов действительно нет.** Единственный класс вопросов,
который вообще уместен на этом этапе — объём видимого изменения — здесь
вырожден: видимого изменения нет вовсе (`User-Visible: no`), и это не
недосмотр автора, а прямое следствие того, что удаляется недостижимый код.
Технические решения (source-contract test, перевод смоков на публичный
flow) корректно оставлены на усмотрение разработчика записью в разделе
«Принятые предположения», как и предписывает §7.1 PROCESS.md.
- **Граница мёртвый-код / persisted-модель проведена точно и без пропусков**
— построчная проверка (см. «Как проверялось» п.5) не нашла ни одного
места, где persisted `kind: 'partition'` зависел бы от `this._tool`, и ни
одного места, где `this._tool === 'partition'` относился бы к чему-то,
кроме активного инструмента разметки.
- **Ссылки на существующие файлы не выдуманы** — все три названных в плане
автотестов смока (`smoke_free_walls.mjs`, `smoke_plan_snap_overlay.mjs`,
`smoke_unified_wall_tool.mjs`) существуют, и первые два действительно
используют приватный `_tool = 'partition'`/`_partitionClick` способом,
который ТЗ обязуется устранить — заявление в AC2 не голословно.
- **Обратная совместимость не сужается.** Legacy warm-viewport токен
`'partition'` продолжает нормализоваться в `'draw'` уже существующим кодом
(`normalizeUnifiedWallTool`), который ТЗ явно не трогает и защищает
unit-тестом (контракт п.2) — соответствует `docs/CONFIG-COMPATIBILITY.md`
в части «не сужать уже принятый вход», хотя формально warm viewport —
page-memory, а не persisted storage schema, которую документ описывает
напрямую.
- **Откат тривиален и корректен** — один revert без миграции данных,
поскольку persisted-схема не меняется ни в одной части.
## Чего не проверял
- Реализацию — её нет: issue в `S4-spec-review`, ветки `issue/176-*` не
существует (`git branch -a` пуст по этому номеру) — проверено.
- Гейты `typecheck`/`test`/`build`/browser smoke/golden — не относятся к
этапу ревью ТЗ; предмет будущего код-ревью (PROCESS.md §2.7/§8).
- `python -m pytest tests_backend` — backend (`custom_components/**/*.py`) не
упомянут ни в issue, ни в затронутых файлах, и не содержит `partition`
как tool-state (только независимая от frontend-инструмента persisted-схема,
если вообще есть — не проверялась специально, так как ТЗ прямо заявляет
«Backend … не меняется», а весь код-путь — frontend-only `MarkupTool`).
- Точный будущий текст source-contract теста и точный способ переписать
`smoke_free_walls.mjs`/`smoke_plan_snap_overlay.mjs` на публичный
draw/finish flow — это техническое решение реализации (§7.1: «всё, чего
пользователь не наблюдает, агенты решают сами»), а не предмет ревью ТЗ.
- Реальный визуальный результат `npm run golden:verify` — CSS/геометрия не
меняются по заявлению ТЗ и по чтению фикстур (см. «Как проверялось» п.7),
но фактический прогон гейта относится к реализации/пре-релизу, не к этому
этапу.
- Точность числовых оценок аналитики (ценность 1/10, сложность 3/10, P3) по
существу — поле владельца (PROCESS.md §2.2), не предмет ревью ТЗ.
## Вердикт
Зелёный. High: 0, Medium: 0, Low: 0. ТЗ описывает точно очерченную,
доказанную чтением кода (не декларированную) границу между мёртвым
tool-state и живой persisted-моделью независимых стен; все технические
утверждения (недостижимость `this._tool === 'partition'`, точный список
осиротевших i18n-ключей, безопасность сужения golden-matrix assert)
проверены построчно и подтвердились. Продуктовых вопросов нет обоснованно —
задача не создаёt наблюдаемого изменения, а не умалчивает о нём. AC однозначны
и снабжены доказуемыми способами проверки.
**Вердикт: зелёный · цикл r1/2 · High: 0 · Medium: 0 → нет · Документ:
docs/reviews/SPEC-REVIEW-176-r1.md**