Compare commits

...
Author SHA1 Message Date
claude[bot] a28a4b7065 docs: review document for #149
Validate / provenance (push) Successful in 1m3s
Validate / process-gate (push) Successful in 38s
Validate / changes (push) Successful in 27s
Validate / hacs (push) Skipped
Validate / hassfest (push) Skipped
Validate / frontend (push) Skipped
Validate / smoke (push) Skipped
Validate / golden (push) Skipped
Validate / performance_smoke (push) Skipped
Validate / backend (push) Skipped
Issue: #149
User-Visible: no
2026-08-18 19:31:47 +00:00
Sergey Matyunin 2d5f0a5f0f docs(spec): define touch view settings affordance
Issue: #149
User-Visible: no
2026-08-15 10:39:05 +03:00
3 changed files with 615 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
# Ревью ТЗ — issue #149, цикл r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/149
- **ТЗ:** `docs/specs/149-touch-view-settings-affordance.md`, коммит `2d5f0a5f0f66faf1614a9f1f367ce108011fcfbe`
- **Этап:** spec (PROCESS.md §2.4)
- **Трек:** обычный (не `small`/`trivial` — сам issue называет причину: новый
видимый элемент, таймер, снятие существующего жеста, попадание в геометрию
контура, затронуты все три персоны); лимит цикла — 4
- **Вердикт:** красный · цикл r1/4 · High: 1 · Medium: 0
## Скоуп ревью
Прочитаны: `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (§1, §2.2–2.5, §3, §4,
§7.1–7.2, §12), тело issue #149 и все три комментария владельца (аналитика с
Q1–Q3 и defaults, решение владельца по Q1–Q3, объявление о готовом ТЗ), весь
текст `docs/specs/149-touch-view-settings-affordance.md`, `docs/UX-MODES.md`
(принцип режимов, политика ввода, kiosk-раздел), `docs/TOUCH-SUPPORT.md`
(контракт, safety floor, deliberate degradation), `docs/CANVAS.md` (§1–4,
модель content frame — чтобы не путать её с новым «physical footprint»),
`docs/WALL-THICKNESS.md` (§1–4, модель стен/partition/column, кэширование
структурного прохода), `docs/USER-GUIDE.ru.md` (раздел 6 «Навигация, масштаб и
жесты», раздел 17 «Киоск-режим», полнотекстовый поиск терминов «настройки
вида» и «киоск»), `docs/CONFIG-COMPATIBILITY.md` (чтобы подтвердить, что задача
действительно не задевает реестр совместимости server config).
Дополнительно, поскольку ТЗ описывает удаляемое/переносимое поведение как
факт о текущем продукте, а не как предположение — прочитан сам код:
`src/houseplan-card.ts` (`_stagePointerDown` ок. строк 5082–5104,
`_kioskScale`/`_kioskDialog`/`LS_KIOSK` ок. строк 1551–1553, 2313–2318,
14244–14277, применение множителя в devlayer — строка 14725,
`_interruptViewGesture` — строка 4300), `src/i18n/ru.json`/`en.json` (ключи
`kiosk.*`), `demo/smoke_kiosk.mjs`, `docs/CHANGELOG.md` (запись v1.41.0, где
эта функция впервые появилась) и `docs/TESTING.md` (пункт «Kiosk mode»,
строки 768–774).
## Как проверялось
1. Сверил формальные обязательные разделы ТЗ (PROCESS.md §7.1) с текстом
`docs/specs/149-touch-view-settings-affordance.md`: сценарий и персона/
поверхность/момент — §1; что человек увидит до/после — §1; скоуп и
не-скоуп — §4/§5; контракт поведения — §6–9; UX — §8/§10; модель данных и
миграция — §11; i18n — §10/§14; AC1…AC12 — §12; план автотестов — §13;
риски — §15; откат — §15; release-артефакты — §14. Раздела с буквальным
заголовком «Проблема» нет, но его содержание (почему долгий тап — плохой
affordance) присутствует в issue и подразумевается мотивацией §1; отмечаю
как Low ниже.
2. Прогнал через код каждое фактическое утверждение ТЗ о «существующем»
поведении (§1, §4 п.4, §9, §11), а не поверил формулировке — именно это
PROCESS.md называет «догадкой, выданной за решение», и именно на это
ревьюер обязан ловить автора.
3. Проверил все три owner-defaults (Q1–Q3 из комментария
`#issuecomment-5298450448`, подтверждённых `#issuecomment-5301106992`) на
соответствие тексту ТЗ §2 — совпадают буквально.
4. Проверил геометрические термины ТЗ (§6: «physical footprint», «unbounded
exterior», holes/courtyard, detached porch/terrace) на отсутствие
конфликта с уже канонической моделью `CANVAS.md` (content frame — другая
сущность, используется для камеры, не для hit-теста фона) и
`WALL-THICKNESS.md` (thick walls, partitions, columns, кэш по structural
fingerprint) — конфликтов не нашёл, переиспользование корректно оговорено
как предположение №1 в §16.
5. Проверил i18n-термин «Настройки вида» на предмет «терминология изобретена,
а не взята из USER-GUIDE» — в `docs/USER-GUIDE.ru.md` этой фразы сейчас
нет вообще (полнотекстовый поиск — 0 совпадений); фраза происходит из
заголовка/тела issue (то есть от владельца), не придумана автором ТЗ, и
ТЗ уже включает обновление USER-GUIDE в release-артефакты (§14) — это
штатный путь введения нового термина, не нарушение.
6. Проверил каждый AC (§12, 12 пунктов) на однозначность и на то, назван ли
способ доказательства в §13 — не построчным сопоставлением заголовков (в
ТЗ они не пронумерованы 1:1 с AC), а по содержательному пересечению
формулировок.
## Находки
### High-1 — §11 утверждает как факт то, что код опровергает; §4 не включает
необходимое для этого утверждения изменение
**Что не так.** §11 «Модель данных и compatibility» пишет: «kiosk и normal
View используют один per-screen value, **как и до изменения**» — то есть
заявляет, что до этой задачи обычный View и kiosk уже одинаково применяют
`_kioskScale` (масштаб иконок/текста) к рендеру. Это не так.
**Воспроизведение по коду (проверено чтением, не исполнением):**
- `src/houseplan-card.ts:5088` — весь блок, открывающий диалог размеров по
долгому тапу, обёрнут в `if (this._kiosk) { … }`. В обычном View
(`kiosk: false`) этот код не выполняется вовсе — то есть сегодня в обычном
View нет *никакого* способа, ни скрытого, ни явного, открыть этот диалог.
- `src/houseplan-card.ts:14725` — множитель фактически применяется тоже
только в kiosk: `` this._kiosk ? this._kioskScale.icon : 1 `` и то же для
`--rl-font`. Значение читается из `localStorage` (`LS_KIOSK`,
строки 2313–2318) независимо от режима, но **применяется к рендеру** icon/
font только когда `this._kiosk === true`.
- `docs/USER-GUIDE.ru.md:185–193` — таблица жестов документирует «Сброс
киоск-масштаба» и «Локальный размер киоска» строго в столбце «Сенсорный
экран» **киоска**, без единой строки для обычного View; это не пробел в
документации, это точное описание текущего продукта.
- `docs/CHANGELOG.md:2826–2836` (v1.41.0) — функция изначально задумана и
описана как kiosk-only: «every tablet/TV tunes itself once». Ограничение
не случайно, это осознанное решение выпуска 1.41.0, а не забытый кусок кода.
**Почему это блокирует, а не техническая деталь на усмотрение автора.**
Вопрос «увидит ли пользователь эффект от сдвига слайдера в новом диалоге,
открытом кнопкой в обычном View» — продуктовый и наблюдаемый, а не
внутренняя реализация. Как написано сейчас, реализация по ТЗ откроет в
обычном View **тот же диалог с теми же слайдерами**, но применение
`this._kiosk ? … : 1` в devlayer никто не просит поменять — §4 «Скоуп» не
называет это пунктом работы, §16 «Принятые технические предположения» не
называет это допущением, а §11 прямо утверждает обратное факту. Результат:
кнопка появится у домочадца и гостя на телефоне/планшете (как и требует
owner-решение «доступны всем touch-поверхностям, отдельного поведения для
киоска не делаем»), но движение слайдера «Размер значков устройств» ничего
не изменит на экране — рабочий контрол, который визуально не работает. Это
прямое попадание в `docs/TOUCH-SUPPORT.md`: «A touch-only failure in View is
a product defect, not an accepted limitation of the editor policy» — только
здесь дефект дня выпуска, а не более позднего открытия.
Это также ровно тот класс дефекта, который PROCESS.md называет «худшим
видом»: утверждение о поведении («используют один per-screen value, как и до
изменения»), которого не подтверждает ни один документ и которое не помечено
как предположение — оно читается как решённый факт и поэтому проходит ревью
на автомате, если его не сверить с кодом.
**Не эскалируется владельцу как новый вопрос.** Сам факт нужности такого
изменения не требует решения владельца: намерение уже зафиксировано его же
формулировкой «Настройки вида доступны всем touch-поверхностям… отдельного
поведения для киоска не делаем» (issue, «Решения владельца»,
подтверждено `#issuecomment-5298450448`). Контрол, открытый на всех
поверхностях, но работающий только на части из них, — это не то, что
владелец согласовывал; значит нужно не спрашивать, а **дописать ТЗ**: явно
включить в §4 «Скоуп» снятие/обобщение условия `this._kiosk ?` в применении
множителя к рендеру (или эквивалентное решение), завести под это отдельный
AC («изменение слайдера в обычном View видимо меняет размер иконок/текста
так же, как в kiosk») и соответствующий unit/smoke пункт в §13, и поправить
§11, чтобы он описывал целевое, а не мнимое текущее поведение.
**Серьёзность.** High — блокирует переход в `S5-ready`. Без исправления
задача либо тихо доставит нерабочий контрол в двух из трёх персон (что
`docs/SCOPE.md`/`TOUCH-SUPPORT.md` не допускают), либо реализация по ходу
дела сама «дорешит» продуктовый вопрос без записи, что запрещено §7.1
(«размытое место не додумывается, а выносится либо явно фиксируется»).
## Что проверено и корректно
- **Owner-решения Q1–Q3 перенесены буквально.** §2 ТЗ воспроизводит все три
default-а (`touch/coarse only`, 5-секундный таймер с перезапуском и
немедленным скрытием на pan/zoom/смену пространства, «вне плана» =
unbounded exterior с исключением detached porch/terrace и holes) без
искажений и без добавления новых решений от себя.
- **Геометрический контракт (§6) не путает две разные модели.** «Physical
footprint» (архитектура: floor+walls+partitions+columns, без decor/devices/
Glow/sun) корректно отличается от `contentFrame` из `CANVAS.md` (камера/
fit, включает decor и devices) — общий пул терминов не смешан, никакой
скрытой ре-дефиниции существующего понятия нет.
- **Производительность (§6.3, AC12).** Требование «топология считается
только при structural fingerprint change, не на pointermove/HA tick»
прямо аналогично уже работающей модели `WALL-THICKNESS.md` §2/§3 («one
cached structural pass in flat renderers; live HA state ticks do not
repeat the boolean topology») — это перенос уже принятого паттерна, а не
новая непроверенная идея.
- **Владение жестом (§7) корректно разграничивает hit-test фона и клики по
интерактивным целям**, явно перечисляет device/room/opening/vacuum/header/
dialog как приоритетные над новым «чистым тапом», и явно требует, чтобы
`preventDefault()` не расширялся за пределы нового чистого тапа — это
прямое соответствие safety floor `TOUCH-SUPPORT.md` (`pinch`/`pointercancel`/
второй палец не должны читаться как клик).
- **Права (§9).** Явно и неоднократно (§9, AC3, AC11) отделяет новый affordance
от административного `_settingsDialog`/«Общих настроек» и от `_canEdit` —
соответствует SCOPE.md (lock invariant в духе «намеренно, не по случайности»
распространён здесь на права: контрол для non-admin не получает доступа к
admin-поверхности).
- **Accessibility (§10).** Требование «focusable button, не SVG-only»,
локализованные aria-label, focus trap диалога, приостановка auto-hide при
keyboard focus — конкретно и проверяемо, ссылается на существующий dialog
contract, а не изобретает новый.
- **Совместимость (§11), помимо разобранного выше пункта.** Отсутствие
миграции, отсутствие новых серверных полей, отсутствие новых полей
`localStorage` подтверждено — задача действительно не задевает
`docs/CONFIG-COMPATIBILITY.md` (реестр — про server config/backend
validation, сюда не относится), и в «Связано» этот документ обоснованно не
включён.
- **Термин «Настройки вида» не изобретён автором ТЗ** — он происходит от
формулировки владельца в issue и вводится в канон через release-артефакты
§14 (обновление `docs/USER-GUIDE.ru.md`), что штатный путь.
- **Не-скоуп (§5) конкретен и вычёркивает реальные соседние риски**
(desktop mouse/hover, «Общие настройки», миграция диапазонов масштаба,
постоянная кнопка в header, показ по внутренним поверхностям) — не общая
фраза «остальное не трогаем».
- **Откат (§15)** описан предметно: удаление временной кнопки, возврат кода
без миграции, совместимость `localStorage`/server config — проверяем без
code review.
- **Трек и лимит цикла** выбраны верно: задача явно не `small`/`trivial` по
собственному разделу «Влияние» issue, лимит цикла — 4, документ лежит по
канону `docs/specs/149-touch-view-settings-affordance.md`.
## Low — не блокируют, зафиксированы с решением ревьюера
1. **Нет раздела с буквальным заголовком «Проблема».** Содержание есть
(в issue и в мотивации §1), но в самом файле ТЗ не выделено отдельным
заголовком, как того просит PROCESS.md §7.1. Снимается без правки: смысл
присутствует и не расходится с issue; при следующей правке (см. High-1)
стоит добавить подзаголовок «Проблема» из issue «Почему это стоит сделать»
заодно, но отдельного цикла ревью на это не требуется.
2. **AC8, AC10, AC12 не имеют явно названного способа доказательства в §13.**
§13 сгруппирован по категориям (Unit / Touch smoke / Golden), а не 1:1 по
номеру AC, и для AC8 («desktop mouse/hover не меняется, mouse click не
показывает кнопку»), AC10 (accessibility/focus) и AC12 (geometry cache)
не нашлось однозначно соответствующего пункта ни в одном из трёх списков.
Не блокирует понимание контракта, но при возврате на исправление High-1
стоит добавить: unit-пункт на `pointerType !== 'touch'/coarse'` → кнопка
не показывается; smoke/unit-пункт на focus trap и aria-label; unit/perf-
пункт, считающий вызовы построения footprint на серию тапов без изменения
геометрии. Снимается автором в той же правке, без нового issue — это
дополнение к тесту уже принятого AC, а не отдельная находка о продукте.
3. **Название кнопки и название диалога расходятся терминологически.**
Предложенный aria-label/title кнопки — «Настройки вида» / «View settings»
(§10), а существующий заголовок диалога в `ru.json`/`en.json`
(`kiosk.title`) — «Размеры на этом экране» / текущий английский эквивалент.
Тап по кнопке «Настройки вида» открывает диалог, который сам себя называет
иначе. Не мешает работе функции и не противоречит ни одному документу —
отмечаю, чтобы при реализации выбрали одно из двух: либо оставить как
мелкое, но заметное расхождение вида, либо привести название диалога и
aria-label к одному термину в release-артефактах (§14 это разрешает).
## Чего не проверял
- Реализацию — кода `src/**` под это issue ещё нет (ветка
`issue/149-touch-view-settings-affordance` на момент ревью содержит только
файл ТЗ, `git log --oneline` подтверждает единственный коммит `2d5f0a5`).
Проверка кода на этом этапе не требуется PROCESS.md §2.4.
- Golden-эталоны и живые performance-профили — не существуют до реализации;
§13 описывает план, не результат.
- Полный текст `docs/SUN.md`/`docs/LIGHT.md` — не читал построчно: задача не
трогает освещение/солнце (§5 явно перечисляет их эффекты как то, что не
расширяет physical footprint, но не меняет), релевантно только фактом, что
их эффекты не входят в hit-test, что уже подтверждено §6.1 текста ТЗ, и
этого достаточно для этого ревью.
- `docs/specs/README.md` — не проверял, зарегистрирована ли запись на этот
документ там; при пересмотре в r2 стоит свериться, но это не продуктовая
находка.
- Существующие браузерные smoke-файлы (`demo/smoke_kiosk*.mjs` и др.) прочитаны
только частично, ровно в объёме, нужном чтобы подтвердить/опровергнуть
утверждения ТЗ о текущем поведении (см. «Как проверялось» и High-1);
полный аудит покрытия smoke-suite не проводился — на этапе ТЗ без
реализации это не даёт дополнительной информации.
## Вывод
ТЗ методологически сильное: geometry-контракт корректно отделён от camera-
модели `CANVAS.md`, owner-решения перенесены буквально, safety floor и права
разобраны конкретно, откат и не-скоуп предметны. Но в основании ТЗ лежит
фактическая ошибка о текущем продукте (§11: «kiosk и normal View используют
один per-screen value, как и до изменения»), которую код прямо опровергает:
сегодня масштаб иконок/текста применяется к рендеру только в kiosk, а в
обычном View — никогда, вне зависимости от значения в `localStorage`. Раз
owner-решение требует единого affordance на всех touch-поверхностях, эта
задача не «делает видимым существующий скрытый вход», а частично **создаёт
новую функцию для обычного View** — и эта часть работы не названа ни в
скоупе, ни в AC, ни как принятое допущение. Это ровно тот guess-as-fact,
который PROCESS.md прямо называет худшим видом дефекта, потому что он
выглядит решением и проходит ревью, если его не сверить с исходником.
Возврат в `S3-spec`. Исправление: явно добавить в §4/§12/§13 работу по
применению множителя иконок/текста в обычном View (не только kiosk),
поправить факт в §11, и по возможности закрыть три Low-пункта той же правкой.
@@ -0,0 +1,350 @@
# Issue #149 — Явный вход в настройки вида на touch
- **Issue:** https://github.com/Matysh/houseplan-card/issues/149
- **Редакция:** первая редакция для независимого ревью; статус задачи определяется
только метками issue
- **Тип / приоритет:** feature + polish / P2
- **Оценка:** пользовательская ценность 8/10 на touch; ценность для разработки
6/10; сложность 5/10, риск 7/10
- **Область:** View и киоск на touch/coarse pointer, pointer routing, внешний
фон плана, временная шестерёнка, локальные настройки размера
- **Модель данных:** server config и backend не меняются; настройки остаются
локальными для экрана в существующем `localStorage` contract
- **Связано:** `docs/UX-MODES.md`, `docs/TOUCH-SUPPORT.md`, `docs/CANVAS.md`,
`docs/WALL-THICKNESS.md`, `docs/USER-GUIDE.ru.md`
## 1. Сценарий и продуктовый контекст
**Персоны:** администратор, домочадец и гость, использующие телефон, планшет
или настенный kiosk display.
**Поверхность и момент:** в View пользователь хочет изменить локальный масштаб
значков и текста на конкретном экране.
**До → после, без терминов реализации:** сейчас вход спрятан за трёхсекундным
удержанием пустого места и его невозможно обнаружить; после изменения обычное
касание снаружи дома на пять секунд показывает шестерёнку в углу, по которой
можно открыть те же настройки, а удержание больше ничего не открывает.
Задача поддерживает J1, J2 и J3 из `docs/SCOPE.md`: вид остаётся понятным и
безопасным для всех трёх персон, включая киоск.
## 2. Решения владельца
Владелец 15.08.2026 принял defaults Q1–Q3. Каноническая запись:
https://github.com/Matysh/houseplan-card/issues/149#issuecomment-5301106992
1. Новый affordance работает только для touch/coarse pointer; desktop
mouse/hover flow не меняется.
2. Background tap показывает кнопку на 5 секунд, повторный background tap
перезапускает таймер, pan/zoom или смена пространства сразу скрывают.
3. «Вне плана» — unbounded exterior вне union видимой физической геометрии
текущего пространства. Detached porch/terrace входят в план; внутренние
holes/courtyards не являются target для показа.
Ранее в body issue владелец также зафиксировал, что кнопка присутствует в
киоске и доступна всем touch-персонам.
## 3. Термины
- **Настройки вида** — существующий локальный диалог с масштабом значков и
текста (`icon`/`font`, сейчас реализован как kiosk/per-screen size popover).
- **Общие настройки** — административный диалог, меняющий внешний вид и
server config плана. Он не является целью этой задачи.
- **Физическая геометрия** — видимый floor/paper комнат вместе с телами
room walls и видимыми независимыми физическими объектами текущего space.
- **Unbounded exterior** — единственная область дополнения этой геометрии,
соединённая с бесконечностью; замкнутые внутренние holes не входят в неё.
- **Чистый tap** — один primary touch/coarse pointer, который не был
классифицирован как pan, swipe, pinch, long-press, double-tap или действие
интерактивного объекта.
## 4. Скоуп
В задачу входят:
1. удаление открытия настроек вида долгим тапом по stage/background;
2. hit test чистого tap по unbounded exterior текущего плана;
3. временная кнопка `mdi:cog-outline` в правом нижнем углу card viewport;
4. один и тот же маршрут View и kiosk для открытия существующего локального
диалога размеров;
5. таймер 5 секунд, reset/hide lifecycle и защита от stale timers;
6. collision-safe размещение, safe-area, touch target и reduced motion;
7. RU/EN, accessibility, unit, touch smoke, golden и документация.
## 5. Не входит в задачу
- изменение desktop mouse/hover flow или показ кнопки от mouse click;
- открытие «Общих настроек», выдача прав редактора или запись в server config;
- новые поля настроек вида, изменение диапазонов icon/font scale или ключа
`localStorage`;
- постоянная кнопка в header;
- показ по касанию комнаты, стены, внутреннего двора, устройства, проёма,
decor/backdrop или любого пустого места внутри внешнего контура;
- изменение device long-press/info card, room tap, opening tap, kiosk swipe,
double-tap reset zoom, pan или pinch;
- изменение геометрии комнат/стен ради hit test;
- миграция, schema version, backend или security permission change.
## 6. Геометрический контракт background target
### 6.1. Каноническая маска
Для текущего space строится `physical footprint` в тех же world coordinates и
из той же структурной render snapshot, что и видимая архитектура:
1. объединение floor/paper всех видимых комнат, включая detached rooms,
крыльцо и террасу, смоделированные комнатами;
2. тела room walls с их текущей толщиной;
3. видимые сохранённые `partitions`, `wall_columns` и другие независимые
физические тела, которые участвуют в архитектурном слое View;
4. без скрытых `show_borders: false` объектов, если они не имеют видимого
физического представления на этой поверхности.
Backdrop, decor, labels, devices, Glow, sun, room hover/fill effects, vacuum
trail и screen-space controls не расширяют physical footprint. Они могут
владеть самим tap согласно разделу 7, но не превращают окружающий фон в дом.
### 6.2. Внешняя область и holes
Target — точка, которая:
- не лежит в physical footprint с действующим geometry epsilon;
- принадлежит unbounded exterior component его дополнения.
Следствия:
- касание на полу или теле стены не показывает кнопку;
- doorway/window cut внутри общего envelope не становится внешним target;
- замкнутый courtyard, atrium или hole внутри здания не показывает кнопку;
- detached porch/terrace сами являются plan geometry, а фон между отдельными
компонентами и вокруг них принадлежит unbounded exterior и может показать
кнопку;
- пространство внутри concave фасада считается по топологии: открытая наружу
ниша относится к exterior, полностью замкнутая — к hole;
- точка на границе/в пределах hit epsilon считается планом, чтобы дрожание
координат не вызывало кнопку поверх стены.
Если geometry union не может быть построен, affordance fail-safe не
показывается. Нельзя заменять точный тест bbox плана: он ошибочен для L-форм,
detached частей и courtyard.
### 6.3. Производительность
Physical footprint и topology вычисляются только при structural fingerprint
change (space, rooms, walls, openings, physical objects, visibility), а не на
каждый pointermove и HA state tick. На tap выполняется bounded point/topology
query по cached result. Cache не является новым persisted extent.
## 7. Владение жестом
Кнопка рассматривается только после завершения чистого primary tap:
1. pointer type — `touch` либо текущая coarse/no-hover capability;
2. режим — View; kiosk является вариантом View;
3. начало и конец не принадлежат device, room action, opening, vacuum control,
header/control/button/dialog или другому интерактивному hit target;
4. движение не превысило действующий tap threshold и не получило final
classification `pan`/`swipe`;
5. не было второго pointer/pinch, pointercancel или long-press action;
6. итоговая world point проходит раздел 6.
Hit test выполняется по release point после окончательной gesture
classification. Он не перехватывает click у объектов и не мешает их обычному
tap/long-press. `preventDefault()` не применяется шире нового чистого tap.
Длительное удержание внешнего фона может завершиться как обычный чистый tap
после release только если общий gesture recognizer не классифицирует его
иначе; оно **никогда не открывает диалог непосредственно по таймеру**. Старый
трёхсекундный `_kioskHoldTimer`/эквивалент удаляется только для stage
background; device long-press остаётся.
## 8. Кнопка и lifecycle
### 8.1. Появление
- успешный background tap показывает одну кнопку с `mdi:cog-outline`;
- если она уже видима, повторный успешный tap не создаёт вторую кнопку, а
перезапускает ровно один 5000 ms timer;
- отсчёт начинается после принятого tap;
- tap по самой кнопке немедленно отменяет timer, скрывает affordance и открывает
диалог настроек вида;
- rapid taps и stale timeout используют generation/token guard: старый timeout
не может скрыть кнопку, показанную более новым tap.
### 8.2. Немедленное скрытие
Кнопка скрывается без ожидания остатка 5 секунд при:
- начале pan, pinch или kiosk swipe;
- wheel/programmatic zoom, zoom controls, double-tap reset и Fit;
- смене space;
- выходе из View, открытии editor, размонтировании card;
- открытии любого modal/dialog, включая настройки вида;
- смене card config или structural recovery, делающей context stale;
- переходе document в hidden, чтобы после возврата не оставался просроченный
control.
Обычный HA state tick сам по себе кнопку не скрывает и не продлевает.
### 8.3. Положение и collision
- кнопка позиционируется относительно видимой области card/stage, а не world
coordinates и не двигается вместе с pan/zoom;
- базовое положение — правый нижний угол с существующим spacing token и
`env(safe-area-inset-right/bottom)`;
- минимальный hit target — 44×44 CSS px;
- если место занято видимыми kiosk dots, home-arrow, zoom/другим control,
применяется детерминированный vertical stack выше него с тем же gap;
- кнопка не перекрывает системный safe area и полностью остаётся в card
viewport в portrait/landscape;
- z-index выше stage content, но ниже modal/dialog/scrim.
### 8.4. Motion
Появление и уход используют существующие short fade/scale motion tokens и
easing редактора; отдельная длительность в config не вводится. При
`prefers-reduced-motion: reduce` состояние меняется без transition, но таймер и
доступность сохраняются.
## 9. Диалог и права
Кнопка открывает существующий per-screen диалог:
- масштаб значков;
- масштаб текста;
- Reset и Close;
- сохранение в существующий локальный ключ этого browser/device.
Этот dialog доступен администратору, домочадцу, гостю и в `kiosk: true`, потому
что меняет только представление данного экрана. Он не зависит от `_canEdit`, не
показывает Plan/Devices/Background controls и не вызывает backend save.
Административный `_settingsDialog`/«Общие настройки» остаётся под действующей
проверкой прав и не открывается новым affordance. Термин и внутреннее имя
`kiosk` могут быть переименованы для ясности, но storage compatibility должна
сохраниться.
## 10. Accessibility и i18n
- кнопка — настоящий focusable `button`, не SVG-only hit area;
- локализованные RU/EN `aria-label` и `title`: «Настройки вида» /
«View settings»;
- иконка декоративна для screen reader;
- появление кнопки не крадёт focus и не объявляется assertively;
- после её активации dialog соблюдает существующий focus trap; после закрытия
focus возвращается на кнопку, если она ещё валидна, иначе на card/stage
container по действующему dialog contract;
- auto-hide не переносит focus неожиданно: если button получил keyboard focus,
timer приостанавливается до blur либо безопасно возвращает focus перед
удалением; поскольку сам trigger touch-only, это необходимо для switch
access, а не для desktop mouse exposure;
- timer не объявляется посекундно.
## 11. Модель данных и compatibility
- `ServerConfig`, space/room data и backend validation не меняются;
- существующий localStorage key и значения icon/font scale читаются и пишутся
без миграции;
- visibility, timer и geometry cache не сериализуются;
- предыдущая версия после rollback продолжает читать локальные масштабы;
- простой background tap без открытия/изменения dialog ничего не пишет;
- kiosk и normal View используют один per-screen value, как и до изменения.
## 12. Acceptance criteria
1. На touch/coarse View чистый tap в unbounded exterior показывает одну
шестерёнку в правом нижнем углу на 5 секунд.
2. Повторный валидный tap перезапускает полный 5-секундный срок без duplicate
control и без риска от stale timeout.
3. Tap по кнопке открывает локальные настройки icon/font scale всем персонам,
включая kiosk, и не открывает/не даёт доступ к «Общим настройкам».
4. После 5 секунд кнопка исчезает; pan, pinch, swipe, любой zoom, Fit, смена
space/mode, dialog и unmount скрывают немедленно.
5. Tap комнаты, стены, detached porch/terrace, внутреннего courtyard/hole,
устройства, проёма или control не показывает кнопку и сохраняет своё
обычное действие.
6. Concave exterior и фон между detached физическими компонентами корректно
показывают кнопку; bbox-эвристика не используется.
7. Старый трёхсекундный background long-press не открывает dialog. Device
long-press, double-tap reset, kiosk swipe и pan/pinch не регрессируют.
8. Desktop mouse/hover flow не меняется и mouse click кнопку не показывает.
9. Кнопка 44×44+, учитывает safe-area/collisions, не выходит за viewport и
использует reduced-motion.
10. RU/EN label, keyboard/switch focus и dialog focus trap проходят проверку.
11. Ни показ кнопки, ни изменение per-screen размеров не пишут server config;
старые localStorage values совместимы.
12. Geometry query использует structural cache и не выполняет boolean union на
pointermove или HA tick.
## 13. Проверки и доказательства
### Unit
- unbounded exterior classification для rectangle, L-shape, concave niche,
courtyard/hole и двух detached components;
- physical mask с разной толщиной стен, partition/column и wall visibility;
- gesture classifier: clean tap против pan/pinch/swipe/double tap/object hit;
- timer reset, generation guard и все immediate-hide causes;
- permission boundary: affordance вызывает только per-screen dialog.
### Touch browser smoke
- phone/Companion View: exterior tap → gear → dialog → icon/font change;
- wall-tablet kiosk: тот же flow, auto-hide, repeated tap;
- tap на floor/wall/courtyard/detached porch/device/opening;
- pan, pinch, zoom controls, double-tap reset, multi-space swipe и space change;
- device long-press по-прежнему открывает info card;
- portrait/landscape, safe-area и collision с другими controls;
- guest/read-only session не получает editor/general settings.
### Golden
- View и kiosk с видимой кнопкой в phone portrait и wall-tablet landscape;
- collision/stack state с существующим control;
- light/dark theme и reduced-motion final state.
Golden, smoke и performance запускаются в release gate перед бетой; цикл
реализации — `typecheck`, `unit`, `build` по процессу.
## 14. Release-артефакты
В том же user-visible коммите реализации обязательны:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- `docs/USER-GUIDE.ru.md` — обнаруживаемый маршрут к настройкам вида;
- `docs/UX-MODES.md` — View/kiosk больше не используют long-press;
- `docs/TOUCH-SUPPORT.md` — background target и gesture ownership;
- при необходимости `docs/CANVAS.md` — только ссылка на определение physical
exterior, без превращения его в stored canvas boundary;
- RU/EN locale files;
- touch smoke и принятые golden/baselines;
- `docs/TESTING.md` — замена long-press smoke новым flow.
Backend/security release artifacts не требуются: права и wire format не
меняются. Если реализация потребует server write, задача возвращается владельцу.
## 15. Риски и rollback
Риски: случайный показ после pan, неверный bbox hit test, stale timer, конфликт
с kiosk swipe и путаница с административными настройками. Их закрывают final
gesture classification, topology test, generation guard и явная permission
граница.
Rollback удаляет временную кнопку и восстанавливает предыдущий UI-код без
миграции. LocalStorage и server config остаются совместимыми; возвращать старый
long-press при rollback допустимо только вместе с откатом всего user-visible
изменения.
## 16. Принятые технические предположения
1. Канонический footprint переиспользует результат текущей wall/paper geometry
и `physicalBodies`, а не строит параллельную модель по DOM nodes.
2. Открытые door/window slots не становятся background targets, потому что
берётся unbounded component дополнения общего physical envelope.
3. Saved unfinished room draft не входит в View footprint, если View его не
рисует; правило «видимая физическая геометрия» важнее наличия записи в config.
4. Existing animation token определяет фактическую short transition duration;
продуктовый контракт фиксирует только 5000 ms visibility window.
5. При keyboard/switch focus auto-hide приостанавливается до blur: это
минимальная accessible гарантия без показа affordance от desktop mouse.
+1
View File
@@ -86,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) |
| [#149](https://github.com/Matysh/houseplan-card/issues/149) Явный вход в настройки вида на touch | [149-touch-view-settings-affordance.md](149-touch-view-settings-affordance.md) |
## Правило актуализации