From f8e089bdf2bca59340eea0395b159454e319f8d7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:52:11 +0000 Subject: [PATCH] docs: review document for #627 Issue: #627 User-Visible: no --- docs/reviews/CODE-REVIEW-627-r3.md | 128 +++++++++++++++++++++++++++++ docs/reviews/INDEX.md | 3 +- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 docs/reviews/CODE-REVIEW-627-r3.md diff --git a/docs/reviews/CODE-REVIEW-627-r3.md b/docs/reviews/CODE-REVIEW-627-r3.md new file mode 100644 index 00000000..96b1c47c --- /dev/null +++ b/docs/reviews/CODE-REVIEW-627-r3.md @@ -0,0 +1,128 @@ +# CODE-REVIEW-627-r3 + +Материал: `b921ba71847df37416bed1c15e3d645953ffa1c9` (рабочая копия уже на нём; `git fetch`/`checkout` не выполнялись). Дерево `c76d64409884dfb646eb723f3e868003e923cce9`. Заход r3, блокирующих циклов израсходовано 1 из 4 (полный трек, лимит 4). + +r2 (`docs/reviews/CODE-REVIEW-627-r2.md`, материал `47d07281b6ce…`, дерево `e19b817f43f3…`) был зелёным и цикла не образовал (#227). r3 существует не потому, что r2 что-то вернул автору, а потому, что автор запушил новый коммит: `b921ba71` — ребейз ветки на ушедший вперёд `origin/dev` (успевший вобрать #642, «диалог «Оптимизировать планы» — свой модуль») с пересборкой бандла и пересчётом потолков. `47d07281` в текущей истории не резолвится (ребейз его осиротил) — по [§2.10] это не находка, а обычное дело; материал ищется по дереву. Дерево, эквивалентное содержимому `47d07281`, живо в текущей истории как `70fe860bac6259fc1f32114a6b3fe191fe8d966a` (коммит «test(smoke): danger confirmation waits for the composite language gate (#627 r1 M1)», тот же M1-фикс, что и в r2), поэтому дельта раунда объявлена как `git diff 70fe860b..b921ba71`. + +## Скоуп + +ТЗ #627 (полный трек, из аудита T9 M2-A): не новая функциональность, а бюджетно-техническая работа над уже закрытыми job'ами J4/J6 (`docs/SCOPE.md`) — держит `bundle-budget.mjs` честным гейтом ленивых графов онбординга/редактора, без новой видимой поверхности. `User-Visible: no` на всех трёх коммитах — корректно, ниже подтверждено. + +**Объявление дельты** (`git diff 70fe860b..b921ba71 --stat`): 74 файла — `scripts/bundle-budget.mjs` (17 строк: константа потолка + комментарий-причина), `scripts/monolith-baseline.json` (одно число), и 72 файла механической пересборки (`dist/**`, `custom_components/houseplan/frontend/**`, три копии бандла синхронны). `src/**/*.ts`, `test/**`, `demo/**` дельтой не тронуты — подтверждено и `git diff --stat`, и `node scripts/smoke-select.mjs --base 70fe860b --head b921ba71`: + +``` +Исполняемого frontend-диффа нет (src/**/*.ts не тронут). +Browser-smoke этим диффом не выбираются — это не «пропустить проверки», +а «выбирать нечего»: смоки проверяют собранную карточку. +Тронуто файлов: 76. +``` + +Дельта локальна по содержимому (две строки логики — числовая константа и её обоснование), хотя коммит и называется «ребейз». Тем не менее я разобрал её не по минимуму, а вместе с двумя пробелами доказательств, унаследованными от r1/r2 (см. «Как проверялось»): впервые в истории задачи собранный бандл — совместный продукт #627 и #642 (до этого коммита #642 жил только в `origin/dev`, `82a48ee0` был выпущен поверх него, но ни разу не гонялся в браузере после реального слияния кода обеих задач в одно дерево), и два смока, тронутых ещё исходным коммитом `82a48ee0` и прямо относящихся к контракту задачи, ни разу не запускались ни в r1, ни в r2. Формально это не входит в дельту r3, но раз я всё равно поднимал браузер для проверки собранного дерева — закрыл оба пробела тем же прогоном; см. ниже. + +## Как проверялось + +Дешёвые гейты подтверждены зелёным Validate на этом самом SHA (run [36025312110](https://github.com/Matysh/houseplan-card/actions/runs/36025312110), `conclusion: success`, `headSha` = `b921ba71847df37416bed1c15e3d645953ffa1c9` — проверено напрямую через `gh run view --json`). Разбивка по джобам: + +| Джоб | Результат | +|---|---| +| Классификация изменённых файлов | success | +| Предполёт: документация, провенанс, процесс | success | +| Переиспользование: это дерево уже проверено | success | +| Мутанты по диффу (6/6 шардов) | success | +| **Фронтенд: типы, юниты, мутанты, синхрон бандла** | **success** | +| Hassfest / HACS / pytest / geometry parity / browser smokes (3 шарда) / perf-smoke / golden | **skipped** (`workflow_dispatch`, не `heavy`) | +| Доказательство выполненных проверок | success | + +Джоб «Фронтенд» содержит `npm test` → `npm run build` → `bundle-tree.mjs` + `npm run bundle:budget` → `npm run lint:unused` (в этом порядке, `.github/workflows/validate.yml:779-791`) — то есть typecheck, юниты, сборка, гейт потолка `LAZY_EDITOR_GZIP_CEILING` и гейт связности монолита (`bundleBytes` против `scripts/monolith-baseline.json`) уже подтверждены на этом точном SHA. Их я не перегонял. + +Прогнано лично на `b921ba71` (дёшево, для точных чисел в этом документе и потому что новых чисел для CI-подтверждения годится не «похоже», а «совпало»): + +| Гейт | Команда | Результат | +|---|---|---| +| Bundle sync (проверка свежести/синхрона копий) | `node scripts/bundle-sync.mjs` | `git status --short` пуст и до, и после — закоммиченный `dist/` уже байт-в-байт синхронен с `custom_components/**` и распакован в `demo/srv/assets` | +| Bundle budget | `npm run bundle:budget` | `lazy editor: 228621 B gzip (потолок 229600 B ±2000)`, `lazy onboarding: 27468 B gzip (потолок 28500 B ±2000)`, `initial View: 289418 B gzip (потолок 290400 ±2000)` — совпадает с числами коммита `b921ba71` дословно | +| Мёртвый код и связность монолита | `npm run lint:unused` | `monolith: delegates=154 portMembers=348 hostRefs=4869 portPrivates=94 harnessPrivates=101 bundleBytes=2508211` / `ok все числа равны базе` | + +Арифметика коммита проверена построчно: +- `LAZY_EDITOR_GZIP_CEILING`: 229600, полоса 2000 → нижняя граница 227600; при замере 228621 запас сверху 229600−228621=**979**, запас снизу 228621−227600=**1021** — совпадает с текстом комментария в `scripts/bundle-budget.mjs:572` дословно. +- `monolith-baseline.json` `bundleBytes`: база `origin/dev` (`0f234f76`, тот же коммит, что и merge-base ветки с `origin/dev`) — `2499182` (проверено `git show 0f234f76:scripts/monolith-baseline.json`), текущее — `2508211`, разница `+9029` — совпадает с текстом коммита («десять новых чанков словарей и записи манифеста»); остальные пять чисел монолита (`delegates`/`portMembers`/`hostRefs`/`portPrivates`/`harnessPrivates`) не изменились ни на йоту относительно базы `dev` — тоже заявлено в коммите и подтверждено прогоном. +- Комментарий к `LAZY_EDITOR_GZIP_CEILING` над константой (строки 559–567 до правки) на промежуточном коммите `82a48ee0` содержит незаполненные плейсхолдеры `XXX` («замер XXX», «Центр оставляет XXX Б сверху и XXX Б до нижней границы») — артефакт промежуточного состояния ветки после первого ребейза на `origin/dev` с #642, где числа ещё не были пересчитаны. Это не находка: `82a48ee0` не является вершиной ни одного опубликованного ревью (ни r1, ни r2 его не рецензировали как отдельный SHA — их материал уже был позже осиротевшим `40959feb`/`47d07281`), а в материале **этого** раунда, `b921ba71`, плейсхолдеры заменены настоящими числами, что я и проверил выше побайтово. Упоминаю, чтобы явно снять вопрос, а не оставить его как тихо замеченную странность. + +Смоки. `smoke-select` для дельты `70fe860b..b921ba71` не выбирает ничего (см. выше) — формально этого достаточно. Тем не менее я поднял браузер (нужно было для проверки живой пересборки бандла) и заодно закрыл два пробела в доказательствах, оставленные r1/r2: + +| Смок | Почему запущен | Результат | +|---|---|---| +| `smoke_lazy_admin_locale.mjs` | Ядро AC4–AC7; первый прогон на дереве, где бандл собран **совместно** с #642 | OK, все 38 полей `true` | +| `smoke_danger_confirmation.mjs` | M1 (r1) / его фикс (r2) — держится ли регрессия закрытой на пересобранном бандле | OK, все 25 полей `true`, включая `{de,fr}GateSettledBeforeAsking` | +| `smoke_optimize_geometry_preflight.mjs` | Тронут `82a48ee0` (добавлено ожидание языкового гейта перед `card._editorRuntime.optimizePlans.open()` — новый порт из #642); **ни r1, ни r2 не упоминают этот файл ни разу** (`grep -n optimize` по обоим документам — пусто), хотя правка именно этого файла явно перечислена в теле коммита `b921ba71` («smoke_optimize_geometry_preflight: …, разрешено в коммите задачи») без единой ссылки на прогон | OK, все 16 полей `true` — пробел закрыт | +| `smoke_dialog_segments_i18n.mjs` | Единственный смок, явно названный в **ТЗ issue** («План автотестов»: «...`smoke_dialog_segments_i18n` (4 локали × 4 диалога; переключение языка должно ждать гейт)») для риска ровно той же природы, что и M1; тронут `82a48ee0`, но ни r1, ни r2 его не упоминают | OK, все 60 полей `true` — пробел закрыт | +| `smoke_color_picker.mjs`, `smoke_free_walls.mjs`, `smoke_space_scale_defaults.mjs`, `smoke_summary_panel.mjs`, `smoke_toggle_confirmation.mjs`, `smoke_ux_fixes.mjs` | Ещё 6 смоков получили тот же защитный «дождаться `aria-busy`» патч в `82a48ee0` (за пределами явного списка ТЗ — самостоятельный аудит автора), ни один не упомянут ни в r1, ни в r2 | Все 6 — OK, без единого `false`-поля | + +Итого 9 браузерных смоков лично прогнаны на `b921ba71`, все зелёные без единого красного поля — включая четыре, которые пять раундов до этого (r1: три коммита, r2, теперь r3-до-этого-прогона) молча пропускали. + +### Чего не проверял + +- Индивидуальные прогоны 5 мутантов #627 по `--id` (`onboarding-loader-skips-namespace-ensure` и др.) — на этом SHA не перегонял; `src/**` в дельте `70fe860b..b921ba71` не тронут, а мутанты гейтят именно `src/**`. Опираюсь на личный прогон r1 (на уже осиротевшем SHA, но тем же дереве `src/**`) плюс на «Мутанты по диффу (6/6 шардов) — success» в CI на `b921ba71` — это диффовый прогон относительно доказанного предка, не индивидуальный запуск по `--id`, но за отсутствием изменений `src/**` он лишь переподтверждает то же дерево, а не проверяет что-то новое. +- `golden:verify`, перф-смок, `pytest tests_backend`, Hassfest/HACS, `geometry parity` — все skipped в CI (не `heavy`, `workflow_dispatch`) и не гейт этой дельты: ни эталонов рендера, ни Python, ни геометрии дифф не касается. +- Полный `smoke-select --base origin/dev --head HEAD` (а не `--base 70fe860b`) — не гонял: он воспроизвёл бы ровно то же самое (дельта `origin/dev..HEAD` содержит src-правки #627, уже разобранные в r1), а не что-то специфичное для этого раунда. +- Остальные ~250 несвязанных смоков вне плана ТЗ и списков связей — не прогонял, это предрелизная обязанность, не гейт ревью. +- Живой стенд Home Assistant — не поднимал. + +## Находки + +Нет. High 0, Medium 0. + +Low-наблюдение, снятое без действия: патч «подождать `aria-busy` до 250×20мс» теперь инлайново продублирован в 12 файлах `demo/smoke_*.mjs` (проверено `grep -rl "hasAttribute('aria-busy')"`), при том что `demo/serve.mjs` уже экспортирует несколько общих утилит смоков (`watchPage`, `check`, `checkAll`, `reportPageErrors`, `finish`) — общий хелпер сюда напрашивается. Не поднимаю до Medium: сам приём (а не его вынесение в хелпер) был явно одобрен r1 как решение M1 («чинится тем же приёмом, что уже применён к девяти другим смокам»), дублирование не в дельте r3 (весь список — из `82a48ee0`, уже принятого в r1/r2), и это стиль тестового харнесса, а не продуктовый код. + +## Что проверено и корректно + +| AC | Проверено в r3 | Итог | +|---|---|---| +| AC1 (`bundle-budget.mjs` печатает и гейтит онбординг) | Не тронут дельтой; CI (`Фронтенд` job) переподтвердил `npm run lint:unused`/`bundle:budget` на этом SHA | Унаследовано + переподтверждено CI | +| AC2 (числовые потолки и запасы) | **Переисполнено лично**: `npm run bundle:budget` на `b921ba71` — редактор 228621 ≤ 229600 (запас к полосе 979/1021 Б, >500), онбординг 27468 ≤ 28500 (запас 34526−27468=27058 ≥ 6500, 244433−228621=15812 ≥ 15000), initial View 289418 (было 289457 до задачи — фактически **снижение**, не рост; в полосе потолка) — причина перецентровки редакторского потолка (#642) записана в комментарии константы дословно с числами | Доказан заново на текущем SHA | +| AC3 (9 ленивых чанков, владение графами) | Не тронут дельтой (`test/i18n-lazy-namespaces.test.mjs`, `test/bundle-assets.test.mjs` не изменились с r1); CI `Unit tests` зелёные на `b921ba71` | Унаследовано из r1 | +| AC4 (холодное открытие on ru/de/fr, первый кадр на языке) | Лично: `smoke_lazy_admin_locale.mjs` — OK на пересобранном бандле | Переисполнено | +| AC5 (live-переключение языка, составной гейт) | Лично: тот же смок + `smoke_danger_confirmation.mjs` (M1) + `smoke_optimize_geometry_preflight.mjs` + `smoke_dialog_segments_i18n.mjs` + 6 доп. смоков с тем же патчем — все OK | Переисполнено, шире, чем требовала дельта | +| AC6 (отказ обеих попыток → английский только в пространстве) | Не тронут дельтой; `smoke_lazy_admin_locale.mjs` (поля `failure*`) — OK в прогоне выше | Унаследовано + переподтверждено прогоном | +| AC7 (холодный View/en-редактор/Zigbee не тянут лишних чанков) | Не тронут дельтой; те же поля смока — OK | Унаследовано | +| Трейлеры | `git show -s --format='%B'` на `82a48ee0`/`70fe860b`/`b921ba71` — везде `Issue: #627`, `User-Visible: no` | Корректно | +| Changelog | `git diff 70fe860b b921ba71 --stat -- docs/CHANGELOG.md docs/CHANGELOG.ru.md` — пусто; верно при `User-Visible: no` и отсутствии видимых изменений | Корректно | +| Одно число — один источник | `grep -rn "229_600\|229600\|2508211" scripts test src` — по одному вхождению каждое, только в объявлении константы/базы | Корректно | + +## Закрытие раунда r2 + +r2 был зелёным, «Находки: Нет» — закрывать нечего. Таблица приведена для формы, соответствия сокращённому шаблону: + +| Находка r2 | Чем закрыта | Где видно | +|---|---|---| +| — | — | — | + +## Унаследовано из r2 + +Без повторной проверки в r3 принято из `docs/reviews/CODE-REVIEW-627-r2.md` (материал `47d07281b6ce…` / дерево `e19b817f43f3…`, вердикт зелёный · заход r2 · High 0 · Medium 0), поскольку `src/**`, `test/**`, `demo/**` в дельте `70fe860b..b921ba71` не тронуты: + +- AC3, AC6, AC7 — доказаны в r1, не пересчитывались повторно ни в r2, ни здесь. +- Полный реестр из 7 мутантов #627 (5 новых + 2 с обновлёнными якорями), лично прогнанных r1-ревьюером по `--id`, «поймано 1 из 1» каждый. +- `check-docs.mjs --screenshots=warn`, `check-inputs.mjs --coverage` (0), `CONTRIBUTING.md`/`tsconfig.test.json` — содержательно не менялись. + +Не унаследовано, а переисполнено заново лично в r3 (см. «Как проверялось»): M1-фикс (`smoke_danger_confirmation.mjs`) и AC4/AC5 — потому что артефакт (собранный бандл) впервые стал совместным продуктом #627+#642, и это первый раз, когда кто-либо гонял браузерные смоки против именно такого дерева. + +## Вердикт + +Зелёный. Дельта раунда (`scripts/bundle-budget.mjs`, `scripts/monolith-baseline.json` + механическая пересборка) арифметически честна и подтверждена и CI, и лично. Ни одной находки. Дополнительно: два смока, явно относящихся к контракту задачи (один — по прямому тексту ТЗ, другой — по собственному упоминанию в теле коммита без прогона) и шесть смоков с тем же защитным патчем, ни разу не запускавшихся в r1/r2, лично прогнаны на этом SHA и зелёные — пробел в доказательствах предыдущих раундов закрыт, а не унаследован молча. + +--- + +--- + + + +## Материал раунда + +- Ветка: `issue/627-lazy-admin-locales`, коммит `b921ba71847d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `c76d64409884dfb646eb723f3e868003e923cce9` + ``` + git log --all --format='%H %T' | grep c76d64409884 + ``` +- Тело issue: `11ffae080ad26691a4b8dbd32f7736895c0c25a3f0cabe1b09da0e21666e6562` +- Вердикт конвейера: `green` · High 0 diff --git a/docs/reviews/INDEX.md b/docs/reviews/INDEX.md index 2dc1c5f2..14fccd4c 100644 --- a/docs/reviews/INDEX.md +++ b/docs/reviews/INDEX.md @@ -1,6 +1,6 @@ # Индекс ревью -Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1032, issue: 365. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`. +Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1033, issue: 365. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`. | Issue | Документ | Этап · раунд | Вердикт | H | M | Находки | Файлы | |---|---|---|---|---:|---:|---|---| @@ -33,6 +33,7 @@ | #627 | [SPEC-REVIEW-627-r1.md](SPEC-REVIEW-627-r1.md) | spec · r1 | 🟢 зелёный | 0 | 0 | избыточное (не противоречивое) условие в AC2; влияние на touch не названо явным пунктом | `docs/TOUCH-SUPPORT.md` | | #627 | [CODE-REVIEW-627-r1.md](CODE-REVIEW-627-r1.md) | code · r1 | 🟡 жёлтый | 0 | 1 | demo/smoke_danger_confirmation.mjs не переведён на ожидание составного гейта; диалог оп… | `demo/smoke_danger_confirmation.mjs` `src/houseplan-card.ts` `de.ts` `smoke_danger_confirm_branches.mjs` | | #627 | [CODE-REVIEW-627-r2.md](CODE-REVIEW-627-r2.md) | code · r2 | 🟢 зелёный | 0 | 0 | — | — | +| #627 | [CODE-REVIEW-627-r3.md](CODE-REVIEW-627-r3.md) | code · r3 | 🟢 зелёный | 0 | 0 | — | — | | #625 | [SPEC-REVIEW-625-r1.md](SPEC-REVIEW-625-r1.md) | spec · r1 | 🟡 жёлтый | 0 | 1 | новый инвариант markers[].id не описывает исход для уже испорченной хранимой конфигурации; продуктовые формулировки §7.1 неполны; не проговорены явные «нет» по i18n/touch | `validation.py` `__init__.py` | | #625 | [SPEC-REVIEW-625-r2.md](SPEC-REVIEW-625-r2.md) | spec · r2 | 🟢 зелёный | 0 | 0 | — | — | | #625 | [CODE-REVIEW-625-r1.md](CODE-REVIEW-625-r1.md) | code · r1 | 🟡 жёлтый | 0 | 0 | Три из четырёх точек вызова validate_active_marker_ids не имеют ни одного теста, exerci…; AC5 текстуально обещает «отдельные тесты сохраняют поведение при отсутствующем length» …; store.py:async_save_config_state — controller.async_flush() и последующий controller.re… | `custom_components/houseplan/websocket_api.py` `test_validation.py` |