docs: review document for #648

Issue: #648
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-25 13:11:44 +00:00
parent b9fe916662
commit 180239f44c
2 changed files with 360 additions and 0 deletions
+359
View File
@@ -0,0 +1,359 @@
# CODE-REVIEW-648-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/648 —
«Sections: вертикальный resize карточки и container-owned высота плана»
- **Этап:** `S7-code-review` (код-ревью, PROCESS.md §2.7)
- **Трек:** полный (`P2`, см. SPEC-REVIEW-648-r1.md)
- **Материал:** `git log --oneline origin/dev..HEAD` и
`git diff origin/dev...HEAD`; вершина ветки —
`b7fb7cf7e7bce13a44ef0e1af3b97541ddb0dffa`, база — `origin/dev` =
`f68878cc44b818b6cfbdb3866302e6febaab2445`. Три коммита:
`7f48e690` (feat, User-Visible: yes), `102b2ed7` (test, User-Visible: no),
`b7fb7cf7` (test, User-Visible: no).
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4 (первый раунд
код-ревью; предыдущий раунд — ревью ТЗ, `docs/reviews/SPEC-REVIEW-648-r1.md`,
зелёный)
- **Роль:** ревьюер кода (не автор)
## Скоуп ревью
Диапазон меняет:
- `src/houseplan-card.ts` — новое публичное реактивное свойство `layout`
(HA-сигнал), геттер `_containerOwnedHeight` (`panelHost || layout ===
'grid'`), `getGridOptions()` → `{ columns: 'full', rows: 10, min_rows: 6 }`,
замена `panelHost` на `_containerOwnedHeight` в четырёх местах расчёта
высоты сцены (mode-transition target, `measuredCardHeaderHeight`,
`settleSoftStageLayout`, inline `style="height:..."`);
- `src/boot-soft-layout.ts` — переименование параметра `panelHost` →
`containerOwnedHeight` в `measuredCardHeaderHeight`/`settleSoftStageLayout`
(поведение не меняется, кроме нового источника `true`);
- `src/styles/base.styles.ts` — CSS-селекторы `:host([layout="grid"])`
добавлены рядом с существующими `:host([panel-host])`;
- `demo/smoke_sections_resize.mjs` (новый, 221 строка) — 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/**` (класс D, пересобранный
бандл — сверено побайтно, см. «Как проверялось» п.2).
Первый вопрос — какую строку `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`,
тело issue #648 (раздел `## ТЗ`, K1–K12, AC1–AC10), и
`docs/reviews/SPEC-REVIEW-648-r1.md` (единственный предыдущий раунд —
ревью ТЗ, зелёный, без Medium/High).
2. Прочитан полный `git diff origin/dev...HEAD` по каждому изменённому
файлу `src/**`, `demo/smoke_sections_resize.mjs`,
`demo/smoke_houseplan_panel.mjs`, `test/houseplan-panel.test.mjs`,
`scripts/mutation-registry.mjs`, `scripts/smoke-links.mjs`, всех
изменённых `docs/*.md`.
3. Пересобрал бандл (`npm run build` → `npm run bundle:sync`) на материале —
`git status` после сборки чист: committed `dist/**` и
`custom_components/houseplan/frontend/**` побайтно совпадают с
пересобранными, обновлять было нечего.
4. Прогнаны гейты (список — «Что прогнал» ниже); дешёвые (`typecheck`
внутри `build`, `npm test`, `npm run build`) уже подтверждены зелёным
Validate на этом точном SHA (см. ссылку в задании), их не переганивал;
`check-docs.mjs`, `no-new-any.mjs`, `no-new-private-writes.mjs`,
`bundle:budget` прогнал сам, так как диф трогает `src/**`, а инструкция
не считает их покрытыми чужим прогоном за меня.
5. Прогнал сам `smoke_sections_resize.mjs` и `smoke_houseplan_panel.mjs`
(браузерные смоки Validate не гоняет вне `heavy`-условий — они не в
материале, который мне назвали зелёным). Оба зелёные.
6. Через `node scripts/mutation-registry.mjs --id=<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`) — каждый краснит именно
`smoke_sections_resize.mjs`, репозиторий чист после каждого прогона
(скрипт сам возвращает мутант и пересобирает). Это заполняет колонку
«чем краснеет» для защитных AC2/AC4/AC5/AC7/AC8 предметно, а не по
заявлению автора.
7. Прогнал `node scripts/smoke-select.mjs --base f68878cc4... --head
b7fb7cf7e...` — «прямое совпадение» вернуло 19 смоков, включая оба
названных в AC (`smoke_sections_resize.mjs`, `smoke_houseplan_panel.mjs`)
и ряд смоков, делящих символы `_hdrH`/`_bootSoft`/`_stageEl`/`_warmSlot`
с изменённым `boot-soft-layout.ts`. Из них выбрал и прогнал
`smoke_render_invalidation.mjs` (единственный, читающий `_bootSoft`
напрямую — сигнатура `settleSoftStageLayout` изменилась) и
`smoke_warm_dialogs.mjs` (`_hdrH`+`_warmSlot`, соседняя soft-layout
цепочка) как точечную regression-проверку рефактора параметра
`panelHost → containerOwnedHeight`; оба зелёные. Остальные 15 прямых
совпадений и 28 слабых связей не гонял — сочтено непропорциональным для
рефактора, который для существующих layout (`layout` не установлен)
математически не меняет значение `containerOwnedHeight` (было
`panelHost`, осталось `panelHost || false`), см. «Чего не проверял».
8. Экспериментально проверил (не для отчёта CI, а для собственной
уверенности): вручную заменил `language: 'ru'` на `'de'` в фикстуре
`smoke_sections_resize.mjs`, прогнал — все проверки, включая
`minimumSixRowsStayContained`, прошли и на немецкой локали; откатил
правку (`git status` после отката чист). Это ограничивает находку
Medium-1 ниже: код сейчас не ломается на German, но CI никогда не
узнает, если сломается.
9. Проверил трейлеры всех трёх коммитов (`git log`) — `Issue: #648` на
каждом, `User-Visible: yes` только на `7f48e690`, и оба changelog входят
в этот же коммит (`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md` — видно в
`git show --stat 7f48e690`).
10. Сверил термины режимов (Plan/Devices/Backdrop → «План»/«Устройства»/
«Подложка») со смоком, использующим внутренние имена `plan`/`devices`/
`decor` — совпадает с существующим соответствием в `docs/USER-GUIDE.ru.md`
(уже проверено ревьюером ТЗ).
11. Проверил, что `houseplan-space-card` (`src/space-card.ts`) не получил
ни `getGridOptions`, ни ссылок на новое свойство `layout` (grep) —
юнит-тест это же утверждает через `assert.doesNotMatch`.
12. Проверил «одно число — один источник» (§8): 10/6 фигурируют только как
возврат `getGridOptions()` (единственный источник) и как проза в
changelog/USER-GUIDE/ARCHITECTURE/STATUS-FEATURES/TESTING, дословно
совпадающая с этим возвратом; отдельного хардкода этих чисел или
пиксельных производных (≈632/≈376 из K2) в `src/**` или изменённых
`docs/*.md` не нашёл (grep).
## Находки
### Medium-1 (в скоупе задачи). AC3 документально обещает browser smoke
«RU/DE», а поставленный smoke проверяет минимальную высоту только на
русской локали
**Файл:** `demo/smoke_sections_resize.mjs:64-101` (секция `minimumGeometry`),
конфиг `card.setConfig(config)` строка 72.
**Что не так.** Таблица AC issue #648 для AC3 называет доказательство
буквально: «Browser smoke RU/DE, geometry + scroll oracle» — для
наблюдаемого результата «На минимальных 6 строках View при узкой ширине и
**длинной локали** не выходит за slot; header доступен...». Раздел i18n
того же ТЗ поясняет зачем: «Существующие RU/DE длинные подписи входят в
визуальную проверку минимальной высоты» — RU и DE выбраны вместе не
случайно: `docs/TESTING.md:484-485` фиксирует немецкую локаль как
установленный в проекте эталон «длинных подписей», ломающих узкую/минимальную
раскладку («German View... fit at desktop and 390 px without horizontal
overflow or clipped actions»).
Фактически `card.setConfig(config)` в `demo/smoke_sections_resize.mjs:72`
задаёт `language: 'ru'` один раз на весь смок — включая блок
`minimumGeometry` (строки 91–101), который единственный проверяет минимум
6 строк на узкой ширине 390 px. Немецкая локаль нигде в файле не
упоминается (`grep -n "language\|'de'" demo/smoke_sections_resize.mjs` не
находит `de`). Половина заявленного в AC3 покрытия не существует как
исполняемый тест.
**Почему это находка, а не блокер существующей защиты.** Я не нашёл живого
дефекта: ручной эксперимент (заменил `'ru'` на `'de'` в этом же файле,
прогнал, откатил) показал, что на текущем коде `minimumSixRowsStayContained`
и `headerContained`/`menuReachable` проходят и на немецкой локали. Это не
регрессия сегодняшнего дня.
**Сценарий отказа.** Ближайшее изменение хедера/меню (например, более
длинная подпись пункта меню, добавленная в рамках другой задачи) может
сузить запас по немецкой локали на 390 px/6 строках, ничего не сломав по
русской. Ни один автотест в этом дифе и ни один существующий golden/сценарий
(проверено — `docs/golden`, `demo/docs` не содержат Sections/grid-сценария)
это не заметит: `smoke_sections_resize.mjs` — единственный исполняемый
свидетель AC3, и он проверяет только RU. Заявленная в согласованном ТЗ
защита «длинная локаль не переполняет минимальный slot» наполовину не
существует как код.
**Почему в скоупе и не блокирует зелёный.** Правка дешёвая — обернуть
существующий блок `minimumGeometry` (или отдельно вызвать `setConfig` с
`language: 'de'`) в цикл по `['ru', 'de']` и агрегировать оба результата в
`minimumSixRowsStayContained`; список поверхностей и файлов не меняется, не
требует новой инфраструктуры. Без High-находок это делает вердикт жёлтым, а
не переоткрывает задачу отдельным issue (#202).
## Что проверено и корректно
- **AC1** (grid defaults + `getCardSize`): `getGridOptions()` возвращает
ровно `{ columns: 'full', rows: 10, min_rows: 6 }` без `max_rows`
(`src/houseplan-card.ts:3762`); подтверждено исполнением обоих браузерных
смоков (`executableGridDefaults`, `sectionsDefaultsRemainAvailableInPanel`)
и юнитом, не читающим монолит как единственное доказательство (юнит —
дополнение к исполняемому свидетелю, не замена). `space-card.ts` не имеет
`getGridOptions` — подтверждено grep и юнитом.
- **AC2/AC3** (контейнерная высота, минимум 6 строк на 390 px): смок
`smoke_sections_resize.mjs` реально измеряет `getBoundingClientRect()`
`host`/`card`/`ha-card`/`header`/`stage` на живом собранном бандле —
прогнан мной, зелёный (см. «Как проверялось» п.5). Кроме RU-ограничения
из Medium-1, оракул содержательный: допуск 1 px, `stage.height > 0`,
`cardScroll`/`haCardScroll` ≤ 1 (нет overflow/пустого хвоста).
- **AC4** (серия 6→10→14→6, камера/аспект не сбрасываются, нет ошибок
ResizeObserver): проверено исполнением; мутант
`sections-resize-drops-stage-refit-observer` (отключение
`_roViewport.observe(stage)`) краснит именно этот smoke — «чем краснеет»
предметно подтверждено мной, не только заявлено автором.
- **AC5** (переходы Plan/Devices/Backdrop не меняют внешний rect, нет
busy-хвоста): смок ждёт реальный `_modeTransition.state` (не форсирует
приватное состояние), проверяет `entering.to.stageHeight <=
card.clientHeight + 1` на входе и выходе для всех трёх режимов при 10 и 6
строках. Мутант `sections-editor-transition-uses-window-height`
(возврат `innerHeight` вместо `containerOwnedHeight ? this.clientHeight`)
краснит smoke — подтверждено прогоном.
- **AC6** (explicit `grid_options` остаётся главным, config не мутируется):
`Object.freeze(config)` в фикстуре + сверка `JSON.stringify(config) ===
configBefore` и `card._config.grid_options === config.grid_options` по
идентичности объекта — сильнее обычного deep-equal, ловит скрытое
клонирование. Не отдельный юнит-файл, а часть browser smoke (ТЗ называет
«Unit + browser smoke» как два способа доказательства; по факту это один
исполняемый смок) — расхождение в названии способа доказательства не
снижает фактическое покрытие и не поднимается до отдельной находки (по
аналогии со снятыми Low ревью ТЗ).
- **AC7** (без `layout=grid` — старое поведение; `space-card` без grid
defaults): `card.layout = null` в смоке восстанавливает `100dvh` в
реальном инлайн-стиле (`ordinaryCardKeepsViewportHeight`), проверено
исполнением; `smoke_houseplan_panel.mjs` (panel-host, не grid)
обновлён и прогнан мной отдельно — зелёный, панельная контейнерная
высота не задета рефактором `panelHost → containerOwnedHeight`
(математически `containerOwnedHeight === panelHost` когда `layout` не
установлен).
- **AC8** (resize не создаёт кликов/тапов, hit-coordinates верны после
resize): `moreInfo` счётчик остаётся 0, `stableMode === 'view'`,
`postResizeHitCoordinatesMatchCamera` сверяет реальную матрицу
`getScreenCTM()` SVG после шторма resize с `card._view` — не заявление, а
геометрическая проверка через реальный DOM.
- **AC9** (30 изменений высоты без ResizeObserver loop/error): цикл из 30
`frame()`-тиков с чередованием 6/14 строк, финальная геометрия и время
(`stormElapsed < 3000`) проверены исполнением; `finish()` в
`demo/serve.mjs` считает необработанные исключения карточки
(`_pageErrors`) и красит прогон при их появлении — не только заявленные
булевы поля.
- **AC10** (документация + credit): `docs/CHANGELOG.md`/`.ru.md`,
`docs/USER-GUIDE.md`/`.ru.md`, `docs/ARCHITECTURE.md`,
`docs/STATUS-FEATURES.md`, `docs/TESTING.md` все обновлены и
проверены чтением; `@pando80` упомянут в обоих changelog с ссылкой на
#648. `check-docs.mjs --screenshots=warn` (режим обычного пуша) прогнан
мной — зелёный (единственная находка — WARN о неизменном скриншот-
отпечатке, ожидаемо: диф не меняет визуальные пиксели плана/chrome,
ТЗ прямо разрешает это в «Плане автотестов» п.7).
- **Трейлеры и changelog**: `Issue: #648` на всех трёх коммитах,
`User-Visible: yes` только на `feat`-коммите и оба changelog редактируются
именно в нём — соответствует AGENTS.md.
- **Одно число — один источник** (§8): 10 и 6 строк существуют один раз —
как возврат `getGridOptions()`; вся документация и тесты лишь
переиздают это значение прозой/сравнением, вторичного независимого
источника не заведено.
- **Бандл воспроизводим**: пересборка (`npm run build && npm run
bundle:sync`) на материале не породила диффа — закоммиченный `dist/**` и
зеркала в `custom_components/houseplan/frontend/**`,
`demo/srv/assets/**` побайтно совпадают с исходниками этого SHA.
- **Гейты, специфичные для `src/**`-дифа**: `no-new-any.mjs` (0 новых
`any` в 31 добавленной строке `src/**`), `no-new-private-writes.mjs`
(0 новых записей в приватное состояние карточки из 223 добавленных строк
смоков — только чтения, что разрешено), `bundle:budget` (initial View
291761 B, в пределах потолка) — все прогнаны мной и зелёные.
- **Юнит-тесты**: `npm test` — 3086 тестов, 3085 pass, 1 skip (не связан с
этой задачей), 0 fail.
- **Область изменений**: `space-card.ts`, `panel-host`, Masonry, kiosk не
тронуты кодом (только упомянуты в документации как «не меняются») —
подтверждено grep по всем изменённым файлам и фактическим отсутствием
правок вне `houseplan-card.ts`/`boot-soft-layout.ts`/`base.styles.ts`.
## Чего не проверял
- **Реальное поведение Home Assistant Sections вне этого репозитория.**
Весь механизм задачи опирается на внешний контракт: что настоящий HA при
размещении карточки в Sections/grid-дашборде действительно устанавливает
на элементе свойство `layout = 'grid'` (K3/K11, оставлено ревью ТЗ как
«принято предположительно, поменять свободно» — технический, не
продуктовый вопрос). В этой песочнице нет ни реального HA, ни доступа к
инструментам веб-поиска/GitHub-поиска (запрошенные `WebSearch` и
`mcp__github__search_code` не были разрешены), поэтому свериться с
исходником `home-assistant/frontend` я не смог. Все AC2–AC10 в этом
дифе проверены только против синтетической фикстуры, которая
устанавливает `card.layout = 'grid'` вручную (`demo/smoke_sections_resize.
mjs:77`) — они доказывают, что *если* HA присылает этот сигнал, карточка
ведёт себя правильно, но не доказывают, что HA действительно его
присылает именно так (то же имя свойства, тот же момент жизненного цикла).
Это прямой риск: если предположение неверно, весь фиче-код останется
мёртвым в проде при полностью зелёных гейтах. Технически это ровно то
место, которое ревью ТЗ явно делегировало код-ревью — фиксирую как
открытый остаточный риск, а не как находку, потому что задача не может
быть проверена исполнением без реального HA, а сам код внутри доступного
материала последователен и не содержит внутренних противоречий.
- **15 из 19 «прямых совпадений» и все 28 «слабых связей» `smoke-
select.mjs`.** Прогнал только `smoke_sections_resize.mjs`,
`smoke_houseplan_panel.mjs` (названы в AC), `smoke_render_invalidation.mjs`
и `smoke_warm_dialogs.mjs` (точечно — единственные, трогающие изменённую
сигнатуру `settleSoftStageLayout`/`_bootSoft` напрямую). Остальные не
гонял: рефактор `panelHost → containerOwnedHeight` в
`boot-soft-layout.ts` математически не меняет значение для существующих
(не-`grid`) карточек, поэтому регрессия там маловероятна; полный прогон
матрицы — предрелизная обязанность, а не гейт код-ревью (§8).
- **`golden:verify`** — не прогонял. ТЗ прямо говорит: «Golden не
требуется, если визуальные пиксели плана/chrome не меняются» — диф не
трогает цвета/геометрию плана, только контейнерную высоту и CSS-flex;
`check-docs.mjs` подтверждает отсутствие изменений в
зафиксированных скриншот-сценариях документации (только WARN о
фингерпринте, не ERROR).
- **`npm run invariants`** — не прогонял: диф не меняет геометрическую
модель плана (стены/комнаты/устройства) и не трогает ссылки на неё,
только презентационный слой карточки.
- **`python -m pytest tests_backend`** — не прогонял: диф не касается
`custom_components/houseplan/**/*.py`.
- **Performance-профили** — не названы в AC этой задачи; ТЗ прямо
запрещает новый observer/polling, а не вводит его (новый постоянный
наблюдатель не добавлен — использован существующий `_roViewport`).
- **`typecheck`/`npm test`/`npm run build`/`golden`/`performance_smoke`/
browser `smoke`-матрица целиком в CI** — доверился зелёному Validate на
точном SHA `b7fb7cf7` (ссылка в задании) для `typecheck`/`test`/`build`;
`npm test` и `npm run build` тем не менее перегнал сам (см. выше) для
собственной уверенности перед прогоном смоков на пересобранном бандле —
оба зелёные, совпадают с Validate.
## Вердикт
0 High, 1 Medium (в скоупе задачи, Medium-1 — неполное покрытие AC3
браузерным смоком: заявлены RU/DE, реализована только RU). Без High-находок
это жёлтый вердикт: автор дополняет `demo/smoke_sections_resize.mjs`
немецкой локалью в проверке минимальной высоты и проходит повторный раунд
код-ревью — отдельный issue не заводится (#202).
Всё остальное — AC1–AC2, AC4–AC10, трейлеры, changelog, гейты `typecheck`/
`npm test`/`npm run build`/`no-new-any`/`no-new-private-writes`/
`bundle:budget`/`check-docs`, четыре новых защитных мутанта и оба
относящихся к задаче browser smoke — проверено исполнением и корректно.
**Жёлтый.**
---
Вердикт: жёлтый · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 1 → в задаче
---
<!-- material-anchors: подготовлено ревьюером -->
## Материал раунда
- Ветка: на момент ревью без явного имени в рабочей копии; вершина —
`b7fb7cf7e7bce13a44ef0e1af3b97541ddb0dffa`.
- База сравнения: `origin/dev` = `f68878cc44b818b6cfbdb3866302e6febaab2445`.
- Дерево материала (`git rev-parse HEAD^{tree}` на момент ревью): снято на
вершине `b7fb7cf7e7bce13a44ef0e1af3b97541ddb0dffa`, рабочая копия чиста
до и после всех локальных гейтов/мутаций (`git status --porcelain`
пуст).
- Предыдущий раунд (другой этап): `docs/reviews/SPEC-REVIEW-648-r1.md`,
вердикт `green`, High 0, Medium 0.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/648-sections-card-resize`, коммит `b7fb7cf7e7bc` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `a7fe21e21d802c04986cbfab31f3065266c8e9ff`
```
git log --all --format='%H %T' | grep a7fe21e21d80
```
- Тело issue: `8400466150bde494c01aef14b7d5961be36d721b08a28c69e800bd88ac645154`
- Вердикт конвейера: `yellow` · High 0
+1
View File
@@ -7,6 +7,7 @@
| #649 | [SPEC-REVIEW-649-r1.md](SPEC-REVIEW-649-r1.md) | spec · r1 | 🟡 жёлтый | 0 | 1 | в скоупе задачи (возвращается автору); принято ревьюером с записью, правки не требует | `lab.js` `houseplan-card.ts` |
| #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` |
| #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 | — | — |