docs: review document for #649

Issue: #649
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-25 18:55:27 +03:00
committed by Claude
parent 2ed050311b
commit e1f1c7d908
2 changed files with 325 additions and 1 deletions
+323
View File
@@ -0,0 +1,323 @@
# CODE-REVIEW-649-r1
Issue: [#649](https://github.com/Matysh/houseplan-card/issues/649) · 2.5D, этап 6:
объёмные иконки-плитки с тенью, мягкий свет по солнцу, слой декора без
изменений, режим из experimental в General settings.
Материал: `git log --oneline origin/dev..HEAD` / `git diff origin/dev...HEAD`,
ровно на SHA `de4521823ba69c390fda4c0534e52e24a63cc66a` (рабочая копия уже на
нём; ветку не переключал, `git fetch`/`pull`/`checkout` не выполнял).
```
de452182 test(mutants): stage3-w12 guard follows the renamed #649 contract test
e59c89df feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
```
164 файла, +5570/−3767. Трейлеры на обоих коммитах корректны: `Issue: #649`,
`User-Visible: yes` на `e59c89df` (оба changelog в том же коммите), `User-Visible:
no` на `de452182` (гейт мутанта, без поведенческих изменений). Заход r1
код-ревью, ТЗ (r2 в теле issue) уже прошло зелёное SPEC-REVIEW.
## Скоуп
Реализация всех 4 пунктов ТЗ: (1) объёмные плитки-маркеры с торцом, тенью и
рамками только в 2.5D; (2) мягкий свет по солнцу в 2.5D вместо проекции
Flat-клиньев; (3) фикс толщины линий мебели + стены/проёмы/подписи не зависят от
темы; (4) `settings.volumetric_view` — публичная установка в General settings,
удаление alpha-входа, кнопки шапки и пункта меню телефона. AC1–AC13 таблицы
приёмки соответствуют этим пунктам; AC13 (перформанс) и часть AC1/AC12 (Linux
golden) по тексту ТЗ — предрелизные гейты.
## Как проверялось
Ревью велось параллельно пятью под-агентами (каждый — отдельная нить чтения
кода/тестов по разделу ТЗ) плюс моя собственная проверка: process-документы,
трейлеры, docs-дифф, golden/mutation-реестр, числовые пересчёты, и **реальный
прогон гейтов** в песочнице (см. таблицу ниже). Каждое утверждение AC
сверялось построчно с кодом; для тестов/смоков проверялось, может ли
конкретная проверка реально упасть (не просто «элемент существует»), и что
парные мутанты реестра патчат существующие, не протухшие строки.
### Гейты — что прогнано и с каким результатом
| Гейт | Прогнан | Результат |
|---|---|---|
| `Validate` (мутанты) на `de452182` | нет, зачтён по ссылке (условие задачи) | success, https://github.com/Matysh/houseplan-card/actions/runs/36153319135 |
| `npx tsc --noEmit` (через `npm run build`) | да | 0 ошибок |
| `npm run build` | да | чисто, дифф `dist/**` после `bundle:sync` — **нулевой** (`git status` пуст), т.е. закоммиченные `dist/`, `custom_components/houseplan/frontend/`, `demo/srv/assets` побайтово совпадают со свежей сборкой |
| `npm test` | нет (принято по Validate) | — |
| `python -m pytest tests_backend` | **нет** — в песочнице нет модуля `pytest` (`pip show pytest` → not found) | не прогнан; см. «Чего не проверял» |
| `node demo/smoke_iso_tiles.mjs` (AC2–AC6) | да | **OK**, все 33 проверки `true` |
| `node demo/smoke_iso_sun.mjs` (AC7) | да | **OK**, все 18 проверок `true` |
| `node demo/smoke_iso_theme_walls.mjs` (AC8, AC9) | да | **OK**, все проверки `true`, включая `prismOpaque`, `furnitureSameWidth` |
| `node demo/smoke_volumetric_setting.mjs` (AC10, AC11) | да | **OK**, все 18 проверок `true` |
| `node demo/smoke_isometric_contract.mjs` | да (прямое совпадение smoke-select) | **OK** |
| `node demo/smoke_isometric_live_touch.mjs` | да (прямое совпадение) | **OK**, включая `noBordersHasNoRaisedOrVolumeLayers`, `kioskReadsSetting`, `kioskSettingOffIsFlat` |
| `node demo/smoke_room_fit.mjs` | да (прямое совпадение, `_effectiveProjection`) | **OK** |
| `node demo/smoke_grid_scale_invariance.mjs` | да (прямое совпадение) | **OK**, включая `isoHasNoCardToggle` |
| `node demo/smoke_live_pan_coverage.mjs` | да (прямое совпадение) | **OK** |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | да | 78 прямых совпадений, множество «слабых связей» — решение по строкам ниже |
| `node scripts/model-invariants.mjs` | нет, признано неприменимым | задача не меняет каноническую геометрию (стены/комнаты/layout); только презентационные множители 2.5D и новые рендер-модули — см. обоснование ниже |
| `npm run golden:verify` | нет | Flat golden — предрелизный гейт по тексту самого AC1; iso-сцены Stage 6 сознательно не в матрице (см. «Проверено и корректно») |
**Решение по `smoke-select` (78 прямых совпадений).** Ядро (`_effectiveProjection`,
`_ensureIsoSceneRuntime`, `_isoSceneRuntime`, `_kiosk` в связке с проекцией) —
прогнано выше (5 смоков). Остальные ~70 совпадений держатся на широко
встречающихся символах (`_spaceModel`, `_mode`, `_openSettingsDialog`,
`_wallUnionGeometry`, `_physicalBodiesR`, `_spaceWalls`), которые задача не
меняет по существу (её дифф не трогает работу стен/junction/диалогов — только
использует их выход для 2.5D-презентации и упомянутые символы совпадают, потому
что строки диффа их вызывают, а не потому что их поведение изменилось). Не
прогонял: риск регресса в этих смоках от данной задачи оцениваю как
пренебрежимо малый (сами эти системы не тронуты); при необходимости — решение
ревьюера, зафиксированное здесь.
## Находки
### Medium (в скоупе — правится в этой же задаче)
**M1. AC7: обрезка барьерами (физические тела и Solid-перегородки) луча света
в 2.5D нигде не доказана тестом.**
`src/iso-sun.ts:111-114` (`computeIsoSunBeams`) корректно переиспользует
`directionalOccluders`/`floorMinusBodies` из `physical-geometry.ts` — тот же
приём, что и Flat в `houseplan-card.ts:9961-9971` (`_sunInputs()` строит общий
`occluders` для обоих рендеров, `houseplan-card.ts:9889-9894`). Формула,
градиент, стопы, лучики, длина от elevation, подоконник — всё это доказано
`test/iso-stage6.test.mjs` и `demo/smoke_iso_sun.mjs` (я прогнал последний —
все 18 проверок `true`). Но ни один юнит, ни смок, ни одна из
`STAGE6_ACCEPTANCE_SCENARIOS`/`GOLDEN_SCENARIOS` не ставит физическое тело или
Solid-перегородку на пути 2.5D-луча и не проверяет, что часть параллелограмма
действительно вырезается.
Конкретный сценарий отказа: если строка `directionalOccluders(input.occluders,
away, travel)` (`src/iso-sun.ts:112`) получит перепутанный знак направления
(`away` вместо вектора к солнцу) или неверную длину экструзии (`travel` вместо
`depth`), луч в 2.5D будет либо не обрезаться преградами вовсе, либо
обрезаться неправильной стороной — а обрезка контуром комнаты по-прежнему даст
непустой результат, так что ни один текущий тест не покраснеет. Это ровно тот
защитный элемент AC, который ТЗ называет прямо («Обрезка... теми же
барьерами, что у Flat: физические тела и Solid-перегородки») и который не
входит ни в один из трёх названных в таблице приёмки мутантов («длина без
elevation», «лучики на тёмном полу», «Flat-клинья остались»).
**Требуется:** один юнит (или расширение `demo/smoke_iso_sun.mjs`) со сценой,
где физическое тело/Solid-перегородка стоит на пути луча, и явная проверка,
что часть полигона вырезана (например, площадь `polys` меньше площади без
преграды, либо конкретная точка за преградой не в `polys`).
### Low (правлю запись/снимаю с примечанием, отдельного цикла не требуют)
**L1. Устаревшие «alpha/hidden»-формулировки, оставшиеся рядом с уже
исправленным текстом в тех же файлах.**
- `docs/DEVELOPMENT.md:231-234` — «Hidden Stage 3 rendering is a separate
`iso-scene-render-*` chunk. An alpha-off View must not request it» —
противоречит исправленному абзацу пятью строками ниже (249-251) того же
файла: «Since #649 the registry is empty: 2.5D left alpha for the General
settings switch `settings.volumetric_view`».
- `docs/ARCHITECTURE.md:31` — таблица модулей всё ещё называет
`iso-scene-render.ts` «hidden alpha-only Stage 4 scene/runtime boundary»,
хотя абзац на строках ~86-88 того же файла уже описывает гейт как
`settings.volumetric_view` (#649).
- `src/houseplan-card.ts:627-628` — комментарий над `_isoSceneRuntimeLoader`:
«Stage 4 is an opt-in alpha surface» — противоречит корректному соседнему
комментарию (~2305) «one installation-wide switch… no alpha».
- `demo/benchmark_large_house.mjs:145` — «experimental Iso runtime»,
косметика.
Ни на поведение, ни на один тест не влияет — только вводит в заблуждение
читателя кода/доков. Не блокирует; правится по усмотрению автора либо снимается.
**L2. Число в хендоффе не совпадает с фактическим значением в диффе.**
Комментарий автора в issue: «`monolith-baseline.json`: `bundleBytes` 2 528 415
→ 2 540 748»; фактический дифф — `2528415` → `2540346` (разница 402 Б).
Артефакт сам по себе непротиворечив (утверждение «монолит 12 880 строк
(потолок 12 889)» подтверждено — `wc -l src/houseplan-card.ts` = 12880); это
опечатка в тексте хендоффа, не в коде. Снимаю как не влияющую на решение.
**L3. `isoTileStateCss()` жёстко считает торец состояний в `theme: 'light'`
без ветки `.theme-dark.<state>`.**
`src/iso-tiles.ts:109-115` — для всех шести состояний (`on/open/lock-locked/
lock-unlocked/unavail/alarm`) правило `--iso-edge` не различает тему. Сейчас
безопасно: ни один из цветов `ISO_STATE_BODIES` не проходит порог luma<70 и не
близок к белому (минимум — `#F0410C`, luma ≈ 98.4), так что смена темы
результата не меняет. Если в будущем добавят тёмное состояние, тёмная тема
получит светлотемный торец `#5b5e5a` вместо `#4a4a4a` — ни юнит, ни смок этого
не поймают. Не требует правки сейчас; фиксирую для следующей задачи, которая
тронет `ISO_STATE_BODIES`.
**L4. AC5: явно не проверены «Alert > чистый Hover» и «Alert > чистый
Selected» по отдельности.** `demo/smoke_iso_tiles.mjs` проверяет alarm поверх
уже активной комбинации `sel + focus-visible`, а не поверх изолированного
hover или изолированного selected. По специфичности CSS-селекторов
(`iso-tiles.styles.ts:61-68`) приоритет должен сохраниться и в этих парах —
проверено чтением, не исполнением.
**L5. Нет отдельного теста на виртуальные устройства (Q1: «без пунктира, как
обычные»).** `noRing` в `smoke_iso_tiles.mjs` проверяется на обычных
маркерах; для `.dev.virtual` полагаюсь на специфичность правил (5 классов
`.stage.projection-iso.mode-view .dev...` против 2 у `.virtual .device-shell-
frame{border-style:dashed}`) — проверено чтением.
**L6. Тени и плитки продолжают рисоваться при `show_borders: false`, хотя
SVG-слой raised-overlay корректно не строится (это отдельный, уже
существующий и по-прежнему проходящий тест `noBordersHasNoRaisedOrVolumeLayers`
в `demo/smoke_isometric_live_touch.mjs`).** Автор сам называет это решением,
которое стоит проверить ревьюеру. Прочитал код: `isoOverlays?.devices.get(d.id)`
(`houseplan-card.ts:11167`) через опциональную цепочку не роняет рендер плиток
и их теней, когда оверлей-сцена не построена — поведение соответствует
намерению (плитки/тени — независимый HTML/CSS слой, не завязанный на SVG
raised-overlay). Автоматической проверки именно этого поведения (ни в одну,
ни в другую сторону) нет ни в одном тесте. Не входит ни в один AC таблицы
приёмки; фиксирую как задокументированный пробел покрытия, не как дефект.
## Проверено и корректно (доказано; для геометрии/формул — построчным
пересчётом, не «на глаз»)
- **AC1 (Flat неизменен).** Структурно исключено смешение: все правила
`iso-tiles.styles.ts` и добавленные материалы стен скопированы под
`.stage.projection-iso.mode-view` (проверено юнит-тестом
`test/styles-split.test.mjs`, который парсит CSS AST и требует префикс на
КАЖДОМ селекторе, кроме `@media`-обёрток) — Flat-каскад физически не может
задеть ни одно правило. Мебель теперь использует ОДИН вызов
`furniturePlanScreenScale(...)` для обеих проекций (ветка `iso ? 1 : …`
убрана целиком, второго места с иной логикой нет — grep подтвердил
единственность). Смоки `flatWedgesUnchanged`, `flatBack`, `offRestoresFlat`,
`editorFlat` — прогнаны, все `true`. Байт-в-байт Flat golden — предрелизный
гейт по тексту самого AC1, вне обязанностей код-ревью.
- **AC2 (плитка/торец/×1.12/бейдж).** Все числа (`radius=22/80`, `depth=8/80`,
`lift=6/80`, `badgeGap=6/80`) и формула торца с тремя особыми случаями
(luma<70 → `#5b5e5a`/`#4a4a4a`, near-white в тёмной теме → `#4a4a4a`,
светлый пол → `brightness(.82) saturate(.8)`) сверены построчно с
`src/iso-tiles.ts` и ТЗ; юнит `test/iso-stage6.test.mjs` и `smoke_iso_tiles.mjs`
(прогнан, `true`) снимают реальный `getComputedStyle`/`getBoundingClientRect`,
а не факт существования DOM-узла.
- **AC3 (коллизии ×1.12, touch ≥44×44).** `iso-scene-render.ts:896,947`
прокидывают `ISO_ICON_SCALE` в `isoRaisedOverlayHalfSize`; юнит строит
реальную сцену через `buildIsoOverlayRenderScene` и алгебраически сверяет
половину footprint. Мутант `iso-collision-without-icon-scale` бьёт именно
эту строку.
- **AC4 (один слой теней, таблица параметров, не поверх соседа).** Таблица
4×(сдвиг, размытие, opacity) в `isoTileShadow()` пересчитана и совпадает с
ТЗ построчно (например, светлая·светлый пол: `dx=8/80=0.1D`, `dy=34/80=
0.425D`, `sigma=11/80=0.1375D`). `shadowNeverOnNeighbourTile` — не
поверхностная проверка: маркер намеренно ставится над соседним по вертикали,
сравнивается внутренность нижней плитки побайтово со включённым/выключенным
слоем теней. `z-index:-1` внутри stacking context `.devlayer` даёт
гарантию сильнее буквы ТЗ (тень всегда под ВСЕМИ маркерами, а не только под
соседним).
- **AC5 (рамки, приоритет, тело не перекрашено).** Приоритет
`Alert > Focus > Selected > Hover` реализован специфичностью и порядком
правил; смок реально включает классы/фокус и сравнивает `border-top-color` и
геометрию рамки. (См. L4 про неполный перебор пар.)
- **AC6 (forced-colors/no-filter).** Оба медиа-блока синхронно гасят тень и
торец, не трогая размер/рамки; смок эмулирует `forcedColors:'active'` и
реально это проверяет.
- **AC7 (свет).** Формула длины, параллелограмм (`s = n·L +
t·L·(dir·t)/(dir·n)`), градиенты, лучики (только светлый пол), подоконник —
все числа совпадают с ТЗ и `docs/SUN.md` дословно; ворота видимости
(elevation, fade, редактор, `sun_rays`) реализованы ОДИН раз и лишь
переключают геометрию по проекции, так что Flat-код не тронут. Три названных
в таблице приёмки мутанта — реальны и указывают на существующие строки. (См.
M1 про пробел в барьерах.)
- **AC8 (мебель).** `stroke-width` идентичен в Flat и 2.5D (смок:
1.1818 px = 1.1818 px, прогнан лично — `true`); юнит `furniture-stroke-
contract` матчит новую строку кода регулярным выражением, которое ловит и
старую (мутант «`iso ? 1 :` возвращён»).
- **AC9 (тема не красит стены).** Пересчитал вручную: `#ffffff` × 0.77 →
`#c4c4c4`, × 0.68 → `#adadad`, × 0.60 → `#999999` — совпадает с ТЗ и
лабораторией. `.theme-dark .iso-*`/`prefers-color-scheme` правила
физически удалены (не ослаблены — `grep` не находит), кроме разрешённого
исключения (`iso-ambient-shadow`, внешняя тень здания). Смок
`themeKeepsWalls`/`colourSchemeKeepsWalls` (прогнан, `true`) снимает
`getComputedStyle` в трёх режимах (light/dark/system) и требует побайтового
совпадения.
- **AC10/AC11 (настройка, alpha-вход убран).** Поле сериализуется только при
`true` (`delete settings.volumetric_view` иначе — `houseplan-editor-
runtime.ts:9512-9513`); единое правило вида для карточки/страницы/киоска
через `_isoEnabled` = `volumetricViewOf(this._settings)`; редакторы и
`houseplan-space-card` структурно без iso-кода (не флаг — код физически
отсутствует); ленивая загрузка `iso-scene-render` условна на `_isoEnabled`;
`LABS_FLAGS` пуст, кнопка и пункт меню физически вырезаны из шаблона (не
CSS-скрытие); `houseplan_card_view_v1` не читается. `validation.py` —
строгий `bool` (тест бьёт `None, 0, 1, "true", "iso", [], {}`).
`config-schema.json`, `support_package.py` синхронны. i18n en/ru/de/fr —
текст совпадает с ТЗ дословно; `view.volumetric`/`view.flat` освобождены и
нигде не используются (grep пуст). Смок `smoke_volumetric_setting.mjs`
(прогнан лично, все 18 `true`) включает нетривиальные детали: превью не
переключает вид до сохранения (`previewDoesNotSwitch`), `false` не
сериализуется (`falseNotStored`).
- **AC12 (макеты).** Прочитал `docs/design/649-25d-stage6/ACCEPTANCE.md`
целиком и все 7 PNG кадров. Единственное осознанное расхождение (цвет рамки
hover — `#0C82F0` продукта против янтарного в лабе для светильников)
объяснено ссылкой на ТЗ и подтверждено кадром. Мебель, стены, тени, свет,
плитки — визуально соответствуют описанию таблицы «элемент · эталон ·
продукт»; необъяснённых расхождений не нашёл.
- **Golden/матрица.** `STAGE6_ACCEPTANCE_SCENARIOS` намеренно отделены от
`GOLDEN_SCENARIOS` (не имеют эталона — по правилу #641 каждая сцена матрицы
обязана иметь принятый baseline, а baseline не может существовать раньше
сцены); `test/golden-matrix.test.mjs` обновлён на модель активации через
`settings.volumetric_view` вместо `alpha`. Корректное, задокументированное
решение, не находка.
- **Мутационный реестр.** 16 новых мутантов (`iso-tile-ring-visible` …
`labs-iso-returns`) — патчи адресуют существующие строки (проверено сверкой
`find`-строк с текущим кодом), guard-команды осмысленны и соответствуют
названным в таблице приёмки красным сценариям. `de452182` чинит один
протухший guard (`stage3-w12-separate-alpha-url-key-restored`) после
переименования теста в `e59c89df` — сам факт находки и починки говорит о
добросовестной проверке автора (обнаружено собственным Validate, не мной).
- **Бюджет бандла.** `INITIAL_VIEW_GZIP_CEILING` 292 600 → 294 900,
обоснование (CSS плиток/материалов не может ждать ленивый граф) правдоподобно
и арифметически сходится (294 004 замер + 896 Б запаса = 294 900).
## Чего не проверял
- `python -m pytest tests_backend` — в песочнице ревью нет модуля `pytest`
(`pip show pytest` → not found); полагаюсь на чтение
`custom_components/houseplan/validation.py`/`support_package.py` и
`tests_backend/test_validation.py`/`test_support_package.py` (проверено
построчно одним из под-агентов) плюс на то, что Validate на этом SHA
включает backend-джобу по стандартному пайплайну push (`AGENTS.md`,
«Gates»). Если backend-джоба Validate не входила в засчитанный success —
это стоит явно подтвердить отдельно.
- `npm run golden:verify` / `npm run golden:capture` — по тексту AC1/AC12 это
предрелизный гейт (iso Stage 6 сцены сознательно не в матрице, baseline
ещё не снят на Linux CI); не гейт код-ревью.
- Полный `npm test` — принят по зелёному Validate на этом SHA, не перегонял.
- Full Performance (AC13) — по тексту AC13 сама предрелизный гейт.
- ~70 «слабых» смоков из `smoke-select` (см. таблицу гейтов) — решение по
каждому: не запускал, риск для не тронутых по существу систем (стены/
диалоги/junction) от этой задачи оцениваю как минимальный.
- Настоящий планшет/телефон, Safari, `@supports not (filter)` — эмулировать
нечем в песочнице; отмечено и автором.
- Наведение мышью в живой лаборатории дизайнера (эталон вложения 11) —
недоступно из песочницы; полагаюсь на приложенный кадр `pair-hover.png` и
форму рамки, подтверждённую тестами.
## Вывод
High: 0. Medium: 1, в скоупе задачи (M1 — обрезка барьерами 2.5D-луча
непроверена автотестом, конкретный сценарий регрессии описан выше). Low: 6,
правятся по усмотрению автора или сняты с примечанием (L2 снята — опечатка
хендоффа, не влияет на решение). Реализация в остальном очень плотная:
все числовые формулы (радиус, торец, тень, стены, свет) пересчитаны вручную и
совпадают с ТЗ и лабораторией; 9 браузерных смоков прогнаны лично в песочнице
и все зелёные; `tsc`/`build`/three-way bundle sync — чисто; docs согласованы
с кодом почти везде (кроме отмеченного в L1). Единственная причина жёлтого
вердикта — M1: узкий, дёшево устранимый пробел защитного покрытия одного
конкретно названного в ТЗ поведения (обрезка барьерами), не общая проблема
реализации.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/649-25d-stage6`, коммит `de4521823ba6` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `571541905136a48c1729755f6d7877e5ac6aece4`
```
git log --all --format='%H %T' | grep 571541905136
```
- Тело issue: `e7d94f185bc9a5d2d7e168933579903e54c88a9819347cb49f0f7c35db60a9e7`
- Вердикт конвейера: `yellow` · High 0
+2 -1
View File
@@ -1,12 +1,13 @@
# Индекс ревью # Индекс ревью
Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1062, issue: 375. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`. Генерируется `node scripts/reviews-index.mjs` (#635) — не редактировать руками. Документов: 1063, issue: 375. Вердикт: 🟢 зелёный · 🟡 жёлтый · 🔴 красный · ⚪ не распознан (свободная форма старых документов). H/M — число High/Medium по строке вердикта или заголовкам находок. Файлы — пути, названные в находках; ищите по имени файла: `grep form-kit INDEX.md`.
| Issue | Документ | Этап · раунд | Вердикт | H | M | Находки | Файлы | | Issue | Документ | Этап · раунд | Вердикт | H | M | Находки | Файлы |
|---|---|---|---|---:|---:|---|---| |---|---|---|---|---:|---:|---|---|
| #650 | [CODE-REVIEW-650-r1.md](CODE-REVIEW-650-r1.md) | code · r1 | 🟢 зелёный | 0 | 0 | — | — | | #650 | [CODE-REVIEW-650-r1.md](CODE-REVIEW-650-r1.md) | code · r1 | 🟢 зелёный | 0 | 0 | — | — |
| #649 | [SPEC-REVIEW-649-r1.md](SPEC-REVIEW-649-r1.md) | spec · r1 | 🟡 жёлтый | 0 | 1 | в скоупе задачи (возвращается автору); принято ревьюером с записью, правки не требует | `lab.js` `houseplan-card.ts` | | #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 | — | — | | #649 | [SPEC-REVIEW-649-r2.md](SPEC-REVIEW-649-r2.md) | spec · r2 | 🟢 зелёный | 0 | 0 | — | — |
| #649 | [CODE-REVIEW-649-r1.md](CODE-REVIEW-649-r1.md) | code · r1 | 🟡 жёлтый | 0 | 1 | AC7: обрезка барьерами (физические тела и Solid-перегородки) луча света в 2.5D нигде не…; Устаревшие «alpha/hidden»-формулировки, оставшиеся рядом с уже исправленным текстом в т…; Число в хендоффе не совпадает с фактическим значением в диффе. Комментарий автора в iss…; isoTileStateCss() жёстко считает торец состояний в theme: 'light' без ветки .theme-dark…; AC5: явно не проверены «Alert > чистый Hover» и «Alert > чистый Selected» по отдельност…; Нет отдельного теста на виртуальные устройства (Q1: «без пунктира, как обычные»). noRin… | `src/iso-sun.ts` `physical-geometry.ts` `houseplan-card.ts` `test/iso-stage6.test.mjs` `demo/smoke_iso_sun.mjs` `docs/DEVELOPMENT.md` `docs/ARCHITECTURE.md` `iso-scene-render.ts` |
| #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 | [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-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 | — | — | | #648 | [CODE-REVIEW-648-r3.md](CODE-REVIEW-648-r3.md) | code · r3 | 🟢 зелёный | 0 | 0 | — | — |