From ca2a00231beeebbbfa45547b6223e82dad03b889 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sat, 5 Sep 2026 11:52:37 +0300 Subject: [PATCH] docs: specify live editor settlement Issue: #460 User-Visible: no --- docs/specs/460-live-editor-settlement.md | 155 +++++++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 156 insertions(+) create mode 100644 docs/specs/460-live-editor-settlement.md diff --git a/docs/specs/460-live-editor-settlement.md b/docs/specs/460-live-editor-settlement.md new file mode 100644 index 00000000..0ab24688 --- /dev/null +++ b/docs/specs/460-live-editor-settlement.md @@ -0,0 +1,155 @@ +# #460 — Детерминированное завершение кадра живого редактора + +Issue: [#460](https://github.com/Matysh/houseplan-card/issues/460) + +## Сценарий + +Администратор дома размещает мебель мышью в редакторе подложки. Браузерные +проверки воспроизводят тот же путь: входят в редактор, двигают указатель, +сравнивают preview с сохранённой фигурой и убирают указатель с плана. + +## Что человек увидит до и после + +До исправления preview мебели после ухода указателя изредка остаётся на плане; +после исправления он всегда исчезает, а внешний вид и результат обычного +размещения мебели не меняются. + +## Проблема + +После #451 pointer-owned состояние редактора может отрисовываться отдельным RAF +вне цикла Lit. `updateComplete` больше не доказывает, что этот кадр уже применён, +а локальная эвристика из двух RAF в browser smoke иногда снимает старую +физическую толщину preview. На чистом `dev` мебельный smoke упал 7 раз из 12. + +Диагностика выявила ещё две части одного сценария: + +- программный smoke мог начать жесты после завершения mode transition, но до + завершения вызванного им refit viewport, поэтому сравнивал разные камеры; +- terminal `null` для hover-состояния шёл через облегчённый live-маршрут. Если + полный Lit-кадр до этого успел нарисовать preview в основном слое, очистка + live-слоя оставляла этот settled preview видимым. + +## Скоуп + +- Внутренняя awaitable-точка завершения последнего уже запрошенного кадра + `live-editor`. +- Корректное завершение ожиданий при coalescing, полном Lit commit и dispose. +- Полный Lit commit для terminal-null hover-состояния. +- Переход трёх затронутых browser smoke с фиксированных RAF на внутренний + контракт; мебельный smoke начинает жесты только после наблюдаемого завершения + mode transition и refit viewport. +- Детерминированные unit-, browser- и mutation-доказательства. + +## Не-скоуп + +- Изменение геометрии, магнита, размеров, толщины или opacity мебели. +- Новый публичный API карточки либо Home Assistant. +- Переписывание `live-viewport`, mode transition или общей архитектуры #451. +- Новое поведение touch/pen, View или kiosk. +- Изменение формата конфигурации, сохранённых данных или истории Undo/Redo. + +## Контракт поведения + +1. Вызов внутренней точки синхронизации сначала дожидается уже ожидавшегося + полного Lit-кадра, затем фиксирует последнюю запрошенную ревизию live-editor. +2. Promise этой ревизии не завершается раньше фактической покраски DOM. + Несколько изменений до одного RAF объединяются, но ожидание покрывает всю + объединённую ревизию. +3. Если полный Lit commit заменил промежуточный live-кадр, этот commit считается + корректным завершением. Dispose отменяет RAF и завершает все ожидания. +4. Присвоение `null` свойствам hover-preview не маршрутизируется только в + live-слой: полный Lit commit обязан убрать и live-, и settled-копию. +5. Browser smoke не определяет готовность через `setTimeout`, sleep либо + фиксированное число RAF. Отдельная подготовка мебельного сценария ждёт + фактическое состояние mode/refit; RAF используется только как цикл доставки + браузерного состояния с проверяемым условием выхода. + +## UX + +Новых контролов и текстов нет. Предпросмотр остаётся тем же полупрозрачным +символом и очищается по уже документированным границам из `docs/FURNITURE.md`: +pointer leave, Escape, смена палитры, инструмента, редактора, пространства и +remount. + +## Модель данных и миграция + +Модель данных не меняется. Ревизии и ожидающие callbacks существуют только в +памяти экземпляра live-editor и уничтожаются вместе с ним. Миграция не нужна. + +## i18n + +Новых строк интерфейса нет. Словари не меняются. + +## Критерии приёмки + +### AC1 — явная синхронизация + +Управляемый unit-тест доказывает, что Promise не завершается до live paint, +покрывает coalesced-изменения и не зависает после полного commit или dispose. + +### AC2 — очистка terminal hover + +Unit-тест доказывает, что terminal-null hover требует полного Lit commit, а +`demo/smoke_furniture.mjs` после `pointerleave` не находит preview ни в live-, +ни в settled-слое. + +### AC3 — browser-путь без временной лотереи + +`smoke_furniture.mjs`, `smoke_decor.mjs` и `smoke_decor_text.mjs` используют +внутренний awaitable-контракт вместо локального двойного RAF. Мебельный smoke +проходит не менее 10 последовательных запусков без падений; mode/refit завершён +до первого программного жеста. + +### AC4 — mutation-защита + +Мутант, который разрешает Promise до live paint, и мутант, возвращающий +мебельному smoke двойной RAF вместо контракта, детерминированно ловятся своими +гейтами. + +## План автотестов + +- `test/live-editor.test.mjs`: fake RAF, coalescing, terminal-null, commit и + dispose; статический контракт трёх smoke. +- `demo/smoke_furniture.mjs`: preview/commit, pointer leave и минимум 10 + последовательных полных запусков. +- `demo/smoke_decor.mjs`, `demo/smoke_decor_text.mjs`: по одному полному + целевому запуску после замены ожидания. +- `scripts/mutation-gate.mjs`: два адресных мутанта из AC4. +- Обычные `typecheck`, unit, build, bundle sync/budget, `no-new-any` и + `check-docs` перед код-ревью. + +## Риски + +- Потерянная ревизия могла бы завершить Promise слишком рано либо оставить его + навсегда ожидающим; fake-RAF тест отдельно проверяет обе стороны. +- Полный commit и dispose могут отменить RAF, поэтому они обязаны завершать ту + же ревизию, а не просто очищать callback браузера. +- Подготовка smoke не должна превращаться в ещё одну временную эвристику: + условие выхода связано с mode/refit state, а не с числом кадров. +- Terminal-null переводится в более дорогой полный render только на границе + hover-сессии, не на каждом pointermove; горячий путь #451 сохраняется. + +## Откат + +Один revert возвращает прежнюю маршрутизацию и smoke-ожидания. Конфигурация и +пользовательские данные не требуют обратного преобразования. + +## Release-артефакты + +- Одновременные записи в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`. +- `docs/FURNITURE.md` уже содержит правильный пользовательский контракт очистки + preview и не требует изменения. +- Стабильный screenshot/golden не меняется: исправляется transient-состояние, + которого после завершения жеста не должно существовать. +- Любое изменение `src/**` требует актуального Linux-артефакта `Docs + screenshots` и приёмки его fingerprint по общему процессу. + +## Принятые технические предположения — можно менять на ревью + +- Контракт остаётся методом lazy editor runtime и не становится API карточки. +- Монотонная ревизия и список ожидающих Promise принадлежат `live-editor`, где + создаётся и завершается RAF. +- Синхронизация mode/refit остаётся частью setup мебельного smoke; отдельный + публичный settlement API для viewport в этой задаче не вводится. +- Изменение считается пользовательски видимым из-за исправления stale preview, + поэтому коммит получает `User-Visible: yes`. diff --git a/docs/specs/README.md b/docs/specs/README.md index e42f4882..a3a8f19a 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -180,6 +180,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#451](https://github.com/Matysh/houseplan-card/issues/451) Фильтрация render и лёгкий live-слой взаимодействий | [451-render-performance.md](451-render-performance.md) | | [#456](https://github.com/Matysh/houseplan-card/issues/456) Копирование пространства без комнат и устройств | [456-copy-space.md](456-copy-space.md) | | [#457](https://github.com/Matysh/houseplan-card/issues/457) Направление Zigbee-связей к координатору | [457-zigbee-route-arrows.md](457-zigbee-route-arrows.md) | +| [#460](https://github.com/Matysh/houseplan-card/issues/460) Детерминированное завершение кадра живого редактора | [460-live-editor-settlement.md](460-live-editor-settlement.md) | ## P3