Files
houseplan-card/docs/specs/460-live-editor-settlement.md
T
2026-09-05 12:10:26 +03:00

11 KiB
Raw Blame History

#460 — Детерминированное завершение кадра живого редактора

Issue: #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.

Затронутые файлы и модули

  • src/live-editor.ts — ревизии, ожидания и terminal-null routing.
  • src/houseplan-editor-runtime.ts — внутренний awaitable-метод для browser harness.
  • test/live-editor.test.mjs, scripts/mutation-gate.mjs и scripts/smoke-links.mjs — unit-, mutation- и smoke-selection доказательства.
  • demo/smoke_furniture.mjs, demo/smoke_decor.mjs, demo/smoke_decor_text.mjs — потребители контракта.
  • docs/DEVELOPMENT.md, оба changelog и сгенерированные bundle-деревья — обязательные сопутствующие артефакты.

Контракт поведения

  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 и не требует изменения.
  • docs/DEVELOPMENT.md фиксирует, что updateComplete не является барьером live-editor и browser harness обязан использовать явный settlement contract.
  • Стабильный 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.