21 KiB
SPEC-REVIEW-489-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/489
- Этап: ТЗ на ревью (PROCESS.md §2.4)
- Материал:
docs/specs/489-data-hp-contract.mdна SHA9cf10bdde7696e13c049fe83b4dc284622386127(commit9cf10bdd, веткаissue/489-data-hp-contract), плюс тело issue #489 и комментарии аналитики/автора. - Трек: полный (обоснование в комментарии аналитики: несколько поверхностей +
новый публичный DOM-контракт — корректно, критерий
smallне пройден). - Заход: r1 (первый).
Скоуп разбора
Первый цикл — разбор полный: прочитаны docs/SCOPE.md, AGENTS.md, PROCESS.md
целиком, тело issue #489 и оба комментария, действующий docs/STYLING-HOOKS.md,
и сам ТЗ-документ целиком. Каждый раздел §6 (контракт поведения) сверен построчно
с текущим исходником: src/houseplan-card.ts (_booting, _mode, header
.hdr/.zoomctl/.tabs/tabadd/.tabedit, пустые/fixed-floor ветки,
toast), src/houseplan-editor-runtime.ts (_renderMarkupBar,
_renderDevicesBar, _renderDecorBar, .editbar/.barclose), src/hp-dialog.ts
(host, fallback .close, отсутствие текущего понятия «kind»), 33 call site
<hp-dialog в 9 файлах (выборочно проверены gs.align_title, backup.*,
kiosk.title, rules.title, import.title), src/houseplan-panel.ts (меню и
заголовок), src/editor-secondary.ts (единая контекстная поверхность —
кандидат на «tray»), src/space-card.ts (houseplan-space-card: свой ha-card,
без _booting/_mode). Issue #486 закрыт и соответствует описанной панели.
Продуктовая рамка (§7.1: сценарий, что видит пользователь) не разбиралась заново для каждой строки — это первый заход, но она короткая и внутренне непротиворечивая: видимого поведения нет, персонажи — Home admin и внешний E2E/card-mod автор, что подтверждено §SCOPE.md (View — продукт, редакторы — admin-only, никакого нового пользовательского функционала не заявлено).
Находки
M1 — Условие существования зум-кнопок противоречит текущему рендеру и не-скоупу (Medium, в скоупе)
Файл: docs/specs/489-data-hp-contract.md, §6.3, строки 121–123 (zoom-in /
zoom-out / zoom-fit, условие «когда отображается header»).
Проблема. В текущем коде (src/houseplan-card.ts:11400,
src/styles/chrome.styles.ts:98) кнопки zoom не имеют отдельного условия
рендера: .zoomctl рендерится безусловно, а .hdr в kiosk получает класс
kioskhide (display:none только через CSS). Сам блок .hdr и его дети,
включая zoom-кнопки, остаются в DOM и в kiosk-режиме — скрывается только
визуально. Явное исключение из DOM по kiosk сделано только для tabadd
(space-add, строка 11435: this._canEdit && !this._kiosk && !this._hasFixedFloor),
и комментарий в исходнике (11430–11434) прямо объясняет, что это осознанное
дополнительное решение именно для этой кнопки — «the whole .hdr is
display:none there, but the button is also not RENDERED».
Формулировка §6.3 «когда отображается header» — единственная в таблице, где
условие не привязано к «как сейчас» (в отличие от settings/pdf/support,
где явно «as сейчас»). Прочитанная буквально, она требует нового условия
рендера (kiosk ⇒ элемент отсутствует в DOM), которого сегодня нет и которое
правило 6.1.4 связывает с «не существует по permission/mode/state-правилам» —
то есть с реальным JS-условием, а не CSS-видимостью. Добавление такого условия
— это правка логики видимости, прямо запрещённая §5 «Не входит: … изменение
логики готовности, загрузки, редакторов или разрешений».
Если же имелось в виду обратное («header отрисован всегда, значит хук
присутствует всегда, в т.ч. в kiosk, только невидимо») — формулировка вводит в
заблуждение и разойдётся с машиночитаемым docs/data-hp-contract.json и текстом
STYLING-HOOKS.md, которые унаследуют ту же фразу.
Почему это находка, а не техническая мелочь. AC2 требует «все доступные в
текущем состоянии header actions находятся по селекторам», AC6 требует падения
теста на любое незадекларированное/несовпадающее поведение, а
demo/smoke_styling_hooks.mjs должен получить kiosk-фикстуру — без
однозначного условия эти проверки нельзя написать правильно с первого раза.
Что нужно. Явно зафиксировать одно из двух:
(a) существование хука = существование DOM-элемента как сегодня (kiosk не
меняет DOM, только CSS) — тогда строку привести к формулировке «как сейчас»,
как у соседних строк; или
(b) добавить !this._kiosk-условие к zoom-кнопкам как новую, но безвредную для
пользователя правку — тогда явно назвать это в §12 (риски) как намеренное,
минимальное расширение существующей практики (по аналогии с tabadd), а не
«не входит».
M2 — Гейт свежести документации (check-docs) не назван обязательным при правке src/** (Medium, в скоупе)
Файл: docs/specs/489-data-hp-contract.md, §10 AC7 (строка 312–313) и §14
(строки 368–375).
Проблема. Задача правит src/houseplan-card.ts,
src/houseplan-editor-runtime.ts, src/hp-dialog.ts, src/houseplan-panel.ts и
другие файлы под src/** — то есть задевает src/** практически целиком по
охвату (шапка, все три редактора, диалоги, панель). PROCESS.md §8 говорит
прямо: «check-docs стоит в обязательной части не по важности, а по механике:
отпечаток скриншотов документации считается по всему src/**, поэтому
любая правка фронтенда делает его устаревшим… Цена пропуска измерена:
скриншоты не пересняли в #230 и #234, и dev стоял с красным job docs, пока
это не нашли при следующей задаче (#237)».
ТЗ этого не отражает: §14 «Release-артефакты» не содержит пункта про
пересъёмку скриншотов документации (npm run build && node demo/docs/capture.mjs,
приёмка npm run docs:accept -- --reviewed --from=…), а единственное упоминание
в AC7 — «npm run check:docs (если такой script доступен в текущем package)» —
условно и, судя по package.json, вообще не тот скрипт: команда называется
node scripts/check-docs.mjs (см. AGENTS.md/PROCESS.md §8), в package.json
нет "check:docs". Формулировка «если доступен» превращает обязательный гейт в
опциональный по формальному основанию, которого нет.
Отсутствие пиксельной дельты (§7 UX, «визуальная дельта равна нулю») не
освобождает от этого гейта: check-docs сравнивает контент-хеш src/**, а не
изображение — он красный при любой правке фронтенда независимо от того,
поменялась ли картинка.
Что нужно. В AC8 (обязательные гейты) добавить точную команду
node scripts/check-docs.mjs (и её ожидаемый результат — предупреждение о
устаревшем отпечатке до пересъёмки, зелёный после), в §14 — пункт про пересъёмку
скриншотов документации и её приёмку, либо явное решение не трогать
экранные раскладки так, чтобы docs:capture не требовался (нужно
аргументировать, почему в этом случае фингерпринт не считается устаревшим —
на сегодняшний день check-docs.mjs считает его по всему src/** без
исключений, поэтому такого пути нет).
M3 — data-hp-mode="device" расходится с уже опубликованным именем mode-devices (Medium, в скоупе)
Файл: docs/specs/489-data-hp-contract.md, §6.2 (строка 109) и §15
(строка 390).
Проблема. STYLING-HOOKS.md §5 уже публично документирует и обещает
стабильность класса mode-devices на .stage («the stage carries mode-view /
mode-plan / mode-devices / mode-decor, and those four names are part of the
contract for exactly this purpose») — это существующий публичный контракт для
того же самого понятия «режим редактора устройств». Новый атрибут
data-hp-mode вводит для того же понятия другое публичное имя — device, в
единственном числе, обосновывая это только внутренним _mode === 'devices'
(§15: «device — публичное имя режима при внутреннем _mode === "devices"»).
Расхождение с уже существующим mode-devices нигде в ТЗ не упомянуто и не
объяснено — можно предположить, что автор не сверился с §5 STYLING-HOOKS.md
при выборе имени.
Для внешнего потребителя (ровно та аудитория, ради которой задача существует —
houseplan-e2e и card-mod-пользователи) это два разных публичных имени для
одного и того же состояния: .stage.mode-devices и
ha-card[data-hp-mode="device"]. Не факт, что это плохо (два независимых
словаря могут быть осознанным решением: старый — CSS-класс на .stage, новый —
атрибут состояния на ha-card, для разных целей), но ТЗ обязано либо явно
согласовать имя с уже опубликованным, либо явно зафиксировать причину
расхождения в блоке принятых предположений — сейчас это не сделано ни там, ни
там, то есть это непомеченная догадка, а не решение.
Что нужно. Одно явное предложение в §15: либо переименовать в
data-hp-mode="devices" вслед за mode-devices, либо аргументировать
намеренное расхождение (например: data-hp-mode — новый, целенаправленно
единообразный словарь с data-kind тулбара, а mode-devices — унаследованное
имя другого поколения контракта, которое при случае стоит выровнять отдельной
задачей).
Что проверено и корректно
- Структура ТЗ содержит все обязательные разделы §7.1 (сценарий · что увидит человек · проблема · скоуп/не-скоуп · контракт поведения · UX · модель данных и миграция · i18n · AC1…AC8 с доказательством · план автотестов · риски · откат · release-артефакты) плюс корректный §15 «Принятые предположения».
- Продуктовая рамка (сценарий, персона, поверхность, «что увидит человек») —
без нового пользовательского поведения, соответствует джобе «инфраструктура
тестирования» — это не отдельная строка
docs/SCOPE.md, но полностью укладывается в J6 «Keep the plan true as the home evolves» через качество тестового покрытия и не создаёт нового пользовательского функционала; прямого конфликта со SCOPE.md нет. - Открытых продуктовых вопросов владельцу нет и не должно быть: все решения в
ТЗ либо повторяют существующее поведение («как сейчас»), либо являются
чисто техническими решениями (формат JSON, словарь
data-tool, вынос имени из внутреннего_tool), которые по PROCESS.md §7.1 агенты решают сами. - §6.3 «space-add» и «space-settings», §6.5 (диалоги) и §6.6 (панель) сверены
построчно с исходником и совпадают с реальным условием рендера — без
расхождений (см. цитаты кода выше):
tabaddуже гейтится!this._kiosk,.tabeditуже завязан наthis._norm && this._canEdit,houseplan-panelуже имеет ровно одинmenu-обработчик, отправляющийhass-toggle-menu(src/houseplan-panel.ts:186), и ровно один заголовок. - §6.4 (редакторы): у каждого из трёх редакторов сегодня один корневой видимый
контейнер (
editbar planbar/editbar devbar/ decor-эквивалент) и одна кнопка закрытия (.barclose) — заявленный «один toolbar, один editor-close» реалистичен без изменения разметки.EditorSecondaryController(src/editor-secondary.ts) — уже единственная контекстная поверхность редакторов, подходящий кандидат под «tray» без новой абстракции. - §6.5 (диалоги): выборочная проверка 10 из 33 call site
<hp-dialogне выявила случая, который не ложился бы в предложенный закрытый словарь из 19 broad kind (включая менее очевидныйgs.align_title, укладывающийся в широкийsettings, так как это подшаг воркфлоу настроек). - §9 i18n: в
docs/USER-GUIDE.ru.mdсегодня нет ссылки на styling hooks, поэтому условие «сохраняет ссылку, если уже есть» ничего не требует — условие корректно тривиально выполнено, это не пропуск. - Пустое состояние (§6.3
empty/create-space): три ветки вsrc/houseplan-card.ts(fixed-floor pending,fixed-floor invalid,!model.length) и условие CTA (this._serverStorage && this._canManageConfiguration, строка 11306) совпадают с текстом ТЗ дословно — данных за расхождение нет.
Чего не проверял
- Полноту словаря
data-tool(§6.4) по всем группам-launcher — сверены только явные кнопки редактора планов (select/draw/column/merge/split/resize/wallthick→wall-thickness/delroom→delete-room); группы, генерируемые_editorToolbarGroups/_renderEditorGroupLauncher(src/houseplan-editor-runtime.ts:5171), не разбирались поэлементно — доверился формулировке «и id существующих групповых launcher-кнопок», это открытая, но не блокирующая техническая деталь для этапа кода, не ТЗ. - Оставшиеся 23 из 33 call site
<hp-dialogне проверены построчно на предмет однозначности broad kind — выборка не выявила проблем, но это не полное покрытие; это уместно оставить код-ревью, где будет виден фактический diff с назначенными kind на каждый call site. - Реализуемость regex/AST-сканера AC6 (риск §12 «Regex gate пропускает динамику») — решение вынесено в план автотестов и риски с осознанной оговоркой, это техническое решение автора кода, не предмет ТЗ-ревью.
- Гейты (typecheck/test/build) не гонялись: на этапе ТЗ нет кода для проверки, разбор — по тексту документа.
Вердикт
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 3 → в задаче · Документ: docs/reviews/SPEC-REVIEW-489-r1.md
Все три находки — Medium, в скоупе задачи (правки самого ТЗ, тех же файлов и разделов, что уже в работе): без High это жёлтый вердикт, отдельный issue не заводится (#202). Автор правит ТЗ по трём пунктам выше и отправляет на повторный цикл (лимит на полном треке — 4, использован 1).
Материал раунда
- Ветка:
issue/489-data-hp-contract, коммит9cf10bdde769— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
aa85b7b01e6fe7a4dce55220b63b6da33f725eb8git log --all --format='%H %T' | grep aa85b7b01e6f - ТЗ
docs/specs/489-data-hp-contract.md, блобb90694718d571ad77abea4fc592a5e641a0c3d19git log --all --find-object=b90694718d571ad77abea4fc592a5e641a0c3d19 -- docs/specs/489-data-hp-contract.md