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

230 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` без возврата на правки.