Files
2026-09-23 06:36:39 +00:00

18 KiB
Raw Permalink Blame History

CODE-REVIEW-610-r1

Issue: #610 · Этап: code (PROCESS.md §2.7) · Заход r1 · блокирующих циклов 0/2 Материал: git log --oneline origin/dev..HEAD / git diff origin/dev...HEAD, рабочая копия на 2aa4dcf5bd15be4d58d4db8a076327970c062cce (проверено git rev-parse HEAD). Три коммита диапазона:

  • 0b1b6bf9 docs: SPEC-REVIEW-610-r1 (спецификационное ревью, код не менялся).
  • 3fb0d7fd fix: clarify unsaved-settings confirmation (#610) — Issue: #610, User-Visible: yes.
  • 2aa4dcf5 test: align device form smoke with atomic inputs (#610) — Issue: #610, User-Visible: no.

Скоуп задачи

RU-копия и иконка диалога «Отменить изменения?» для четырёх сценариев несохранённых настроек (room/space/marker/general settings): кнопки Продолжить/Отменить → Вернуться/Не сохранять, иконка открытого замка → mdi:content-save-off-outline в заголовке и на кнопке подтверждения. Данные, схема, порядок кнопок, EN/DE/FR, прочие warning/destructive-подтверждения — не трогаются. Соответствует ТЗ из тела issue (§2-3) и продолжает J4/J6 из docs/SCOPE.md («keep the plan true», понятное безопасное действие в редакторах). Спецификационное ревью (docs/reviews/SPEC-REVIEW-610-r1.md, зелёный, 0 High/Medium) уже проверило исполнимость ТЗ; данный документ читает только реализацию.

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

Дешёвые гейты на 2aa4dcf5 уже подтверждены зелёным Validate (https://github.com/Matysh/houseplan-card/actions/runs/35824681786, headSha сверен командой gh run view 35824681786 --json headSha — совпадает с HEAD), поэтому npx tsc --noEmit/npm test/npm run build со сверкой bundle tree не перегонялись отдельно как самостоятельный гейт. Но для проверки дисциплины «тест умеет падать» ниже они были задействованы вручную, локально:

  1. Построчно сверил каждый файл диффа (git diff origin/dev...HEAD) с контрактом ТЗ п.4 и п.6 («Затронутые файлы»):
    • src/danger-confirm.ts — новое поле confirmIcon?: string в HpConfirmRequest, комментарий поясняет назначение.
    • src/hp-confirm.ts — кнопка подтверждения теперь берёт request.confirmIcon || (destructive ? ... : ...), существующий fallback не тронут; заголовочная иконка (request.icon || ...) не менялась — этот путь был реализован до задачи (см. SPEC-REVIEW находку про п.2 ТЗ).
    • src/editors/{general-settings-dialog,marker-dialog,room-settings-dialog,space-form}.ts — ровно в четырёх вызовах _confirmDanger добавлена пара icon: 'mdi:content-save-off-outline', confirmIcon: 'mdi:content-save-off-outline', без изменения остальных полей запроса.
    • src/i18n/settings/ru.json — изменены ровно два ключа (dialog.discard_confirm, dialog.discard_keep); dialog.discard_title/ _message не тронуты.
  2. grep -rn "content-save-off-outline" src/ demo/ docs/ — иконка встречается ровно в 4 продуктовых call site + в тестах/документации; ни одного лишнего вызова _confirmDanger не задето.
  3. grep -rln "Продолжить|Отменить" demo src docs scripts (исключая demo/srv, сгенерированный) — все прочие вхождения принадлежат другим i18n-ключам (vac.route_*_body, space.copy_optimize_body, history.undo*, radar.discard_setup_title, заголовок dialog.discard_title) — пересечения со скоупом нет, не-скоуп из ТЗ §3 реален, а не декларативен.
  4. Проверил data-kind на всех четырёх диалогов (marker, room, space — в space-settings-dialog.ts и space-copy-runtime.ts, settings), чтобы убедиться, что новый demo/smoke_discard_copy.mjs действительно находит каждый из четырёх диалогов по селектору, который использует.
  5. Тест умеет падать (проверено исполнением, не на слово). Вручную применил патч мутанта discard-confirm-action-icon-falls-back-to-lock из scripts/mutation-registry.mjs к src/hp-confirm.ts (откатил request.confirmIcon || обратно к жёсткому fallback), пересобрал (npm run build — прошёл, tsc --noEmit внутри чистый), синхронизировал бандл (node scripts/bundle-sync.mjs) и прогнал node demo/smoke_discard_copy.mjs: упали ровно 4 проверки ({marker,room,space,settings}CopyAndIcons), остальные (draft/discard поведение, fallback чужого warning) остались зелёными — мутант пойман ровно там, где и должен. Вернул файл (cp из бэкапа), пересобрал и пересинхронизировал — git status --porcelain пуст, дерево совпадает с коммитом; node demo/smoke_discard_copy.mjs снова зелёный (14/14). Round-trip заодно подтвердил AC6: три копии bundle tree (dist, custom_components/houseplan/frontend, demo/srv/assets) совпадают на этом SHA, раз пересборка не создала диффа.
  6. Прогнал вручную (все зелёные, полный вывод в терминале сессии):
    • node demo/smoke_discard_copy.mjs — новый смок задачи, все 14 полей true.
    • node demo/smoke_dialog_polish_603.mjs — RU/EN/DE/FR × light/dark × DPR 1/2 × ширины 320/360/560/640 (AC2), включая добавленную проверку discardIcons для каждой комбинации.
    • node demo/smoke_room_settings_form.mjs — обновлённые подписи Вернуться/Не сохранять в контексте формы комнаты.
    • node demo/smoke_danger_confirmation.mjs — Escape/крестик/scrim/replace, unlock-подтверждение (unlockCancelAccept) — доказывает, что чужие warning/destructive сценарии не задеты (AC4, ТЗ п.4.7).
    • node demo/smoke_device_settings_form.mjs — смок, изменённый вторым коммитом (2aa4dcf5); зелёный, подтверждает, что правка приводит его к контракту атомарных числовых полей #608, не меняя продуктовый код.
    • node scripts/check-docs.mjs — обязателен, т.к. диф трогает src/** (изменяет отпечаток скриншотов); «Documentation checks passed (7 files, 12 external links)».
  7. node scripts/smoke-select.mjs --base origin/dev --head HEAD — НЕОПРЕДЕЛЁННОСТЬ: «дифф исполняемый, но ни один смок не связан доказуемо», 0 символов на изменённых строках. Причина понятна и не тревожна: инструмент строит таблицу символов по объявлениям верхнего уровня/членов класса с модификатором или _-префиксом (scripts/smoke-select.mjs DECLARATIONS); новое поле confirmIcon?: string внутри interface HpConfirmRequest — рядовой публичный член без модификатора и без _ — таким объявлением не считается, поэтому зарегистрированная связь из scripts/smoke-links.mjs (символы confirmIcon, HpConfirmRequest → smoke_discard_copy.mjs, smoke_dialog_polish_603.mjs) не сработала на автомате. Решение по строке: прогнал оба зарегистрированных смока вручную (см. п.6) — они прямо и единственно доказывают AC1-AC4, поэтому автоматическое молчание инструмента не меняет вывод, просто не освобождает от чтения AC.
  8. Инварианты модели (npm run invariants) не запускал — диф не трогает геометрию, layout, marker.space, толщину стен или open_spans. pytest tests_backend не запускал — Python не тронут. npm run golden:verify не запускал по существу изображения (см. ниже), но проверил, что диф в demo/golden/ ограничен одной строкой ожидаемых лейблов в harness.mjs, а сам baseline-эталон не тронут — ровно то, что требует AC5 и п.10 ТЗ (принятие golden только из pre-release Linux CI).

Находки

High: 0. Medium: 0. Задача реализована точно по ТЗ, без расширения и без сужения скоупа.

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

  • AC1/AC2 (browser smoke). Все четыре discard-*-dialog показывают Вернуться/Не сохранять; smoke_dialog_polish_603.mjs подтверждает точные подписи, одну строку без переноса на 320-640px, autofocus безопасной кнопки и возврат к форме — во всех языках/темах/DPR (не только RU, что важно: EN/DE/FR подписи не изменились — проверено выражением expected в смоке и явным сравнением с ТЗ п.4.6).
  • AC3 (иконка). mdi:content-save-off-outline передаётся явно в заголовок и на кнопку подтверждения из всех четырёх call site; других мест правка не касается — подтверждено и построчным чтением, и grep по всему дереву.
  • AC4 (fallback других confirmation). HpConfirm.render() использует request.confirmIcon || (destructive ? 'mdi:trash-can-outline' : 'mdi:lock-open-variant') — при отсутствии override (все прочие вызывающие сайты) поведение идентично коду до правки. smoke_discard_copy.mjs проверяет это прямо (unrelatedWarningKeepsLockGlyphs), а smoke_danger_confirmation.mjs — что unlock-сценарий не деградировал.
  • AC5 (доки/golden). docs/USER-GUIDE.ru.md однозначно объясняет обе кнопки в разделе, общем для всех четырёх форм (единый form-kit). Golden harness (demo/golden/harness.mjs) обновлён под новые подписи, сам эталон room-discard-dialog-mobile-ru не тронут — приёмка по обычному pre-release-процессу, как и требует ТЗ п.10.
  • AC6 (гейты). Validate на этом самом SHA зелёный (сверено gh run view по headSha); дополнительно вручную воспроизведена сборка + bundle-sync + round-trip мутанта без остаточного диффа — три копии бандла синхронны.
  • Мутационный тест ловит регресс. Отдельно от заявления автора — воспроизвёл мутацию discard-confirm-action-icon-falls-back-to-lock руками и убедился, что именно smoke_discard_copy.mjs и только он падает (4 из 4 полей *CopyAndIcons), остальное поведение остаётся зелёным.
  • Второй коммит (2aa4dcf5) не расширяет скоуп. Это тестовое выравнивание smoke_device_settings_form.mjs под уже существующий атомарный контракт числовых полей из #608 (input — черновик, change — фиксация); продуктовый код не тронут, User-Visible: no верно.
  • Трейлеры и changelog. Оба продуктовых коммита несут Issue: #610; User-Visible: yes у коммита с реальным изменением сопровождается правкой обоих changelog (docs/CHANGELOG.md, docs/CHANGELOG.ru.md) в том же коммите 3fb0d7fd.
  • Не найдено дублирования источника значения («одно число — один источник»): диф не вводит новых видимых пользователю величин — только текст двух кнопок и один и тот же строковый литерал иконки, используемый ровно там, где он должен отображаться (заголовок + кнопка одного диалога), не в двух независимых местах с потенциально разными источниками.

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

  • Не гонял npx tsc --noEmit/npm test/npm run build как самостоятельный формальный гейт — они уже зелёные на этом SHA по ссылке Validate; вместо этого использовал npm run build дважды как побочный инструмент для ручного round-trip мутанта (что фактически их и покрыло).
  • Не гонял полный npm run golden:verify (реальный рендер и сравнение пикселей) — диф не меняет геометрию/CSS, только текст кнопок и строку иконки внутри уже существующего slot; визуальную часть придётся принять на pre-release Linux CI, как и предписывает ТЗ (эталон умышленно не обновлён в этом коммите).
  • Не гонял npm run invariants и pytest tests_backend — diff не касается геометрии модели и Python-кода.
  • Не гонял весь demo/smoke_*.mjs (263 файла) — задача узкая и локальная (один компонент, один i18n-ключ, четыре вызывающих сайта); прогнал шесть смоков, названных в ТЗ/выбранных по прямому текстовому упоминанию символов задачи (smoke_discard_copy, smoke_dialog_polish_603, smoke_room_settings_form, smoke_danger_confirmation, smoke_device_settings_form — единственный смок второго коммита, check-docs.mjs — обязателен для src/**). smoke-select.mjs вернул НЕОПРЕДЕЛЁННОСТЬ (см. «Как проверялось», п.7) — расширять выбор дальше не было оснований: ни один другой смок не упоминает изменённые символы (confirmIcon, discard_confirm, discard_keep) и не тестирует discard-диалоги.

Вердикт

Зелёный. Реализация точно соответствует ТЗ #610: изменены ровно те два RU-ключа и ровно четыре call site, что были указаны; fallback прочих warning/destructive confirmation не сломан (проверено и чтением, и воспроизведением мутанта); AC1-AC6 доказаны либо автотестом, который подтверждённо умеет падать, либо чтением кода с явной пометкой. High: 0, Medium: 0.


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

  • HEAD: 2aa4dcf5bd15be4d58d4db8a076327970c062cce (сверено git rev-parse HEAD).
  • Диапазон: origin/dev..HEAD, три коммита (0b1b6bf9, 3fb0d7fd, 2aa4dcf5).
  • Validate на этом SHA: https://github.com/Matysh/houseplan-card/actions/runs/35824681786 (conclusion: success, headSha сверен командой gh run view).

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

  • Ветка: issue/610-discard-copy-icon, коммит 2aa4dcf5bd15 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 1826c6f7b4fc1e4136644304d6aa38299134378c
    git log --all --format='%H %T' | grep 1826c6f7b4fc
    
  • Тело issue: ba5e9ea73d14fd143edf181ea90e230889146e7e2a6ce3ed6a46625deae355f1
  • Вердикт конвейера: green · High 0