Files
houseplan-card/docs/reviews/CODE-REVIEW-535-r1.md
T
claude[bot] 05a2c68da2
Проверка (CI) / Классификация изменённых файлов (push) Successful in 27s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 53s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 25s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 1m16s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 21s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 55s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 12m10s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
docs: review document for #535
Issue: #535
User-Visible: no
2026-09-11 22:42:21 +00:00

212 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-535-r1
Issue: #535 · Заход r1 · блокирующих циклов израсходовано 0/4
Материал ревью: `063de0eef4bfb96802e8ff94fcc0e35177cfde0b` (`origin/dev...HEAD`, один коммит `063de0ee`, база `1ca3b5fa`)
Ревьюер: Claude (код-ревью) · Автор кода: Codex
## Скоуп
Правка меняет один плагин сборки, `entryFallbackPlugin` в `scripts/bundle-manifest.mjs`:
панель (`houseplan-panel.js`) теперь импортирует реализацию карточки по
content-hashed адресу (`houseplan-assets/houseplan-card-<hash>.js`) напрямую, а не
через стабильный фасад `houseplan-card.js`, — устраняя единственный адрес в
дистрибутиве, по которому карточка отдаётся без токена версии. `try/catch` и
фолбэк («House Plan обновился — перезагрузите страницу», 4 языка) сохраняются;
при недоступном чанке теперь срабатывает панельный фолбэк, а не карточный внутри
живой панели.
Диапазон изменённых файлов: `scripts/bundle-manifest.mjs`, `scripts/bundle-tree.mjs`
(инвариант манифеста переформулирован под новую топологию), `scripts/mutation-gate.mjs`
(новый мутант + переоформление существующего), четыре тестовых файла
(`bundle-assets`, `bundle-freshness`, `bundle-sync`, `houseplan-zip`), `docs/ARCHITECTURE.md`,
оба `CHANGELOG`, три копии бандла (`dist/`, `custom_components/houseplan/frontend/`,
манифест `houseplan-assets.json`) и отпечаток скриншотов документации (только
отпечаток исходников, кадры не менялись).
По механическому признаку §1 диапазон файлов класса A не задет (только B/C/D) —
классификация полным треком по продуктовому доводу автора (видимое поведение
на устаревшем загрузчике) была спорной точкой ревью ТЗ r1 и признана
обоснованной; я согласен с этим выводом (см. «Ответ на продуктовый вопрос» ниже).
`src/**` не тронут вовсе — подтверждено `git diff origin/dev...HEAD --stat -- src/`
(пусто).
## Как проверялось
Дешёвые гейты (`tsc`, `npm test`, `npm run build` + сверка бандла) уже
подтверждены зелёным Validate на этом SHA
(https://github.com/Matysh/houseplan-card/actions/runs/34652944181) — не
перегонялись целиком. Дальше — то, что Validate не покрывает и что нужно
именно этой правке, прогнано лично, командой и результатом:
| Гейт | Команда | Результат |
|---|---|---|
| Юниты по изменённым файлам (перепроверка независимо от Validate) | `node --test test/bundle-assets.test.mjs test/bundle-freshness.test.mjs test/bundle-sync.test.mjs test/houseplan-zip.test.mjs test/mutation-gate.test.mjs` | 91/91 pass |
| Мутант AC1/AC2 | `node scripts/mutation-gate.mjs --id=panel-imports-unversioned-card-entry` | поймано 1 из 1 |
| Мутант AC4 | `node scripts/mutation-gate.mjs --id=panel-entry-bypasses-card-graph` | поймано 1 из 1 |
| Мутант AC3 | `node scripts/mutation-gate.mjs --id=panel-entry-fallback-rewrite-skipped` | поймано 1 из 1 |
| Бюджет панели (К4) | `node scripts/bundle-budget.mjs` | exit 0; `initial panel-only: 2325 B` при потолке 8192 B (запас 5867 Б) |
| Инвентарь манифеста (К4) | ручное чтение `dist/houseplan-assets.json` | `initialPanelFiles` не содержит `houseplan-card.js`; `initialViewFiles` минус фасад ⊆ `initialPanelFiles` |
| Три копии бандла в синхроне | `cmp dist/houseplan-card.js custom_components/.../houseplan-card.js`, `cmp … houseplan-panel.js`, sha256 по `houseplan-assets/*.js` | совпадают |
| Реальный build детерминирован | `node scripts/bundle-sync.mjs` поверх коммита | 0 изменений в рабочем дереве после — сборка воспроизводима |
| Документация (доп. проверка, `src/**` не тронут, но диффит `docs/`) | `node scripts/check-docs.mjs` | «Documentation checks passed (7 files, 12 external links)» — совпадает с заявленным |
| Скриншоты (отпечаток исходников) | сверка `docs/images/screenshots.json` в диффе | `sourceFingerprint` обновлён на `3eba35b1…`, байты PNG и `imageSha256` не менялись — соответствует заявленному `docs:accept --identical` |
| Смок панели (браузер, счастливый путь) | `node demo/smoke_houseplan_panel.mjs` (после `node scripts/bundle-sync.mjs`, стенд не был синхронизирован) | OK, все проверки true |
| Смок устаревшего входа (браузер, AC3 «чем краснеет» на реальном dist) | `node demo/smoke_entry_stale.mjs` | OK — панельный и карточный фолбэки, все 4 языка, без необработанных ошибок страницы |
**Не прогонялось и почему:**
- `npx tsc --noEmit`, полный `npm test`, `npm run build` со сверкой копий — приняты по зелёному Validate на этом SHA (§ дешёвые гейты).
- `python -m pytest tests_backend` — бэкенд (`custom_components/**/*.py`) не тронут.
- `npm run golden:verify` — диффом не задет ни один пиксель карточки (правка — только адрес импорта и текст комментария/лога), сам автор это заявил, и `docs:accept --identical` (нулевые попиксельные различия по 11 кадрам) независимо это подтверждает.
- perf-профили — в AC не названы, диапазон файлов их не касается.
- `npm run invariants` (геометрия/toolThickness/layout) — диффом не задет: правка не касается ни рёбер комнат, ни `layout`, ни `marker.space`.
- Полный набор `demo/smoke_*.mjs` — `smoke-select.mjs --base origin/dev --head HEAD` отвечает «исполняемого frontend-диффа нет» (`src/**` не менялся), выбор двух смоков выше сделан вручную по факту, что именно они гоняют реальный собранный `dist/houseplan-panel.js` и `dist/houseplan-card.js` через браузер — это ровно то, что диффит правка. Остальные ~табор смоков карточки/редакторов не относятся к маршрутизации входов и не выбраны.
## AC · чем доказан · чем краснеет
| # | AC | Доказательство (перепроверено) | Чем краснеет (прогнано лично) |
|---|---|---|---|
| AC1 | Плагин переписывает импорт панели на content-hashed чанк, `imports` содержит чанк | `#486 both stable entries install a visible stale-load fallback` (юнит на плагине напрямую, без сборки) — прочитан и перепрогнан | мутант `panel-imports-unversioned-card-entry` — лично: «поймано 1 из 1» |
| AC2 | В собранном `dist/houseplan-panel.js` нет ссылки на карточку без версии | `#535 panel entry reaches the card implementation by its hashed name` (читает `dist/`) + лично: `grep -o 'try{await import(...)}' dist/houseplan-panel.js` → `houseplan-assets/houseplan-card-B09T8YNT.js`; `grep -c houseplan-card.js dist/houseplan-panel.js` → 0 | тот же мутант (юнит-гвард на плагине; дист-тест не пересобирается на мутанте — законно для чистого юнита, §2.7) |
| AC3 | Фолбэк цел: элемент определяется, сообщение на месте (4 языка), статических рёбер нет | поведенческая часть теста без изменений + **браузерный** `demo/smoke_entry_stale.mjs` на реальном `dist/houseplan-panel.js` с 404 на все хешированные чанки | мутант `panel-entry-fallback-rewrite-skipped` — лично: «поймано 1 из 1»; смок независимо воспроизводит тот же сценарий против настоящего собранного файла |
| AC4 | Манифест: граф панели содержит граф реализации карточки, не содержит фасад; бюджет соблюдён | `scripts/bundle-tree.mjs` (`assertBundleManifest`, переформулирован) + `scripts/bundle-budget.mjs` — лично прогнан, exit 0, `initial panel-only: 2325 B` (потолок 8192 Б) | мутант `panel-entry-bypasses-card-graph` — лично: «поймано 1 из 1» |
| AC5 | Ни один входной файл не ссылается на карточку по адресу без версии | новый тест `#535 no built entry reaches the card by an address without a version` + лично: `grep -rl "houseplan-card.js" dist/` → только сам фасад и манифест, никаких других файлов | мутант AC1/AC2 задевает тот же код-путь; дополнительно — ручной `grep` по всему `dist/` подтвердил отсутствие иных ссылок |
| AC6 | Оба чейнджлога и `ARCHITECTURE.md` описывают новую маршрутизацию | построчная сверка (см. ниже) + `node scripts/check-docs.mjs` — «passed (7 files, 12 external links)», лично воспроизведено | — (не защитный AC) |
## Находки
### Low — L1: текст `console.error` панели изменён вопреки букве К2 ТЗ
`scripts/bundle-manifest.mjs`, панельная ветвь `entryFallbackPlugin`.
К2 ТЗ дословно: «Текст, четыре языка, класс элемента и `console.error` не
меняются.» Дифф меняет именно `console.error`:
```
- 'console.error("[houseplan] stale houseplan-panel.js: the card entry is unavailable",e)}',
+ 'console.error("[houseplan] stale houseplan-panel.js: the card implementation '
+ 'chunk is unavailable",e)}',
```
Это не покрыто ни одним тестом (ни один `assert` не проверяет содержимое этой
строки) и не названо в разделе «Отклонения от ТЗ — называю сам» хендоффа, хотя
там перечислены четыре других отклонения. По существу изменение оправдано:
после #535 недоступен именно chunk реализации, а не «card entry» — старый текст
стал бы неточным. Строка видна только в консоли разработчика, не пользователю,
и никак не входит в измеряемый функционал AC1–AC6.
**Решение ревьюера:** снимаю без цикла — фактическая точность лога важнее
буквы «дословно» применительно к диагностической строке, которую К2 явно
перечисляет вместе с пользовательским текстом (это, возможно, недосмотр самого
К2, а не сознательный контракт). Отмечаю как принятое отклонение, чтобы
дальнейшие ревью не искали здесь скрытую регрессию.
Других High/Medium/Low не найдено.
## Одно число — один источник
Дифф не добавляет и не меняет ни одной пользователем видимой величины
(строку с толщиной, площадью, размером и т.п.) — только адрес импорта и
служебные тексты (лог, комментарии, changelog). Проверка неприменима к этой
задаче.
## Что проверено и корректно
- **К1** — построчно: `dist/houseplan-panel.js` содержит ровно
`try{await import("./houseplan-assets/houseplan-card-B09T8YNT.js")}catch(e){…}`,
ссылок на `./houseplan-card.js` — ноль.
- **К2** — фолбэк (4 языка, класс элемента, стили) не тронут; см. L1 про
единственное отклонение — сам текст лога, не сам фолбэк.
- **К3** — `dist/houseplan-card.js` не изменился по контракту: тот же
`try{await import("./houseplan-assets/houseplan-card-B09T8YNT.js")}catch` в
фасаде, отдельная функция `cardEntry`/`cardPattern` в диффе не тронута.
- **К4** — манифест и рантайм-валидатор согласованы: `initialPanelFiles` не
содержит фасад, `initialViewFiles` (минус сам фасад) ⊆ `initialPanelFiles`,
бюджет 2325 Б при потолке 8192 Б.
- **К5** — оба теста `#486` действительно перевёрнуты по смыслу (не
подогнаны): `assert.doesNotMatch(panel, /houseplan-card\.js/)` появился там,
где раньше стоял `assert.match` на тот же паттерн; переименование второго
теста в `#535 …` оправдано сменой контракта, поведенческая часть (customElements,
фолбэк) не ослаблена.
- **К6** — новый тест на весь дистрибутив плюс ручной `grep -rl
"houseplan-card.js" dist/` подтверждают: во всём собранном дереве
единственная ссылка на карточку без версии — она же сам стабильный вход,
что и требуется контрактом (фасад продолжает существовать для Lovelace).
- **Мутанты** — все три названных автором (`panel-imports-unversioned-card-entry`,
`panel-entry-bypasses-card-graph`, `panel-entry-fallback-rewrite-skipped`)
лично перепрогнаны, каждый «поймано 1 из 1»; четвёртый пинned-тест
(`#486 bundle topology and both stale entries have mutation witnesses`)
подтверждает, что реестр не потерял старые id.
Новый мутант `panel-imports-unversioned-card-entry` не входит в pinned-список
`test/mutation-gate.test.mjs` — это нормально: список пиннует только уже
существовавшие до #535 id, новый свидетель добавлять туда не требовалось.
- **Отклонения от ТЗ, названные автором** (файл `bundle-tree.mjs`, девять
задетых фикстур, переоформление мутанта, отсутствие сдвига
`initialPanelOnlyGzipBytes`) — все проверены по диффу и корректны; ни одно
не ослабляет проверяемое утверждение (арифметика gzip-сумм в фикстурах
`bundle-freshness`/`houseplan-zip` сходится: убранный из графа фасад вычтен
из `initialPanelGzipBytes` ровно на его размер).
- **Документация**: `ARCHITECTURE.md` описывает ровно новую маршрутизацию и
причину (единственный неверсионированный адрес, отсутствие
`Cache-Control`, `immutable`-чанки); `CHANGELOG.md`/`CHANGELOG.ru.md`
синхронны по смыслу, ссылка на #535 в обоих, `User-Visible: yes` в коммите
соответствует правкам в обоих чейнджлогах в том же коммите. `USER-GUIDE.ru.md`
не тронут и не должен быть: описывает только фасад карточки (К3, не
изменился), к панели напрямую не относится.
- **Трейлеры и трассируемость**: один коммит, `Issue: #535`,
`User-Visible: yes`, ветка `issue/535-panel-card-url` (хендофф) слита без
дополнительных пушей — ровно то, что заявлено в хендоффе.
- **Сборка воспроизводима**: `node scripts/bundle-sync.mjs` поверх коммита не
дал ни одного изменения в рабочем дереве — три копии бандла (`dist/`,
`custom_components/.../frontend/`, стенд `demo/srv/assets/`) детерминированы
и синхронны.
## Ответ на продуктовый вопрос автора
Автор спрашивает, согласен ли я, что громкое сообщение лучше тихой работы на
старом коде, и не стоит ли отдельной задачей различать «фронтенд новее» и
«бэкенд новее» в самой плашке. Согласен с первым — это и есть суть задачи, она
уже была продуктово одобрена на ревью ТЗ r1. Второе — разумное предложение, но
оно за скоуп #535 (и уже названо автором как таковое в ТЗ и аналитике); отдельным
issue его заводить не обязываю, это на усмотрение владельца/автора при следующей
итерации плашки.
## Чего не проверял
- Полный `npm test` (2530 тестов) — не перегонял целиком, принят по зелёному
Validate на этом SHA; перепрогнал только пять файлов, прямо затронутых
диффом (91 тест, все зелёные).
- `pytest tests_backend`, golden, perf-профили, model-invariants — не
запускал: diff их не касается (см. таблицу гейтов выше).
- Полный список `demo/smoke_*.mjs` — не прогонял; выбрал два по факту, что они
гоняют именно собранные `dist/houseplan-panel.js`/`dist/houseplan-card.js`
в браузере (`smoke_houseplan_panel`, `smoke_entry_stale`), `smoke-select.mjs`
подтвердил отсутствие исполняемого frontend-диффа.
- Поведение в реальном Home Assistant (proxy-кэш, заголовки боевого стенда) —
вне возможностей код-ревью; измерения из issue (заголовки боевого стенда)
приняты как представленный автором факт, независимая браузерная проверка
(`smoke_entry_stale.mjs`) подтверждает механику фолбэка на уровне
собранного кода.
## Вердикт
Зелёный. Контракт К1–К6 реализован и подтверждён чтением кода, юнитами,
лично перепрогнанными мутантами и двумя независимыми браузерными смоками
против настоящего `dist`. Единственная находка — Low (L1, текст лога),
снята без цикла с записью выше. Медиум/High нет.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/535-panel-card-url`, коммит `063de0eef4bf` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `40cb7affb33a6e9975239d4d9ac9b87fb92b7daa`
```
git log --all --format='%H %T' | grep 40cb7affb33a
```
- Тело issue: `77204930eaea05482f2a7da2bac3af79247e7d7356e0449551cb997da2c76fc8`
- Вердикт конвейера: `green` · High 0