mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
260 lines
25 KiB
Markdown
260 lines
25 KiB
Markdown
# 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**
|