Compare commits

..
Author SHA1 Message Date
Sergey Matyunin ac2bd86166 docs(spec): define tool-first Plan editor
Issue: #148
User-Visible: no
2026-08-15 10:36:28 +03:00
4 changed files with 434 additions and 595 deletions
-295
View File
@@ -1,295 +0,0 @@
# Ревью ТЗ — issue #154, цикл r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/154
- **ТЗ:** `docs/specs/154-touch-hover-reset.md`, коммит `cd3c7752a8846c11508a7a6b4c46b6d990624408`
- **Этап:** spec (PROCESS.md §2.4)
- **Вердикт:** жёлтый · цикл r1/4 · High: 3 · Medium: 1
## Скоуп ревью
Прочитаны в указанном порядке: `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md`
(§1–§4, §7, §12), тело issue #154 и оба комментария владельца (аналитика
2026-08-15: P1/bug/polish, обычный трек, «trivial запрещён из-за touch
impact», «вопросов нет»; публикация ТЗ), `docs/USER-GUIDE.ru.md` (термины
«hover», раздел ограничений «Комнатные подсказки основаны на hover»),
`docs/TOUCH-SUPPORT.md` (input classification, deliberate degradation),
`docs/UX-MODES.md` (contract View: «room hover highlight, hover tooltips»),
`docs/CANVAS.md` (проверка, действительно ли это канонический документ для
hover-контракта — нет, см. Medium-1) и `docs/TESTING.md` (существующий
контракт «No hover tooltips on touch», v1.43.3/v1.42.2).
Issue не помечен `small`/`trivial`; ТЗ корректно лежит файлом в
`docs/specs/154-touch-hover-reset.md`, а не в теле issue. Формат ревью —
полный документ, не комментарий.
Для калибровки того, что этот репозиторий уже считает обязательным
оформлением §7.1, сверился с `docs/specs/138-adjacent-room-autoclose.md`
(разделы «1. Сценарий и продуктовый контекст» / «2. Что человек увидит до и
после») и его ревью `docs/reviews/SPEC-REVIEW-138-r1.md` — оно отдельной
строкой подтверждает наличие обоих разделов как условие «обязательные
разделы §7.1 на месте».
## Как проверялось
Ревью также читало код, на который ТЗ опирается как на текущее поведение —
чтобы отличить обоснованное утверждение от догадки, выданной за факт:
- `src/houseplan-card.ts:5442–5470` (`_notePointer`) — подтверждает
формулировку issue и ТЗ: touch/pen действительно закрывает `_tip`
(`5446`), но не трогает `_hoverRoom`. Совпадает.
- `src/houseplan-card.ts:659,662,1759–1760` — `_tip`/`_hoverRoom` как
реактивный state; `14619–14646` — room hover действительно навешан через
`@mouseenter`/`@mouseleave` на path/polygon/rect комнаты, без
`pointerenter`/`pointerleave`. Совпадает с §6 ТЗ.
- `src/houseplan-card.ts:1285,5443,9494,11167` — уже существует
`_boundaryPointerType`, session-local поле «последний реальный
pointerType», используемое для decor-snap радиуса (`_decorSnap`) и
touch-детекции (`9494`). ТЗ §5 говорит «Houseplan вводит один
session-local источник фактической modality», не упоминая, что подобное
поле уже есть. Это не продуктовая двусмысленность (реализация — дело
автора, PROCESS §7.1), но реальный риск дублирования authority; отмечено
ниже как **Low**, без отдельного issue.
- `grep ':hover'` по `src/` — 32 вхождения в 6 файлах
(`houseplan-card.ts`, `styles.ts`, `space-card.ts`, `hp-dialog.ts`,
`hp-device-preview.ts`, `hp-color-opacity.ts`, `hp-help.ts`). Ни один не
назван в ТЗ — см. **High-3**.
- `docs/CANVAS.md` — единственное вхождение слова «hover» (строка 563,
«hover is only a preview and never authoritative») относится к preview
hit-резолвера архитектурного connection overlay в редакторе Плана
(endpoint/line snapping), а не к View-контракту room/device hover,
который меняет issue #154. `docs/UX-MODES.md:69–70` прямо перечисляет
«room hover highlight, hover tooltips» как часть допустимых
взаимодействий View — см. **Medium-1**.
- `docs/TESTING.md:175–176,750–752` — существующий, уже протестированный
контракт «a hover tooltip never appears after ANY touch/pen pointer
event, even if the browser claims hover: hover» — ТЗ корректно описывает
его как частично реализованный и подлежащий расширению.
- issue #152 (`gh issue view 152`) — действительно ещё `S4-spec-review`, не
реализован; ссылка ТЗ на «будущий #152» и решение не читать `_hoverRoom`
из его hit-резолвера корректны и не создают ложной зависимости.
## Находки
### High-1 — нет обязательных разделов «Сценарий» и «Что человек увидит до/после» (§7.1)
**Где:** `docs/specs/154-touch-hover-reset.md`, весь документ; ближе всего
раздел «1. Проблема и требуемый результат» (строки 9–19).
PROCESS.md §7.1 требует ТЗ начинать с двух продуктовых разделов: «какая
персона (`docs/SCOPE.md`), на какой поверхности, в какой момент это
встретит» и «что человек увидит до и после, одной фразой без терминов
реализации» — и подчёркивает, что «два первых раздела — продуктовые, и они
идут первыми не случайно». Это не стилистическая рекомендация: сам
репозиторий уже применяет это буквально в `docs/specs/138-...md`
(разделы «1. Сценарий и продуктовый контекст» / «2. Что человек увидит до и
после»), и предыдущее ревью того ТЗ отдельно проверяло их наличие как
условие «обязательные разделы §7.1 на месте».
В ТЗ #154 такого раздела нет вообще. Раздел 1 сразу входит в техническое
описание бага (`_notePointer()`, CSS `:hover`, `mouseenter`/`mouseleave`), а
второй абзац, который мог бы быть «что человек увидит», написан в терминах
реализации: «показывает только краткий pressed feedback», «настоящие mouse и
trackpad», «на гибридном устройстве последующий mouse input восстанавливает
его» — это не «одна фраза без терминов реализации», а пересказ технического
контракта.
Персона и поверхность из `docs/SCOPE.md` — не абстракция: J1–J3 явно относят
View к household members/guests на wall tablet и companion-приложении
(«View mode is the product for two of the three personas»), а issue
явно называет ещё и «мобильный браузер / HA Companion». Отсутствие
явного раздела означает, что читатель ТЗ вынужден реконструировать это
сам, а вопрос «какая персона важнее при конфликте» (например, «жертвуем ли
чем-то ради гибридного admin-устройства ради гарантии touch у household
member») в тексте не решён явно — только подразумевается порядком risk-строк
§16.
**Почему High:** это единственная площадка процесса, где владелец мог бы
опротестовать выбор персоны/поверхности/степени деградации ДО написания
кода (PROCESS §7.1: «Владелец отвечает на продуктовые вопросы… их место —
этап ТЗ»); без явного раздела с этим согласиться (или оспорить) нечего —
ревьюер не может подтвердить, что «что человек увидит» действительно решено,
а не растворено в технической формулировке.
**Что нужно поправить:** добавить два раздела по образцу #138 — «Сценарий»
(персона/поверхность/момент, со ссылкой на J1–J3 `docs/SCOPE.md`) и «Что
человек увидит до/после» одной фразой без «modality», «pointerType»,
«hybrid», «pressed feedback» (например: «раньше подсветка комнаты/маркера
после тапа могла остаться до следующего действия; теперь она гаснет сразу
после отпускания пальца, а мышь на компьютере работает как раньше»).
### High-2 — критерии приёмки не указывают способ доказательства (DoR §2.5)
**Где:** `docs/specs/154-touch-hover-reset.md` §12 «Acceptance criteria»
(строки 197–212), сверено с §13 «План тестирования» (214–252).
PROCESS.md §2.5 (вход в «Готово к разработке») требует буквально: «AC1…ACn —
пронумерованные проверяемые критерии приёмки; **у каждого указано, чем он
доказывается**: `unit` / `backend` / `smoke` / `golden` / «ревью кода»» — и
«если хоть один пункт не выполнен — статус не «Готово к разработке»».
Все 12 пунктов §12 сформулированы как утверждения о поведении (хорошо,
проверяемо и однозначно сформулированы — отдельная претензия к ним
отсутствует), но ни один не помечен способом доказательства. §13 группирует
тесты по категориям (Unit/source contract, Browser smoke, Golden,
Performance), но не сопоставляет их с номерами AC — например, неясно из
текста, доказывается ли AC7 («desktop mouse hover не изменён») smoke-тестом,
golden-скриншотом, или обоими одновременно, и то же для AC9 (keyboard focus)
и AC12 (naked hover selector — очевидно «ревью кода» по source-inventory,
но это нигде не написано явно).
**Почему High:** это не редакторская придирка, а буквальный пункт входного
чек-листа перед `S5-ready`. Без явной привязки разработчик и код-ревьюер
(другая модель, без контекста этой сессии — AGENTS.md «Two-agent workflow»)
должны будут заново реконструировать, каким тестом каждый AC закрыт, что
именно и должен исключать этот пункт DoR.
**Что нужно поправить:** добавить к каждому из 12 пунктов §12 тег
доказательства, например `[unit]`, `[smoke]`, `[golden]`, `[unit+smoke]`,
`[ревью кода]` — материал для этого уже есть в §13, требуется только явное
сопоставление 1:1.
### High-3 — не перечислены затронутые файлы и модули (DoR §2.5)
**Где:** весь документ; ближайший к теме раздел — «14. План реализации»
(строки 254–262).
Тот же пункт DoR §2.5 требует отдельно: «перечислены затронутые файлы и
модули». В ТЗ этого списка нет — §14 ограничивается общими шагами
(«провести inventory JS state и View/shared CSS hover selectors», «ввести
component-local pointer modality authority»), не называя ни одного
конкретного файла.
Это не тривиальный однофайловый фикс: `grep ':hover' src/` даёт 32
вхождения в 6 файлах — `houseplan-card.ts`, `styles.ts`, `space-card.ts`,
`hp-dialog.ts`, `hp-device-preview.ts`, `hp-color-opacity.ts` (плюс
`hp-help.ts`, у которого есть top-level `mouseenter`/`_notePointer`-подобная
логика — исключён по контракту §4, но это тоже стоило бы явно назвать).
Раз §3 ТЗ прямо говорит, что «общие компоненты редакторов получают
исправление, если используют тот же pointer-modality gate», а этот gate
должен быть «единым» и «передан в обязательные shadow child components»
(§5), явно неясно без списка файлов, какие из этих 6+ компонентов входят в
обязательный охват, а какие — только в «если не требуют отдельной сложной
адаптации» (§3, необязательная часть).
**Почему High:** без списка файлов нельзя проверить полноту заявленного
«обязательного охвата» View (issue, раздел «Обязательный охват») — то же
самое, что нельзя проверить AC без способа доказательства.
**Что нужно поправить:** добавить раздел «Затронутые файлы» с явным списком
(минимум все 6 файлов с `:hover`, плюс модуль(и), где будет жить pointer
modality authority), и отметить для каждого — «обязательный охват» или
«если дёшево, без специальной адаптации» (§3).
### Medium-1 — план документации (§15) называет неверный канонический документ для hover-контракта
**Где:** `docs/specs/154-touch-hover-reset.md` §15 (строки 264–276), пункт
«`docs/CANVAS.md` — hover/pressed/semantic ownership».
`docs/CANVAS.md` — канонический документ infinite canvas (координаты,
content frame, zoom/pan, grid, drag limits, snap contract, wall geometry).
Слово «hover» встречается там ровно один раз (строка 563) и относится к
preview hit-резолверу architectural connection overlay в редакторе Плана
(«the same resolver runs again on click, so hover is only a preview and
never authoritative») — это про предпросмотр примыкания к стене при
рисовании, а не про View-контракт room/device/opening hover, который меняет
эта задача.
Настоящий канонический источник контракта — `docs/UX-MODES.md`, раздел
«View — display and device interaction only» (строки 63–70): «room hover
highlight, hover tooltips (name, clean-floor area, temperature, signal)» —
именно эта строка перечисляет ровно то поведение, которое ТЗ #154 меняет
(добавляет pointer-modality gate и запрещает sticky/synthetic hover). ТЗ
§15 не включает `docs/UX-MODES.md` в список документов на обновление
вообще.
**Последствие, если не исправить:** implementer, следуя §15 буквально,
допишет в `docs/CANVAS.md` абзац не по адресу (документ и так не про
интеракции), а `docs/UX-MODES.md` продолжит говорить просто «room hover
highlight, hover tooltips» без указания на pointer-modality/no-sticky-hover
контракт — расхождение документации с реальным поведением ровно там, где
его будут искать в первую очередь (`UX-MODES.md` явно значится и в
AGENTS.md как канонический документ подсистемы).
**Что нужно поправить:** заменить `docs/CANVAS.md` на `docs/UX-MODES.md` в
списке §15 (либо добавить `UX-MODES.md` к списку и явно решить, нужен ли
`CANVAS.md` вообще — по факту нет, там нет ownership hover).
Заведён отдельный issue: см. итог ниже.
## Что проверено и корректно
- **Проблема описана по реальному коду, не по догадке.** `_notePointer()`
(`houseplan-card.ts:5442`) действительно закрывает только `_tip`, room
hover действительно висит на `mouseenter`/`mouseleave`
(`14619–14646`) — оба факта, на которых строится вся мотивация ТЗ,
подтверждены чтением, не голословны.
- **Границы transient hover (§2) чёткие и без скрытого расширения скоупа.**
Working/alarm/unavailable/Glow/pulse, `hp-help`, selected/checked,
keyboard focus прямо исключены из очистки — совпадает с исключениями,
которые перечислил сам владелец в теле issue («Исключения»).
- **Synthetic-mouse policy (§6) корректно закрывает главный технический
риск бага** («мобильный браузер может синтезировать mouse-события») —
явно требует trusted `PointerEvent` с реальным `pointerType`, запрещает
произвольный wall-clock timeout как основной механизм. Это прямое,
проверяемое техническое решение, а не «наверное сработает».
- **Не создаёт ложной зависимости от нереализованного #152.** Проверено:
#152 (`click-to-fit`) сам ещё в `S4-spec-review`; §9 ТЗ прямо пишет, что
#152 должен использовать canonical hit resolver независимо от
`_hoverRoom`, не читая его как заменитель выбора — корректно, не
предвосхищает нерешённый ТЗ #152.
- **«Принятые предположения» (§17) — действительно технические или уже
решённые владельцем, не спрятанные продуктовые вопросы.** Пункт «touch и
pen следуют одинаковой no-hover policy» дословно совпадает с «Pen tap
следует touch-политике» из тела issue (владелец уже решил это), а не
является домыслом автора ТЗ; «media query — только второй gate»,
«mouse modality — только trusted PointerEvent» — реализационные решения,
которые PROCESS §7.1 явно отдаёт на усмотрение автора.
- **Accessibility (§11) не оставляет открытых вопросов**: DOM focus,
`:focus-visible`, `aria-*`, dialog focus trap явно выведены из-под
touch-cleanup — совпадает с «Keyboard focus и `:focus-visible` сохраняют
штатное поведение» из тела issue.
- **i18n и модель данных закрыты корректно и коротко, а не отпиской.** §15
прямо говорит: новых строк не ожидается, если появятся — обе локали и
parity test обязательны; §16 — «Config, storage и backend data не
меняются; миграция и data rollback не нужны» — соответствует тому, что
задача чисто presentation/pointer-lifecycle, backend не тронут.
- **Откат (§16) реалистичен**: «возвращает прежние JS/CSS hover paths» —
для чисто клиентской, не мигрирующей данные задачи это адекватный и
проверяемый уровень детализации отката.
- **Трек и трейлеры соответствуют аналитике.** Комментарий 2026-08-15
корректно исключил `trivial` (touch impact) и `small` (несколько
поверхностей: room/device/opening/controls/dialogs); полный трек и файл
в `docs/specs/` — правильный выбор, подтверждён `S4-spec-review` без
`small`.
## Чего не проверял
- Реализацию — её нет, это ревью ТЗ, а не кода (PROCESS.md §2.4).
- Golden/browser smoke живьём — на этапе ТЗ не запускаются; план в §13
выглядит технически осмысленным (real touch context, CDP touch input,
`(any-hover: hover)` контекст), но существование конкретных сценариев не
верифицировалось запуском.
- Производительность — утверждения §13 «Performance» (pointermove не
вызывает full Lit update и т.п.) не профилировались; на этапе ТЗ это не
требуется, оценка отложена до реализации/пре-релизного гейта.
- Полный список всех `:hover`-селекторов в `styles.ts` построчно (нужен ли
каждому modality gate) — за пределами ревью ТЗ; сосчитано только число
файлов/вхождений, чтобы обосновать High-3.
- Реализуемость unit-теста «compatibility MouseEvent не включает mouse
modality» в текущем test harness (`test/**`) — не проверялась: на этапе
спецификации это заявление о контракте, а не код.
## Вывод
Технический контракт (§5–11, §16–17) продуман, обоснован кодом и не
содержит выданных за факт догадок — это сильная сторона документа. Но три
independent High-находки — отсутствие обязательных продуктовых разделов
§7.1, отсутствие способа доказательства у каждого AC и отсутствие списка
затронутых файлов — это три отдельных, буквальных пункта DoR §2.5,
неотвеченных полностью. Ни один из них не требует пересмотра решения, все
три чинятся добавлением текста в это же ТЗ без изменения контракта.
Возврат в «ТЗ в работе» (`S3-spec`), цикл r1/4.
Medium-1 заведён отдельным issue со ссылкой на #154 (не блокирует переход
сам по себе, но должен быть закрыт до релиза документации).
+433
View File
@@ -0,0 +1,433 @@
# Issue #148 — Tool-first редактирование комнаты в Plan Editor
- **Issue:** https://github.com/Matysh/houseplan-card/issues/148
- **Редакция:** первая редакция для независимого ревью; статус задачи определяется
только метками issue
- **Тип / приоритет:** feature + polish / P2
- **Оценка:** пользовательская ценность 8/10; ценность для разработки 7/10;
сложность и риск 8/10
- **Область:** нижняя панель Plan Editor, шесть инструментов редактирования
комнаты, выбор целей, локальные параметры, Undo/Redo, keyboard и accessibility
- **Модель данных:** сохранённый формат плана не меняется; `ToolSession` — только
runtime-состояние редактора
- **Связано:** `docs/UX-MODES.md`, `docs/CANVAS.md`, `docs/RESIZE.md`,
`docs/WALL-THICKNESS.md`, `docs/TOUCH-SUPPORT.md`
## 1. Сценарий и продуктовый контекст
**Персона:** администратор дома, который строит и исправляет план в
desktop-браузере.
**Поверхность и момент:** пользователь открывает Plan Editor и хочет выполнить
операцию над комнатой, ещё не выбрав комнату или стену.
**До → после, без терминов реализации:** сейчас состав нижней панели зависит от
того, сколько комнат уже выбрано, и заставляет сначала угадывать нужное
выделение; после изменения пользователь одним и тем же способом сначала
выбирает действие, затем требуемые объекты и только после валидного выбора
настраивает и применяет результат.
Это поддерживает J4 и J6 из `docs/SCOPE.md`: встроенный редактор становится
предсказуемым инструментом построения и последующего исправления плана.
## 2. Решение владельца и источник UX-контракта
Владелец 15.08.2026 отклонил предложенное разбиение на этапы: общий framework и
все шесть действий поставляются **одним релизом**. Каноническая запись:
https://github.com/Matysh/houseplan-card/issues/148#issuecomment-5301106841
Приложенный к issue архив `Plan Editor.zip` является UX-референсом. Его
`DECISION.md`, `UX-SPECIFICATION.md`, `WIREFRAMES.md`,
`interactive-prototype.html` и `states.html` уточняют поведение, но не являются
готовым кодом или отдельной системой стилей. При расхождении приоритет таков:
1. решения владельца в issue;
2. это ТЗ;
3. текстовые документы архива;
4. визуальная реализация прототипа.
## 3. Скоуп одного релиза
В задачу входят:
1. единый runtime lifecycle сессии инструмента;
2. стабильная группа «Редактировать комнату» с действиями «Объединить»,
«Разделить», «Размер», «Граница», «Толщина», «Удалить»;
3. перевод на tool-first контракт всех шести действий без сохранения скрытого
preselection-зависимого маршрута;
4. инструкции, счётчики, validation errors и визуальные состояния active,
hover, selected и invalid;
5. локальная панель толщины и контекстные настройки остальных инструментов;
6. единые правила отмены, переключения, фокуса, клавиатуры и Undo/Redo;
7. сохранение верхней навигации и занимаемой нижней панелью геометрии;
8. unit, browser smoke, visual golden, документация и RU/EN changelog.
Поставка только framework, Merge или Thickness при незавершённых остальных
четырёх инструментах не выполняет задачу.
## 4. Не входит в задачу
- новая верхняя навигация, постоянный правый inspector или перенос редактора в
отдельную страницу;
- новые архитектурные операции, которых сейчас нет в Plan Editor;
- изменение результата Merge/Split/Resize/Boundary/Thickness/Delete после
подтверждения, кроме необходимого выравнивания их UX lifecycle;
- изменение моделей комнат, стен, проёмов, перегородок, колонн и HA Areas;
- миграция, schema version или backend API;
- переход Devices/Background editor на новый lifecycle;
- полноценный touch-first Plan Editor: редактор остаётся desktop-first, при
обязательном safety floor из раздела 11;
- изменение View, киоска и верхней segmented navigation;
- новый верхнеуровневый режим «Удаление».
## 5. Информационная архитектура нижней панели
### 5.1. Стабильные группы
В Plan сохраняются существующая верхняя навигация и общий каркас нижней панели.
Панель предоставляет стабильные группы:
- «Выбрать»;
- «Редактировать комнату»;
- «Контур комнаты»;
- «Конструкции»;
- «Проём».
Внутри «Редактировать комнату» состав не меняется от выделения на холсте:
1. Объединить;
2. Разделить;
3. Размер;
4. Граница;
5. Толщина;
6. Удалить.
Группа доступна без предварительного выбора комнаты. Условия конкретной
операции показывает активная сессия, а не disabled-состав подпанели.
### 5.2. Шесть обзорных состояний
Требуемый комплект продуктовых состояний/скриншотов:
1. без выбора;
2. редактирование комнаты;
3. контур комнаты;
4. конструкции;
5. проём;
6. подтверждение удаления.
«Подтверждение удаления» — временное destructive-состояние внутри группы
«Редактировать комнату». Оно не добавляет шестую постоянную группу или вкладку.
### 5.3. Геометрия панели
- раскрытие группы и смена фазы не меняют размеры stage и не сдвигают холст;
- содержимое остаётся в выделенной нижней области и использует существующий
shared secondary controller;
- overflow прокручивается внутри панели; кнопка подтверждения и текущая
инструкция не уходят за viewport;
- локальная плавающая панель может перекрывать stage, но не участвует в layout.
## 6. Каноническая сессия инструмента
Реализация может уточнить имена типов, но обязана иметь один явный источник
runtime-истины со следующим смыслом:
```ts
interface ToolSession {
tool: 'merge' | 'split' | 'resize' | 'boundary' | 'wallthick' | 'delete' | null;
phase: 'idle' | 'selecting_targets' | 'validating_targets' | 'configuring' | 'applying';
targets: ToolTarget[];
anchor?: { x: number; y: number };
draft: Record<string, unknown>;
validationError?: { key: string; targetId?: string };
}
```
Обязательные инварианты:
1. `tool === null` означает отсутствие незавершённой операции;
2. тип, количество, порядок и совместимость целей задаёт активный инструмент;
3. `selectedRooms.length` и другие глобальные selection counts не определяют
состав подпанели;
4. hover не является target и не меняет конфигурацию;
5. invalid target не добавляется в `targets`;
6. `draft` и targets не пишутся в server config, localStorage или history;
7. единственная граница Undo/Redo — успешно применённая команда;
8. после apply сессия завершается и очищается до `idle`; повтор операции
требует нового явного выбора инструмента;
9. переключение space/mode, закрытие редактора и потеря актуальной цели
безопасно отменяют незавершённую сессию;
10. asynchronous validation/apply проверяют revision сессии: запоздалый ответ
отменённого или заменённого инструмента не меняет UI и конфигурацию.
Существующие разрозненные поля допустимы как внутренние детали перехода, но не
могут оставаться конкурирующими источниками lifecycle.
## 7. Контракты шести инструментов
### 7.1. Объединить
1. Клик по «Объединить» включает инструмент и показывает «Выберите две
смежные комнаты», счётчик `0 из 2`.
2. Первая валидная комната получает selected-state, счётчик становится
`1 из 2`.
3. Повторный клик по уже выбранной комнате снимает её; порядок остальных целей
сохраняется.
4. Вторая кандидатура валидируется до добавления: это другая существующая
комната того же space/floor с подходящей общей границей, не занятая
конфликтующей операцией.
5. Невалидная комната не заменяет первую и не становится третьей целью. Рядом
с подпанелью показывается конкретная причина, а кандидат получает временный
invalid-state.
6. После второй валидной комнаты автоматически открываются действующие
настройки Merge. Фокус переходит на их заголовок/первое поле.
7. Пользователь может применить, отменить всю сессию или вернуться к выбору
комнат, сохранив первую/обе валидные цели согласно явной кнопке «Назад».
8. Конфигурация и history меняются только после подтверждения.
### 7.2. Разделить
1. Инструмент просит выбрать одну существующую комнату.
2. После валидного выбора начинается действующий маршрут задания линии Split;
незавершённые точки являются `draft`, а не history-командами.
3. Начальная/конечная точки, пересечения и результирующие полигоны валидируются
по текущим правилам Split; invalid click не повреждает уже валидный draft.
4. Настройки новой комнаты открываются только после законченной валидной линии.
5. «Назад» из настроек возвращает к редактированию линии; подтверждение
применяет Split атомарно, cancel очищает его полностью.
### 7.3. Размер
1. Инструмент просит выбрать одну комнату либо допустимый её сегмент.
2. Первый валидный target включает существующие resize handles и preview.
3. Live preview хранится только в сессии; server config и соседние render paths
не видят частично применённую геометрию.
4. Настройки/контролы показываются после валидного target, а apply фиксирует
одну атомарную history-команду.
5. Cancel восстанавливает точную исходную геометрию без записи и без Undo item.
### 7.4. Граница
1. Инструмент объясняет требуемую текущей операцией цель и принимает только
допустимую общую границу/её endpoints.
2. Первая точка или boundary target переводит сессию в выбор следующей цели;
pan/pinch не подтверждает точку.
3. Невалидная цель не заменяет валидный anchor и сопровождается конкретной
причиной.
4. Конфигурация границы применяется одной подтверждённой командой. Esc во
время двухточечного выбора сначала возвращает к выбору целей по правилам
раздела 9, не создавая history.
### 7.5. Толщина
1. Инструмент показывает «Выберите стену или несколько стен».
2. Доступный atomic wall segment получает hover-state; клик добавляет/снимает
его из набора targets. Открытый span и другой невалидный объект не
добавляются.
3. После первого выбранного сегмента появляется локальная панель рядом с ним:
поле «Толщина», диапазон `1–100 см` (с действующим imperial-equivalent),
«Применить ко всем стенам комнаты» и кнопка подтверждения с галочкой.
4. При нескольких targets anchor панели — общий bbox выбранных сегментов либо
последний выбранный сегмент; выбор должен быть детерминирован.
5. Предпочтительное положение — под anchor с отступом 8–12 px. Панель
переворачивается/сдвигается, чтобы целиком остаться в card viewport, не
закрывать выбранный сегмент и критичные handles.
6. Поле может содержать draft, но стены не меняются до подтверждения. Ошибка
диапазона показывается рядом с полем и блокирует apply.
7. «Ко всем стенам комнаты» вычисляет цели от комнаты выбранного anchor в
момент подтверждения; неоднозначность shared wall решается действующим
владельцем atomic interval и не распространяется на соседнюю комнату
скрыто.
8. Одна галочка создаёт одну атомарную history-команду для всех targets.
### 7.6. Удалить
1. Инструмент просит выбрать одну комнату; hover/selected не удаляют данные.
2. После валидного target открывается встроенное состояние подтверждения с
именем комнаты и явным destructive action.
3. Красный цвет используется для финальной destructive-кнопки и invalid/error,
но не как постоянная окраска всей группы.
4. «Назад» возвращает к выбору комнаты; cancel не создаёт history.
5. Подтверждение удаляет только явно выбранную комнату существующей атомарной
командой и затем очищает сессию.
## 8. Визуальные состояния и обратная связь
- **active tool:** постоянный cyan/accent state кнопки и текстовая инструкция;
- **hover target:** лёгкий контур без изменения target count;
- **selected target:** стабильный заметный контур/заливка и текстовый счётчик;
- **invalid target:** краткий красный/not-allowed state вместе с текстовой
причиной; цвет не является единственным каналом;
- **applying:** повторное подтверждение блокируется, но отменённый устаревший
completion не может завершить новую сессию;
- status/instruction использует `aria-live="polite"`, validation error —
`aria-live="assertive"` или эквивалентный alert.
Стили берутся из текущих токенов редактора. Прототип определяет иерархию и
состояния, но не разрешает вводить параллельную палитру.
## 9. Отмена, keyboard и focus
1. Повторный клик по активному инструменту отменяет всю незавершённую сессию.
2. Переключение инструмента атомарно очищает targets, anchors, draft, preview,
validation error и локальную панель предыдущего.
3. Esc в `configuring` закрывает настройки и возвращает к `selecting_targets`
с последним валидным набором targets.
4. Esc в `selecting_targets` очищает targets и выключает инструмент.
5. Для многошагового Split/Boundary первый Esc может снять последнюю
незавершённую точку только если это явно показано пользователю; следующий
следует общему правилу отмены сессии.
6. Enter применяет только валидную configuring-фазу и никогда не подтверждает
destructive Delete без фокуса на явной кнопке.
7. После автоматического открытия настроек фокус переходит на их heading или
первое поле; после «Назад» — на active tool/instruction; после apply/cancel
— на кнопку запуска инструмента.
8. Кнопки имеют `aria-pressed`, локализованное имя и понятный disabled reason.
## 10. Модель данных, persistence и compatibility
- форматы `spaces`, `rooms`, `walls`, `open_spans`, openings и физических
объектов не меняются;
- `ToolSession`, targets, anchors и drafts не сериализуются;
- миграции, backfill и schema version нет;
- открытие и отмена любого инструмента не вызывает `_saveConfig()`;
- применённые команды используют действующий config/write contract и остаются
читаемыми предыдущей версией;
- import/export не получает новых полей;
- текущие планы открываются без materialisation и побайтовых изменений.
## 11. Touch, zoom, resilience и performance
Plan Editor остаётся desktop-first. На coarse pointer полный ergonomic parity
не требуется, но:
- второй палец, pan, pinch и pointercancel не могут подтвердить цель или apply;
- touch target controls не меньше действующего minimum;
- cancel/switch/space change не оставляет preview или заблокированный stage;
- zoom между выбором targets не меняет их identity и корректно перепривязывает
локальную панель;
- удаление target внешним config update переводит сессию в безопасную отмену с
сообщением, а не применяет операцию к другому объекту.
Hover/pointermove меняют только дешёвый preview. Полная геометрия, render model
и secondary-panel composition не пересчитываются на каждый pixel движения.
Target validation и локальная раскладка входят в существующие bounded caches;
HA state ticks не перестраивают tool session без структурной причины.
## 12. i18n
Все новые пользовательские строки добавляются одновременно в RU и EN:
- название группы и шести действий, если действующие ключи не подходят;
- инструкции каждой фазы;
- счётчики целей (`0 из 2`, `1 из 2`, `2 из 2`) через plural/parameter contract;
- конкретные validation reasons Merge и остальных инструментов;
- «Назад», «Применить», «Применить ко всем стенам комнаты»;
- accessible labels/status для панели и targets.
Нельзя собирать предложения конкатенацией или оставлять prototype-only English.
Термины сверяются с `docs/USER-GUIDE.ru.md`.
## 13. Acceptance criteria
1. «Редактировать комнату» доступно без preselection и всегда показывает все
шесть действий в заданном порядке.
2. В основной панели нет компоновок «выбрана 1 комната»/«выбраны 2 комнаты» и
ветвления состава от `selectedRooms.length`.
3. Каждое из шести действий следует фазам tool → targets → validation →
configuring → apply и не пишет draft в config/history.
4. Merge показывает `0/2` и `1/2`, не принимает третью/невалидную комнату и
автоматически открывает настройки после второй валидной.
5. Thickness допускает один/несколько atomic segments, открывает рядом панель,
валидирует `1–100 см`, поддерживает all-room и применяет только по галочке.
6. Split, Resize и Boundary сохраняют действующую геометрическую семантику, но
получают единые выбор, отмену, настройки и apply boundary.
7. Delete сначала выбирает одну комнату и показывает отдельное подтверждение;
до финальной кнопки данные не меняются.
8. Active, hover, selected и invalid различимы визуально и доступны без цвета.
9. Esc, повторный tool click, смена tool/mode/space и stale target очищают
незавершённое состояние по разделам 6 и 9.
10. Undo/Redo содержит только подтверждённые операции — ровно одну запись на
один apply независимо от числа targets.
11. Верхняя navigation не меняется, secondary content не сдвигает stage, а
локальная панель целиком остаётся в viewport при zoom и у всех краёв.
12. RU/EN, keyboard, focus, ARIA status/error и reduced-motion проходят
проверки; destructive Enter не срабатывает неявно.
13. Существующие планы и wire format не меняются; cancel не вызывает save.
14. Все шесть состояний раздела 5.2 обновлены и приняты visual review.
## 14. Проверки и доказательства
### Unit
- переходы state machine для каждого инструмента, повторного клика, switch,
Esc, apply, stale async completion и external target removal;
- target cardinality/identity и invalid-target exclusion;
- Merge same floor/shared boundary и автоматический configuring transition;
- Thickness multi-select, `1–100`, all-room и одна history-команда;
- отсутствие save/history для всех cancel paths.
### Browser smoke
- полный happy path и cancel/back path всех шести инструментов мышью;
- keyboard-only Merge, Thickness и Delete safety;
- zoom/pan во время выбора и позиционирование локальной панели у четырёх краёв;
- mode/space switch, pointercancel и structural config update;
- верхняя навигация и размеры stage до/после раскрытия групп.
### Golden
Обновляется и reviewится комплект из шести состояний раздела 5.2 минимум в
desktop theme light/dark, плюс узкие golden для:
- Merge `1 из 2`, invalid second target и configuring;
- Thickness hover, multi-selected и local panel у края;
- Delete confirmation.
Baseline меняется только после проверки, что diff соответствует ТЗ. Golden,
smoke и performance запускаются в release gate перед бетой; цикл реализации —
`typecheck`, `unit`, `build` по процессу.
## 15. Release-артефакты
В том же user-visible коммите реализации обязательны:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- `docs/USER-GUIDE.ru.md` — новый порядок редактирования всех шести операций;
- `docs/UX-MODES.md` — стабильные группы и tool-first lifecycle;
- при необходимости `docs/CANVAS.md`, `docs/RESIZE.md` и
`docs/WALL-THICKNESS.md`, если реализация уточнит их взаимодействие;
- RU/EN locale files;
- принятые golden/baseline и smoke scenario либо ссылка на канонический
артефакт CI по правилам репозитория;
- `docs/TESTING.md`, если добавлен новый сценарий/gate.
Security и backend release artifacts не требуются, пока реализация не меняет
права или wire format. Если такой change окажется необходим, задача должна
вернуться к владельцу до кода.
## 16. Риски и rollback
Основные риски: два конкурирующих источника selection state, применение stale
draft, потеря Undo boundary, недоступный focus и floating panel вне viewport.
Они закрываются единой сессией, revision guard и проверками раздела 14.
Rollback — возврат UI/runtime-кода и locale/docs без миграции данных. Уже
сохранённые планы остаются совместимыми.
## 17. Принятые технические предположения
1. Shared secondary controller остаётся хозяином нижней contextual surface;
новый lifecycle расширяет его, а не создаёт второй overlay stack.
2. Existing domain helpers Merge/Split/Resize/Boundary/Thickness/Delete
сохраняются; задача унифицирует orchestration и transaction boundaries.
3. Для multi-select Thickness стабильная identity — нормализованный atomic wall
interval вместе с room/space context, а не DOM element или экранные
координаты.
4. «Заблокирована другим процессом» на первом этапе означает локальную
конфликтующую сессию/структурно устаревшую цель; новая distributed locking
модель не вводится.
5. Overview prototype state «Удаление» трактуется как подтверждение внутри
«Редактировать комнату», поскольку это одновременно сохраняет заявленную
стабильную иерархию и шесть продуктовых скриншотов.
-299
View File
@@ -1,299 +0,0 @@
# Issue #154 — transient hover не залипает после touch
- **Issue:** https://github.com/Matysh/houseplan-card/issues/154
- **Статус документа:** готово к будущей реализации; issue остаётся на `S3-spec`
- **Приоритет:** P1
- **Тип:** bug/polish, обычный трек
- **Пользовательское изменение:** да
## 1. Проблема и требуемый результат
Мобильный браузер после tap может синтезировать mouse-события и сохранять CSS
`:hover`. Одновременно room hover в View управляется `mouseenter`/`mouseleave`,
а touch sequence не гарантирует `mouseleave`. Текущий `_notePointer()` закрывает
`_tip`, но не очищает `_hoverRoom` и не нейтрализует CSS hover.
После исправления touch/pen показывает только краткий pressed feedback на время
жеста. После его завершения transient hover отсутствует. Настоящие mouse и
trackpad сохраняют обычный hover, а на гибридном устройстве последующий mouse
input восстанавливает его после touch без reload.
## 2. Термины и границы состояния
**Transient hover** существует только из-за текущего hover-capable pointer:
- `_hoverRoom`, room fill/outline и room tooltip;
- device marker lift/shadow/brightness;
- opening, room label и control hover styles;
- dialog close/control hover;
- аналогичные чисто визуальные CSS `:hover` состояния общих View-компонентов.
Не являются transient hover и не очищаются этой задачей:
- `active`, `pressed` во время незавершённого pointer sequence;
- selected/checked/current состояния редакторов;
- working, unavailable, alarm, warning, Glow/pulse и иное HA-derived состояние;
- keyboard focus и `:focus-visible`;
- открытый по click/tap toggle-popover `hp-help`;
- открытый dialog либо выполненное action.
## 3. Scope
Обязательный охват основного View:
- room floor/fill/outline/label/tooltip;
- device marker, opening и vacuum marker;
- controls поверх плана и close/action controls диалога;
- tap комнаты из будущей #152;
- long press, pinch, multi-touch и pointer capture cancellation;
- touch → mouse/trackpad на гибридном устройстве;
- смена режима, visibility и lifecycle компонента.
Общие компоненты редакторов получают исправление, если используют тот же
pointer-modality gate без специальной адаптации. Полная поддержка touch-editing
остаётся вне scope согласно `docs/TOUCH-SUPPORT.md`.
## 4. Не входит в задачу
- изменение semantic device/room state;
- новый visual design hover/pressed/focus;
- изменение действий tap, long press или click;
- превращение `hp-help` в transient tooltip;
- исправление touch settings из #149;
- полная переработка gesture arbiter #152;
- глобальный `blur()`, снятие DOM focus либо synthetic click suppression,
способный отменить пользовательское action.
## 5. Единый pointer-modality authority
Houseplan вводит один session-local источник фактической modality:
```text
unknown | mouse | touch | pen
```
Начальное значение — `unknown`; pointer-only hover при нём выключен. Переходы:
- trusted `PointerEvent` с `pointerType === 'mouse'` включает `mouse`;
- trusted `touch` включает `touch` и немедленно очищает transient hover;
- trusted `pen` включает `pen` и следует touch policy;
- следующий настоящий mouse/trackpad pointer event переводит touch/pen → mouse;
- смена mode/space, `visibilitychange` в hidden и disconnect очищают transient
hover, не подменяя semantic state.
Media queries не являются authority: браузер может ошибочно сообщать
`(hover: hover)` после touch. Для включения CSS hover одновременно нужны:
1. последняя фактическая modality `mouse`;
2. hover/fine-pointer capability среды.
Компонент применяет единый class/data-attribute gate к View tree; дочерние
shadow components получают ту же modality явным property/attribute contract.
Независимые локальные детекторы и глобальный mutable singleton запрещены.
Modality не сохраняется в localStorage/config и не синхронизируется между
карточками. Она не обязана быть reactive product state, если один безопасный
DOM gate и явная очистка обновляются без лишнего full render.
## 6. Synthetic mouse policy
Touch-generated compatibility `MouseEvent` не может включить mouse modality.
Authority дают только Pointer Events с фактическим `pointerType`; существующие
`mouseenter`/`mouseleave` не меняют modality.
JS room hover переводится на `pointerenter`/`pointerleave` либо общий delegated
pointer path и устанавливается только для разрешённого mouse modality. Touch и
pen никогда не записывают `_hoverRoom`/`_tip` через hover path.
Не использовать произвольный таймаут «после touch игнорировать mouse N ms» как
основной механизм: он ломает быстрый touch → mouse сценарий и зависит от
браузера. Если конкретный браузер отправляет compatibility event как
`PointerEvent(pointerType='mouse')`, допускается только детерминированная
проверка provenance/capabilities этого же input sequence с unit/browser
доказательством; wall-clock suppression без идентичности sequence запрещён.
## 7. Очистка transient hover
Единый idempotent helper очищает только transient hover state. Он вызывается:
- в начале каждого touch/pen `pointerdown`;
- на соответствующих `pointerup` и `pointercancel`;
- при `lostpointercapture`;
- после terminal click/action path до следующего painted frame;
- при начале multi-touch/pinch и после его завершения/cancel;
- при mode/space change;
- при `document.visibilityState === 'hidden'`;
- при disconnect/remount boundary.
Повторный вызов — no-op и не должен создавать render loop. Pointer capture
снимается только владельцем gesture по текущему contract; hover cleanup не
отменяет service call, dialog open или room fit.
После touch terminal event pressed feedback очищается обычным gesture owner.
Transient hover не остаётся дольше одного animation frame и не используется для
имитации pressed.
## 8. CSS contract
Все пользовательски заметные View `:hover` selectors получают общий modality
gate. Как минимум это:
- room overlay/yard/styled fill;
- device normal/alarm lift, shadow и brightness;
- opening outline;
- room-label controls;
- stage controls/options;
- dialog close/action controls и общие card controls, видимые в View.
Один selector не должен случайно смешивать hover с semantic state. Если текущая
rule объединяет `:hover` и `:focus-visible`, её разделяют: mouse hover получает
modality gate, focus-visible остаётся без него. `:active`/explicit pressed styles
также остаются независимы.
Selectors редакторских handles можно оставить вне обязательного охвата, если
они не используются в View/shared component; решение фиксируется inventory в
review. Naked View `:hover` после изменения считается source-contract ошибкой.
## 9. JS hover и tooltip contract
- `_hoverRoom` и `_tip` не устанавливаются touch/pen событиями.
- `pointerleave` реальной мыши очищает принадлежащее target состояние.
- touch `pointerdown` очищает старый mouse hover до выполнения tap action.
- открытие/закрытие dialog не восстанавливает старый hover snapshot.
- tooltip, открытый keyboard focus либо explicit click contract, не должен
ошибочно классифицироваться как hover; owner хранится явно.
- следующий mouse enter/move заново вычисляет current hit и показывает hover,
а не восстанавливает устаревший room/device id.
Room fit #152 использует canonical hit resolver непосредственно из gesture
sequence и не зависит от `_hoverRoom`; очистка hover не должна терять room tap.
## 10. Touch, gestures и lifecycle
- Второй pointer немедленно исключает single-tap activation по действующему
gesture contract и очищает hover.
- Pinch/long press могут выполнить своё существующее действие, но после terminal
event не оставляют hover.
- `pointercancel`/lost capture всегда безопасны, даже если target удалён renderом.
- Tap → dialog → close не возвращает marker lift/shadow из пред-dialog frame.
- Pen tap следует touch policy; hover stylus не входит в обязательный контракт.
- Kiosk, light/dark theme, Flat/Isometric используют одинаковую modality policy.
Listener `visibilitychange` регистрируется/удаляется симметрично lifecycle и не
создаёт утечку при повторных mount/unmount.
## 11. Accessibility
- DOM focus не снимается touch cleanup helper.
- `:focus-visible` и keyboard tooltip/action продолжают работать независимо от
последней pointer modality.
- Touch cleanup не меняет `aria-expanded`, `aria-pressed`, selection либо dialog
focus trap.
- Mouse hover не является единственным способом получить обязательную
информацию или действие.
- Reduced motion не влияет на state machine; pressed feedback #22 остаётся
кратким и не превращается в hover.
## 12. Acceptance criteria
1. После touch tap на каждом обязательном View target transient hover исчезает
не позднее следующего frame после завершения pressed feedback.
2. `_hoverRoom`/hover-owned `_tip` равны null после touch terminal event.
3. Device lift/shadow и CSS hover controls отсутствуют после tap/dialog close.
4. Working/alarm/unavailable/Glow/pulse/selected state не изменяется.
5. Pinch, long press, multi-touch, cancel и lost capture не оставляют hover.
6. Браузер с touch и ложным `(hover: hover)` не показывает sticky hover.
7. Настоящий desktop mouse hover визуально и функционально не изменён.
8. На hybrid device touch → mouse восстанавливает hover первым настоящим mouse
pointer event без reload и без произвольной задержки.
9. Keyboard focus и `:focus-visible` сохраняются.
10. Cleanup не отменяет click action, dialog, #152 room fit или help popover.
11. Mode/space/visibility/disconnect очищают transient hover без listener leak.
12. В View/shared CSS не остаётся naked hover selector из inventory.
## 13. План тестирования
### Unit/source contract
- transitions `unknown → mouse/touch/pen → mouse`;
- compatibility MouseEvent не включает mouse modality;
- touch/pen down/up/cancel и lost capture вызывают idempotent cleanup;
- mode/space/visibility/disconnect lifecycle;
- cleanup очищает только hover-owned state;
- room hover gate и #152 hit независимы;
- CSS inventory: View hover selectors требуют modality gate, focus-visible — нет.
### Browser smoke
- реальный touch context/CDP touch input: room, device, opening, label, control;
- tap → dialog → close и tap при активном semantic device state;
- browser context с touch и `(any-hover: hover)`;
- two-finger pinch, long press, pointercancel и lost capture;
- touch room click-to-fit #152 без sticky room highlight;
- touch → hardware/simulated mouse pointer: hover снова появляется;
- desktop mouse enter/leave и keyboard Tab/Enter regression;
- Flat/Isometric, kiosk, light/dark и visibility round-trip.
Тест проверяет computed styles и JS state после painted frame, а не только
отсутствие class. `dispatchEvent(new MouseEvent(...))` не заменяет настоящий
touch browser scenario.
### Golden
- touch post-tap screenshot без hover, но с прежним semantic state;
- desktop mouse-hover и keyboard-focus screenshots;
- visual diff не переакцептует unrelated colors/shadows.
### Performance
- pointermove не вызывает full Lit update на каждый пиксель;
- modality gate не добавляет unbounded listeners/observers;
- canonical pan/pinch smoke сохраняет frame responsiveness;
- performance profile перед beta подтверждает отсутствие новых long tasks.
## 14. План реализации
1. Провести inventory JS state и View/shared CSS hover selectors.
2. Ввести component-local pointer modality authority и lifecycle cleanup.
3. Перевести room hover на pointer events с mouse gate.
4. Ввести общий CSS modality gate, разделив hover/focus/semantic rules.
5. Передать modality в обязательные shadow child components.
6. Добавить unit/source-contract и real-touch/hybrid browser tests.
7. Прогнать typecheck, unit и build; перед beta — smoke, golden, performance.
## 15. Документация и release-артефакты
Поскольку bug виден пользователю, implementation commit обязан иметь
`User-Visible: yes` и в том же коммите обновить:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- `docs/TOUCH-SUPPORT.md` — pointer modality и отсутствие sticky hover;
- `docs/CANVAS.md` — hover/pressed/semantic ownership;
- `docs/TESTING.md` — real-touch и hybrid smoke contract.
Нужны reviewed touch post-tap, desktop-hover и keyboard-focus golden artifacts,
а также browser smoke report. Новых пользовательских строк не ожидается; если
появятся, обе локали и parity test обязательны.
## 16. Риски и откат
| Риск | Мера |
| --- | --- |
| Touch cleanup отменяет action | state-only helper, gesture smoke |
| Mouse hover пропадает на hybrid | event-derived transition back to mouse |
| Focus styling попадает под gate | отдельные selectors/source contract |
| Semantic state очищается как hover | явный inventory и state ownership tests |
| Pointermove вызывает rerender storm | DOM gate без full render per move |
| Lifecycle listener течёт | symmetric connect/disconnect test |
Откат возвращает прежние JS/CSS hover paths. Config, storage и backend data не
меняются; миграция и data rollback не нужны.
## 17. Принятые предположения
- `touch` и `pen` следуют одинаковой no-hover policy;
- initial `unknown` не включает pointer-only hover до фактической мыши;
- mouse modality определяется trusted PointerEvent, не compatibility MouseEvent;
- media query используется только вторым gate, а не источником modality;
- #152 room activation не должна читать `_hoverRoom` как selected hit;
- `hp-help`, focus-visible и semantic states не относятся к transient hover.
+1 -1
View File
@@ -48,7 +48,6 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#131](https://github.com/Matysh/houseplan-card/issues/131) Полный первый кадр View у read-only-пользователя | [131-readonly-cold-start.md](131-readonly-cold-start.md) |
| [#138](https://github.com/Matysh/houseplan-card/issues/138) Автозамыкание комнаты по существующей стене | [138-adjacent-room-autoclose.md](138-adjacent-room-autoclose.md) |
| [#146](https://github.com/Matysh/houseplan-card/issues/146) Четырёхфазный фон «Следует за Солнцем» | [146-four-phase-sun-background.md](146-four-phase-sun-background.md) |
| [#154](https://github.com/Matysh/houseplan-card/issues/154) Touch: hover-состояние залипает после tap | [154-touch-hover-reset.md](154-touch-hover-reset.md) |
| [#156](https://github.com/Matysh/houseplan-card/issues/156) Регрессии Full Performance перед v1.64.0 stable | [156-full-performance-regressions.md](156-full-performance-regressions.md) |
## P2
@@ -87,6 +86,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#123](https://github.com/Matysh/houseplan-card/issues/123) Split из вершины не меняет наружную геометрию стен | [123-corner-split-wall.md](123-corner-split-wall.md) |
| [#137](https://github.com/Matysh/houseplan-card/issues/137) Узлы и линии привязки в редакторе Плана | [137-plan-snap-overlay.md](137-plan-snap-overlay.md) |
| [#141](https://github.com/Matysh/houseplan-card/issues/141) Бесшовные стыки перегородок и открытых контуров | [141-wall-junctions.md](141-wall-junctions.md) |
| [#148](https://github.com/Matysh/houseplan-card/issues/148) Tool-first редактирование комнаты | [148-tool-first-plan-editor.md](148-tool-first-plan-editor.md) |
## Правило актуализации