22 KiB
CODE-REVIEW-195-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/195
- Трек:
trivial(короткий, §5.1 PROCESS.md) — ТЗ живёт в теле issue, AC записаны автором в блоке «Диагностика и AC короткого трека (2026-08-19)» до перевода вS5-ready. Файлаdocs/specs/195-*.mdнет и не требуется. - Диапазон:
git log --oneline origin/dev..HEAD/git diff origin/dev...HEAD— два коммита на веткеissue/195-editor-close-hit-target:140a56ffix: enlarge editor close hit target(Issue: #195,User-Visible: yes)a169cc6test: sample editor close glyph before removal(Issue: #195,User-Visible: no)
- Роль: ревьюер кода (не автор), этап
S7-code-review - Цикл: r1/2 (лимит короткого трека). Два предыдущих запуска автоматического конвейера (comments 2026-08-19T10:47:37Z, 10:51:30Z) упали инфраструктурно, без публикации вердикта — по §4 PROCESS.md это не расходует цикл.
Скоуп ревью
Диагностика в issue (аналитика 2026-08-19) подтвердила ровно одну причину
бага «крестик не закрывает редактор с первого клика» — К1: фактическая
DOM hit-zone .modetab .closex равна 13×13 px, промах в 2–3 px попадает в
кнопку активной вкладки и превращается в документированный no-op
(docs/UX-MODES.md). Кандидаты К2 (незавершённая цепочка стен) и К3 (клик во
время перехода режима) не подтвердились и явным решением автора не
переписываются — только закрепляются регрессионными проверками. Ревью
проверяло:
- что продуктовая правка ограничена CSS
.modetab .closexи не касается_setMode/_finishWallChain(иначе трекtrivialбыл бы неверным выбором и надо было возвращать issue вS3-spec, §5.1); - что новый hit-target
≥24×24 pxне сдвигает и не меняет высоту/перенос header modes-строки (контракт AC1,docs/UX-MODES.md); - что все три AC действительно доказаны исполняемым тестом, а не только заявлены, и что этот тест умеет падать;
- трейлеры, классы файлов, оба changelog в одном
User-Visible: yesкоммите (§10 AGENTS.md).
Как проверялось
- Прочитан весь diff:
git diff origin/dev...HEAD --statи файлы по отдельности. Продуктовый код класса A — толькоsrc/styles.ts, 10 изменённых строк в одном селекторе.modetab .closex.src/houseplan-card.tsне тронут (git diff origin/dev...HEAD -- src/houseplan-card.ts— 0 строк), что подтверждает решение автора не переписывать К2/К3. - Прочитан
docs/SCOPE.md: правка — не новая функциональность, а починка существующего интерфейса выхода из редактора, часть основы J6/UX-MODES («View mode is the product», editors admin-only). В скоупе. - Прочитан
docs/UX-MODES.mdиdocs/USER-GUIDE.ru.md— термин «крестик активного редактора» (USER-GUIDE.ru.md:166) совпадает с формулировкой в изменённыхdocs/UX-MODES.md,docs/CHANGELOG.ru.md. - Прочитан
AGENTS.md/PROCESS.md§5.1, §8, §10 — критерии короткого трека (типbug, одна поверхность, без миграций/i18n/perf/touch-контракта, AC ≤3, поведение уже зафиксировано) сверены построчно с фактическим diff и issue — выполнены. - Прочитан весь текст issue #195 и все комментарии (
gh issue view 195 --comments): исходный отчёт владельца, аналитика автора (К1 подтверждён, К2/К3 — нет), два хендоффа реализации (первый и скорректированный после найденного автором дефекта собственного теста), два упавших инфраструктурно прогона ревью. - Пересчитана вручную CSS-геометрия нового правила
.modetab .closex(width/height: 24px,box-sizing: border-box,margin: -5.5px -5.5px -5.5px -3.5px): при неизменномgap: 6pxмежду flex-элементами.modetabитоговая позиция 13-пиксельного глифа (центрируемогоjustify-content/align-items: centerвнутри нового бокса) совпадает пиксель-в-пиксель со старой позицией (глиф равен border-box старого правила13×13+margin-left: 2px), а суммарный вклад элемента в поток (width + marginLeft + marginRight = 15px,height + marginTop + marginBottom = 13px) идентичен старому. Проверено чтением/расчётом, не исполнением — дополнительно подтверждено эмпирически в п.9. - Прогнаны обязательные дешёвые гейты лично (не переиспользована декларация автора) — см. таблицу ниже.
- Для каждого нового AC-утверждения в
demo/smoke_editor_tabs.mjsцеленаправленно откачен продуктовый CSS-фикс (git show 140a56f^:src/styles.tsвосстановлен во временную рабочую копию, бандл пересобран и скопирован вdemo/srv/assets/) и подтверждено, что смок падает именно на новой проверкеtabCrossTargetsAtLeast24(FAILED (1): tabCrossTargetsAtLeast24: expected true, got false, exit code 1); затем рабочее дерево восстановлено к состоянию коммита (git statusчист), бандл пересобран заново — три копии совпадают и идентичны варианту до отката. - Независимой ad hoc проверкой (page.evaluate вне закоммиченного смока)
измерены
getBoundingClientRect()для.hdr,.modes, активной.modetabдо и после входа в редактор: высота.modetab(27px) и.modes(35px) не меняется при активации вкладки, а прямоугольник расширенного.closex(24×24) полностью лежит внутри границ своей.modetab(crossOverflowsTabVertically: false,crossOverflowsTabHorizontally: false) — эмпирическое подтверждение расчёта из п.6, устраняющее опасение, что отрицательный margin может вытолкнуть бокс за пределы кнопки или строки табов. - Проверены трейлеры и классы файлов:
git show 140a56f --stat/git show a169cc6 --stat—Issue: #195на обоих,User-Visible: yesтолько на продуктовом коммите, оба changelog (docs/CHANGELOG.md,docs/CHANGELOG.ru.md) редактируются в том же коммите140a56f, что и CSS-фикс. - Прочитан второй коммит
a169cc6целиком: исправляет реальный дефект теста (computed style глифа читался после клика, когда активный.closexуже удалён из DOM) — не сокрытие, а корректная правка измерения, аналогично прецеденту #89 (правка фикстуры/измерения при доказанном дефекте в нём самом, не в проверяемом коде).
Обязательные гейты (всегда)
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
чисто, без вывода |
| Unit-тесты | npm test |
900/900, 0 fail |
| Build + сверка бандлов | npm run build затем cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js и cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js |
оба cmp без вывода — три копии идентичны; git status --short после сборки пуст (рабочее дерево уже содержало актуальный бандл) |
Falsifiability (тест должен уметь падать), применена к прогнанному browser smoke:
- временно восстановлена дореформенная версия
.modetab .closex(margin-left: 2px, безwidth/height: 24px) →demo/smoke_editor_tabs.mjsкорректно упал ровно на новой проверкеtabCrossTargetsAtLeast24(exit code 1); остальные 47 проверок остались зелёными, что ожидаемо — они не зависят от размера hit-zone. Код восстановлен, бандл пересобран, три копии совпадают,git statusчист.
Гейты по необходимости
| Гейт | Почему запускался / не запускался | Результат |
|---|---|---|
node demo/smoke_editor_tabs.mjs |
назван прямо в AC1–AC3 issue как способ доказательства, единственный смок из 127, затрагивающий изменённую поверхность (.modetab, header editor tabs) |
зелёный, все 48 проверок true; falsifiability подтверждена выше |
Остальные 126 demo/smoke_*.mjs |
diff — один CSS-селектор на header editor tabs, не затрагивает остальные поверхности (canvas, диалоги, устройства и т.д.); полный прогон соразмерен только задачам, задевающим всё (PROCESS.md §8) | не прогонялись — сознательное сужение |
npm run golden:verify |
AC1 требует «геометрия/переносы header modes не меняются». Ручной пересчёт CSS-геометрии (п.6) и эмпирическая проверка рендера (п.9) показывают, что видимая позиция 13 px глифа и размеры .modetab/.modes пиксель-в-пиксель совпадают со старой версией — видимый результат не меняется, только невидимая hit-zone. Существующих golden-сценариев, специально нацеленных на header modes tabs, в матрице нет (grep по demo/golden/* ничего не нашёл) |
не запускался — обоснованное сужение, задокументировано как решение, а не молчаливый пропуск |
python -m pytest tests_backend -q |
diff не касается custom_components/**/*.py (git diff --stat подтверждает) |
не запускался — неприменим |
| performance-профили | AC не называют производительность, правка — статический CSS без анимаций/новых расчётов в hot path | не запускались — неприменимо |
Что проверено и корректно
- AC1 (DOM + browser smoke, hit-zone ≥24×24, глиф 13 px, layout не
меняется). Для всех трёх активных вкладок (
Plan editor,Device editor,Background editor).closex.getBoundingClientRect()даёт24×24,--mdc-icon-sizeостаётся13px, а клик по расширенной зоне вне старого 13 px глифа одним действием переводит карточку вview(tabCrossTargetsAtLeast24,tabCrossGlyphStays13,tabCrossExpandedEdgeWorks,tabCrossWorks— всеtrue, п.8 подтверждает падение до фикса). Геометрия header-строки не меняется — подтверждено расчётом (п.6) и независимым измерением (п.9): высота.modetab/.modesидентична до/после активации вкладки, расширенный бокс.closexне выходит за границы своей кнопки. - AC2 (клик по активной вкладке вне hit-zone остаётся no-op; клик X во время
_modeTransitionBusyзавершает переход).reclickNoopподтверждает общий no-op контракт клика по кнопке вкладки (не изменён этой правкой — обработчик_setMode(m)с ранним return по-прежнему единственный путь). Новые проверкиtabCrossCloseStartsDuringEnter/tabCrossCloseDuringEnterWorksподтверждают, что клик по.closexвходящей (ещё анимирующейся) вкладки завершает переход вview— воспроизводит вывод аналитики «К3 не подтвердился» тестом, а не только текстом. - AC3 (незавершённая Walls chain закрывается тем же кликом; при
MAX_PARTITIONSредактор остаётся открыт с тостом, без потери draft).tabCrossFinishesWallChainподтверждает материализацию двухсегментного черновика вpartitionsи переход вviewтем же кликом по X;tabCrossLimitKeepsDraftWithFeedbackподтверждает, что при насильно переполненномpartitions(new Array(2000)) карточка остаётся вplan, черновик (_path.length === 2) не теряется, и показывается существующий локализованный тостtoast.physical_limit. Дополнительно подтверждено чтением:src/houseplan-card.tsне изменился, значит guard_finishWallChain/MAX_PARTITIONS— тот же код, что был в dev, поведение не переписано, только закреплено регрессией. - Причина бага устранена корректно, а не замаскирована. Обработчик
.closex(houseplan-card.ts:15484-15486) — отдельный элемент сstopPropagation; расширение именно его border-box до24×24— это расширение реальной кликабельной area браузера для этого узла, а не хак через::before/псевдоэлемент с ручным перехватом координат. Решение устраняет саму причину К1 (промах координат в родительскую кнопку), а не прячет симптом. - Второй коммит (
a169cc6) — легитимная правка теста. Дефект («computed style глифа читался после клика, когда.closexуже удалён из DOM») найден и исправлен самим автором до передачи на ревью; исправление меняет порядок измерения, а не ослабляет проверку — эквивалент прецедента #89 (правка доказанного дефекта в фикстуре/измерении, не сокрытие). - Трейлеры/классы файлов/changelog.
140a56f:Issue: #195,User-Visible: yes,docs/CHANGELOG.mdиdocs/CHANGELOG.ru.mdв том же коммите, три копии бандла (dist/,custom_components/houseplan/frontend/,demo/srv/assets/) синхронны.a169cc6:Issue: #195,User-Visible: no, касается толькоdemo/smoke_editor_tabs.mjs— корректно без changelog. - Терминология.
docs/CHANGELOG.md/.ru.md,docs/UX-MODES.md,docs/STATUS.mdиспользуют формулировку, совпадающую сdocs/USER-GUIDE.ru.md:166(«крестик активного редактора»), новых терминов интерфейса не изобретено. - Скоуп не расширен. Продуктовый diff — 10 строк в одном CSS-селекторе; К2/К3 не переписаны, toggle-поведение активной вкладки не введено (это было явно продуктовым вопросом в issue и аналитик решил его сам — «Toggle-поведение активной вкладки не вводить» — корректно, т.к. это решение не наблюдаемо пользователем как новое поведение, а лишь выбор одного из предложенных в issue направлений фикса, не требующий эскалации владельцу).
Находки
Находок нет. High: 0, Medium: 0, Low: 0.
Чего не проверял
- Полный набор
demo/smoke_*.mjs(127 файлов). Diff — один CSS-селектор на header editor tabs; остальные смоки проверяют не связанные поверхности (canvas, диалоги устройств, декор, стены и т.д.), которые эта правка не трогает. Прогон толькоsmoke_editor_tabs.mjs, единственного смока по затронутой поверхности — соразмерное решение по PROCESS.md §8. npm run golden:verify. Не запускался. Обоснование — не молчаливый пропуск: ручной пересчёт CSS box-модели (п.6) и независимое измерение реального рендера (п.9) показывают, что видимая позиция глифа и размеры header modes-строки не меняются ни на пиксель, то есть критерий «менялся визуал» из PROCESS.md §8 не выполнен для этой конкретной правки. Полный golden-прогон всё равно случится перед бетой (предрелизный гейт) и покажет любое расхождение, если этот анализ ошибочен.python -m pytest tests_backend -qи performance-профили. Не применимы — diff не касаетсяcustom_components/**/*.pyи не имеет измеримого влияния на производительность (статический CSS, без изменений в hot path рендера/анимаций).- Multi-touch/реальное тач-устройство. Issue и аналитика прямо
зафиксировали, что touch-контракт не меняется (
docs/TOUCH-SUPPORT.md: editors — desktop-first, best effort);24×24— WCAG-минимум, названный в самом issue, не HA-тач-рекомендация 40 px, и это соответствует заявленному контракту, а не занижает его.
Вердикт
Зелёный. High: 0, Medium: 0. Все три AC доказаны исполняемым
demo/smoke_editor_tabs.mjs, ревьюер лично прогнал смок, подтвердил падение
целевой проверки на дореформенном CSS и убедился, что после восстановления
фикса дерево идентично коммиту. Продуктовая правка ограничена одним CSS-
селектором, не переписывает подтверждённо-нерелевантные пути (К2/К3),
геометрия header modes-строки пиксель-в-пиксель сохранена (проверено расчётом
и независимым измерением). Трейлеры, классы файлов и оба changelog в
одном User-Visible: yes коммите — в порядке. Задача готова к слиянию в
dev без возврата на правки.