Files
2026-09-26 10:25:12 +00:00

12 KiB
Raw Permalink Blame History

CODE-REVIEW-664-r1

Issue: #664 · этап: code · заход: r1 · материал: 40b73cc5999be6fc8f06eb5cac63f74e6c41c4f6 (1 коммит поверх origin/dev 6cdcb1ea) · трек: trivial, короткий · блокирующих циклов использовано 0 из 2 (бюджет тратят только жёлтый/красный, а этот вердикт зелёный).

Скоуп

Регрессия #564 (v1.76.0-beta.2): общие стили маркеров открывают pointer-events: auto на .dev::before (44 px пол) и .device-shell-frame (капсула) — для интерактивного плана это нужно, но статическая карточка houseplan-space-card импортирует те же стили целиком, и потомки внутри .hp-static-stage (которая объявлена pointer-events: none) снова стали целью хит-теста: курсор-«рука» и клики, уходящие в никуда, на контрактно read-only поверхности.

Работа обслуживает J1/J3 (docs/SCOPE.md) косвенно — не ломает их, а убирает ложное приглашение к действию там, где действия по контракту нет; это починка нарушенного UX-контракта, а не новая функциональность.

Диапазон: src/space-card.ts, demo/smoke_space_card.mjs, scripts/mutation-registry.mjs, docs/ARCHITECTURE.md, docs/CHANGELOG.md, docs/CHANGELOG.ru.md, docs/images/screenshots.json (только отпечаток). 7 файлов, +95/−14 — совпадает с хендоффом.

Как проверялось

Прочитан весь дифф построчно (git diff origin/dev...HEAD), тело issue, оценка владельца и хендофф автора. Правило .hp-static-stage *, *::before, *::after { pointer-events: none } (src/space-card.ts:952-963) разобрано по каскаду: специфичность .hp-static-stage *::before и .dev::before из devices.styles.ts:185 равна (0,1,1) — как и .hp-static-stage * против .device-shell-frame (0,1,0) — побеждает источник, идущий позже в массиве static styles = [cardStyles, css\...`](Lit сохраняет порядок вставки правил,cardStyles` первый); значит новое правило переопределяет оба opt-in на равной специфичности не случайно, а по объявленному порядку каскада. Проверено чтением, подтверждено исполнением (ниже).

