Files
houseplan-card/docs/reviews/CODE-REVIEW-378-r2.md
T
2026-08-29 20:04:28 +00:00

197 lines
19 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-378-r2
- **Issue:** #378 — «Значение + состояние»: выбор источника значения, как у бейджа
- **Этап:** код-ревью (PROCESS.md §2.7)
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 до этого вердикта
- **SHA материала:** `e524f07ddecf81c3e6f8cbd6e64775ff97b69d26` (`git rev-parse HEAD`
сверен непосредственно перед подведением итогов)
- **Диапазон дельты:** предыдущий вердикт (r1) получен на `552b78a134026f08...`;
дельта — `552b78a1..e524f07d` (`git diff 552b78a1..e524f07d`)
- **Полный диапазон:** `origin/dev...HEAD` = `6e1d9364..e524f07d`; `origin/dev`
не сдвинулся с r1 (`git merge-base origin/dev HEAD` = `6e1d9364`) — ребейза
не было, полный повторный разбор не требуется
- **Спецификация:** `docs/specs/378-value-face-source.md`, ревью ТЗ зелёное
- **Предыдущее код-ревью:** `docs/reviews/CODE-REVIEW-378-r1.md` — жёлтый,
High: 0, Medium: 1 (в скоупе)
- **Вердикт:** зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0
## Скоуп разбора
Дельта локальна и мала: два коммита, оба вне `src/**`:
- `dbbe94ae` — `demo/golden/matrix.mjs` (+19 строк, новый сценарий),
`test/golden-matrix.test.mjs` (+18 строк, структурный тест), плюс
пересобранные bundle-копии (см. ниже почему).
- `e524f07d` — `docs/images/06-device-editor.png`,
`docs/images/09-device-info.png`, `docs/images/screenshots.json`
(пересъёмка после смены source fingerprint).
`origin/dev` не двигался, поведенческий контракт не менялся, новая подсистема
не задета, объём дельты много меньше исходной задачи → разбор ведётся по
дельте, а не заново, per PROCESS.md §2.9.
Единственная находка r1 (Medium: golden-доказательство AC2 отсутствует) —
предмет этого раунда. Всё остальное — унаследовано.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| Medium — AC2 не доказан golden-сценарием: ТЗ трижды требует golden-проверку явного источника (`cover.current_position=42` → `42 %`), но `demo/golden/**` не был тронут в r1 | В `dbbe94ae` добавлен сценарий `device-value-face-cover-source-dark` в `demo/golden/matrix.mjs`: маркер `golden-left-linkquality` переопределён на `binding: 'entity:cover.golden_door_half'`, `display: 'value'`, `value_source: {kind: 'entity_attribute', entity_id: 'cover.golden_door_half', attribute: 'current_position'}`, `stateOverrides` даёт `current_position: 42`. `test/golden-matrix.test.mjs` (+тест «explicit cover value-source golden pins the 42 percent value face») проверяет структуру сценария построчно. `GOLDEN_MATRIX_VERSION` поднят 50→51 | `demo/golden/matrix.mjs:496-514`, `test/golden-matrix.test.mjs:591-607`. Лично прогнал `node --test test/golden-matrix.test.mjs` — 43/43 зелёных; смутировал `attribute: 'current_position'` → `'wrong_attribute'` — тест честно упал (`not ok 26`, diff показывает ожидаемое/полученное значение); откатил мутацию, дерево чистое. Лично прогнал `npm run golden:capture -- --scenario=device-value-face-cover-source-dark` и визуально проверил PNG-кадр: капсула показывает ровно `42 %` на изолированном маркере — контракт AC2 выполнен буквально |
Baseline в `demo/golden/baselines/**` сознательно не принят разработчиком —
это верно по процессу, не пропуск: PROCESS.md §6 (таблица ролей) прямо
запрещает роли «Разработчик» «принимать golden»; принятие эталонов относится к
предрелизному окну между `S8-merged` и выпуском (§11.4). Ревьюер тоже не
принимает golden — не входит в роль «Ревьюер кода» (§6). Проверить AC2 без
принятого baseline можно и нужно ручным просмотром снятого кадра — это и
сделано (см. таблицу выше).
## Унаследовано из r1
Из `docs/reviews/CODE-REVIEW-378-r1.md` (документ на SHA `552b78a1`) принято без
повторной проверки — дельта этого раунда их не задевает:
- **AC1** (список источников/сохранение) — `valueBadgeCandidates()`,
`valueSourceWriteFields()`, unit+smoke доказательства.
- **AC3** (паритет formatter badge/face) — общий `resolveValueSource()`.
- **AC4** (legacy-совместимость) — auto-ветка байт-в-байт прежняя.
- **AC5** (fail explicit: dash + диагностика) — мутационная проверка
выполнена в r1.
- **AC6** (независимость от лица virtual/derived_lqi) — приоритет в коде,
не только в комментарии.
- **AC7** (backend-валидация и reference seam: rewrite/rebind/export/import) —
`validate_source()`, `_drop_invalid_import_marker_links`,
`_repair_target_space_refs`, счётчик `MAX_MARKERS*(MAX_CONTROLS+2)`.
- **AC8** (preview/Cancel/binding reset) — единственная точка сброса
`value_source` на смену binding.
- **AC9** (локализация 4 языков, документация в том же коммите,
`User-Visible: yes` + оба CHANGELOG) — сверено построчно в r1.
- **AC10** (гейты и бюджет на SHA `552b78a1`) — Validate зелёный
(`https://github.com/Matysh/houseplan-card/actions/runs/33270791009`),
smoke/golden лично прогнаны ревьюером r1.
- Трейлеры и процесс на коммите `591f8f6a`/`552b78a1`.
Обоснование доверия: ни один из этих ACs не завязан на файлы, тронутые
дельтой r2 (`demo/golden/matrix.mjs`, `test/golden-matrix.test.mjs`,
`docs/images/**`). Функциональный код (`src/**`, `custom_components/**/*.py`)
не менялся ни байтом между r1 и r2 — подтверждено самой дельтой
(`git diff --stat 552b78a1..e524f07d`, нет ни одного файла `src/**` или `*.py`).
## Как проверялось (r2)
| Гейт | Статус | Как |
|---|---|---|
| `npx tsc --noEmit`, `python -m pytest tests_backend -q` | не гонял — код не менялся | `git diff --stat 552b78a1..e524f07d` не содержит `src/**` и `custom_components/**/*.py`; на неизменном коде результат тот же, что доказан в r1 на SHA `552b78a1` |
| `npm test` (включая новый `golden-matrix.test.mjs`) | **прогнал лично** | `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/golden-matrix.test.mjs` → 43/43 passed; полный `npm test` на этом же содержимом кода уже зелёный на CI (см. ниже) |
| Тест «explicit cover value-source golden…» умеет падать | **проверил** | мутация `attribute: 'current_position'` → `'wrong_attribute'` дала честный `not ok` с диагностическим diff; откат вернул зелёный прогон и чистое дерево (`git status --short` пуст) |
| `node scripts/check-docs.mjs` (диф трогает docs/images) | **прогнал** | `Documentation checks passed (7 files, 10 external links)` |
| `node scripts/bundle-sync.mjs` + сверка копий | **прогнал** | `git status --short` пуст после синка — `dist/`, `custom_components/.../frontend`, `demo/srv/assets` побайтово совпадают |
| `node scripts/bundle-budget.mjs` | **прогнал** | initial View `277941 B` при бюджете `300000 B` |
| `npm run golden:capture -- --scenario=device-value-face-cover-source-dark` | **прогнал, визуально проверил кадр** | статус `missing-baseline` (ожидаемо — эталон не принят по процессу); PNG открыт лично — капсула показывает `42 %`, кадр не пуст и не обрезан |
| `node scripts/smoke-select.mjs --base 552b78a1 --head HEAD` | **прогнал** | «Исполняемого frontend-диффа нет (`src/**/*.ts` не тронут). Browser-smoke этим диффом не выбираются — выбирать нечего»; браузерные смоки в этом раунде не прогонялись — инструмент сам подтвердил отсутствие связи, а не то, что связь пропущена |
| Полный `npm run golden:verify` (78+1 сценариев) | не гонял повторно целиком | не требуется: единственный новый сценарий проверен точечно (`golden:capture --scenario=...`) визуальным просмотром; остальные 78 не зависят от этой дельты и уже зелёные в r1 (`https://github.com/Matysh/houseplan-card/actions/runs/33270791009`) и повторно в CI-прогоне автора на этом же коде (`https://github.com/Matysh/houseplan-card/actions/runs/33271690654`, 142 passed / 1 missing-baseline — расхождение только по новому, ещё не принятому сценарию) |
| `npm run invariants -- --config <…>` | не требуется | дельта не трогает геометрию/ссылки (только тестовый маркер в data-only golden-фикстуре, не влияет на модель проекта) |
| Полный performance-профиль | не требуется | AC не называют влияние на перф, дельта не трогает горячие пути |
### Разбор CI-статусов на промежуточных SHA (важное уточнение)
Автор сослался на «Validate на `e524f07d` завершился success»
(`https://github.com/Matysh/houseplan-card/actions/runs/33271765594`). Это
верно буквально, но проверил его смысл отдельно, потому что он вводит в
заблуждение при поверхностном чтении: в этом прогоне job'ы `frontend`,
`golden`, `smoke`, `backend` — **все `skipped`** (реюз/классификация путей:
коммит `e524f07d` трогает только `docs/images/**`, которые не входят в
`sourceFingerprint`). То есть этот конкретный зелёный прогон НЕ является
источником доказательства для tsc/unit/build/golden — только для
`check-docs`.
Источник для тех гейтов — прогон на `dbbe94ae`
(`https://github.com/Matysh/houseplan-card/actions/runs/33271690654`): job
`Фронтенд: типы, юниты, мутанты, синхрон бандла` = success (tsc, `npm test`,
bundle-sync/no-new-any все прошли), job `Golden` = failure (ожидаемо:
`missing-baseline` для одного нового сценария — это не дефект, это
единственно возможный результат `verify`-режима при непринятом эталоне), job
`Документация` = failure (`screenshot source fingerprint is stale` — именно
это чинит следующий коммит `e524f07d`). Итоговый `conclusion: failure` этого
прогона — ожидаемое и корректное поведение процесса, а не красный флаг:
golden не может стать зелёным до приёмки эталона, а приёмка — вне роли
разработчика и вне роли ревьюера (§6, §11.4).
Поскольку код (`src/**`, `*.py`) идентичен между `dbbe94ae` и `e524f07d`
(докс-коммит их не трогает), а `sourceFingerprint` тоже совпадает (докс вне
корпуса отпечатка), гейты tsc/unit/build, зафиксированные зелёными на
`dbbe94ae`, действительны и для HEAD `e524f07d` без повторного прогона.
## Находки
Нет находок High или Medium.
### Low — нет новых. Low из r1 (`docs/specs/README.md:113`, нарушение
сортировки по номеру issue) не тронут этой дельтой и остаётся снятым решением
ревьюера r1 без правки — повторно фиксировать нечего.
## Что проверено и корректно
- Новый golden-сценарий `device-value-face-cover-source-dark` синтаксически и
семантически корректен: `binding: 'entity:cover.golden_door_half'` — валидный
и уже используемый в этом же файле формат биндинга (`src/devices.ts:75-76`,
`src/device-inbox.ts:177`), `cover.golden_door_half` — существующая fixture-
сущность (`demo/fixtures/visual-matrix.mjs:155-156`, уже используется как
`contact` стены в этом же матриксе). Форма `value_source` совпадает буква в
букву с типом `{kind: 'entity_attribute'; entity_id; attribute}`
(`src/types.ts:102`), `current_position` числится в
`VALUE_BADGE_ATTRIBUTES.cover` (`src/device-value-badge.ts:13`) и форматируется
как процент — то есть сценарий бьёт ровно в тот путь кода, который тестирует
AC2, а не в косвенный.
- Приём `markerOverrides` + `deviceOnly` для превращения существующего id
маркера в изолированный тестовый маркер — не новый паттерн: тот же приём уже
используется в этом файле сценарием `device-icon-state-table-*`
(`demo/golden/matrix.mjs:569-585`) на том же id `golden-left-linkquality`.
Коллизии между сценариями нет: каждый сценарий строит независимую копию
конфигурации (`prepareGoldenFixture`), `deviceOnly` изолирует рендер одним
маркером.
- Пересборка трёх копий бандла (`dist/`, `custom_components/.../frontend`,
`demo/srv/assets`) с новым `__HOUSEPLAN_SOURCE_FINGERPRINT__` в `dbbe94ae` —
ожидаемый побочный эффект: `scripts/source-fingerprint.mjs` намеренно
включает `demo/golden/**/*.mjs` (кроме `accept.mjs`/`policy.mjs`) в корпус
отпечатка (комментарий в файле, #344) — правка `matrix.mjs` обязана
поменять fingerprint и, значит, пересобранный бандл. Сверка
`bundle-sync.mjs` подтвердила побайтовое согласие копий на HEAD.
- Докс-скриншоты (`06-device-editor.png`, `09-device-info.png`,
`screenshots.json`) пересчитаны корректно: `sourceFingerprint`/`sourceSha256`
синхронно обновлены везде, `imageSha256` поменялся только у двух реально
изменившихся PNG (разница в несколько байт — согласуется с перерисовкой из-за
смены встроенного build fingerprint, не визуальная регрессия — сам
`check-docs.mjs` подтверждает согласованность инструментально).
- Трейлеры `Issue: #378`, `User-Visible: no` на обоих коммитах дельты —
корректно: поведение не менялось, changelog не требуется.
## Чего не проверял
- Не прогонял `npx tsc --noEmit`, `python -m pytest tests_backend -q`,
полный `npm run build` повторно на HEAD — код (`src/**`, `*.py`) не менялся
с `552b78a1`/`dbbe94ae`, где эти гейты уже зафиксированы зелёными (см.
таблицу и разбор CI-статусов выше).
- Не прогонял ни один `demo/smoke_*.mjs` — `smoke-select.mjs` сам подтвердил
отсутствие исполняемого frontend-диффа; выбирать было нечего, а не
пропущено с риском.
- Не прогонял полный `npm run golden:verify` по всем 78+1 сценариям заново —
78 старых не задеты дельтой и уже дважды зелёные (r1-ревью и авторский прогон
на `dbbe94ae`), новый проверен точечно личным просмотром кадра.
- Не принимал и не буду принимать `demo/golden/baselines/**` — вне роли
ревьюера (§6); это релиз-менеджерское действие в окне `S8-merged` →
выпуск (§11.4).
- Не выполнял ручное тестирование в браузере вне golden/smoke-инфраструктуры.
- Полный performance-профиль не гонял — дельта не создаёт новых горячих
путей (только data-only тестовая фикстура).
## Вывод
Единственная находка r1 закрыта по существу и лично перепроверена: новый
golden-сценарий синтаксически верен, бьёт точно в контракт AC2, структурный
тест умеет падать, а визуальный кадр лично просмотрен и показывает `42 %`.
Baseline не принят намеренно и корректно по процессу (не ошибка). Дельта вне
этого — только докс-пересъёмка, мотивированная и подтверждённая
инструментально. Новых находок нет. Вердикт — зелёный.