24 KiB
SPEC-REVIEW-654-r1
Issue: #654 — «2.5D: цвет бумаги читается getComputedStyle в render() — на первом кадре все полы «тёмные»; вероятная вспышка Flat→2.5D при холодной загрузке дашборда»
Этап: spec (ревью ТЗ, PROCESS.md §2.4)
Заход: r1 · блокирующих циклов израсходовано (после этого вердикта): 1 из 4
Вердикт
Жёлтый. High: 0 · Medium: 1 (в скоупе, возвращается автору) · Low: 0.
Единственная находка — «Release-артефакты» ТЗ не называют обновление
docs/ISOMETRIC.md («Activation»), хотя задача вводит новый, ранее нигде не
документированный элемент этого контракта: план скрыт загрузочной
поверхностью, пока ленивый 2.5D-рантайм не установлен (в т.ч. в kiosk, где
общий boot veil сегодня отключается мгновенно). Остальная часть ТЗ —
обязательные разделы §7.1, однозначность и доказуемость AC1–AC6, явный блок
технических предположений — выполнена корректно, а фактические технические
предпосылки (getComputedStyle на .hp-paper, _effectiveProjection(),
_isoSceneRuntimeLoader, поведение kiosk в setConfig()) проверены по коду
dev и подтвердились буквально.
Скоуп проверки
- Какую строку
docs/SCOPE.mdзакрывает задача — J1 («живой обзор дома») в рамках уже принятого узкого исключения #89 (детерминированная 2.5D-презентация той же геометрии, без второй модели/свободной камеры); задача не расширяет это исключение, а чинит дефект внутри него. - Обязательные разделы ТЗ по PROCESS.md §7.1 (сценарий и «что человек увидит», проблема, скоуп/не-скоуп, поведенческий контракт, UX, данные/миграция, i18n, AC1…ACn с доказательством, план автотестов, риски, откат, release-артефакты).
- Однозначность и проверяемость каждого AC (AC1–AC6) и то, что заявленный способ доказательства (unit/AST/smoke) реален, а не ссылается на несуществующий код.
- Соответствие фактических технических утверждений ТЗ действительному
состоянию
src/houseplan-card.ts,src/iso-materials.tsнаdev(это не код-ревью диффа — диффа ещё нет; это проверка, что ТЗ не выдаёт догадку за факт о существующем поведении). - Терминология видимого поведения — против
docs/USER-GUIDE.ru.mdи каноническогоdocs/ISOMETRIC.md(обязательное чтение для видимого поведения по инструкции ревью). - Наличие открытых продуктовых вопросов, которые ТЗ решило само вместо вынесения владельцу, и их обоснованность.
Как проверялось
- Тело issue #654 и комментарии получены через
gh issue view 654 --json body,comments,labels,state(MCPmcp__github__get_issue/get_issue_commentsбыли недоступны без разрешения пользователя в этой сессии —ghCLI как эквивалентный путь к тому же публичному API). - Прочитаны целиком
docs/SCOPE.md,docs/process/REVIEWER.md,AGENTS.md(обязательный порядок чтения ревьюера), раздел PROCESS.md §7.1 (обязательные разделы ТЗ, шаблон вердикта). - Прочитан целиком
docs/ISOMETRIC.md(в первую очередь «Activation» и Stage 6 «Raised tiles» — источник формулыisoLightFloorRooms/luma > 0.55). - Код
dev(git rev-parse HEAD=803c0f0eaab3aea1ea9a87215c35b638a679dafb) проверен построчно на все фактические утверждения ТЗ:src/houseplan-card.ts:10701— буквально совпадает с цитатой в issue (getComputedStyle(this.renderRoot.querySelector('.hp-paper') ?? this).fillвнутри_renderBody(), вызываемой изrender()).src/houseplan-card.ts:6002-6008(_effectiveProjection()) — подтверждён раннийreturn finish('flat'), пока!this._isoSceneRuntime.src/houseplan-card.ts:2568(connectedCallback) — подтверждена предзагрузкаif (this._isoEnabled) void this._ensureIsoSceneRuntime();.src/houseplan-card.ts:3095(setConfig()) — подтверждено, что kiosk немедленно гасит_booting/_bootFading(«kiosk: 100dvh, nothing to settle»), т.е. общий boot veil в kiosk сегодня действительно не появляется — ровно то, что называет issue.src/houseplan-card.ts:332-337(BOOT_MIN_MS/BOOT_QUIET_MS/BOOT_MAX_MS,_bootWatch():6356-6373) — существующий boot-veil таймер имеет безусловный потолокBOOT_MAX_MS = 1200мс. Это прямое технической напряжение с «переиспользовать существующий bootveil» (если ленивый чанк ставится дольше 1.2 с, безусловное снятие veil обнажит Flat-кадр вопреки AC3) — ТЗ уже закрывает этот риск не как факт, а как открытый технический выбор в разделе «Принято предположительно»: «использование существующегоbootveilлибо визуально идентичного внутреннего состояния iso-pending». Разбор ниже, в «Что проверено и корректно».src/iso-materials.ts:91-99(isoLightFloorRooms) — подтверждено, что функция уже является чистой и принимает явныйRgb, DOM не трогает; баг живёт только в вызывающем коде (houseplan-card.ts:10701), что напрямую подтверждает реалистичность AC1 (unit-тест на чистую функцию уже возможен без новой архитектуры).test/iso-stage6.test.mjs:7,43—isoLightFloorRoomsуже юнит-тестируется без DOM, ссылка AC1 не на пустое место.demo/smoke_volumetric_setting.mjs(90 строк, безpage.reload()) — подтверждена цитата issue «проверяет только переключение без перезагрузки».demo/smoke_isometric_contract.mjs,demo/smoke_iso_tiles.mjs,demo/smoke_iso_theme_walls.mjs— существуют (план автотестов ссылается на реальные файлы, не выдуманные).
docs/reviews/INDEX.mdпроверен на прецедент: обе смежные задачи (#651, #649) в списке затронутых файлов называлиdocs/ISOMETRIC.md, а ревью #651 прямо содержало Medium-находку за расхождение терминологии ТЗ с этим же каноническим документом — это основа находки ниже, а не единичное мнение.- Код продукта не менялся, гейты (
tsc/npm test/npm run build) не запускались: на этапеspecпредмет ревью — текст ТЗ, диффа ещё нет (ветка реализации не создана).
Находки
Medium (в скоупе) — «Release-артефакты» не называют обновление docs/ISOMETRIC.md
Файл: тело issue #654, раздел ## ТЗ, подраздел «Release-артефакты» (и
смежно — «Контракт поведения и UX», п. 1–2).
Воспроизведение:
- «Release-артефакты» ТЗ перечисляют только:
docs/CHANGELOG.md+docs/CHANGELOG.ru.md(да), «пользовательское руководство/i18n/config migration: нет», golden/perf/security — без единого упоминанияdocs/ISOMETRIC.md. - При этом «Контракт поведения и UX» вводит новый пункт жизненного цикла,
которого сегодня в
docs/ISOMETRIC.md(«Activation») нет ни в каком виде: «Пока ленивый 2.5D-рантайм загружается, план не показывается в промежуточном Flat-виде… Это относится и к kiosk, где общий boot veil обычно отключён» — то есть kiosk получает новое видимое поведение (кратковременная загрузочная поверхность), которого у него сегодня нет вовсе (setConfig()гасит_booting/_bootFadingбезусловно, см. «Как проверялось»). - Текущий текст
docs/ISOMETRIC.md:10-28(«Activation») описывает только: когда включена ленивая загрузка чанка, что переключение без перезагрузки мгновенно, и фингерпринт-фолбэк — ни слова о том, что скрывается сам план на время установки чанка при холодном старте, и что это распространяется на kiosk, где обычный boot veil не работает.
Почему это находка, а не придирка к формулировке. docs/ISOMETRIC.md —
канонический документ подсистемы (AGENTS.md, «Reading this first»; PROCESS.md
§2.10 требует смотреть его строки перед разбором подсистемы), и обе соседние
задачи по той же подсистеме (#649, #651 — см. docs/reviews/INDEX.md)
называли именно этот файл среди затронутых при заметно менее существенных
изменениях поведения (терминология, geometry). Здесь же вводится реальный
новый инвариант жизненного цикла — «первый кадр 2.5D скрыт до готовности
рантайма, включая kiosk» — который следующий разработчик, открывший только
docs/ISOMETRIC.md, не увидит и рискует нарушить неосознанно (например, при
следующей правке _bootWatch()/BOOT_MAX_MS или логики setConfig() для
kiosk). Отсутствие этого пункта в «Release-артефактах» — это ровно тот
паттерн, что уже стоил Medium-находки в #651.
Чем закрывается. Добавить в «Release-артефакты» строку об обновлении
docs/ISOMETRIC.md (раздел «Activation» — или новый короткий подраздел о
first-frame gating), фиксирующую: план скрыт до готовности ленивого рантайма
при volumetric_view: true, это распространяется на kiosk, terminal failure
безопасно возвращает Flat. Формулировка — на усмотрение автора, это
техническая правка текста ТЗ, а не новый продуктовый вопрос владельцу.
Класс: Medium, в скоупе задачи — правится прямо в теле ТЗ, отдельный issue не заводится (#202).
Что проверено и корректно
- Обязательные разделы §7.1 — присутствуют все: «Сценарий и видимое изменение» (отвечает на оба продуктовых вопроса — какая персона/поверхность/ момент и что человек видит одной фразой, без терминов реализации), «Проблема и причины», «Скоуп»/«Не скоуп», «Контракт поведения и UX», «Модель данных и совместимость», «i18n», «Критерии приёмки» (AC1–AC6 — см. ниже), «План автотестов и доказательств», «Производительность и touch» (не обязателен, но уместен для render-path фикса), «Риски», «Принято предположительно, поменять свободно», «Откат», «Release-артефакты».
- Однозначность и доказуемость AC1–AC6. Каждый AC называет конкретный вид
доказательства (unit/AST/smoke) и наблюдаемый критерий, а не самоцель
реализации:
- AC1 — чистая функция классификации без DOM; белая бумага + светлая заливка
→ light-floor, тёмная → нет; cold-reload smoke сверяет первый кадр с
установившимся. Подтверждено технически:
isoLightFloorRoomsуже чистая (см. «Как проверялось»), баг — только в вызове. - AC2 — отсутствие
getComputedStyle/getBoundingClientRectвrender/_renderBody/willUpdateи в iso render-helper поверхностях, AST-проверка + браузерный счётчик обращений на повторных HA-рендерах без смены темы/режима. Способ доказательства (AST, не regex по тексту монолита) соответствует требованию ревьюера не полагаться на текстовые якоря. - AC3 — атомарность холодного старта при искусственно задержанном чанке, для
обычной карточки и kiosk; критерий (
.stage.projection-iso, устойчивый viewBox) — наблюдаемый и не расплывчатый. - AC4 — безопасный отказ: конечная загрузочная поверхность, доступный Flat,
настройка не меняется, приватные данные не в диагностике — опирается на
уже существующий контур
EditorRuntimeLoader/safeRuntimeDiagnostic, не изобретает новый механизм отказа. - AC5 — точная инвалидация memo по бумаге/fills/rooms; переход в редактор и обратно не переносит цвет одного режима в классификацию другого.
- AC6 — соседние контракты (
volumetric_viewкак есть, Flat не запрашивает чанк, structural fallback, zoom/устройства) явно перечислены как регрессионный периметр.
- AC1 — чистая функция классификации без DOM; белая бумага + светлая заливка
→ light-floor, тёмная → нет; cold-reload smoke сверяет первый кадр с
установившимся. Подтверждено технически:
- Технические предпосылки ТЗ проверены по коду, а не приняты на слово.
Все процитированные строки и функции (
_renderBody/render,_effectiveProjection,connectedCallback-предзагрузка,setConfig()-поведение kiosk,EditorRuntimeLoader) существуют буквально там, где заявлено, и ведут себя так, как описано — включая менее очевидное наблюдение issue про kiosk (setConfig()действительно гасит_bootingраньше, чем что-либо успевает отрисоваться). Догадок, выданных за факт, в части воспроизведения дефекта не найдено. - Раздел «Принято предположительно» реалистично закрывает найденное
техническое напряжение.
BOOT_MAX_MS = 1200мс — безусловный потолок существующего boot veil — потенциально конфликтует с «дождаться ленивого рантайма» при медленной сети. ТЗ не скрывает эту развилку: явно оставляет автору выбор между переиспользованиемbootveilи отдельнымiso-pending-состоянием, а ревьюер вправе его оспорить (§7.1). Технической ошибки/недосказанности здесь нет — граница ответственности проведена верно. - Отсутствие открытых продуктовых вопросов проверено, а не принято на слово. Кроме разобранной выше Medium-находки, ни одного места, где догадка о видимом поведении подана как факт без пометки предположения, не найдено. Формулировка «Продуктовых вопросов для владельца нет» в аналитике — обоснована: единственная пограничная зона (новое поведение kiosk) уже прямо зафиксирована как решение в контракте, а не спрятана.
- Соответствие
docs/SCOPE.md. Задача остаётся строго внутри узкого исключения #89 (никакой новой камеры/модели/настройки), корректно исключает #651 (дрейф значков при зуме) и_pointInRoomиз скоупа со ссылкой на конкретные issue. - i18n — новых строк нет, что верно: явно запрещён новый текст/кнопка/ индикатор (Контракт поведения, п. 1).
- Модель данных/совместимость — эфемерный runtime-кэш экземпляра карточки, без схемы/YAML/localStorage/миграции; согласуется с характером фикса (render-path + lifecycle-гейт, не персистентное состояние).
- Откат — простой revert продуктового коммита, без обратной миграции; корректно для чисто presentation-слойного изменения.
- Артефакты плана автотестов существуют.
demo/smoke_isometric_contract.mjs,demo/smoke_iso_tiles.mjs,demo/smoke_iso_theme_walls.mjs,demo/smoke_volumetric_setting.mjs— реальные файлы, не выдуманные ссылки; последний подтверждённо не покрываетpage.reload(), что и обосновывает необходимость нового/расширенного smoke по AC3.
Чего не проверял
tsc/npm test/npm run buildи прочие гейты §8 не запускал — на этапеspecпредмет ревью текст ТЗ, реализации ещё нет (ветка задачи не создана на момент ревью).- Не проверял браузерное поведение вручную — реализации нет, нечего запускать; это работа код-ревью следующего этапа.
- Не читал построчно весь
src/iso-scene-render.ts/src/iso-sun.ts/src/iso-tiles.ts— только точечно то, что нужно для проверки фактических утверждений ТЗ (существованиеisoLightFloorRooms,_isoSceneRuntime, boot-veil таймеров). Полный аудит «какие именно файлы входят в 2.5D render-helper поверхности» для AC2 — задача код-ревью, когда появится AST-тест и станет видно его фактический периметр. - Не оценивал real-world длительность загрузки чанка
iso-scene-render(совпадает ли она обычно сBOOT_MAX_MS) — это эмпирический вопрос реализации/код-ревью, а не спецификации; отметил риск как технически реальный, но признал его закрытым явным «принято предположительно», а не находкой. - Полные наборы golden/perf/smoke не прогонял — кода для прогона ещё нет; это часть «Плана автотестов» и войдёт в работу при реализации (§2.6) и последующем код-ревью (§2.7).
Рекомендация автору
Одна точечная правка текста ТЗ — дополнить «Release-артефакты» пунктом об
обновлении docs/ISOMETRIC.md. Правка не требует нового цикла анализа по
существу поведенческого контракта: сам контракт (AC1–AC6, риски, допущения)
корректен и не меняется.
Материал раунда
- Ветка:
dev, коммит803c0f0eaab3— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
b4eeb2b0840189d8031f291834e57fab092af49bgit log --all --format='%H %T' | grep b4eeb2b08401 - Тело issue:
bd46753eac9a5a3b908addb0135b2054eea85c66f89eda4b4f8bdae8d80d78cd - Вердикт конвейера:
yellow· High 0