Files
houseplan-card/docs/reviews/CODE-REVIEW-86-r1.md
T
2026-08-30 14:49:08 +03:00

243 lines
21 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-86-r1
- **Issue:** #86 — Тексты подсказок к настройкам, партия 1
- **ТЗ:** `docs/specs/086-settings-help-content-party1.md` (актуализировано 2026-08-30, зелёный SPEC-REVIEW-86-r1)
- **SHA материала:** `bd27fca107f61eee74d69bffeb1e15506402b6a4` (`origin/dev...HEAD`, diff трёх коммитов 97c807ba/58fb2ef5/bd27fca1)
- **Трек:** полный (нарушен критерий light-track «одна поверхность», сам автор это фиксирует в шапке ТЗ)
- **Заход:** r1 · блокирующих циклов израсходовано 0/4
## Скоуп
Diff добавляет 11 согласованных Party-1 `hp-help` подсказок (масштаб пространства,
общий Glow-радиус, режим заливки, роль источника света — обновлён текст пилота,
управление другими источниками, общий/локальный север, общий/локальный фон, стиль
стен нулевой толщины, «Показывать скрытые на плане») в `houseplan-editor-runtime.ts`
и параллельно — 5 space-подсказок в `houseplan-onboarding-runtime.ts`; переводит их
на en/ru/de/fr; удаляет 4 старых `.rhint`; добавляет unit-тесты инвентаря/parity/
no-duplicate, мутационный тест, один браузерный смок и правки трёх документов
(`CHANGELOG`×2, `USER-GUIDE.ru.md`, `TESTING.md`). Он же трогает
`docs/images/screenshots.json` (без файлов картинок).
## Как проверялось
Прогнано лично на SHA `bd27fca1` (окружение содержит Node 22 и Chromium, `npm ci`
не требовался):
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | зелёный |
| Unit | `npm test` | 1607 тестов, 1606 pass, 1 skip (тот же тест, что скипается и на `origin/dev`, к #86 не относится), 0 fail |
| Build + 3 копии бандла | `npm run build` затем `cmp dist/houseplan-card.js custom_components/.../houseplan-card.js` и `cmp dist/houseplan-assets.json custom_components/.../houseplan-assets.json` | идентичны |
| Bundle budget | `npm run bundle:budget` | initial View 280 313 B гзип, бюджет 300 000 — в норме |
| check-docs | `node scripts/check-docs.mjs` | зелёный (**но см. находку H1** — зелёный статус здесь ложноположителен) |
| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 12 «прямое совпадение», 18 «слабая связь» (см. ниже) |
| Смоки прямого совпадения (12/12) | `node demo/smoke_help_affordance.mjs`, `smoke_bg_color`, `smoke_color_picker_consumers`, `smoke_device_inbox`, `smoke_dialog_zombie`, `smoke_disabled_device`, `smoke_esc_dialogs`, `smoke_gs_always`, `smoke_ha_controls`, `smoke_hidden_flag`, `smoke_motion_sense`, `smoke_sun` | все 12 зелёные |
| Мутационный гейт задачи | `node scripts/mutation-gate.mjs --id=settings-help-party1-placement-removed` | мутант пойман (`node --test` покраснел, как обязан) |
| **Golden** | `npm run golden:verify` | **красный, exit 1** — см. находку H2 |
**Что не прогонял и почему:**
- 18 «слабых связей» из `smoke-select` (общее имя `_config`) — не прогонял: каждая
из них про геометрию/локали/optimize, не про диалоги настроек, диф не касается их
логики, и после прогона всех 12 прямых совпадений сигнала для расширения нет.
- `python -m pytest tests_backend -q` — не прогонял: diff не касается
`custom_components/**/*.py`, backend не затронут (среда ревью к тому же не имеет
установленного pytest — `No module named pytest`, что подтверждает отсутствие
окружения, а не маскирует красный результат: гейт неприменим по diff).
- Остальные 193 браузерных смока — задача не задевает геометрию/логику модели
целиком, полный прогон здесь избыточен (PROCESS §8: полные наборы — предрелизный
гейт).
- `model-invariants` — diff не трогает геометрию, ссылки, `layout`, `marker.space`
или `open_spans`; не применимо.
- Реальный CI Validate на этом SHA не найден зелёным — прогнал дешёвые гейты сам,
как и предписано.
## Находки
### H1 — Манифест скриншотов документации подделан, а не пересчитан (High, в скоупе)
`docs/images/screenshots.json` в коммите `bd27fca1` меняет `sourceFingerprint`
(`d77faa6e…` → `401722…`) и все десять `sourceSha256` на то же новое значение —
но ни один PNG в `docs/images/**` в этом коммите не изменился
(`git show bd27fca1 --stat -- docs/images/` показывает только правку JSON,
0 файлов картинок). Я запустил `npm run build` и `node scripts/check-docs.mjs` на
HEAD — гейт зелёный, потому что зафиксированный `sourceFingerprint` теперь
математически совпадает с текущим `src/**`. То есть кто-то отдельно посчитал
`visualFingerprint()` и вписал результат в JSON руками, не выполнив настоящую
пересъёмку.
Это прямо запрещённый в проекте путь. `scripts/docs-accept.mjs` (единственный
легитимный писатель этого файла) копирует новые PNG из артефакта CI
`Docs screenshots` и **не может** обновить манифест без файлов: он сверяет
`imageSha256` каждого кадра с байтами кандидата и требует `--reviewed`.
Исторический прецедент в этом же репозитории — коммиты «`docs: refresh screenshot
fingerprint`» (например `b1b1e743`) — **всегда** одновременно переписывают все
десять PNG (см. `git show b1b1e743 --stat`), потому что даже визуально идентичный
кадр даёт другие байты при пересъёмке (комментарий в `docs-accept.mjs` объясняет
это явно). Здесь PNG не тронуты вовсе — списанный ярлык не соответствует
содержимому.
Хуже того, содержимое двух сценариев из этого манифеста **действительно
устарело**: `space-create` (`fixture: 'empty'` → `card._openSpaceDialog('create')`
→ рендерится ровно тот `_renderSpaceDialog()` из
`houseplan-onboarding-runtime.ts`, куда этот diff добавил 5 подсказок) и
`device-editor` (`dialog: 'device'` → маркер-диалог, где `marker.controls_label` +
`.rhint` заменены на `helpfieldlabel` с новой иконкой) должны показывать новые
значки подсказок — а показывают старую картинку без них. Спецификация §17 прямо
требует «актуальные screenshots» как release-артефакт; вместо этого CI-гейт
`docs` обманут значением, которое ему не принадлежит.
**Как воспроизвести:** `git show bd27fca1 --stat -- docs/images/` → 0 изменённых
`.png`; `git show b1b1e743 --stat` → для сравнения, легитимный рефреш всегда меняет
все 10 файлов.
**Что нужно:** прогнать GH Actions `Docs screenshots` (`workflow_dispatch`),
скачать артефакт, `npm run docs:accept -- --reviewed --from=<artifact>`, отдать
на ревью человеку и закоммитить обновлённые PNG вместе с манифестом. Понижать
трек до «просто поправить фингерпринт» — то самое сокрытие, которое AGENTS.md
называет запрещённым по отношению к golden-эталонам и что не менее верно для
docs-скриншотов.
### H2 — `npm run golden:verify` красный: 6 эталонов расходятся с рендером (High, в скоупе)
Прогнал `npm run golden:verify` на `bd27fca1`: `exitCode=1`. Расхождения:
```
different device-inbox-desktop-en-light
different device-inbox-desktop-ru-dark
different device-inbox-narrow-ru-dark
different toggle-entity-dialog-desktop-en
different toggle-entity-dialog-mobile-ru
different space-room-color-popover-desktop-ru
```
Все шесть напрямую объясняются этим diff'ом, не совпадением имени символа:
- `device-inbox-*` — сценарии `dialog: 'device-inbox'` (`demo/golden/matrix.mjs:383-392`)
снимают ровно тот блок, где чекбокс «Показывать скрытые на плане» обёрнут в
новый `<span class="device-inbox-filter-help">` с триггером
`device_inbox.show_hidden.help` (`houseplan-editor-runtime.ts:11815-11825`).
- `toggle-entity-dialog-*` — сценарии `dialog: 'device'` для `golden-washer`
(`matrix.mjs:765-778`) открывают тот же маркер-диалог, где
`marker.controls_label` + постоянный `.rhint` заменены на
`<div class="helpfieldlabel">` с триггером `marker.controls.help`
(`houseplan-editor-runtime.ts:12588-12592`) — сдвиг разметки докатывается вниз до
`toggle_entity`-секции того же диалога.
- `space-room-color-popover-desktop-ru` — сценарий `dialog: 'space-room-color'`
(`matrix.mjs:795-797`) открывает попап цвета комнаты в диалоге пространства,
выше которого теперь стоит новый `helpfieldlabel` для `space.fill_mode.help`.
Ни один `demo/golden/baselines/**` файл в diff'е не менялся
(`git diff --stat origin/dev...HEAD -- demo/golden/` — пусто), значит эти шесть
эталонов не пересматривались вовсе. Хендофф-комментарий автора в issue
(«Проверки: npm test… smoke_help_affordance… mutation… docs и bundle budget — OK»)
`golden:verify` не упоминает — гейт не прогонялся.
Отдельно (не блокирует и не относится к #86): `furniture-transform-light/dark` —
`missing-baseline`, но baseline для них отсутствует в дереве и на `origin/dev` тоже
— унаследованный, не внесённый этим diff'ом пробел, к задаче не относится.
**Как воспроизвести:** `npm run bundle:sync && npm run golden:verify` на
`bd27fca1` → `EXIT=1`, шесть `different`.
**Что нужно:** просмотреть `artifacts/golden/diff/{device-inbox-*,
toggle-entity-dialog-*, space-room-color-popover-desktop-ru}.png`, убедиться, что
новая раскладка — то, что задумано (правдоподобно, раз это ровно новые триггеры
подсказок), и принять через `npm run golden:accept -- --reviewed` на полном
Linux CI-артефакте. ТЗ §14 прямо планировал «Golden: general settings and space
dialog with help open… full Linux artifact review required» — этот пункт плана
не выполнен, а не просто пропущен по недосмотру мелочи.
### M1 — Onboarding-путь 5 space-подсказок не доказан исполнением (Medium, в скоупе)
ТЗ §8 требует: «Пять space-подсказок присутствуют и в обычном editor runtime, и в
отдельном onboarding runtime… их текст и поведение идентичны», а §14 планирует
браузерный смок «по одному placement на… space». `demo/smoke_help_affordance.mjs`
целиком работает через обычный редактор (`card._openSpaceDialog('edit', card._space)`),
и ни один смок/golden не открывает cold-onboarding путь
(`_openSpaceDialog('create')` при пустом `_serverCfg.spaces`), который ведёт в
`HouseplanOnboardingRuntime._renderSpaceDialog()` с отдельной, скопированной
реализацией `_help()` (`houseplan-onboarding-runtime.ts:54-60`). Единственная
проверка, которая реально открывала бы этот путь в браузере — сценарий
`space-create` документационных скриншотов — сам оказался несвежим (H1).
Статический regex-тест в `test/i18n.test.mjs` (`helpSource` = конкатенация двух
файлов) доказывает только, что строка `this._help('space.xxx.help')` присутствует
в исходнике `houseplan-onboarding-runtime.ts`, а не что она реально рендерится,
не мутирует `_spaceDialog` и работает мышью/клавиатурой/тапом в этом отдельном
компоненте.
Прочитал код: `_help()` в онбординге текстуально идентичен варианту в
`houseplan-editor-runtime.ts`, импорт `./hp-help` добавлен, сигнатура и порядок
аргументов совпадают — на вид рабочий. Но это «проверено чтением, не
исполнением», а не автотест, и явно заявленного как такового в
`docs/reviews`/handoff нет. Учитывая, что H1 обесценил единственный кандидат на
исполняемое доказательство, AC5/AC7 для onboarding-поверхности сейчас не доказаны
никак, кроме моего чтения.
**Что нужно:** либо добавить в `demo/smoke_help_affordance.mjs` (или отдельный
файл) сценарий, открывающий `_openSpaceDialog('create')` из пустого состояния и
повторяющий read-only/inventory проверки для всех пяти onboarding-триггеров, либо
явно зафиксировать в ТЗ/тестовом плане, что onboarding-путь покрывается только
docs-скриншотом — и тогда почему это допустимо. Совместно с H1 естественно
чинится одним заходом: настоящая пересъёмка `space-create` подтвердит хотя бы
рендер, а отдельный смок — поведение.
### L1 — `.help`-ключ не совпадает буквально с ключом видимого label (Low, снимается без правки)
Унаследовано из ревью ТЗ (SPEC-REVIEW-86-r1): `space.cell_cm.help` соседствует с
`space.scale_label`, `space.fill_mode.help` — с `space.fill_label`. Не создаёт
двусмысленности контракта, тот же паттерн уже был у пилотов #68, находится в
рамках технической свободы реализации по PROCESS.md §7.1. Оставляю без правки.
## Что проверено и корректно
- **AC1 (инвентарь).** `grep -oE "_help\('[^']+\.help'\)"` по обоим рантаймам даёт
ровно 11 Party-1 ключей плюс уже существующие 8 пилотных `marker.*` ключей из
#68 — новых мест за пределами §6 нет. Пять `space.*`-ключей продублированы
один-в-один в `houseplan-onboarding-runtime.ts`. Доказано и чтением, и тестом
`issue 86 Party 1 has the exact help inventory` (`test/i18n.test.mjs`) — тест
запускался (`npm test`, зелёный) и умеет падать: мутационный гейт
`settings-help-party1-placement-removed` подтверждает (прогнал лично).
- **AC2/AC3 (канонический текст, parity).** RU/EN тексты в `en.json`/`ru.json`
побайтово совпадают с таблицей §6 ТЗ — сверил вручную все 11 строк. DE/FR
прочитаны целиком (см. выдержку выше) — передают тот же смысл, не пропускают
предложений. `.help.aria` пары присутствуют во всех 4 словарях, тест
`i18n: every literal help call has body and full aria keys in every language`
прогонялся и прошёл.
- **AC4 (без дублей).** `marker.controls_hint`, `gs.bg_daynight_hint`,
`gs.north_hint`, `space.zero_wall_help` удалены из всех 4 словарей и из обоих
рантаймов — подтверждено тестом `issue 86 removes explanatory hints…` (прошёл)
и `grep` лично.
- **AC6 (input/a11y).** Переиспользует lifecycle #68 без форка
(`import './hp-help'`, тот же `_help()` контракт); `smoke_help_affordance.mjs`
(прогнал, зелёный) покрывает hover/focus/click/Escape/disabled/Popover-fallback
для нового содержимого партии наравне со старым пилотом.
- **AC7 (узкий viewport 390 px).** `party1ShowHiddenHelpInsideNarrowViewport` в
смоке проверяет геометрию попапа в 390 px — прошло. 200%-zoom и «reviewed
golden» часть AC7 не доказана — см. H2.
- **AC8 (модель не меняется).** Прочитан весь diff `src/**` — ни одного изменения
вне JSX/CSS шаблонов и двух новых `_help()`-методов; `strictNumber`,
`touchSpaceDisplay`, обработчики `@change`/`@input` не тронуты. `npm test`
(1606/1607 pass) включает существующие config-serialization снапшоты — без
регрессий.
- **AC9 (партии 2/3 отсутствуют).** Diff `src/**` просмотрен построчно — нет ни
одного `_help()` вне §6 сверх уже существующих `marker.*` пилотов; в i18n-файлах
нет добавленных ключей `opening.*`, `wallthick.*`, `decor.*`,
`device.binding.*` и т. п. из партий 2/3.
- **Гейты общего назначения.** typecheck/test/build/bundle-sync/bundle-budget/
check-docs (формально) — все зелёные, три копии бандла идентичны.
- **Одно число — один источник.** Diff не добавляет и не меняет ни одной
пользовательской величины (только текст подсказок) — правило неприменимо.
- **Трейлеры.** `Issue: #86` на всех трёх коммитах; `User-Visible: yes` в
`bd27fca1` — оба changelog (`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`) правлены
в этом же коммите, формулировки на месте.
## Вывод
Контент-часть партии 1 (11 подсказок, переводы, source contract, mutation-тест)
выполнена аккуратно и проходит собственные unit- и smoke-гейты. Но два
предрелизных для видимого рендера гейта, обязательных именно для этой задачи —
`docs`-скриншоты и `golden` — либо подделаны (H1), либо действительно красные и
не прогонялись (H2). Оба находятся в скоупе issue (сам diff их и ломает) и
требуют настоящей пересъёмки/приёмки, а не текстовой правки. Пока это не сделано,
вердикт не может быть даже жёлтым.