From a55f5644987f32c958514b39b4cd7de78af45833 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:33:26 +0000 Subject: [PATCH] docs: review document for #648 Issue: #648 User-Visible: no --- docs/reviews/CODE-REVIEW-648-r3.md | 352 +++++++++++++++++++++++++++++ docs/reviews/INDEX.md | 3 +- 2 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 docs/reviews/CODE-REVIEW-648-r3.md diff --git a/docs/reviews/CODE-REVIEW-648-r3.md b/docs/reviews/CODE-REVIEW-648-r3.md new file mode 100644 index 00000000..66e8fe1a --- /dev/null +++ b/docs/reviews/CODE-REVIEW-648-r3.md @@ -0,0 +1,352 @@ +# CODE-REVIEW-648-r3 + +- **Issue:** https://github.com/Matysh/houseplan-card/issues/648 — + «Sections: вертикальный resize карточки и container-owned высота плана» +- **Этап:** `S7-code-review` (код-ревью, PROCESS.md §2.7) +- **Трек:** полный (`P2`, см. `docs/reviews/SPEC-REVIEW-648-r1.md`) +- **Материал:** `git log --oneline origin/dev..HEAD` и + `git diff origin/dev...HEAD`; вершина ветки — ровно + `4168b1cfcedcc7979e17a380427d4466b45f6b2b`, база — `origin/dev` = + `7372e977b89a0a907b67cf00c1b2cd2b9ed2ddfa`. Семь коммитов (в порядке + предок→потомок): `230813b3` (docs, SPEC-REVIEW-648-r1), `2452c51d` (feat, + User-Visible: yes), `dc0a5ccc` (test), `b9fe9166` (test), `180239f4` (docs, + CODE-REVIEW-648-r1), `4168b1cf` (docs, индекс), `8f13325c` (test). +- **Заход:** r3 · блокирующих циклов израсходовано 2 из 4 (по заданию + оркестратора) +- **Роль:** ревьюер кода (не автор) + +## Аномалия материала — зафиксировано, не блокирует + +Прежде чем перейти к разбору: я не смог найти в репозитории и в issue #648 +никакого артефакта раунда r2 код-ревью. + +- `docs/reviews/` содержит только `CODE-REVIEW-648-r1.md` (git-история + подтверждает — `git log --all -- 'docs/reviews/CODE-REVIEW-648*'` называет + единственный коммит `180239f4`). +- В issue 11 комментариев; последний — от автора в 13:07:27 UTC + («Medium-1 ревью r1 исправлен... Жду зелёного push Validate и затем заново + запускаю S7»). Комментария с вердиктом r2 нет. +- Таймлайн меток issue показывает `S7-code-review` → `S6-in-progress` в + 13:04:13 (это возврат по жёлтому r1), затем `S6-in-progress` → + `S7-code-review` в 13:11:02 (после коммита с RU/DE-фиксом), и ещё одну + перестановку той же метки в 13:15:30→13:15:32 без промежуточного + комментария. Ни один из этих переходов не сопровождён вторым + жёлтым/красным комментарием ревьюера. +- SHA, которые называл r1 (`b7fb7cf7…`, `76a1d5c0…` и т. д.), в текущей + истории не существуют (`git cat-file -t` — «could not get object info»): + ветка была перебазирована на ушедший вперёд `dev` (текущий `origin/dev` — + `7372e977`, слияние другой задачи #649; старая база r1 `f68878cc` — + предок текущего `dev`, не наоборот). Это подтверждённый ребейз на + продвинувшийся `dev`. + +Согласно условию задания «дельта не локальна, если… ребейз на ушедший вперёд +`dev`» — это ровно тот случай, поэтому ниже **полный** разбор, а не +дифф-от-r2. Как единственный найденный предыдущий раунд использую +`CODE-REVIEW-648-r1.md`; расхождение номера (r1 найден, а не r2) — вопрос +целостности конвейера/бюджета, а не кода этой задачи, и не влияет на +техническую строгость проверки ниже: полный прогон гейтов и AC перекрывает +любые находки, которые мог бы содержать пропавший r2. + +## Скоуп ревью + +Диапазон правит: + +- `src/houseplan-card.ts` — публичное реактивное свойство `layout` + (HA-сигнал), геттер `_containerOwnedHeight` (`panelHost || layout === + 'grid'`), `getGridOptions()` → `{ columns: 'full', rows: 10, min_rows: 6 }`, + использование `_containerOwnedHeight` вместо `panelHost` в расчёте высоты + mode-transition, `measuredCardHeaderHeight`, `settleSoftStageLayout` и + inline `style="height:…"`; +- `src/boot-soft-layout.ts` — переименование параметра `panelHost` → + `containerOwnedHeight` в тех же двух функциях (поведение не меняется, + кроме нового источника `true`); +- `src/styles/base.styles.ts` — `:host([layout="grid"])` добавлен рядом с + `:host([panel-host])` во всех соответствующих селекторах; +- `demo/smoke_sections_resize.mjs` (новый, 225 строк) — browser smoke; + `demo/smoke_houseplan_panel.mjs` — соседний regression-oracle (AC7); + `test/houseplan-panel.test.mjs` — обновлён текстовый oracle контракта #486; +- `scripts/mutation-registry.mjs` (4 новых мутанта), `scripts/smoke-links.mjs` + (регистрация smoke по символам); +- документация: `docs/ARCHITECTURE.md`, `docs/CHANGELOG.md`, + `docs/CHANGELOG.ru.md`, `docs/STATUS-FEATURES.md`, `docs/TESTING.md`, + `docs/USER-GUIDE.md`, `docs/USER-GUIDE.ru.md`; +- `dist/**`, `custom_components/houseplan/frontend/**`, `demo/srv/assets/**` + (класс D, пересобранный бандл — сверено побайтно, «Как проверялось» п.3); +- `docs/reviews/CODE-REVIEW-648-r1.md`, `docs/reviews/SPEC-REVIEW-648-r1.md`, + `docs/reviews/INDEX.md` — материал предыдущих раундов, не код продукта. + +Первый вопрос — какую строку `docs/SCOPE.md` задача обслуживает: прямого +Core-job нет, это инфраструктурная UX-правка поверхности, на которой живут +J1–J7 для персоны Home admin (Sections — основной тип dashboard в HA; задача +не расширяет функциональность плана и не входит в «Out of scope»). Так же +было принято ревьюером ТЗ в SPEC-REVIEW-648-r1.md — конфликта со SCOPE.md +нет; за прошедшие раунды это не изменилось. + +## Как проверялось + +1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `docs/process/REVIEWER.md` + (в материале отсутствует — читал разделы PROCESS.md §2.4, §2.7, §2.10, + §4, §7.2, §8, §12 напрямую), тело issue #648 (`gh issue view 648 + --json body`, разделы ТЗ, K1–K12, AC1–AC10) и все 11 комментариев + issue (`gh issue view 648 --json comments`), таймлайн меток + (`gh api repos/Matysh/houseplan-card/issues/648/timeline`). +2. Прочитан полный `git diff origin/dev...HEAD` по каждому изменённому файлу + `src/**`, обоим browser smoke, юнит-тесту, `mutation-registry.mjs`, + `smoke-links.mjs`, всем изменённым `docs/*.md`. +3. Пересобрал бандл (`npm run build`, затем `npm run bundle:sync`) на + материале — `git status --porcelain` до и после пуст: `dist/**`, + `custom_components/houseplan/frontend/**` и `demo/srv/assets/**` + побайтно совпадают с пересобранными. +4. Дешёвые гейты (`typecheck` внутри `build`, `npm test`, `npm run build`) + уже подтверждены зелёным Validate на этом точном SHA (ссылка в задании); + тем не менее перегнал `npm run build` (п.3) и полный `npm test` сам для + собственной уверенности перед прогоном смоков на пересобранном бандле — + **3086 тестов, 3085 pass, 1 skip, 0 fail** — совпадает с отчётом Validate + и с r1. +5. Прогнал сам, так как диф трогает `src/**`: `node scripts/no-new-any.mjs` + (0 новых `any` в 31 добавленной строке), `node + scripts/no-new-private-writes.mjs` (0 новых записей в приватное состояние + из 227 добавленных строк smoke), `npm run bundle:budget` (initial View + 291761 B — совпадает с r1, в пределах потолка), `node + scripts/check-docs.mjs --screenshots=warn` (WARN про фингерпринт + скриншотов — ожидаемо, диф не меняет пиксели плана), `node + scripts/validate-commit-provenance.mjs --range=origin/dev..HEAD` + (exit 0) — все зелёные. +6. Прогнал `node scripts/smoke-select.mjs --base origin/dev --head HEAD` — + «прямое совпадение» вернуло те же 19 смоков, что и в r1 (диф с r1 не + меняет затронутые символы `src/**`), включая оба названных в AC + (`smoke_sections_resize.mjs`, `smoke_houseplan_panel.mjs`). Прогнал их + плюс `smoke_render_invalidation.mjs` и `smoke_warm_dialogs.mjs` (те же + точечные регрессионные свидетели рефактора `panelHost → + containerOwnedHeight`, что r1 выбрал по символам `_bootSoft`/`_hdrH`/ + `_warmSlot`) — все четыре зелёные (полный вывод в п.7 ниже и в разделе + «Что проверено»). +7. Через `node scripts/mutation-gate.mjs --id=` прогнал все четыре + защитных мутанта задачи по отдельности: + `sections-grid-falls-back-to-viewport-height`, + `sections-editor-transition-uses-window-height`, + `sections-grid-drops-stage-flex-chain`, + `sections-resize-drops-stage-refit-observer` — каждый вывел «поймано 1 из + 1» (`smoke_sections_resize.mjs` краснеет предметно на каждом), `git + status --porcelain` пуст после каждого прогона (скрипт сам возвращает + патч и пересобирает). +8. Целевая проверка закрытия Medium-1 из r1: прочитал полный diff коммита + `8f13325c` — `minimumGeometry` теперь `for (const language of ['ru', + 'de'])`, каждая итерация делает реальный `card.setConfig` с этим + `language` и меряет геометрию; финальный булев + `minimumSixRowsStayContainedInRuAndDe` требует `.every(...)` по обеим + записям, а не только по одной. Прогнал сам `smoke_sections_resize.mjs` + целиком — `minimumSixRowsStayContainedInRuAndDe: true` в выводе. +9. Проверил трейлеры всех коммитов (`git show -s --format='%H%n%s%n%b'`) — + `Issue: #648` на каждом из шести некод-ревью-документных коммитов; + `User-Visible: yes` только на `2452c51d` (feat), и `docs/CHANGELOG.md` + + `docs/CHANGELOG.ru.md` входят именно в этот коммит (проверено `git show + --stat 2452c51d`). +10. Проверил «одно число — один источник» (§8): `grep -n "rows: 10\| + min_rows\|632\s*px\|376\s*px"` по `src/houseplan-card.ts` и всем + изменённым `docs/*.md` — единственное определение чисел 10/6 — + возврат `getGridOptions()`; документация переиздаёт их прозой без + отдельного хардкода. +11. Проверил `space-card.ts` (grep `getGridOptions|layout`) — не получил ни + нового реактивного свойства, ни grid-defaults; юнит-тест + `test/houseplan-panel.test.mjs` подтверждает тем же + `assert.doesNotMatch`. +12. Прочитал комментарий `@pando80` от 13:06:46 UTC: внешний автор + подтвердил на реальном HA-дашборде Sections (`grid_options: rows: 6, + columns: full`, HA/интеграция 1.78.0-beta.2, включая полный restart), + что план вписывается в карточку без переполнения. Это не гейт этого + ревью и не заменяет автотест, но существенно снижает остаточный риск + r1 «реальное поведение HA Sections не проверено» (см. «Чего не + проверял»). +13. Инварианты модели (`npm run invariants`) и `pytest tests_backend` не + прогонял — диф не меняет геометрическую модель (стены/комнаты/ + устройства) и не касается `custom_components/**/*.py`; обоснование + подробнее в «Чего не проверял». + +## Закрытие раунда r1 (единственный найденный предыдущий раунд) + +| Находка r1 | Чем закрыта | Где видно | +|---|---|---| +| Medium-1: AC3 обещает browser smoke RU/DE для минимальной высоты, но `demo/smoke_sections_resize.mjs` проверял только `language: 'ru'` | Коммит `8f13325c` переписал блок `minimumGeometry` в цикл `for (const language of ['ru', 'de'])` с реальным `card.setConfig({..., language})` на каждой итерации; итоговый флаг `minimumSixRowsStayContainedInRuAndDe` требует `.every(...)` по обеим записям | `demo/smoke_sections_resize.mjs:91-104,199-201`; лично прогнан — `minimumSixRowsStayContainedInRuAndDe: true` в выводе (см. «Как проверялось» п.8) | + +## Унаследовано из r1 (не перепроверялось заново, а подтверждено полным прогоном) + +Поскольку разбор в этом раунде полный (ребейз на ушедший вперёд `dev`), ниже +не «унаследовано без проверки», а формально повторно подтверждено — +перечисляю соответствие r1, чтобы не дублировать формулировки находок: + +- AC1, AC2, AC4–AC10, четыре защитных мутанта, оба relevant browser smoke, + трейлеры/changelog, `typecheck`/`npm test`/`npm run build`/`no-new-any`/ + `no-new-private-writes`/`bundle:budget`/`check-docs` — то же самое + доказательство, что в `CODE-REVIEW-648-r1.md`, лично перегнано заново в + этом раунде на текущем SHA (см. «Как проверялось» пп. 4–7, 9–11) и + совпадает с описанием r1 буквально (тот же код: `2452c51d` содержит + идентичный итог трёх исходных коммитов, которые ревьюировал r1, просто + под новыми SHA после ребейза). +- Остаточный риск «реальное поведение HA Sections вне репозитория не + проверено» — не закрыт кодом (репозиторий по-прежнему не может это + проверить исполнением), но частично снижен независимым внешним + подтверждением `@pando80` на реальном HA (см. «Как проверялось» п.12) — + остаётся в «Чего не проверял» с этим уточнением. + +## Находки + +Ни одной новой находки. Единственная находка r1 (Medium-1) закрыта +предметно и проверена исполнением (см. таблицу выше). + +## Что проверено и корректно + +- **AC1** — `getGridOptions()` возвращает ровно `{ columns: 'full', rows: + 10, min_rows: 6 }` без `max_rows` (`src/houseplan-card.ts:3762`); + `executableGridDefaults: true`; `getCardSize() === 12` не изменён + (`src/houseplan-card.ts:3758`); `space-card.ts` без `getGridOptions` — + подтверждено grep и юнитом. +- **AC2** — `defaultTenRowsFillTheSlot: true`; + `gridUsesContainerOwnedInlineHeight: true`; + `gridSignalReflectsForScopedCss: true` — реальный `getBoundingClientRect` + на живом бандле, допуск 1 px, `cardScroll`/`haCardScroll` ≤ 1. +- **AC3** — `minimumSixRowsStayContainedInRuAndDe: true` — теперь по обеим + локалям (см. закрытие Medium-1 выше), header не обрезан, меню достижимо + в обеих. +- **AC4** — серия 6→10→14→6: + `resizeChangesStageAtEveryRowCount: true`; мутант + `sections-resize-drops-stage-refit-observer` краснит smoke — прогнан + лично. +- **AC5** — `allEditorsStayInsideTheFixedSlot: true`, + `editorTransitionFramesUseTheFixedSlot: true`, + `editorTransitionsLeaveNoBusyTail: true` — реальный + `card._modeTransition.state`, не форсированное приватное состояние; + мутант `sections-editor-transition-uses-window-height` краснит smoke — + прогнан лично. +- **AC6** — `explicitGridOptionsStayExternal: true` — `Object.freeze(config)` + + сверка `JSON.stringify(config) === configBefore` и + `card._config.grid_options === config.grid_options` по идентичности + объекта. +- **AC7** — `ordinaryCardKeepsViewportHeight: true` (реальный inline-style + `100dvh` при `card.layout = null`); `smoke_houseplan_panel.mjs` → + `sectionsDefaultsRemainAvailableInPanel: true` (панель не задета + рефактором `panelHost → containerOwnedHeight`, поскольку + `_containerOwnedHeight === panelHost`, когда `layout` не установлен) — + прогнан лично, зелёный. +- **AC8** — `resizePreservesCameraAndIntent: true` (0 `hass-more-info`, + `_space`/`_mode` не смещаются); + `postResizeHitCoordinatesMatchCamera: true` — сверка реальной + `getScreenCTM()` после шторма resize. +- **AC9** — `resizeStormSettlesWithoutLoop: true` — 30 переключений 6/14 + строк, `stormElapsed < 3000`; `finish()` красит прогон при необработанных + исключениях карточки (`_pageErrors`), не только по заявленным булевым + полям. +- **AC10** — все шесть документов обновлены и прочитаны целиком; числа 10/6 + и пиксельные ссылки (632/376) существуют только как проза, единственный + источник — `getGridOptions()` (см. «Как проверялось» п.10); `@pando80` + кредитован в обоих changelog со ссылкой на #648; + `check-docs.mjs --screenshots=warn` — WARN (не ERROR), не относится к + этому дифу. +- **Трейлеры и changelog**: `Issue: #648` на всех некод-ревью-документных + коммитах; `User-Visible: yes` только на `2452c51d`, оба changelog — в том + же коммите. +- **Бандл воспроизводим**: `npm run build && npm run bundle:sync` на + материале не породил диффа — все три копии (`dist/**`, + `custom_components/houseplan/frontend/**`, `demo/srv/assets/**`) + побайтно совпадают с исходниками этого SHA. +- **Гейты для `src/**`-дифа**: `no-new-any`, `no-new-private-writes`, + `bundle:budget`, `validate-commit-provenance` — все зелёные (см. «Как + проверялось» пп. 5, 9). +- **Юнит-тесты**: `npm test` — 3086, 3085 pass, 1 skip (не относится к + задаче), 0 fail. +- **Область изменений**: `panel-host`, Masonry, kiosk, `space-card.ts` не + тронуты кодом — подтверждено grep и фактическим составом дифа. + +## Чего не проверял + +- **Реальное поведение Home Assistant Sections вне этого репозитория.** + Как и в r1 — нет ни реального HA, ни доступа к `WebSearch`/поиску по коду + GitHub в этой среде (инструменты `mcp__github__get_issue`/ + `get_issue_comments` тоже были недоступны без явного разрешения; тело и + комментарии issue получены через `gh` CLI, который разрешён). AC2–AC10 в + этом дифе по-прежнему проверены только против синтетической фикстуры, + устанавливающей `card.layout = 'grid'` вручную. Новое по сравнению с r1: + комментарий `@pando80` (см. «Как проверялось» п.12) — независимое + подтверждение на реальном HA 1.78.0-beta.2 с `grid_options: rows: 6`, план + не переполняет карточку. Это анекдотическое, не воспроизводимое + автоматически свидетельство, но оно прямо адресует технический риск, + который ревью ТЗ делегировало код-ревью, и снижает его до низкого: + внешний пользователь уже наблюдал ровно тот сигнал (`layout`), на который + рассчитывает код, на реальной установке. +- **15 из 19 «прямых совпадений» и все 28 «слабых связей» + `smoke-select.mjs`.** Тот же список, что в r1 (диф с r1 не расширил + затронутые символы `src/**`); прогнал только 4 релевантных + (`smoke_sections_resize.mjs`, `smoke_houseplan_panel.mjs`, + `smoke_render_invalidation.mjs`, `smoke_warm_dialogs.mjs`) — обоснование + то же: рефактор `panelHost → containerOwnedHeight` математически не + меняет значение для существующих (не-`grid`) карточек; полная матрица — + предрелизная обязанность (§8), не гейт код-ревью. +- **`golden:verify`** — не прогонял: диф не меняет визуальные пиксели + плана/chrome, только контейнерную высоту и CSS-flex; + `check-docs.mjs` подтверждает отсутствие изменений в зафиксированных + скриншот-сценариях документации (только WARN о фингерпринте). +- **`npm run invariants`** — не прогонял: диф не меняет геометрическую + модель плана и не трогает ссылки на неё, только презентационный слой + карточки. +- **`python -m pytest tests_backend`** — не прогонял: диф не касается + `custom_components/**/*.py`. +- **Performance-профили** — не названы в AC; новый постоянный + observer/polling не введён (используется существующий `_roViewport`). +- Полная матрица browser smoke в CI (271 смок) — не прогонял целиком, + только выбранные по диффу и AC (см. выше); это соразмерно объёму гейтов + код-ревью (§8), не предрелизному гейту. + +## Вердикт + +0 High, 0 Medium. Единственная находка предыдущего раунда (Medium-1: неполное +покрытие AC3 браузерным смоком RU/DE) закрыта предметно — исполняемый смок +теперь проверяет минимальную высоту на обеих локалях и был лично прогнан +зелёным. Новых находок при полном разборе (обязателен из-за ребейза на +ушедший вперёд `dev`) не выявлено: все AC1–AC10, четыре защитных мутанта, +оба относящихся к задаче browser smoke плюс два точечных regression-смока, +трейлеры, оба changelog и гейты, специфичные для `src/**`-диффа +(`no-new-any`, `no-new-private-writes`, `bundle:budget`, +`validate-commit-provenance`, `check-docs`), — лично перегнаны на этом SHA и +корректны. + +Материал ревью содержит аномалию бухгалтерии раундов (не найден артефакт r2 — +см. раздел выше); это вопрос целостности конвейера, а не кода задачи, и не +меняет техническую оценку: код на SHA `4168b1cf` готов. + +**Зелёный.** + +--- + +Вердикт: зелёный · заход r3 · блокирующих циклов 2/4 · High: 0 · Medium: 0 + +--- + + + +## Материал раунда + +- Ветка: без явного имени в рабочей копии на момент ревью (детектирована + как `HEAD` detached); вершина — `4168b1cfcedcc7979e17a380427d4466b45f6b2b`. +- База сравнения: `origin/dev` = `7372e977b89a0a907b67cf00c1b2cd2b9ed2ddfa`. +- Дерево материала (`git rev-parse HEAD^{tree}` на момент ревью): снято на + вершине `4168b1cfcedcc7979e17a380427d4466b45f6b2b`, рабочая копия чиста до + и после всех локальных гейтов/мутаций/пересборки бандла (`git status + --porcelain` пуст). +- Предыдущий раунд (найден): `docs/reviews/CODE-REVIEW-648-r1.md`, вердикт + `yellow`, High 0, Medium 1 (закрыт — см. таблицу выше). Раунд r2 код-ревью + не найден ни в `docs/reviews/`, ни в комментариях issue #648, ни в её + таймлайне меток — см. раздел «Аномалия материала» выше. + +--- + + + +## Материал раунда + +- Ветка: `issue/648-sections-card-resize`, коммит `4168b1cfcedc` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `06653e5b743ecdf436899320c19a411f04af6286` + ``` + git log --all --format='%H %T' | grep 06653e5b743e + ``` +- Тело issue: `8400466150bde494c01aef14b7d5961be36d721b08a28c69e800bd88ac645154` +- Вердикт конвейера: `green` · High 0 diff --git a/docs/reviews/INDEX.md b/docs/reviews/INDEX.md index 9c1d8d9e..6db020c1 100644 --- a/docs/reviews/INDEX.md +++ b/docs/reviews/INDEX.md @@ -1,6 +1,6 @@ # Индекс ревью -Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1060, issue: 374. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`. +Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1061, issue: 374. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`. | Issue | Документ | Этап · раунд | Вердикт | H | M | Находки | Файлы | |---|---|---|---|---:|---:|---|---| @@ -8,6 +8,7 @@ | #649 | [SPEC-REVIEW-649-r2.md](SPEC-REVIEW-649-r2.md) | spec · r2 | 🟢 зелёный | 0 | 0 | — | — | | #648 | [SPEC-REVIEW-648-r1.md](SPEC-REVIEW-648-r1.md) | spec · r1 | 🟢 зелёный | 0 | 0 | «Сценарий» не называет персону и; AC10 называет provenance-gate | `docs/SCOPE.md` `scripts/validate-commit-provenance.mjs` `validate.yml` | | #648 | [CODE-REVIEW-648-r1.md](CODE-REVIEW-648-r1.md) | code · r1 | 🟡 жёлтый | 0 | 1 | AC3 документально обещает browser smoke | `demo/smoke_sections_resize.mjs` `docs/TESTING.md` `smoke_sections_resize.mjs` | +| #648 | [CODE-REVIEW-648-r3.md](CODE-REVIEW-648-r3.md) | code · r3 | 🟢 зелёный | 0 | 0 | — | — | | #647 | [SPEC-REVIEW-647-r1.md](SPEC-REVIEW-647-r1.md) | spec · r1 | 🟡 жёлтый | 0 | 2 | П.4 ТЗ переносит на новый слот прежнее; ТЗ не упоминает и не защищает документированный | `src/styles/dialogs.styles.ts` `src/styles.ts` `smoke_glow_blending.mjs` `smoke_test_facade.mjs` `smoke_unified_wall_tool.mjs` `docs/UX-MODES.md` `docs/reviews/CODE-REVIEW-195-r1.md` `src/styles/chrome.styles.ts` | | #647 | [SPEC-REVIEW-647-r2.md](SPEC-REVIEW-647-r2.md) | spec · r2 | 🟢 зелёный | 0 | 0 | — | — | | #647 | [CODE-REVIEW-647-r1.md](CODE-REVIEW-647-r1.md) | code · r1 | 🟢 зелёный | 0 | 0 | — | — |