mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
@@ -0,0 +1,236 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user