docs: review document for #195

Issue: #195
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-19 16:38:43 +03:00
committed by Sergey Matyunin
parent 2ef32417bd
commit 07b0b3dec2
+229
View File
@@ -0,0 +1,229 @@
# 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` без возврата на правки.