Files
houseplan-card/docs/reviews/CODE-REVIEW-219-r1.md
T
2026-08-20 12:09:51 +00:00

186 lines
17 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 #219 — r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/219
- **Spec:** `docs/specs/219-lock-orange-palette.md`, зелёное `SPEC-REVIEW-219-r2.md`
- **Reviewed branch:** `issue/219-lock-orange-palette`
- **Reviewed range:** `origin/dev..HEAD` = `28d6b9c`…`84fa434` (ТЗ и ревью ТЗ,
уже приняты на этапе spec) → `bc75e00` (единственный продуктовый коммит)
- **Base:** `origin/dev` at `b56e122`
- **Reviewer:** Claude, независимая сессия без контекста реализации
## Вердикт
**Зелёный · цикл r1/4 · High: 0 · Medium: 0.**
Единственный продуктовый коммит `bc75e00` заменяет старую black/amber
lock-палитру на red/green и унифицирует foreground всех цветных подложек
(`on`/`open`) по теме. Изменение точечное и декларативное — только
`src/styles.ts` — и покрыто исполняемыми contract-тестом и browser-smoke,
которые я лично прогнал, а contract-тест дополнительно проверил на
способность падать. Все 7 AC закрыты либо автотестом, либо чтением кода с
явной пометкой. Одна Low-находка снята без правки.
## Скоуп
Продуктовый файл — `src/styles.ts`:
- `.oplock.locked`/`.oplock.unlocked` (compact opening lock badge) и
`.dev.lock-locked`/`.dev.lock-unlocked` (обычный marker) получили core/stroke
`#66D17A`/`#F0410C` вместо `light-dark(#000,#252525)`/`#F0A00C`; foreground
переведён на общий паттерн `light-dark(#fff,#252525)` + explicit
`.theme-light`/`.theme-dark` overrides, как уже сделано у `.dev.on`;
- `.dev.open` получил тот же foreground-паттерн вместо постоянного `#4a2800`,
плюс явные `.dev.theme-light.open`/`.dev.theme-dark.open`;
- ни geometry, ни stroke-ratio, ни selector order, ни alarm/hover/focus/
selected/unavailable/virtual/press-правила не тронуты — весь остальной файл
вне двух изменённых блоков идентичен `dev`.
Сопутствующие изменения: `test/device-marker-polish-contract.test.mjs` (новый
contract-тест issue 219), `demo/smoke_device_icon_design.mjs` (новый
`openingLockPaletteMatches`/`orangeGlyphsFollowTheme`/
`darkLockUsesGreenCoreAndDarkGlyph`, обновлённый reference override),
`demo/capture_device_icon_reference.mjs` + `demo/srv/reference/device-icons/README.md`
(runtime-override red/green поверх byte-identical designer SVG),
`demo/golden/matrix.mjs` + `test/golden-matrix.test.mjs` (сцена
`device-icon-state-table-*` теперь показывает `open` рядом с `on` и обоими
lock-состояниями), `docs/TESTING.md`, `docs/USER-GUIDE.md` +
`docs/USER-GUIDE.ru.md` (раздел «Замок» и таблица постоянных статусов —
заменяемый black/amber текст убран), оба changelog, `docs/specs/README.md`,
`docs/images/screenshots.json` (обновлён только `sourceFingerprint`, все 10
`imageSha256` без изменений), три сгенерированные копии бандла.
Ровно один продуктовый коммит `bc75e00`, трейлеры `Issue: #219` /
`User-Visible: yes` на месте, оба changelog в том же коммите, документация
(USER-GUIDE, TESTING) в том же коммите, что и поведение. Ветка
`issue/219-lock-orange-palette` соответствует правилу именования.
## Как проверялось
| Гейт | Результат |
|---|---|
| `npx tsc --noEmit` | pass |
| `npm test` | **962/962 pass**, 0 skipped в этом окружении (`npm run inventory` подтверждает канонический счётчик 962 Node unit); хендофф указывает «961 passed, 1 skipped» — расхождение объяснимо `test/process-gate.test.mjs`, где 1 подтест условно `t.skip('нужен исполняемый stub gh')`/«git недоступен» в зависимости от окружения автора, файл не затронут этим диффом |
| `npm run build` + сверка трёх копий бандла | pass, все три `sha256` = `9c7df844ce825c0d56cfb65e82a0fbbb98620428d5aa5806abec0c40b1d9b1a2`, совпадает с хендоффом |
| `node --test test/device-marker-polish-contract.test.mjs` (targeted contract, назван в AC1/AC2/AC3) | pass (8/8) |
| Временный откат `--oplock-core-bg: #66D17A` → `#000` и повтор того же теста | **тест `issue 219 gives locks red-open green-closed…` красный** — дисциплина «тест умеет падать» подтверждена мной, а не только на слово автора |
| `node --test test/golden-matrix.test.mjs` | pass (26/26), включая новую проверку `deviceClassOverrides['golden-right-linkquality'] === ['open']` |
| `node demo/smoke_device_icon_design.mjs` (назван в AC1/AC3, хендоффе) | pass, все поля true, включая новые `openingLockPaletteMatches`, `orangeGlyphsFollowTheme`, `darkLockUsesGreenCoreAndDarkGlyph` |
| `node demo/smoke_device_preview_parity.mjs` (поверхность AC5 — Device preview) | pass |
| `node demo/smoke_static_icon.mjs` (поверхность AC5 — static space card) | pass |
| `node demo/smoke_lock_action.mjs` (смежная поверхность — lock action не должен был измениться) | pass |
| `node demo/smoke_lock_invariant.mjs` (смежная поверхность — lock invariant/SCOPE.md guard) | pass |
| `node scripts/check-docs.mjs --external` (AC7) | pass, 7 файлов, 10 внешних ссылок |
| `npm run golden:verify` | `device-icon-state-table-light`/`-dark` — **different**, ожидаемо (см. ниже); плюс 3 сцены, не названные в ТЗ — разобраны отдельно |
### Разбор `golden:verify`: почему «different» больше, чем в ТЗ
ТЗ (§10, §15, §16) называет ревьюеру только `device-icon-state-table-light`/
`-dark` как ожидаемую визуальную дельту. Фактический прогон показал ещё три
«different»: `isometric-large-warm-remount-dark`, `large-house-zoom-250-dark`,
`large-house-warm-remount-dark` (у всех пяти одинаковый `maxObservedDelta: 218`
— общий признак одного и того же цветового изменения).
Я не принял это на слово и сравнил с чистым `origin/dev` в отдельном
`git worktree` (та же сборка, тот же `golden:verify`): на `dev` эти три сцены
проходят как `passed`, а `device-icon-state-table-*` уже были `different`/
`missing-baseline` **до** этой задачи — предсуществующий дрейф baseline
(`GOLDEN_MATRIX_VERSION` 32→33 внесён более ранним `39456dc`, эталоны не
приняты), не имеющий отношения к #219. Причина трёх новых «different» —
`demo/fixtures/large-house.mjs` строка 129 циклически включает
`['lock', 'locked']` в синтетический набор устройств, которым пользуются
`perf-floor-1`/`perf-floor-2`; палитра lock-marker в этих сценах меняется тем
же общим CSS, что и ожидалось для shared renderer (AC5). Это не регрессия и
не «постороннее» изменение из риска в §13 — только более широкий, чем
задокументировано, набор golden-сцен, которым в предрелизном гейте потребуется
`npm run golden:accept -- --reviewed` вместе с state-table.
`device-text-shell-long-light/-dark` — `missing-baseline` независимо от этой
задачи (тот же предсуществующий дрейф `dev`, issue #217, вне скоупа #219).
### Не прогонялось, и почему
- **Полный набор из 155 browser-smoke.** Задача — точечное изменение общих CSS
state-токенов; прогнаны названные в AC/хендоффе плюс смежные поверхности
(device preview parity, static card, lock action, lock invariant). Остальные
150 смоков не относятся к lock/orange-подложке (проёмы без замков, Glow,
wall geometry, UI-хром и т. д.).
- **`python -m pytest tests_backend`.** Ни один файл
`custom_components/**/*.py` не тронут.
- **Performance-профили.** Не названы в AC; §12 ТЗ явно фиксирует «CSS
variables и fixture/test metadata», без нового DOM/JS в render path.
## Проверка AC1–AC7
| AC | Метод по ТЗ | Статус | Как закрыт |
|---|---|---|---|
| AC1 | contract unit + light/dark visual artifact | ✅ | `device-marker-polish-contract.test.mjs` проверяет `.oplock.locked/unlocked` core/stroke/fg по обеим темам; artifact — `smoke_device_icon_design.mjs` `openingLockPaletteMatches` (реальный `.oplock-core`/`.oplock-shell` computed style против reference с runtime-override) |
| AC2 | presentation/unit source contract + shared-renderer test + golden state-table | ✅ | Тот же contract-тест проверяет `.dev.lock-locked/unlocked` идентичными hex теми же селекторами, что и `.oplock.*`; `houseplan-card.ts`/`hp-device-preview.ts`/`space-card.ts` подтверждены источником как использующие общий `cardStyles`; golden `device-icon-state-table-*` включает оба lock-состояния |
| AC3 | contract unit + state-table light/dark golden с одновременно видимыми `on`/`open` | ✅ | Contract-тест проверяет `.dev.open` не содержит `#4a2800` и использует `light-dark(#fff,#252525)`; smoke `orangeGlyphsFollowTheme` подтверждает `rgb(255,255,255)`/`rgb(37,37,37)` для `on` и `open` в обеих темах на реальном рендере; golden-сцена теперь содержит `open` (`golden-right-linkquality`) рядом с `on` |
| AC4 | существующие device presentation/polish/pointer tests + source review | ✅ (чтением) | Diff `src/styles.ts` ограничен двумя блоками (`oplock.locked/unlocked`, `dev.open/lock-locked/lock-unlocked`); alarm/hover/focus/selected/unavailable/virtual/press/pulse селекторы вне диффа побайтово идентичны `dev`; полный regression (962/962) не покраснел |
| AC5 | shared-renderer unit + golden matrix | ✅ | Contract-тест проверяет, что `houseplan-card.ts`, `hp-device-preview.ts`, `space-card.ts` импортируют `cardStyles`; smoke `smoke_device_preview_parity.mjs`/`smoke_static_icon.mjs` подтверждают parity на реальных DOM для plan/preview/static; golden `device-icon-state-table-{light,dark}` — одна fixture, обе темы |
| AC6 | diff review, typecheck, полный unit и build | ✅ | Diff не касается config/backend/i18n (нет файлов вне `src/styles.ts`, тестов, demo, docs, generated); typecheck/unit/build зелёные |
| AC7 | diff, hash comparison, `check-docs` | ✅ | Оба changelog, `docs/TESTING.md`, `docs/USER-GUIDE.md`+`.ru.md` (раздел «Замок», таблица «Жёлтая подложка»/«Чёрный значок замка» переписаны на red/green) — все в `bc75e00`; три bundle sha256 идентичны; `check-docs.mjs --external` pass; `sourceFingerprint` в `docs/images/screenshots.json` обновлён и совпадает с фактическим build fingerprint, все 10 `imageSha256` не изменились |
## Находки
### Low-1 — AC7/§16 не называют все golden-сцены, которые изменит эта правка
ТЗ и хендофф называют только `device-icon-state-table-light`/`-dark` как
ожидаемую golden-дельту. Фактически общий CSS задевает ещё три сцены
(`isometric-large-warm-remount-dark`, `large-house-zoom-250-dark`,
`large-house-warm-remount-dark`) через lock-устройства в синтетической `large`
fixture (см. разбор выше) — я подтвердил это сравнением с чистым `dev`, а не
предположением. Риск §13 «Golden покажет ожидаемую дельту вместе с
посторонней» ровно это и предвидел, возлагая проверку на ревьюера — что я и
сделал.
**Вердикт:** снимается без правки. Не блокирует AC7 (диапазон изменяемых
сцен явно не был ограничен списком) и не создаёт риска для релиза — я
проверил происхождение всех пяти «different» и подтвердил, что это одно и то
же корректное цветовое изменение, а не регрессия. Фиксирую для предрелизного
гейта: `npm run golden:accept -- --reviewed` должен принять **все пять**
сцен разом (`device-icon-state-table-{light,dark}` +
`isometric-large-warm-remount-dark` + `large-house-zoom-250-dark` +
`large-house-warm-remount-dark`), не только две названные в ТЗ.
`device-text-shell-long-{light,dark}` (`missing-baseline`) — предсуществующий
дрейф `dev` вне скоупа #219, не путать с этой правкой при приёмке.
## Что проверено и корректно
- `.oplock.locked`/`.oplock.unlocked` и `.dev.lock-locked`/`.dev.lock-unlocked`
используют идентичные hex (`#66D17A`/`#F0410C`) и идентичный
`light-dark`+explicit-override паттерн foreground — одна lock-сущность даёт
одинаковый цвет как compact badge, так и обычный marker (риск §13 «Compact
opening badge и device marker разойдутся» закрыт одним contract-тестом на
обе пары).
- Исходные designer SVG (`demo/srv/reference/device-icons/**`) остаются
byte-identical; runtime-override в `capture_device_icon_reference.mjs`
подтверждён построчным чтением фактических `fill="black"`/`fill="#252525"`/
`fill="#1DC21D"`/`fill="#F0A00C"` — каждое встречается в SVG ровно один раз,
`replaceAll` не задевает ничего постороннего.
- `--oplock-stroke-ratio: .025`/`--device-shell-stroke-ratio: .025` (dark
theme) и вся геометрия/MDI-path/hit-area не изменены — совпадает с
«не входит в задачу» §7 ТЗ.
- `mdi:lock`/`mdi:lock-open-variant` classification не менялась (regex-проверка
в contract-тесте против `houseplan-card.ts`).
- `docs/USER-GUIDE.md`/`.ru.md`: заменяемые формулировки («чёрный/тёмный
замок», «жёлтая подложка» применительно к lock, «Чёрный значок замка»)
убраны полностью, включая таблицу постоянных статусов и `lock.*` в общей
таблице поведений — сверено построчно, не на слово хендоффа.
- Trailers, changelog RU/EN, `docs/specs/README.md` — все в одном продуктовом
коммите, соответствуют PROCESS.md §7.1/§10.1.
## Чего не проверял
- Визуальный итог обновлённых golden-сцен (baseline не принимается
исполнителем/ревьюером по контракту — только `npm run golden:accept
-- --reviewed` на полном Linux CI artifact перед бетой).
- Полный набор из 155 browser-smoke и `performance_smoke` — не относятся к
этому точечному CSS-изменению; остаются обязательными на предрелизном
гейте.
- Backend/HA harness — не затронут, ни один файл `custom_components/**/*.py`
не изменён.
- Мобильный/touch путь отдельно не тестировал: правка ограничена CSS-цветом,
не добавляет и не убирает pointer-modality селекторы (подтверждено чтением
диффа — новых `:hover`/`[data-pointer-*]` правил нет).