Files
houseplan-card/docs/reviews/CODE-REVIEW-195-r1.md
2026-08-19 16:38:43 +03:00

22 KiB
Raw Permalink Blame History

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:
    • 140a56f fix: enlarge editor close hit target (Issue: #195, User-Visible: yes)
    • a169cc6 test: 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).

Как проверялось

  1. Прочитан весь 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.
  2. Прочитан docs/SCOPE.md: правка — не новая функциональность, а починка существующего интерфейса выхода из редактора, часть основы J6/UX-MODES («View mode is the product», editors admin-only). В скоупе.
  3. Прочитан docs/UX-MODES.md и docs/USER-GUIDE.ru.md — термин «крестик активного редактора» (USER-GUIDE.ru.md:166) совпадает с формулировкой в изменённых docs/UX-MODES.md, docs/CHANGELOG.ru.md.
  4. Прочитан AGENTS.md/PROCESS.md §5.1, §8, §10 — критерии короткого трека (тип bug, одна поверхность, без миграций/i18n/perf/touch-контракта, AC ≤3, поведение уже зафиксировано) сверены построчно с фактическим diff и issue — выполнены.
  5. Прочитан весь текст issue #195 и все комментарии (gh issue view 195 --comments): исходный отчёт владельца, аналитика автора (К1 подтверждён, К2/К3 — нет), два хендоффа реализации (первый и скорректированный после найденного автором дефекта собственного теста), два упавших инфраструктурно прогона ревью.
  6. Пересчитана вручную 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.
  7. Прогнаны обязательные дешёвые гейты лично (не переиспользована декларация автора) — см. таблицу ниже.
  8. Для каждого нового 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 чист), бандл пересобран заново — три копии совпадают и идентичны варианту до отката.
  9. Независимой ad hoc проверкой (page.evaluate вне закоммиченного смока) измерены getBoundingClientRect() для .hdr, .modes, активной .modetab до и после входа в редактор: высота .modetab (27px) и .modes (35px) не меняется при активации вкладки, а прямоугольник расширенного .closex (24×24) полностью лежит внутри границ своей .modetab (crossOverflowsTabVertically: false, crossOverflowsTabHorizontally: false) — эмпирическое подтверждение расчёта из п.6, устраняющее опасение, что отрицательный margin может вытолкнуть бокс за пределы кнопки или строки табов.
  10. Проверены трейлеры и классы файлов: git show 140a56f --stat / git show a169cc6 --stat — Issue: #195 на обоих, User-Visible: yes только на продуктовом коммите, оба changelog (docs/CHANGELOG.md, docs/CHANGELOG.ru.md) редактируются в том же коммите 140a56f, что и CSS-фикс.
  11. Прочитан второй коммит 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 без возврата на правки.