19 KiB
CODE-REVIEW-406-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/406
- Этап: code (PROCESS.md §2.7)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4 (перед этим раундом)
- Ветка:
issue/406-beta2-polish - Коммит на ревью:
fe385cbf9d5a0fc11f0bcfacbe4ba03e01167949 - ТЗ:
docs/specs/406-beta2-polish.md, revision 4, зелёныйSPEC-REVIEW-406-r4 - База сравнения:
origin/dev=b04387ce914aa8559fb3bc446f99a5751f67f32c
Скоуп
Четыре независимые правки из ТЗ:
(а) удаление 13 мёртвых ключей i18n + AST-гейт test/i18n-dead-keys.test.mjs;
(б) role="alertdialog" + aria-describedby для hp-confirm (destructive и
warning), обычные диалоги остаются на HA-ветке (src/hp-dialog.ts,
src/hp-confirm.ts);
(в) смок demo/smoke_danger_confirm_branches.mjs покрывает и HA-, и
нативную ветку;
(г) уборка marker_area_snapshot для исчезнувших устройств только при
авторитетном реестре + разворот правила усечения по лимиту
(src/device-area-relocation.ts).
Материал ревью — git log --oneline origin/dev..HEAD (9 коммитов, из них 5
docs: review document for #406/docs: resolve.../docs: revise... — все
имеют отношение к ТЗ, не к коду) и git diff origin/dev...HEAD (42 файла:
продуктовый код, тесты, смоки, мутанты, i18n, бандлы, changelog).
Как проверялось
Ручного тестирования в цикле нет — правильность работы устанавливается чтением кода, автотестами и браузерными смоками.
Прогнано лично на fe385cbf (зелёного Validate на этом SHA нет):
| Гейт | Результат |
|---|---|
npx tsc --noEmit |
green, без вывода |
npm test |
1706 passed, 0 failed, 1 skipped — совпадает с заявкой автора |
npm run build |
green, dist собран за 16.3s |
node scripts/bundle-sync.mjs --check (после build) |
green, три копии бандла синхронны |
npm run bundle:budget |
initial 287320 B / budget 300000 B, headroom 12680 B (>0 → AC12 выполнен); есть предупреждение о низком запасе — это фон #367, не регрессия этой задачи |
node scripts/mutation-gate.mjs --check |
все мутанты, включая три новых (i18n-dead-key-returns, confirm-dialog-loses-alertdialog, area-snapshot-cleanup-ignores-authority), — ok |
node scripts/check-docs.mjs |
RED — см. находку ниже |
node demo/smoke_danger_confirm_branches.mjs |
green, все 24 поля true (AC6–AC8) |
node demo/smoke_area_relocation.mjs |
green, все 20 полей true, включая authoritativeOrphanRemoved/nonAuthoritativeOrphanPreserved (AC9–AC10) |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«НЕОПРЕДЕЛЁННОСТЬ»: изменённых символов проекта — 6 (MARKER_AREA_SNAPSHOT_LIMIT, MarkerAreaBinding, _useHaDialog, _usesHaDialog, validBinding, validText), матрица — 212 смоков, ни один не назван связанным доказуемо |
Не прогонял и почему:
- Полная матрица
demo/smoke_*.mjs(212 файлов) — диапазон затрагивает толькоhp-dialog/hp-confirmиdevice-area-relocation, у обоих есть прямо названные в AC6–AC10 смоки, которые я прогнал. Символы из выводаsmoke-select— внутренние помощники (_usesHaDialog,validBinding,MARKER_AREA_SNAPSHOT_LIMIT), уже покрытые юнитамиtest/device-area-relocation.test.mjsпострочно (проверено чтением: тест на переполнение лимита обращается кMARKER_AREA_SNAPSHOT_LIMITнапрямую). Полный прогон матрицы — предрелизная обязанность (§8), не гейт этого раунда. npm run golden:verify— прочиталdemo/golden/matrix.mjs: ни один сценарий не открывает destructive/warninghp-confirm(есть толькоdevice-dialog-*,decor-color,optimize-preflight-dialog— другие диалоги). Видимый рендер, который затрагивает диапазон, в голден-матрице не зафиксирован — прогон не даст сигнала по этой задаче.npm run invariants -- --config …— диапазон не трогает рёбра комнат, записи толщины,layout-формат,marker.spaceилиopen_spans;device-area-relocation.tsменяет только момент уборки записи снапшота и порядок среза при переполнении, не геометрию. Не применимо.python -m pytest tests_backend -q—custom_components/**/*.pyв диффе нет (только скомпилированные frontend-бандлы).- Перфоманс-профили — не названы в AC, чувствительные к перфу пути не тронуты.
Находки
Medium (в скоупе, чинится в этой же задаче) — стал устаревшим отпечаток скриншотов документации
node scripts/check-docs.mjs красный на fe385cbf:
ERROR screenshot source fingerprint is stale; run npm run build && node demo/docs/capture.mjs
Воспроизведение (после npm run build, который уже входит в проверки
автора):
$ node scripts/check-docs.mjs
ERROR screenshot source fingerprint is stale; run npm run build && node demo/docs/capture.mjs
Причина механическая и ожидаемая: visualFingerprint(ROOT)
(scripts/source-fingerprint.mjs) считается по всему src/**, а диапазон
меняет src/hp-dialog.ts, src/hp-confirm.ts, src/device-area-relocation.ts
и все четыре словаря src/i18n/*.json — то есть отпечаток обязан был
измениться, и docs/images/screenshots.json обязан был обновиться вместе с
ним, независимо от того, меняются ли реально показанные пиксели. Я
дополнительно прогнал node demo/docs/capture.mjs во временной копии рабочей
дерева и подтвердил: все 10 сценариев (view-desktop, view-touch,
space-create, room-contour-close, plan-context-tray, device-editor,
device-display-preview, background-editor, room-card, device-info) —
из demo/docs/screenshots.mjs, ни один не открывает destructive/warning
hp-confirm, так что заявление автора «скриншоты не меняются» по существу,
вероятно, верно (файлы отличались побайтово в моей проверке, но это из-за
отсутствующего в песочнице oxipng, не из-за иного содержимого кадра — само
сообщение инструмента oxipng не найден: кадры пишутся как есть, без перепаковки называет причину; я откатил эту пробную капчу перед
продолжением ревью). Но идентичность пикселей не отменяет обязательности
шага: docs/images/screenshots.json в диффе не тронут вообще
(git diff origin/dev...HEAD -- docs/images/ — пусто), а check-docs.mjs
проверяет именно факт совпадения записанного отпечатка с текущим деревом, не
факт визуального совпадения.
Это ровно тот класс регрессии, который уже дважды оставлял dev с красным
job docs до следующей задачи (#230, #234, #237): гейт красный на коммите,
который автор передаёт на ревью, и останется красным после мержа, если его
не прогнать перед мержем.
Ожидаемо: npm run build && node demo/docs/capture.mjs && npm run docs:accept (или эквивалент), обновлённые docs/images/screenshots.json и
(если байты правда меняются вне зависимости от oxipng) PNG — в том же PR.
Что проверено и корректно
- AC1–AC3 (мёртвые ключи, гейт, законные динамические/производные
семейства).
test/i18n-dead-keys.test.mjsстроит множество через реальный TS AST (ts.createSourceFileпо каждому файлуsrc/**/*.ts): литералы, шаблонные выражения с горизонтом+/`, и производное правило_help('x.help')→x.help.aria. Второй тест в файле жёстко фиксируетderivedHelpAria.size === 19— то есть падает, если производных пар станет больше или меньше 19, а не просто «не меньше».src/i18n/en.jsonдо/после: 13 ключей удалены (history.partition_add,confirm.delete_draft,confirm.delete_draft_segment,title.markup,history.delete_room,markup.delete,marker.display_hint,marker.display_hint_icon,confirm.delete_room,confirm.remove_marker,confirm.delete_space,confirm.unlock,confirm.delete_plan) — совпадает построчно со списком ТЗ.npm testвключает оба теста файла и прошёл. - AC4 (коллизия с
test/unified-wall-tool-source.test.mjs). Строка'history.partition_add'убрана из проверяемого списка теста тем же коммитом, что и ключ из словарей — оба гейта зелёные одновременно (проверено прогономnpm test). - AC5 (паритет словарей). Диф по
de.json/fr.json/ru.jsonдаёт ту же тринадцатку удалённых строк, что иen.json(сверилgit diffпо каждому файлу построчно) — синхронно, 52 строки суммарно.test/i18n.test.mjsвходит вnpm testи прошёл. - AC6/AC7 (роль и
aria-describedby).src/hp-confirm.ts: обаkind(destructiveиwarning) получают.alert=${true}безусловно — дихотомии по виду нет, что и требовал r1/r2 SPEC-REVIEW.src/hp-dialog.tsдобавляетalert/describedByкак реактивные свойства,_usesHaDialog()возвращаетfalse, когдаalertистинен, — то есть alert-подтверждение принудительно идёт в нативную ветку независимо отcustomElements.get('ha-dialog'); нативный<dialog>получаетrole=${this.alert ? 'alertdialog' : 'dialog'}иaria-describedby=${this.describedBy || nothing}. Проверил по всемуsrc/(grep -n '\.alert='), что.alertвыставляет толькоhp-confirm.ts— ни один из ~30 других вызовов<hp-dialog>(маркер, калибровка, бэкдроп, импорт/экспорт, kiosk, слияние комнат и т.д.) не трогаетalert/describedBy, то есть остаются обычнымrole="dialog"на HA-ветке.demo/smoke_danger_confirm_branches.mjs— green, поляnoHaDestructiveIsDescribedAlert/noHaWarningIsDescribedAlert/haDestructiveStaysNativeAlert/haWarningStaysNativeAlertистинны; последняя пара доказывает именно то, что было провалом в r2 SPEC-REVIEW — alert не уходит в зарегистрированныйha-dialog. ОтдельныйgetByRoleна реальном accessibility-дереве Chromium (realAccessibilityTreeIncludesConsequence) подтверждает, что текст последствий действительно долетает до дерева доступности, а не только до DOM-атрибута. - AC8 (инварианты подтверждения в обоих окружениях). Тот же смок:
фокус на «Отмена» (
noHa*FocusesCancel/ha*FocusesCancel), Esc →false(noHaWarningEscapeResolvesFalse/haWarningEscapeResolvesFalse), клик по «Отмена» →falseв обоих окружениях — всеtrue. - AC9/AC10 (уборка снапшота только при авторитетном реестре).
resolveDeviceAreaRelocationsстроитliveIds/liveBindingsизoptions.devicesи помечаетremoveSnapshot: trueдля записей снапшота, чей id и binding не совпадают ни с одним живым устройством/маркером, — но делает это только послеif (!options.authoritative) return ...в начале функции, то есть цикл недостижим при неавторитетном реестре. Проверил обоих вызывающих в продукте:houseplan-card.ts:5054/5166передаётdevices: this._devices— полный список, только что пересобранныйbuildDevices(...)из актуальногоregistry/markers/settings, а не отфильтрованное подмножество;_syncAreaRelocations(запись) вызывается только приthis._haRegistry.authoritative. Читающие проекции (space-render.ts:283,space-card.ts:455) используют только.relocateIdsи никогда не вызываютapplyAreaRelocationResolution— то есть read-only карточка не может стереть запись сама (staticMadeNoWrites: trueв смоке это подтверждает). Юнитыtest/device-area-relocation.test.mjs(«authoritative registry removes orphan snapshots but preserves live marker ids» / «non-authoritative registry preserves orphan snapshots») и смокdemo/smoke_area_relocation.mjs(authoritativeOrphanRemoved/nonAuthoritativeOrphanPreserved) оба зелёные. Мутантarea-snapshot-cleanup-ignores-authority(снимает условиеauthoritative) ловится именно этим юнитом — проверил прогономmutation-gate.mjs --check. - AC11 (усечение снапшота сохраняет последние записи). Один символ
правки:
.slice(0, MARKER_AREA_SNAPSHOT_LIMIT)→.slice(-MARKER_AREA_SNAPSHOT_LIMIT). Новый юнит-тест строит входLIMIT + 2записей и проверяет, что первые две (entry-0,entry-1) исчезли, а последняя (entry-${LIMIT+1}) осталась, — тест умеет падать: на старом.slice(0, LIMIT)он утверждал бы обратное и провалился бы. - AC12 (бюджет initial).
npm run bundle:budget→ 287320 B при бюджете 300000 B, положительный запас — не растёт (совпадает с ожиданием ТЗ: удаление 52 строк уменьшает бюджет, атрибуты добавляют единицы байт). - Трейлеры и changelog. Коммит
fe385cbf:Issue: #406,User-Visible: yes.docs/CHANGELOG.md/docs/CHANGELOG.ru.mdполучили по одному пункту про переход на нативную оболочку иalertdialog— в том же коммите, что и код (провереноgit show --stat fe385cbf). - Одно число — один источник. Диапазон не добавляет и не меняет ни одной
видимой пользователю величины (роль/aria — не число; лимит
20_000не показывается пользователю ни в каком UI) — раздел неприменим по существу, а не пропущен.test/single-source-numbers.test.mjsвходит вnpm testи прошёл.
Чего не проверял
- Полную матрицу
demo/smoke_*.mjs(212 файлов) — обоснование в разделе «Как проверялось». npm run golden:verify— ни один голден-сценарий не открывает затронутый диалог; обоснование там же.npm run invariantsиpython -m pytest tests_backend— диапазон их не задевает (геометрия/толщина/layoutне тронуты;.py-файлов в диффе нет).- Перфоманс-профили — не названы в AC, не тронуты.
- Реальный визуальный вид нативного диалога в живом Home Assistant (смена HA-хрома на нативную оболочку, которую ТЗ прямо называет видимым компромиссом) — только по коду и accessibility-дереву Chromium; ручного просмотра в настоящем HA в этом цикле нет и не может быть.
Вердикт
Единственная находка — Medium, в скоупе задачи (устаревший отпечаток
docs/images/screenshots.json после правок в src/**), чинится тем же
шагом, что автор уже применяет в релизном процессе. Все 12 AC доказаны
тестом или смоком, который я лично прогнал и который умеет падать (мутанты
подтверждают это для трёх ключевых контрактов). High-находок нет.
Жёлтый.