diff --git a/docs/reviews/CODE-REVIEW-649-r1.md b/docs/reviews/CODE-REVIEW-649-r1.md new file mode 100644 index 00000000..3299f763 --- /dev/null +++ b/docs/reviews/CODE-REVIEW-649-r1.md @@ -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.`.** +`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: узкий, дёшево устранимый пробел защитного покрытия одного +конкретно названного в ТЗ поведения (обрезка барьерами), не общая проблема +реализации. + +--- + + + +## Материал раунда + +- Ветка: `issue/649-25d-stage6`, коммит `de4521823ba6` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `571541905136a48c1729755f6d7877e5ac6aece4` + ``` + git log --all --format='%H %T' | grep 571541905136 + ``` +- Тело issue: `e7d94f185bc9a5d2d7e168933579903e54c88a9819347cb49f0f7c35db60a9e7` +- Вердикт конвейера: `yellow` · High 0 diff --git a/docs/reviews/INDEX.md b/docs/reviews/INDEX.md index 7201c519..5d49b822 100644 --- a/docs/reviews/INDEX.md +++ b/docs/reviews/INDEX.md @@ -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 | Находки | Файлы | |---|---|---|---|---:|---:|---|---| | #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-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 | [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 | — | — |