Проверено исполнением (не только чтением):

  1. npm run bundle:sync — сборка чистая (tsc + rollup + bundle-sync).
  2. node demo/smoke_space_card.mjs на материале — зелёный, markerProbe.insideStage=false, stageGridHitsInside=0/63, stageGridPointerCursors=0 — совпадает с числами из хендоффа.
  3. Мутация вручную: вырезал правило .hp-static-stage *, *::before, *::after {...} из src/space-card.ts, пересобрал, прогнал тот же смок — FAIL space-card smoke, markerProbe = {insideStage: true, element: "span.device-shell-frame", cursor: "pointer"}, stageGridHitsInside=1 — дословно совпадает с заявленным в таблице AC «чем краснеет». Файл восстановлен, пересобран (git status — чисто).
  4. node scripts/mutation-gate.mjs --check — весь реестр ok, включая новую запись static-card-descendants-hit-testable (это то же самое, что пункт 3, но через штатный гейт, а не только вручную).
  5. node scripts/smoke-select.mjs --base origin/dev --head HEAD — «НЕОПРЕДЕЛЁННОСТЬ», 0 символов проекта на изменённых строках — совпадает с тем, что назвал автор; связанный смок (smoke_space_card) назван и прогнан отдельно (пункты 2-3).
  6. node scripts/check-docs.mjs — passed (дифф трогает src/**).
  7. Разобрана таблица AC/доказательство/мутация из хендоффа автора — числа и имена элементов (span.device-shell-frame, div.hp-static-body) совпадают с моим независимым прогоном пункта 3, это не переписанное заявление автора, а воспроизведённый результат.
  8. Проверено (чтением), что фикстура смока не задевает AC1 в части пылесоса: spaceId = window.__card._model[0].id — первое пространство (f1); d_mower (единственное устройство-пылесос демо-конфига, demo/srv/demo.html) размещён в пространстве garden, а не f1. Условие «при наличии пылесоса в фикстуре» не наступает для карточки, которую рендерит этот смок — пропуск отдельной проверки пылесоса не является пробелом AC1, а корректным отсутствием применимого случая.
  9. Проверено (чтением), что _renderRecoveryOverlay() — сосед stage внутри .hp-static-body, а не потомок .hp-static-stage (src/space-card.ts:914-917), поэтому новое правило .hp-static-stage * не может случайно закрыть интерактивность overlay восстановления, если он когда-либо получит собственные контролы.
  10. Проверено (чтением) git diff на docs/images/screenshots.json: из 11 изменённых блоков во всех меняется только sourceSha256/ sourceFingerprint (хеш собранного бандла), ни одна строка imageSha256 (хеш самого PNG) не входит в добавленные/удалённые строки диффа — 11 кадров действительно попиксельно идентичны, как заявлено.
  11. Трейлеры коммита: Issue: #664, User-Visible: yes; оба changelog (docs/CHANGELOG.md, docs/CHANGELOG.ru.md) правятся в этом же коммите — условие §3 п.10 выполнено.
  12. Единственное видимое пользователю число в дифф-тексте (Changelog) — ссылка на версию v1.76.0-beta.2, это ссылка на существующий релиз, не новое число с двумя источниками; отдельного «одно число — один источник» риска в этом диффе нет.

Дешёвые гейты (typecheck/test/build+bundle-policy) не перегонялись отдельно: подтверждены зелёным Validate на этом же SHA (https://github.com/Matysh/houseplan-card/actions/runs/36234983969), что совпадает с независимым прогоном npm run bundle:sync (build+tsc) в пункте 1.

Находки

Нет ни одной находки High или Medium. Дифф закрывает ровно заявленный дефект, не трогает интерактивную карточку (#564 там не отменяется), контракт docs/ARCHITECTURE.md обновлён вместе с кодом, оба changelog присутствуют, защитный AC3 доказан таблицей с непустым третьим столбцом и я независимо воспроизвёл его результат.

Что проверено и корректно

  • AC1 — ничто в .hp-static-stage не цель хит-теста: воспроизведено (elementFromPoint по маркеру и сетке 9×9 не находит ничего внутри сцены).
  • AC2 — курсор не pointer, кнопка кликабельна: воспроизведено (cursor: auto на цели хит-теста, deepLink содержит #space=, было и раньше).
  • AC3 — мутант красит AC1/AC2: воспроизведено вручную и штатным mutation-gate.mjs, результат совпадает с заявленным дословно.
  • Каскад CSS выбирает нужное правило по source order при равной специфичности — разобран по коду, не только принят на слово.
  • Область поражения — только статическая карточка; полная карточка (#564) не затронута дифф-текстом за пределами src/space-card.ts.
  • Документация (ARCHITECTURE.md) и оба changelog обновлены в одном коммите с трейлерами Issue/User-Visible.
  • check-docs.mjs зелёный.
  • Скриншот-снапшот — только отпечаток источника, изображения не менялись.

Чего не проверял

  • Полную матрицу смоков, golden:verify, perf-профили, pytest tests_backend — не названы в AC, диффа Python нет, перф не задет; это совпадает с тем, что назвал автор в разделе «не прогонял», и я с этим согласен: изменение — CSS-правило одной поверхности плюс тест, который его ловит.
  • npm test полным прогоном (все node --test test/*.test.mjs части) и npm run build со сверкой трёх копий бандла отдельно от Validate — принял зелёный статус Validate на этом же SHA как подтверждение (ссылка выше); npm run bundle:sync, который я прогнал сам, включает build+tsc и является независимым частичным повторением.
  • Ручное тестирование в браузере вне playwright-смока (реальный HA, реальный тачскрин) — вне рамок код-ревью на коротком треке; смок elementFromPoint — адекватная замена для контракта read-only-курсора, сам провёл его дважды (материал + мутация).

Вывод

Все три AC доказаны исполнением, а не только заявлением автора; защитный AC3 закрашен воспроизведённой мутацией с совпадающими деталями. Скоуп, трейлеры, changelog, документация архитектуры и мутационный реестр — на месте. Находок нет.

Вердикт: зелёный.


Материал раунда

  • Ветка: issue/664-static-card-cursor, коммит 40b73cc5999b — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 5086e070529bcf9e4aecf4936e3a68185f7b4385
    git log --all --format='%H %T' | grep 5086e070529b
    
  • Тело issue: 02dc2f7bc5b0b56b6f6e007b39eeae8b17b7bef2ec66a7a1a8461dea63435dd3
  • Вердикт конвейера: green · High 0