Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
14 KiB
CODE-REVIEW-204-r1
- Issue: #204
- ТЗ: docs/specs/204-space-create-display-defaults.md
- Ветка:
issue/204-space-create-display-defaults, коммит12c4f77 - Диапазон:
origin/dev..HEAD=f703242(spec) →ec0ccaf(spec review doc) →12c4f77(реализация) - Вердикт: зелёный
Скоуп ревью
Этап code по PROCESS.md §2.7: реализация против ТЗ #204 (единственный
implementation-коммит 12c4f77, User-Visible: yes). Задача — честные
create-dialog defaults для «Всегда отображать границы комнат» и «Отображать
названия комнат»: File false/false, Draw true/true, сохранение выбора
пользователя (displayTouched) при последующих File↔Draw, и убрать скрытую
Save-подмену draw && mode==='create' ? true : d.showBorders. Обычный Create
и Floors/Areas onboarding должны следовать одной политике.
Как проверялось
- Прочитаны в порядке:
docs/SCOPE.md,AGENTS.md,PROCESS.md§2.7, тело issue #204 и все комментарии (аналитика, ТЗ, код-ревью спеки, реализация),docs/USER-GUIDE.ru.md(раздел «Пространства» — таблица «Источник плана» уже содержала термин «Без изображения» до этого коммита),docs/UX-MODES.md(подтверждено: описывает только рантайм-резолверshow_borders/show_names, не create-dialog UX — не затронут, non-scope ТЗ верен). - Прочитан весь
git diff origin/dev...HEADпострочно: новый модульsrc/space-dialog.ts, точки интеграции вsrc/houseplan-card.ts(_openSpaceDialog('create'),_openNextImport,_saveSpaceDialog, радиокнопки File/Draw, оба_boolInputпереключателя), новый unit-файлtest/space-dialog.test.mjs, новый браузерный smokedemo/smoke_space_create_display_defaults.mjs, правка существующегоdemo/smoke_space_settings.mjs, запись мутанта вscripts/mutation-gate.mjs, документация (CHANGELOG.md/.ru.md,USER-GUIDE.ru.md,TESTING.md,STATUS.md,docs/specs/README.md),tsconfig.test.json. - Построчно сверена корневая причина ТЗ (§3) с диффом: скрытая ветка
show_borders: draw && d.mode === 'create' ? true : d.showBorders(и аналог дляshow_names) в_saveSpaceDialog()заменена на прямую записьd.showBorders/d.showNames— подмена действительно убрана, не переименована. - Проверен весь конечный автомат
displayTouched(§6 ТЗ) противsrc/space-dialog.ts:initialSpaceDisplayDraft(Filefalse/false,displayTouched:false),switchSpacePlanSource(проецирует source-default пока!displayTouched, иначе меняет толькоsource),touchSpaceDisplay(ставитdisplayTouched:true, второй control не трогает) — все 6 пунктов контракта покрыты один в один, скрытых переходов не осталось. - Проверено, что обычный Create (
_openSpaceDialog('create'),src/houseplan-card.ts:13239) и каждый шаг onboarding (_openNextImport,src/houseplan-card.ts:13613) используют один и тот же...initialSpaceDisplayDraft()— общий чистый state, без риска утечки touched между этажами (AC5). - Проверено, что Edit (
src/houseplan-card.ts:13214) выставляетdisplayTouched: trueсразу — источник переключается без авто-сброса уже сохранённых values (AC6), что и есть корректное поведение «Edit не должен применять create-only defaults». - Прогнаны все заявленные автором гейты локально (см. таблицу ниже), включая исполнение (не чтение) целевого мутанта — тест обязан был покраснеть без патча и покраснел на мутанте.
- Проверены трейлеры всех трёх коммитов диапазона (
Issue: #204на каждом;User-Visible: noна двух docs-коммитах,User-Visible: yesна реализации) и что оба changelog (docs/CHANGELOG.md,docs/CHANGELOG.ru.md) правлены в том же коммите12c4f77, что и продукт (git show --stat 12c4f77). - Проверено побайтовое совпадение трёх копий бандла после чистой пересборки
(
dist,custom_components/houseplan/frontend,demo/srv/assets) — идентичны иgit diffпосле пересборки пуст, т.е. закоммиченные копии не разошлись с исходником.
Гейты — что прогнано и результат
| Гейт | Статус | Результат |
|---|---|---|
npx tsc --noEmit |
прогнан | green, без ошибок |
npm test |
прогнан | 916/916 green (включая 4 новых теста space-dialog.test.mjs) |
npm run build + сверка 3 копий бандла |
прогнан | build green; md5sum трёх копий совпадает; git diff --stat после build пуст |
git diff --check |
прогнан | без ошибок пробелов |
node demo/smoke_space_create_display_defaults.mjs |
прогнан | green (OK), проверены все состояния AC1–AC8 |
node demo/smoke_space_settings.mjs |
прогнан | green (OK), regression не сломан |
node scripts/mutation-gate.mjs --check |
прогнан | green, якорь мутанта найден ровно 1 раз |
node scripts/mutation-gate.mjs --id=space-create-hidden-display-override |
прогнан (исполнение) | «чистый прогон» зелёный без мутанта, с мутантом — «тест покраснел, как обязан», поймано 1/1 |
Не прогонялось и почему:
npm run golden:verify— диф не меняет геометрию, рендер, стили или слои (только checked-state двух HA switches и порядок записи двух boolean в Save); визуального следствия нет, ТЗ §10 явно исключает golden.- Полный набор
demo/smoke_*.mjs(127 файлов) — задача касается одной поверхности (create-dialog + onboarding wizard), два целевых смока плюс regression-смок покрывают её; по процессу полный набор — предрелизный гейт, а не гейт ревью. python -m pytest tests_backend -q— диф не затрагиваетcustom_components/houseplan/**/*.py; backend принимает произвольные bool без изменений (подтверждено в самой ТЗ и не оспаривается диффом).- performance-профили — путь не является perf-чувствительным (локальные boolean transitions без I/O), в AC не назван.
Проверка AC (PROCESS §2.7: автотест + доказано, что умеет падать, либо чтение с явной пометкой)
| AC | Как доказан | Итог |
|---|---|---|
| AC1 | smoke_space_create_display_defaults.mjs: freshFile/untouchedDraw |
подтверждено исполнением |
| AC2 | тот же smoke: Save сразу после Draw → savedMixed/reopenedMixed (мутантно доказано отдельно на mixed-паре) |
подтверждено исполнением |
| AC3 | unit space-dialog.test.mjs («every supported Draw pair...») + smoke mixedTouched/savedMixed/reopenedMixed |
подтверждено исполнением |
| AC4 | smoke untouchedDraw/untouchedFileAgain (до touch) и mixedFile/mixedDrawAgain (после touch) |
подтверждено исполнением |
| AC5 | smoke onboardingFirst/onboardingSecond — второй этаж не наследует touched первого |
подтверждено исполнением |
| AC6 | smoke savedFileDefaults (File create) + editSourcePreservesPair (Edit не сбрасывает при смене source) |
подтверждено исполнением |
| AC7 | smoke cancelDidNotWrite (снапшот _serverCfg до/после Cancel идентичен) + freshAfterCancel |
подтверждено исполнением |
| AC8 | unit «source and display transitions are immutable and preserve unrelated fields» (title/cellCm через spread) + regression smoke_space_settings.mjs (atticSettings побайтно) |
подтверждено исполнением |
| AC9 | mutation-gate.mjs --id=space-create-hidden-display-override: старый hidden override восстановлен патчем, гард обязан покраснеть — исполнено, поймано 1/1 |
подтверждено исполнением, тест доказанно умеет падать |
| AC10 | таблица гейтов выше | подтверждено исполнением |
Отдельно проверено чтением, не исполнением: путь _warmReviveDialog (§2810)
восстанавливает весь снятый снапшот _spaceDialog целиком через spread —
поле displayTouched уже присутствует в снапшоте на момент захвата, разрыва
контракта при warm-remount нет.
Находки
Нет ни High, ни Medium, ни Low.
Что проверено и корректно
- Скрытая Save-подмена
draw && mode==='create' ? true : d.showBorders(и аналог дляshow_names) удалена буквально, а не переименована —src/houseplan-card.tsтеперь пишет ровноd.showBorders/d.showNames. - Общий helper
src/space-dialog.tsиспользуется одинаково обычным Create, onboarding (_openNextImport) и радиокнопками — реализация п. «Onboarding расходится с обычным Create» риска (§11 ТЗ) закрыта одной точкой правды, а не дублированием логики. - Edit-режим стартует с
displayTouched: true, что предотвращает create-only default reset при смене источника существующего пространства. - Мутационный тест не просто присутствует «для галочки»: реально исполнен,
подтверждает что тест красный без патча (
runCleanGuards) и красный именно на мутанте — дисциплина «тест умеет падать» соблюдена. - Три копии бандла синхронны, трейлеры и оба changelog в одном коммите — требования AGENTS.md/PROCESS.md выполнены.
- Терминология
docs/USER-GUIDE.ru.md(«Без изображения») уже существовала в этом разделе до правки — не изобретена, взята из документа. docs/specs/README.md: порядок строк в таблице был не отсортирован по номеру issue уже до этого коммита (не новая проблема, ссылка на #204 вставлена корректно).
Чего не проверял
- Полный browser smoke-набор (127 файлов),
golden:verify,performance_smokeи backend pytest — не запускались; обоснование см. таблицу гейтов выше. Остаются предрелизным гейтом по процессу. - Ручная проверка в реальном браузере (визуальный клик по диалогу) не проводилась — путь доказан Playwright-смоками поверх собранного бандла, что эквивалентно для этой поверхности (нет ручного цикла тестирования на этапе code review по регламенту).
- Touch/keyboard input specifics (real device) не проверялись — ТЗ §8
утверждает общий
change-обработчик для pointer/touch/keyboard, что подтверждено чтением кода (_boolInput— единая точка на<ha-switch>/<input type=checkbox>), не отдельным touch-тестом. docs/specs/040-floor-area-onboarding.mdцеликом не перечитывался повторно на этапе code review — при spec review уже подтверждено отсутствие конфликтующих утверждений про defaults; код (_openNextImport) реализует именно контракт §6 ТЗ #204, что и было предметом проверки здесь.