28 KiB
SPEC-REVIEW-173-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/173
- ТЗ под ревью:
docs/specs/173-unified-wall-tool.md(коммитd096a02, веткаissue/173-unified-wall-tool) - Роль: ревьюер ТЗ (не автор), этап
S4-spec-review - Трек: обычный (не
small/trivial) — аналитика владельца прямо говорит «small/trivialнеприменимы: это новый UX-контракт, несколько модулей и высокая геометрическая цена ошибки», сложность/риск 9/10. Полный трек и отдельный файл ТЗ выбраны верно. - Цикл: r1/4
Скоуп ревью
Проверялось соответствие ТЗ:
docs/SCOPE.md— попадание в Core user jobs (J4/J6), отсутствие расширения скоупа за пределы описанного объединения инструментов;PROCESS.md§2.4/§2.5 (DoR), §7.1 (обязательные разделы ТЗ, продуктовые vs технические вопросы), §3/§12 (запрет «догадка вместо решения»);AGENTS.md— классы файлов, имя ветки, трейлеры коммита ТЗ;- каноническим документам подсистемы:
docs/UX-MODES.md(toolbar/tool names, режимы, touch-политика),docs/TOUCH-SUPPORT.md(обязательная формула деградации),docs/CANVAS.md/docs/WALL-THICKNESS.md(planar-геометрия, provenance, толщина, island-контракт),docs/ARCHITECTURE.md(polyContainsPoly/islandsOf); docs/USER-GUIDE.ru.md— текущая терминология «Контур комнаты» / «Перегородка»;- фактическому коду (
src/houseplan-card.ts,src/plan-snap-overlay.ts,src/i18n/{ru,en}.json) — чтобы диагноз текущего состояния (§3 ТЗ) не оказался непроверенной догадкой, выданной за факт; - полному треду issue #173 — продуктовая идея владельца, аналитика Codex, пакет вопросов Q1–Q7 с default'ами, решения владельца, хендофф автора ТЗ.
Как проверялось
- Прочитан весь тред issue #173: исходная продуктовая идея владельца
(18.08.2026), аналитика Codex (ценность 9/10, сложность/риск 9/10, P1,
явное «
small/trivialнеприменимы»), пакет вопросов Q1–Q7 (каждый с предложенным default), ответ владельца «Q1 принят с правкой… Q2–Q7 приняты предложенные defaults без изменений», занятие автора ТЗ и хендофф со ссылкой наdocs/specs/173-unified-wall-tool.md. - Сверены обязательные разделы ТЗ (§7.1 PROCESS.md) построчно — таблица ниже.
- Прочитан код и построчно сверены ключевые фактические утверждения §3 ТЗ
о текущем состоянии:
type MarkupTool = ... | 'draw' | 'partition' | ...(src/houseplan-card.ts:497) — два отдельных инструмента подтверждены;markup.add= «Контур комнаты» / «Room outline»,markup.partition= «Перегородка» / «Partition» (src/i18n/ru.json:13,74,src/i18n/en.json:13,74) — «До:» из §2 ТЗ совпадает с реальными текущими подписями кнопок, а не с уже переименованным текстом канона (см. Low-2 ниже про сам канон);MAX_PARTITIONS = 2000(src/houseplan-card.ts:531),_keepClosedAsPartitions(:11647),_contourClosedgetter (:11177) — существующий механизм ровно такой, как описан в §3 п.2/5;src/plan-snap-overlay.tsсуществует отдельным модулем (692 строки иначе не совпало бы), собираетroom/draft/partitionсегменты с opening/open-span cuts (:115-153) и строит только endpoint/line-snap кандидаты (:265-289) — не строит faces. Это подтверждает §3 п.4 и §13.2 («второй opening resolver запрещён», «делит с detector общий pure collector») как техническую границу, реально существующую в коде, а не придуманную;docs/ARCHITECTURE.md:990-991подтверждает «действующий island-room контракт» (polyContainsPoly,islandsOf), на который ссылается §10.1 ТЗ, — не изобретённое понятие;docs/WALL-THICKNESS.md:207(«an interior↔interior X crossing keeps normal…») иdocs/specs/141-wall-junctions.mdподтверждают, что X не становится persisted-узлом в #141 — ровно то ограничение, которое §3 п.7 и §9.2.3 ТЗ фиксируют явно («X становится вычисляемым узлом только для face topology; persisted записи и физический join-контракт #141 не меняются»). Технической коллизии с #141 нет.toast.room_overlap(src/houseplan-card.ts:6442,6692) подтверждает, что «текущая validation/toast семантика» partial-overlap (§10.1) — реально существующий, а не гипотетический путь.
- Прочитан
docs/UX-MODES.mdцеликом (раздел «Plan — independent physical objects», строки 164–180) иdocs/TOUCH-SUPPORT.mdцеликом. Формулировка ТЗ §12 «Touch editor: best effort / intentionally degraded» — буквальная канон-метка, требуемаяTOUCH-SUPPORT.md(«Documentation rule»). Контракт §12 (pinch/pan/pointercancel/second pointer не завершают цепочку и не создают геометрию) прямо реализует «Safety floor», в частности пункт «saving unintended geometry merely because a pinch, pointer cancellation or second touch was misread as a click» — не изобретение автора. - Проверено разделение продуктовых и технических вопросов (PROCESS.md §7.1: владельцу — только «что видит/делает» и «объём видимых изменений»). Q1–Q7 — все о наблюдаемом поведении (когда завершается цепочка, что считать эквивалентом «замыкания», в каком порядке идут диалоги, нужен ли тихий режим, остаётся ли Split). Ни один не является техническим вопросом, ошибочно вынесенным владельцу. Блок §20 («Принятые технические предположения») отдельно перечисляет 10 технических решений, прямо помеченных как «можно менять свободно», и завершается явным «Открытых продуктовых вопросов нет: … Q2–Q7 — defaults, 18.08.2026» — это корректное разделение, а не спрятанная догадка.
- Проверены все 17 AC (§14) на однозначность и явное указание способа
доказательства — у каждого в скобках назван метод (
unit/smoke/golden/performance/code review) и исполнитель. Отдельно проверено, что AC1–AC13 покрывают все пункты Scope (§5), AC14–AC17 — совместимость, производительность, touch и гейты реализации; расхождений между Scope и AC не найдено. - Сверена трассируемость:
docs/specs/README.md:96обновлён тем же коммитомd096a02;git diff --stat origin/dev...HEADсодержит толькоdocs/specs/173-unified-wall-tool.mdиdocs/specs/README.md(класс C, ни одного файла класса A — правило №1 не нарушено на этапе ТЗ). Коммит несётIssue: #173,User-Visible: no— верно для документа ТЗ. - Проверено, не дублирует ли задача открытые основания (#137, #138, #141, #150, #172): все перечислены в ТЗ (заголовок «Связано») как готовые строительные блоки (snap, adjacent-autoclose, стыки, толщина), а не как пересекающийся скоуп; #138 (adjacent room autoclose) явно указан как регрессионный сценарий, который должен остаться зелёным (AC7) — граница с #173 (полный planar graph против узкого room-owned-edge контракта #138) проведена явно в §3 п.3.
Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1 — администратор дома, desktop Plan editor, момент выбора инструмента перед первым кликом |
| Что человек увидит до/после | ✅ | §2, «До:»/«После:», проверено по факту i18n (см. «Как проверялось» п.3) |
| Проблема | ⚠️ | Явного раздела «Проблема» нет — см. Low-1; содержательно проблема сформулирована во вступлении и §1, а §3 «Подтверждённое текущее состояние» несёт функцию причины/диагноза |
| Скоуп / не-скоуп | ✅ | §5 / §6, 13 и 12 пунктов соответственно, границы явные (Split не трогается, нет persisted node/face, нет новой настройки) |
| Контракт поведения | ✅ | §7 (термины/инварианты) + §8 (UX) + §9 (planar graph) + §10 (создание комнат) |
| Модель данных и миграция | ✅ | §11 — явное «новых persisted полей нет», совместимость старых drafts/partitions/warm tool расписана по каждому случаю |
| UX, i18n, accessibility, touch | ✅ | §12, включая буквальную канон-метку touch-деградации |
| AC1…ACn с доказательством | ✅ | §14, 17 штук, каждый помечен методом и исполнителем |
| План автотестов | ✅ | §15, unit / targeted smoke / golden / performance+backend / implementation loop |
| Риски | ✅ | §18, 12 строк риск → мера |
| Откат | ✅ | §19 |
| Release-артефакты | ✅ | §17, конкретный список из 13 файлов/каналов, включая оба changelog в implementation-коммите |
Единственное отклонение от конвенции — отсутствие отдельного заголовка «Проблема» (все предыдущие ТЗ, например #138, #172, выделяют её отдельным разделом). Содержательно проблема присутствует (см. вступление и §1: «перед первым кликом надо решить внутренний вопрос модели… при ошибочном выборе приходится менять инструмент либо перерисовывать геометрию»), поэтому это Low, не High/Medium — см. находки.
Находки
Находок уровня High нет. Находок уровня Medium нет — новых issue не требуется.
Low-1 — нет отдельного раздела «Проблема»
Файл: docs/specs/173-unified-wall-tool.md (между §2 и §4; текущий §3
называется «Подтверждённое текущее состояние»)
PROCESS.md §7.1 перечисляет «проблема» отдельным обязательным разделом, и все
предшествующие ТЗ в репозитории (138-adjacent-room-autoclose.md:44,
172-zero-divider-taper.md:36) держат его отдельно от «текущего состояния».
В ТЗ #173 формулировка проблемы («пользователь должен заранее решить, что он
рисует… при ошибочном выборе — менять инструмент или перерисовывать») звучит
только во вступлении к issue и в первом абзаце §1, а раздел §3 сразу
переходит к построчному разбору кода. По существу пропуска нет — читатель
получает и мотивацию, и диагноз, — но формально это отход от структуры,
которая делает разделы машинно/визуально сверяемыми.
Решение ревьюера: Low, не блокирует. Можно поправить косметически (дать §3 заголовок «Проблема и подтверждённая причина», как в #172) при следующей редакции либо снять записью без нового цикла.
Low-2 — docs/UX-MODES.md уже называет текущий инструмент «Walls»
Файл: docs/UX-MODES.md:83, 166 vs docs/specs/173-unified-wall-tool.md
§2 «До:», §8.1
Канонический документ подсистемы в описании текущего (до #173) состояния
Plan-редактора пишет «Toolbar tools: Walls / room outline… Partition,
Column…» и «Walls still draws room contours» — то есть уже сейчас
называет инструмент «Walls», хотя фактический публичный лейбл кнопки
(markup.add) — «Контур комнаты» / «Room outline» (проверено по
src/i18n/{ru,en}.json:74, см. «Как проверялось» п.3). ТЗ #173 корректно
использует настоящую текущую подпись («Контур комнаты» / «Room outline») в
§2/§3, но не упоминает, что UX-MODES.md уже содержит забежавшую вперёд
формулировку «Walls» для другого инструмента (сегодняшнего draw), что
создаёт двусмысленность при будущем чтении истории документа. Это дрейф,
существовавший в UX-MODES.md до #173, не внесённый этим ТЗ, и раздел
release-артефактов (§17) обязывает обновить UX-MODES.md в implementation
коммите — то есть у автора кода будет естественная точка исправить
формулировку заодно.
Решение ревьюера: Low, не блокирует приёмку ТЗ. Фиксирую для code review
#173: при правке docs/UX-MODES.md в рамках этой задачи убедиться, что
итоговый текст однозначно называет единственный инструмент «Walls»/«Стены» и
не оставляет старой двусмысленной фразы про «Walls / room outline» рядом с
отдельным «Partition».
Low-3 — опечатка в AC17
Файл: docs/specs/173-unified-wall-tool.md:522
- **AC17 (typecheck+unit+build + documentation review; разработчик):**— лишний обратный апостроф после «review» перед точкой с запятой (должно быть... + documentation review; разработчик)`). Чисто косметическая опечатка
форматирования Markdown, не меняющая смысл критерия.
Решение ревьюера: Low, не блокирует. Правится одним символом при следующей редакции.
Что проверено и корректно
- Соответствие
docs/SCOPE.md: задача закрывает J4 («от нуля до плана без Inkscape/YAML» — рисование стен без знания внутренней модели данных) и J6 («план остаётся правдивым по мере развития» — merge/split/ drag редактируются тем же единым инструментом). Обе строки Closed; это улучшение UX-контракта существующей функциональности, а не расширение продукта за пределы SCOPE. - Легитимность полного трека: сложность/риск 9/10 по собственной оценке
аналитики, новый UX-контракт, минимум четыре затронутых модуля/поверхности
(toolbar/tool state, planar-graph detector, room-creation/Split, snap-
overlay) — критерии
small/trivial(§5/§5.1 PROCESS.md) не выполняются ни по одному пункту, что аналитика прямо констатирует. - Продуктовые вопросы закрыты по процессу: пакет Q1–Q7 задан одним
комментарием, каждый вопрос — с предложенным default и явной формулировкой
«что человек видит/делает»; issue корректно ушёл в
blockedдо ответа владельца и вышел изblockedсразу после решения. Ни один вопрос не является техническим, ошибочно вынесенным владельцу — все семь про наблюдаемое поведение (завершение цепочки, эквиваленты замыкания, порядок диалогов, судьба Split). - Диагноз текущего состояния не голословен. Утверждения §3 о
MarkupTool,_contourClosed,_keepClosedAsPartitions,MAX_PARTITIONS,plan-snap-overlay.tsи реальных подписях кнопок построчно сверены сsrc/houseplan-card.ts,src/plan-snap-overlay.tsиsrc/i18n/*.json— см. «Как проверялось» п.3. - Границы с соседними задачами (#137/#138/#141/#150/#172) проведены явно,
без пересечения скоупа: #138 остаётся регрессионным контрактом (AC7), #141
не получает нового persisted X-узла (§3 п.7, §9.2.3, сверено с
docs/WALL-THICKNESS.mdиdocs/specs/141-wall-junctions.md), #150/#172 остаются единственным источником физической геометрии стен (§10.4 п.7). - Не-скоуп (§6) корректно отсекает соседние соблазны: удаление Split, автоматические предложения при reload/move/Align/Undo, фоновая миграция старых drafts, persisted node/face, новая настройка «не предлагать», создание комнаты через проём — всё явно исключено с обоснованием и соответствует принятым Q2–Q7.
- Модель данных и миграция (§11): корректно заявлено «новых persisted
полей нет», отдельно расписана судьба legacy warm-tool значения
partition, старых drafts/partitions (остаются читаемыми, не конвертируются автоматически), downgrade без rollback — соответствуетdocs/CONFIG-COMPATIBILITY.md. - UX/touch (§12): дословно использует обязательную канон-формулировку
docs/TOUCH-SUPPORT.mdи отдельно перечисляет touch safety floor (pinch/pan/second-pointer/pointercancel не создают геометрию) — прямая реализация «Safety floor that still applies to touch editors». - Planar-graph контракт (§9) методологически надёжен: явно запрещён «pairwise любой цикл без выделения faces» (частая ошибка naive-детекторов), явно требуется canonical ring identity, инвариантная к порядку/id/ направлению — это именно то, что делает AC5 проверяемым unit-тестом с перестановками, а не подверженным флаки.
- Дисциплина «тест должен уметь падать»: §15.1 прямо требует «минимум
один topology test должен краснеть на
origin/devбез detector», а ревьюеру кода прямо поручено проверить его mutation discipline — соответствует PROCESS.md §2.7/§18. - Performance-контракт (§13, AC15) ссылается на реально существующий
профиль
large-house-plan-snap-v1(demo/performance/budgets-large-house-plan-snap.json) и требует не ослаблять исходные бюджеты — не изобретённая, а существующая точка измерения. - Release-артефакты (§17) перечисляют оба changelog в implementation-
коммите, конкретные документы для обновления (
USER-GUIDE.ru.md,UX-MODES.md,CANVAS.md,ARCHITECTURE.md,WALL-THICKNESS.md,TESTING.md), golden matrix и три синхронные копии бандла — golden явно ограниченgolden:accept -- --reviewedпо полному Linux-артефакту (согласуется с PROCESS.md §8/§11.4/правилом 13). - Трассируемость:
docs/specs/README.md:96обновлён тем же коммитомd096a02; веткаissue/173-unified-wall-toolи трейлеры (Issue: #173,User-Visible: no) корректны для документа ТЗ, который сам не меняет поведение продукта.git diff --stat origin/dev...HEADне содержит ни одного файла класса A — правило №1 не нарушено на этапе ТЗ. - Явный блок технических предположений (§20) из 10 пунктов корректно отделяет техническую свободу («какой именно алгоритм DCEL», «структура кэша», «имена файлов/smoke/golden id») от продуктового решения и явно фиксирует «открытых продуктовых вопросов нет» со ссылкой на дату и комментарий владельца.
Чего не проверял
- Не проверял реализуемость конкретного алгоритма half-edge/DCEL как единственно возможного решения planar-graph детектора — §20 п.1 явно оставляет выбор алгоритма технической свободой; код-ревью будет проверять выполнение AC4/AC5, а не конкретную структуру данных.
- Не запускал автотесты,
golden, browser-смоки или performance-профили — на этапеspecэто не требуется и большинство названных файлов (test/wall-face-graph.test.mjs,demo/smoke_unified_wall_tool.mjs) ещё не существуют по плану ТЗ, а не по недосмотру; существование уже используемых зависимостей (src/plan-snap-overlay.ts,large-house-plan-snap-v1профиль,polyContainsPoly/islandsOf) проверено чтением кода/конфигурации, не исполнением. - Не проверял корректность числовых оценок аналитики (9/10 · 6/10 · 9/10 · P1) по существу — это поле владельца (PROCESS.md §2.2), уже принято явным решением до написания ТЗ.
- Не проверял детально совместимость с #137 (
docs/specs/137-plan-snap-overlay.md) за пределами того, что понадобилось для подтверждения текущей функциональности snap-коллектора (endpoint/line-snap без face-построения); само #137 не входит в предмет этого ревью. - Не оценивал, достаточно ли 17 AC с точки зрения «можно ли перепутать порядок исполнения» на этапе кода — это будет предметом code review, когда появится реализация и её тесты; на этапе ТЗ проверялось только то, что каждый AC однозначен и называет способ доказательства.
Вердикт
Зелёный. High: 0, Medium: 0. Три находки Low (отсутствие отдельного
заголовка «Проблема»; дрейф терминологии «Walls» в текущем тексте
UX-MODES.md, не внесённый этим ТЗ, но подлежащий исправлению в рамках
release-артефактов §17; опечатка-апостроф в AC17) — ни одна не блокирует
приёмку, все либо правятся косметически при следующей редакции, либо
снимаются этой записью без нового цикла.