# SPEC-REVIEW-374-r1 Issue: [#374](https://github.com/Matysh/houseplan-card/issues/374) — Feature request: optional light pools and wall shadows in `houseplan-space-card` ТЗ: [`docs/specs/374-space-card-light-pools.md`](https://github.com/Matysh/houseplan-card/blob/issue/374-space-card-light-pools/docs/specs/374-space-card-light-pools.md) Ветка: `issue/374-space-card-light-pools`, SHA материала ревью: `c4f37986` Заход: r1 · блокирующих циклов израсходовано 0 из 4 ## Скоуп ревью Дифф ветки против `origin/dev`: ``` docs/specs/374-space-card-light-pools.md | 434 +++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) ``` Только новый файл ТЗ, продуктовый код не тронут. Трек — полный (не `small`, не `trivial`): лейблы issue — `P2`, `feature`, `S4-spec-review`, признака лёгкого трека нет; аналитика в issue явно называет причину («появляется новый публичный конфигурационный контракт, затрагиваются производительность и touch/View, а каноническую световую модель надо разделить между двумя рендерами»), что корректно по критериям §5 PROCESS.md (сложность/риск 8/10 — далеко за порогом ≤3). Файл ТЗ в `docs/specs/374-*.md` — верное место. ## Как проверялось 1. Прочитан `docs/SCOPE.md` — сверена персона и job (J1), закрытая задача. 2. Прочитаны `AGENTS.md`, `PROCESS.md` целиком (жизненный цикл, §5 критерии трека, §7.1 обязательные разделы ТЗ). 3. Прочитано тело issue #374, все комментарии (аналитика + «ТЗ готово»), и связанный закрытый документационный issue #370 с ответом владельца — источник контекста, откуда взялся запрос. 4. Прочитан `docs/LIGHT.md` целиком — канон световой модели, включая раздел «Which surfaces render pools», прямо описывающий текущее ограничение static card, которое ТЗ снимает. 5. Прочитан `docs/USER-GUIDE.ru.md` (раздел про `live_states`) для сверки терминологии. 6. Прочитан сам ТЗ целиком (435 строк). 7. Технические утверждения ТЗ о **текущем** поведении кода сверены с исходниками, а не приняты на слово: - `resolvedLightSources`, `selectSpatialGlowSource`, `resolveGlowAppearance`, `glowAlpha`, `GLOW_FALLOFF` — существуют в `src/devices.ts`, `src/logic.ts`, `src/houseplan-card.ts` ровно с той сигнатурой использования, что описана в контракте (раздел «Источники и видимость»). - Независимость Glow от `live_states` (`_renderGlowLayer` не читает `live_states`, `live_states === false` гасит только `_syncActivityRuntime`/`_activityRuntime`, т.е. иконки/пульс, но не сам слой pool) — проверено чтением `src/houseplan-card.ts:10500-10589` и `src/space-card.ts:363-467`. Совпадает с заявлением ТЗ п.2.5. - Порядок слоёв («room/tunnel fills → Glow base/tunnels → radial pools → стены/openings → labels/devices») — сверен с фактическим порядком `svg` в `_renderPlanBody`/аналоге (`src/houseplan-card.ts:11290-11317`): room fills → opening tunnel fills → Glow base → decor → `_renderGlowLayer` → sun rays → zero walls → wall bodies. Совпадает. - Кеш-ключи (`_glowClipCache` = `space.id|fingerprint|pos|R`, barrier по geometry fingerprint + квантованной сигнатуре проёмов) — сверены с `src/houseplan-card.ts:10586-10588` и с разделом «Caching» `docs/LIGHT.md`. Совпадает дословно. - Утверждение «static renderer получает marker positions в plan coordinates до процентного преобразования devlayer» — сверено: `src/space-geometry.ts` строит модели в `NORM_W`×`NORM_W` (план- координаты), а `src/space-render.ts:399,435` конвертирует их в `left:%`/`top:%` только в момент рендера HTML-оверлея маркеров. Значит план-координаты действительно доступны раньше процентного шага — утверждение точное, не догадка. - Поведение неизвестного `space` («сохраняет текущую error card», AC1) — сверено с `src/space-card.ts:812` (`_errorCard(t(..., 'space_card.not_found'...))`). Существует уже сейчас. - Все файлы, упомянутые в «Затронутые файлы» и «План автотестов» (`demo/smoke_space_card.mjs`, `demo/performance/budgets-large-house-glow-overlay.json`, `src/i18n/{en,ru,de,fr}.json` с параллельными ключами `editor.live_states` как образец конвенции) — существуют. 8. Проверено отсутствие открытых продуктовых вопросов и незакрытых меток неопределённости (`?`, «уточнить», TBD) в тексте ТЗ — не найдено ни одной вне markdown-разметки типа `light_pools?: boolean`. ## Гейты Стадия — ревью ТЗ, продуктовый код в диффе отсутствует. `typecheck`/`test`/ `build`/`check-docs`/смоки/golden/performance/invariants — гейты код-ревью (§8 PROCESS.md), к спек-ревью неприменимы и не запускались. Единственная проверка этой стадии — чтение и сверка ТЗ с кодом и канонами, что сделано в разделе выше. ## Находки Ни одной блокирующей (High) или Medium-находки, в скоупе или вне скоупа, не обнаружено. Рассмотренные, но не подтвердившиеся кандидаты (для прозрачности разбора): - **AC3 явно не называет «room draft» как отдельный оккludер**, хотя раздел «Скоуп» и канон `docs/LIGHT.md` перечисляют draft отдельно. Проверка: `docs/LIGHT.md` группирует draft вместе с partition как один класс «independent bodies», геометрически объединяемых в одно тело перед visibility sweep. AC3 и план автотестов покрывают «partition», которое по канону механически идентично draft. Не находка — терминологическое сокращение, а не пробел контракта. - **Термин «semantic pixel witness»** в доказательствах AC2–AC4 не определён в `docs/TESTING.md` как канонический термин проекта — он введён и уже использовался автором в предыдущем спеке (`372-space-card-empty-title.md`). В этом же документе термин раскрыт операционально («План автотестов» п.5–6: конкретные точки сэмплинга пикселей, свет/тёмная тема, DPR/ширины). Не находка — определён внутри документа, а не оставлен голой ссылкой на неписаный процесс. - **Дублирование места переключателя в UI** («UX и доступность»: «рядом с live-state visual settings» вместо точного «рядом с `live_states`», уточнённого только в блоке «Принято предположительно») — не противоречие, второе уточняет первое, оба текста согласуются. ## Что проверено и корректно - Оба обязательных продуктовых раздела (§7.1) — «Сценарий» и «Что человек увидит до и после» — называют персону из `docs/SCOPE.md` (домочадец/гость- киоск), поверхность (`houseplan-space-card` как основной визуальный элемент дашборда) и однозначно формулируют видимое изменение без терминов реализации. - Задача укладывается в J1 `docs/SCOPE.md` — прямое усиление «Show the whole home and what's happening right now» для отдельной карточки. Не пересекает «Out of scope» (не sunlight/window wedges — прямо исключено в «Не-скоуп» со ссылкой на независимость `docs/SUN.md`; не 3D/интерьер; не automations). - Все 13 обязательных разделов ТЗ по §7.1 присутствуют: сценарий · что увидит · проблема · скоуп/не-скоуп · контракт поведения · UX · модель данных и миграция · i18n · AC1…AC10 с доказательством · план автотестов · риски · откат · release-артефакты. Плюс необязательные, но полезные: архитектурный контракт, перф/bundle, touch/темы/размеры, затронутые файлы, блок принятых предположений. - Каждый AC (AC1…AC10) сформулирован как проверяемое утверждение и несёт явный способ доказательства (`unit`/`browser smoke`/`golden`/`performance artifact`/`CI checks`/«ревью кода» через ссылку на п.5 архитектурного контракта). Ни одного AC без названного способа проверки. - Продуктовых вопросов владельцу не осталось — комментарий аналитики прямо фиксирует это, и текст ТЗ подтверждает: единственная развилка (`light_pools` vs `show_light_shadows`) закрыта решением в пользу одного канонического ключа, без alias. - Технические решения, не наблюдаемые пользователем (имена shared-модулей, форма runtime API, конкретный smoke-фикстур, точные raster tolerances, место переключателя в форме редактора), явно вынесены в блок «Принято предположительно, поменять свободно» — именно туда, где им место по §7.1, а не разбросаны по тексту как скрытые решения. - Не-скоуп корректно отсекает соседние риски: миграцию существующих карточек, визуальные изменения полной карточки, деградацию/лимиты источников (запрещены явно — важно, т.к. issue упоминал «возможно с warning», ТЗ не поддалось соблазну добавить скрытую деградацию), редакторы геометрии, повышение бюджетов без отдельного решения. - Архитектурный контракт формулирует пять проверяемых инвариантов извлечения общего модуля (нет DOM/hass/приватных полей в shared-модуле, no module-level unbounded cache, full-card output regression-equivalent, единый источник истины по aperture classification/visibility/source guard/falloff/SVG field) — ровно то, что нужно ревьюеру кода на код-ревью, без диктовки конкретных имён файлов (эти имена сознательно оставлены реализации). - i18n-раздел соответствует существующей конвенции проекта (`editor.` во всех четырёх словарях, без английского fallback для de/fr). - Откат описан на двух уровнях (пользовательский — снять флаг/false; кодовый — раздельный откат static-адаптера и shared-extraction) с явным критерием, когда откатывается всё целиком. - Риски (8 штук) названы конкретно и с указанием, чем каждый снижается в самом контракте, а не общими словами. ## Чего не проверял - Реализуемость перформанс-цели (AC7) на практике — на этой стадии нет кода, профиль `large-space-card-glow-overlay-v1` кандидатный и явно помечен как подлежащий уточнению в реализации; численные бюджеты по правилу процесса утверждаются только полным Linux performance artifact, не на спек-ревью. - Точные имена shared-модулей и границы API — сознательно оставлены реализации согласно блоку «Принято предположительно», ревью не оспаривает. - Гейты код-ревью (`typecheck`, `test`, `build`, `check-docs`, смоки, golden, performance, `model-invariants`) — неприменимы на этой стадии, кода нет. ## Унаследовано из r0 Не применимо — это первый заход (r1), предыдущего цикла нет. ## Вердикт Полный, детально проработанный ТЗ без блокирующих или Medium-находок. Технические утверждения о текущем поведении кода выборочно, но представительно сверены с исходниками и каноном (`docs/LIGHT.md`) и во всех проверенных случаях подтвердились — включая нетривиальные детали вроде точных ключей кэша и независимости Glow от `live_states`. Продуктовые разделы отвечают на оба обязательных вопроса §7.1, открытых вопросов владельцу не осталось, технические развилки корректно вынесены в блок предположений вместо того, чтобы быть выданными за решённые факты. **Вердикт: зелёный.**