25 KiB
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).
Как проверялось
- Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.mdцеликом (актуальная редакция, включая §2.4/§2.5/§5/§7.1/§8) и тело issue #176 со всеми комментариями (аналитика владельца, передача на ревью, отметка о сбое автоматического прогона). - Построчно сверены технические утверждения ТЗ с текущим
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».
- Проверена причина недостижимости
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 «код недостижим, а не редко используем» подтверждён чтением, не декларацией автора.
- Проверено, что тип
MarkupToolиMARKUP_TOOLSне используются больше нигде вsrc/(grep -rn "MarkupTool\|MARKUP_TOOLS" src/) — оба контейнера частные дляhouseplan-card.ts; удаление литерала не пробивает границу модуля. - Проверена граница «мёртвый код инструмента» 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), реально проходит там, где заявлено. - Проверены 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-объектом и диалогами, ТЗ верно не включает их в список удаления. - Проверен
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'не меняет ни одного изображения — заявление ТЗ «визуальные эталоны не меняются» подтверждено чтением фикстур, а не предположением. - Проверены названные в плане автотестов файлы:
demo/smoke_free_walls.mjs(существует; строки 57-60, 177-181 напрямую ставятc._tool = 'partition'и зовутc._partitionClick(...)в обход публичного dispatch — ровно то приватное использование, которое ТЗ обязуется перевести на публичный flow),demo/smoke_plan_snap_overlay.mjs(существует; строка 185card._tool = 'partition'перед_markupClick, что действительно проходит через диспетчер строки 6834-6835), иdemo/smoke_unified_wall_tool.mjs(существует; не использует приватный tool-state — уже написан против публичного_tool = 'draw'+_keepClosedAsPartitions(), строка 128 — служит рабочим образцом того, как должны выглядеть переписанные сценарии). - Проверено существование паттерна «source-contract test», на который
ссылается AC1 как способ доказательства:
test/isometric-contract.test.mjs,open-passage-contract.test.mjs,optional-space-model-contract.test.mjs,release-contract.test.mjsи другие — это существующая практика репозитория (в т.ч. для внутренних инвариантов), а не изобретённый под это ТЗ механизм. - Проверено
docs/USER-GUIDE.ru.md:343— таблица инструментов Плана уже фиксирует «отдельного инструмента «Перегородка» нет» на пользовательском уровне; терминология ТЗ («persisted-объект остаётся «Перегородка», внутренний tool-token удаляется») не расходится и не переизобретает то, что задокументировано. - Проверено
docs/TESTING.md:486-497(«Room markup editor») иdocs/STATUS.md(grep -in partition— без совпадений) — ни один текущий пункт не описывает приватный_tool='partition'; изменение кода не требует правки формулировок этих файлов по существу, перечисление их в «Затрагиваемые поверхности» — корректная страховка на случай появления новых заметок в реализации, не декларация обязательной правки. - Проверено, что issue #176 не оспорил
small: сложность/риск 3/10, одна поверхность (src/houseplan-card.ts+ производные тесты/i18n), нет миграции, нового UX-контракта, влияния на perf/touch — все критерии §5 выполнены одновременно;trivialкорректно не применён (типtech-debt, неbug). - Проверено отсутствие ветки
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-onlyMarkupTool).- Точный будущий текст 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