From b583d663e39407760b391117c0184a6e4a10ac2d Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:42:10 +0000 Subject: [PATCH] docs: review document for #200 Issue: #200 User-Visible: no --- docs/reviews/CODE-REVIEW-200-r1.md | 151 +++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/reviews/CODE-REVIEW-200-r1.md diff --git a/docs/reviews/CODE-REVIEW-200-r1.md b/docs/reviews/CODE-REVIEW-200-r1.md new file mode 100644 index 00000000..b1c90529 --- /dev/null +++ b/docs/reviews/CODE-REVIEW-200-r1.md @@ -0,0 +1,151 @@ +# Код-ревью issue #200 — r1 + +- Issue: [#200](https://github.com/Matysh/houseplan-card/issues/200) +- ТЗ: [docs/specs/200-room-label-parity.md](../specs/200-room-label-parity.md) +- ТЗ-ревью: [SPEC-REVIEW-200-r1.md](SPEC-REVIEW-200-r1.md) — зелёный +- Диапазон: `origin/dev..HEAD` (детач на `origin/issue/200-room-label-parity`), + коммиты `4089c91`, `bf83246`, `cd029a0`, `88a2877` +- Продуктовая реализация: `cd029a0` (`Issue: #200`, `User-Visible: yes`) +- Сопутствующий коммит: `88a2877` (обновление docs-скриншотов, `User-Visible: no`) + +## Вердикт + +**Зелёный · цикл r1/4 · High: 0 · Medium: 0** + +## Скоуп проверки + +Только код-ревью реализации (`cd029a0`, `88a2877`) против принятого ТЗ. +ТЗ и его ревью не пересматривались повторно. Проверялись: + +- продуктовый код `src/houseplan-card.ts`, `src/styles.ts`; +- тесты/гейты: `demo/smoke_room_link.mjs`, `demo/golden/{harness,matrix}.mjs`, + `test/golden-matrix.test.mjs`, `scripts/mutation-gate.mjs`; +- документация: `docs/TESTING.md`, `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`, + `docs/USER-GUIDE.ru.md` (на предмет противоречий), сгенерированные бандлы и + docs-скриншоты. + +## Как проверялось — гейты + +| Гейт | Команда | Результат | +|---|---|---| +| Typecheck | `npx tsc --noEmit` | зелёный | +| Unit | `npm test` | 909 passed, 0 failed | +| Build + 3 копии бандла | `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` | зелёный, все три идентичны | +| Целевой smoke (AC1, AC2, AC3, AC4, AC5, AC6 частично) | `node demo/smoke_room_link.mjs` | зелёный; во всех 4 комбинациях DPR×theme `nameDeltas`/`metricDeltas` = `[0,0,0,0]` | +| Mutation gate, якоря | `node scripts/mutation-gate.mjs --check` | зелёный, все 30 мутантов (включая 2 новых) применяются ровно один раз | +| Mutation gate, мутант 1 (AC9) | `node scripts/mutation-gate.mjs --id=plan-room-area-icon-hidden` | чистый прогон зелёный, на мутанте `demo/smoke_room_link.mjs` **упал**, как обязано | +| Mutation gate, мутант 2 (AC9) | `node scripts/mutation-gate.mjs --id=plan-room-area-icon-navigates` | чистый прогон зелёный, на мутанте `demo/smoke_room_link.mjs` **упал**, как обязано | +| Соседний smoke (AC6, regression) | `node demo/smoke_room_cards.mjs` | зелёный — handles/gear/scale/placeholder-cases не задеты | +| Соседний smoke (AC3, pointer routing regression) | `node demo/smoke_card_tool_conflict.mjs` | зелёный — маршрутизация клика/pointerdown между инструментом и карточкой не сломана | +| Визуальный результат | `npm run golden:verify` | все существующие сценарии — `passed`; 4 новых (`room-label-parity-*`) — `missing-baseline` (ожидаемо, см. ниже) | +| Офлайн process-gate | `node scripts/process-gate.mjs` | «гейт пройден, предупреждений 0» | +| CI-провенанс (по ссылкам автора) | `gh api .../jobs/96101891192` → job `smoke` на SHA `cd029a0` | `success`; run `32263084569` на SHA `88a2877` → `docs`/`provenance`/`process-gate` все `success` | + +Полный browser-smoke набор (127 файлов), `python -m pytest tests_backend` и +performance-профили **не гонялись** локально: + +- backend/Python не тронуты (diff не содержит `custom_components/**/*.py`) — + прогон бэкенд-тестов не нужен; +- полный smoke-набор избыточен: диапазон изменений — рендер и event-routing + одной подсистемы (room-label), целевой smoke плюс два смежных + (`room_cards`, `card_tool_conflict`) покрывают затронутую поверхность; + Linux CI `smoke`-job на этом же SHA (см. таблицу) прошёл целиком; +- performance profiles не названы в AC; риск в ТЗ (§11 спека) обоснован как + пренебрежимый (не добавляются timers/subscriptions/network calls), и + `performance_smoke`-job Linux CI на SHA `cd029a0` зелёный (проверено через + `gh api`, не процитировано автором явно, но подтверждает отсутствие + регрессии). + +## AC — как каждый доказан + +| AC | Доказательство | Итог | +|---|---|---| +| AC1 | `smoke_room_link.mjs`: `view-link-per-area-room`, `plan-keeps-links` — счётчик `.rlgo` равен числу комнат с `area` в обоих режимах, во всех 4 комбинациях DPR×theme | доказано автотестом, тест умеет падать (мутант `plan-room-area-icon-hidden`) | +| AC2 | `smoke_room_link.mjs`: `view-navigates` — перехват `history.pushState`, URL содержит `/config/areas/area/` | доказано автотестом | +| AC3 | `smoke_room_link.mjs`: `plan-does-not-navigate` (клик по иконке + реальный pointer-drag не меняют route/режим) и `plan-icon-drags-label` (drag, начатый на иконке через `page.mouse`, меняет `layout.rl_`) | доказано автотестом, тест умеет падать (мутант `plan-room-area-icon-navigates`) | +| AC4 | `smoke_room_link.mjs`: `core-name-parity`/`core-metrics-parity`, допуск ≤0,5 CSS px — фактические дельты 0 во всех комбинациях | доказано автотестом, оба мутанта дополнительно валят именно этот AC | +| AC5 | Тот же smoke прогнан в light/dark при DPR 1 и 2 (`theme-applied` подтверждает применение темы); `plan-controls-preserved` проверяет, что `.rlhandle`×4 и кнопка настроек не входят в core-измерение | доказано автотестом; golden-review см. ниже | +| AC6 | Диагностика по коду: код, отвечающий за `room.unnamed`-placeholder (`r.name || (this._markup ? this._t('room.unnamed') : '')`), диффом не тронут — проверено чтением, не исполнением. Отсутствие иконки у area-less room — тот же численный inline-инвариант AC1 (счётчик `.rlgo`). Regression прогнаны `smoke_room_cards.mjs` (handles/gear/scale/partial-metrics) и `smoke_card_tool_conflict.mjs` (маршрутизация клика/резайза) — оба зелёные | доказано смесью автотеста и чтения кода | +| AC7 | `docs/CHANGELOG.md`/`docs/CHANGELOG.ru.md` и `docs/TESTING.md` правлены в том же коммите `cd029a0`, что и поведение | проверено чтением diff | +| AC8 | Таблица гейтов выше — все зелёные до постановки `S7-code-review` | доказано | +| AC9 | Два новых entries в `scripts/mutation-gate.mjs`, guard `node demo/smoke_room_link.mjs`; лично прогнаны `--id=plan-room-area-icon-hidden` и `--id=plan-room-area-icon-navigates` — оба: чистый прогон зелёный, на мутанте guard красный | доказано выполнением, не только чтением реестра | + +## Что проверено и корректно + +- **Механизм фикса.** `.rlgo` теперь рендерится всегда, когда `r.area` истинен + (`showAreaLink = !!r.area`), а не только вне `_markup`. Интерактивность + (`title`, `@click`, `@pointerdown`-`stopPropagation`) остаётся только при + `areaLinkInteractive = !this._markup`, то есть **в Plan `pointerdown` с + иконки не глушится и всплывает** в `.roomlabel`, где его подхватывает уже + существующий `_labelDown()` — drag с иконки использует тот же threshold, + pointer capture и сохранение layout, что и drag с текста. Реализует ровно + Q1/AC3 контракт из ТЗ. +- **Геометрический инвариант (AC4/AC5).** Новый приватный метод + `_roomLabelReferenceViewWidth()` (houseplan-card.ts:5011) активен только при + `this._markup` (то есть только в Plan editor — Devices/Background editors и + View не задеты, что соответствует non-scope ТЗ) и вычисляет «гипотетическую» + View-ширину через уже существующий `_viewForModeTarget()`, подставляя + `stage.clientHeight + editorchrome.height` вместо фактической высоты stage. + Через новую CSS-переменную `--rl-icon-size` (`src/styles.ts:876`) это + значение становится базовым `font-size` для `.roomlabel`, от которого через + `em` наследуются `.rlname` (897) и `.rlmetrics` (975, `0.75em`) — то есть + правится не только размер иконки, а вся core-карточка целиком, что и + требует AC4. В View/Devices/Background `_roomLabelReferenceViewWidth` + возвращает `view.w` без изменений — визуальный результат этих режимов + побайтово не меняется, что подтверждает `golden:verify`: все прежние + сценарии (включая `geometry-*`, `lighting-*`) остались `passed` без единого + diff. +- **Числовой smoke** (расширенный `demo/smoke_room_link.mjs`) измеряет + bounding box `.rlname`/`.rlmetrics` относительно центра `.roomlabel` (а не + viewport), что соответствует решению владельца Q2 — chrome/stage-высота не + участвует в сравнении. Проверено выполнением: дельты действительно 0, а не + «в пределах допуска, но не ноль» — запас на реальных условиях больше + требуемого. +- **Mutation gate.** Оба обязательных по ТЗ (§10.3) мутанта зарегистрированы + и лично проверены на способность красить guard в красный — это снимает + главный риск процесса («зелёный тест, который ничего не проверяет»). +- **Трейлеры и changelog.** `cd029a0`: `Issue: #200`, `User-Visible: yes`, + оба changelog правлены в этом же коммите. `88a2877`: `User-Visible: no`, + документация без поведенческих изменений. +- **i18n.** Diff не содержит правок `src/i18n/*.json` — соответствует ТЗ + («новых строк и переводов нет»); в Plan иконка не получает новый + `title`/`role`, что и обещано в §8 ТЗ. +- **`docs/USER-GUIDE.ru.md`** не содержит формулировок, противоречащих новому + поведению (искал по «зон», «rlgo», «перейти к комнате» — совпадений с + описанием этого взаимодействия нет). + +## Находки + +Нет находок High или Medium. Одна Low, снимается без цикла: + +- **Low.** CSS-переменная `--rl-icon-size` (`src/styles.ts:876`, + `houseplan-card.ts:15820`) по имени выглядит как «размер только значка», но + фактически задаёт базовый `font-size` всей `.roomlabel`-карточки — и имени, + и значка, и (через каскад `em`) `.rlmetrics`. Название сбивает при будущем + чтении кода рядом с уже существующим `--icon-size`/`--dev-size`, которые + влияют только на маркеры устройств. Функционально не является дефектом: + поведение проверено численно и совпадает с контрактом AC4/AC5. Снимаю с + записью — переименование не меняет наблюдаемое поведение и не стоит + отдельного цикла ревью. + +## Чего не проверял + +- Полный browser-smoke набор (127 файлов) — прогнаны только целевой + (`smoke_room_link`) и два смежных по поверхности (`smoke_room_cards`, + `smoke_card_tool_conflict`); остальные не относятся к затронутому коду. +- `python -m pytest tests_backend` — backend/Python не тронуты этим diff’ом. +- Golden baseline acceptance — 4 новых сценария `room-label-parity-*` не + имеют эталона (`missing-baseline` в `golden:verify`), что ожидаемо и + соответствует процессу: эталоны принимаются только `npm run golden:accept + -- --reviewed` на полном Linux CI артефакте перед бетой, не в issue-ветке. + Визуально просмотрел сгенерированные `artifacts/golden/actual/room-label- + parity-{view,plan}-{light,dark}.png` — состав и выравнивание карточки + совпадают между View и Plan в обеих темах, дополнительных дефектов не + увидел. +- Ручной тест на реальном touch-устройстве — вне контракта задачи (Plan + editor — best effort по `docs/TOUCH-SUPPORT.md`); pointer-маршрутизация + для touch не отличается от мыши на уровне событий, проверено чтением. +- Performance-профили (`performance_smoke`) не гонялись локально; риск в ТЗ + назван пренебрежимым и подтверждается зелёным `performance_smoke`-job на + SHA `cd029a0` в CI (обнаружено через `gh api`, не было процитировано + автором в хендоффе).