mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
237 lines
21 KiB
Markdown
237 lines
21 KiB
Markdown
# SPEC-REVIEW-489-r1
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/489
|
||
- **Этап:** ТЗ на ревью (PROCESS.md §2.4)
|
||
- **Материал:** `docs/specs/489-data-hp-contract.md` на SHA `9cf10bdde7696e13c049fe83b4dc284622386127`
|
||
(commit `9cf10bdd`, ветка `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).
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/489-data-hp-contract`, коммит `9cf10bdde769` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `aa85b7b01e6fe7a4dce55220b63b6da33f725eb8`
|
||
```
|
||
git log --all --format='%H %T' | grep aa85b7b01e6f
|
||
```
|
||
- ТЗ `docs/specs/489-data-hp-contract.md`, блоб `b90694718d571ad77abea4fc592a5e641a0c3d19`
|
||
```
|
||
git log --all --find-object=b90694718d571ad77abea4fc592a5e641a0c3d19 -- docs/specs/489-data-hp-contract.md
|
||
```
|