diff --git a/docs/specs/451-render-performance.md b/docs/specs/451-render-performance.md new file mode 100644 index 00000000..d9c4dfdc --- /dev/null +++ b/docs/specs/451-render-performance.md @@ -0,0 +1,515 @@ +# ТЗ #451 — фильтрация render и лёгкий live-слой взаимодействий + +- **Issue:** https://github.com/Matysh/houseplan-card/issues/451 +- **Редакция:** первая редакция для независимого ревью; статус определяется только метками issue +- **Тип / приоритет:** bug / P1 +- **Оценка:** пользовательская ценность 10/10; ценность для разработки 10/10; + сложность и риск 9/10 +- **Область:** общий lifecycle `houseplan-card`, HA render snapshot и registry, + View, редакторы Плана/Устройств/Подложки, камера и continuous interactions, + diagnostics, performance harness +- **Модель данных:** без новых полей, миграции и backend-изменений +- **Связано:** #34, #82, #137, #156, #380, #396, #449, + `docs/PERFORMANCE.md`, `demo/performance/README.md` + +## 1. Сценарий + +**Персона:** администратор дома, который использует средний или большой план в +Home Assistant на desktop, планшете или настенной панели. + +**Поверхности и момент:** обычный View и все три редактора во время движения +мыши, pan/pinch/zoom, рисования, размещения, перетаскивания, вращения или +resize; также обычная работа карточки, когда HA присылает фоновые state ticks. + +На измеренном плане с 5 пространствами, 139 маркерами и 144 устройствами один +полный update занимает десятки миллисекунд. Hover запускает его почти на каждое +движение указателя, pan дал 76 полных updates за четыре протяжки, а нерелевантные +HA ticks продолжают делать ту же работу в покое. Пользователь видит рывки и +замирания вплоть до 451 мс, хотя неподвижная уже нарисованная сцена сама по себе +не требует постоянной работы. + +## 2. Что человек увидит до и после + +**До:** план отстаёт от указателя, hover и редакторские preview дёргаются, а +фоновые изменения посторонних HA entities могут вызвать заметный фриз. + +**После:** тот же план, те же эффекты и те же результаты действий двигаются +плавно; HA-изменения, относящиеся к плану, остаются актуальными, а посторонние +изменения не заставляют карточку заново строить всю сцену. + +Преднамеренных визуальных изменений нет. После завершения любого жеста +канонический итог и итоговый кадр совпадают с текущим поведением. + +## 3. Проблема и установленные причины + +### 3.1 Диагностика находится в горячем render-path + +`_renderBody()` безусловно вызывает `houseplanDiagnostics()`. Метод снова +обходит все сохранённые HA-привязки и вызывает `_bindingStatus()` для каждой, +хотя render использует результат лишь в трёх диагностических `data-*` +атрибутах корневого `ha-card`. На боевом плане это 99 resolutions и около +20–27 мс на каждый render во всех четырёх режимах. + +Проверенный на живой странице cache одного результата уменьшил стоимость +принудительного render примерно вдвое, а суммарные Long Tasks pan — на 70 %. +Однако cache «навсегда» неверен: значения должны меняться при изменении +registry, config или доступности привязки. + +### 3.2 Continuous state реактивно перестраивает всю карточку + +`_view`, `_zoom`, hover, draft и большая часть drag/preview state объявлены Lit +state. Их изменение на каждом pointer/animation step планирует полный проход +`HouseplanCard.render()`: комнаты, стены, проёмы, декор, Glow, солнце, +устройства и подписи строятся заново, хотя между соседними шагами меняется +только камера или небольшой интерактивный слой. + +Уже существующий fast path initial Plan snap hover решает один частный случай, +но не создаёт общего контракта для View и редакторов. + +### 3.3 Любой новый объект `hass` считается причиной render + +Карточка не отличает изменение используемой планом entity от изменения одной +из сотен посторонних entities. Вместе с render сейчас выполняется и +операционная обработка HA snapshot: registry authority, reconnect/load, +activity и vacuum histories, device rebuild и visual-continuity snapshot. +Просто вернуть `false` из `shouldUpdate()` недостаточно: это могло бы убрать +видимый render вместе с обязательной обработкой входящего состояния. + +### 3.4 Performance gate проверяет другие сценарии + +Абсолютные `hardMaxMs` уже есть. Пробел состоит не в отсутствии абсолютного +механизма, а в сценариях: `panZoomMs` измеряет один wheel transition, +`stateUpdateMs` меняет участвующую entity, а настоящий drag, View hover, +нерелевантный HA tick и количество тяжёлых render-проходов не проверяются. +Постоянно дорогой путь поэтому может быть одинаковым в base и candidate и +оставаться зелёным. + +## 4. Scope + +В issue входят: + +1. Разделение приёма каждого нового `hass` snapshot, операционной обработки и + решения о визуальной инвалидизации. +2. Явный dependency projection всех HA данных, которые использует план или + открытая UI-поверхность, и пропуск полного render для нерелевантного tick. +3. Cache статической части diagnostics с точной инвалидизацией; публичный + support-report и три `data-*` атрибута остаются актуальными. +4. Единый lightweight live-interaction путь для камеры, hover и непрерывных + preview во View и трёх редакторах. +5. Coalescing pointer/animation updates до не более одного лёгкого paint на + animation frame без повторной шаблонизации тяжёлой сцены. +6. Один полный reconciliation render после commit/cancel жеста; применение + последнего отложенного релевантного HA snapshot в том же итоговом кадре. +7. Сохранение desktop, touch/pen, flat/hidden-iso, kiosk, fixed-floor, + visibility/reconnect и lazy-runtime контрактов. +8. Новый performance-профиль или совместимое расширение harness с настоящими + pointer series, абсолютными потолками, Long Task и структурными assertions. +9. Unit, targeted smoke, canonical screenshots/golden и release artifacts. + +## 5. Non-scope + +Не входят: + +- изменение внешнего вида комнат, стен, проёмов, устройств, подсказок, Glow, + солнечных лучей, пылесосов, декора или hidden iso; +- изменение hit areas, snap tolerance, grid, gesture thresholds, animation + duration/easing, click/double-click/long-press или commit/cancel semantics; +- виртуализация SVG/DOM, spatial index, изменение числа отображаемых объектов; +- оптимизация конкретных geometry algorithms, room labels или decor renderer, + кроме прекращения их повторного вызова без причины; +- изменение persisted config/layout, websocket/backend API, schema, + import/export или миграция данных; +- ослабление существующих relative/absolute performance budgets; +- публичная настройка, feature flag или новое пользовательское сообщение. + +## 6. Контракт входящих HA snapshots + +### 6.1 Приём не равен render + +Каждое присваивание `card.hass = next` обязано обновить актуальную ссылку на HA +и пройти безопасный intake, даже если visual render не нужен. Intake продолжает: + +- отслеживать connection/reconnect и registry authority; +- запускать начальную/повторную загрузку, когда она требуется; +- поддерживать device roster, finite activity runtime и vacuum telemetry/trail; +- обновлять данные, которыми воспользуются event handlers и HA service calls; +- поддерживать visual-continuity lifecycle без публикации половины кадра. + +Пропуск visual render не имеет права пропустить подписку, history point, +terminal activity edge, vacuum sample, reconnect или capability change. + +### 6.2 Dependency projection + +Для решения о render существует один канонический набор зависимостей, а не +отдельные списки в фильтре и в renderer. В него входят как минимум: + +- entity/device/area bindings всех сохранённых маркеров и их `controls`; +- room temperature/humidity sources; +- opening contact/lock references; +- entities в live-text; +- `sun.sun`, light/Glow sources, vacuum source/map/telemetry references; +- entities и registry metadata, необходимые открытому dialog/picker/info card; +- active/disabled/removed registry resolution для bindings; +- top-level HA значения, влияющие на язык, локаль, units, theme, user/write + permissions, capabilities, connection и frontend formatting. + +Entity state считается изменившимся, если для dependency id изменилось +наличие или identity соответствующего HA state object. Это включает изменение +`state`, attributes, timestamps и unavailable/recovery без дорогого deep +comparison. Изменение нерелевантной entity не инвалидирует visual frame. + +Dependency set пересобирается после config/layout/marker/space/dialog/registry +изменения до следующего решения о фильтрации. Если классификатор не может +доказать нерелевантность snapshot, он fail-open: разрешает render. + +### 6.3 Обычный режим и активный жест + +- Вне continuous interaction релевантный HA snapshot публикуется без новой + намеренной задержки — в обычном ближайшем Lit update. +- Нерелевантный HA snapshot не вызывает `HouseplanCard.render()` и не меняет + тяжёлый DOM, но становится актуальным для последующих действий и intake. +- Во время активного pan/pinch/resize/drag/draft релевантный HA snapshot не + прерывает лёгкий live paint. Сохраняется только последний snapshot; сразу + после `pointerup` или `pointercancel` он входит в единственный итоговый full + render. Промежуточные snapshots не проигрываются кадр за кадром. +- `lostpointercapture`, уход со страницы, смена mode/space и structural config + change завершают либо отменяют interaction штатным общим terminal path и не + оставляют pending snapshot навсегда. Структурное изменение не откладывается, + если продолжение жеста с ним небезопасно. +- HA actions и safety checks всегда читают последний принятый `hass`, а не + отложенный visual snapshot. + +## 7. Контракт diagnostics cache + +1. Полный обход marker bindings не выполняется из `_renderBody()` и не + повторяется на camera/hover/editor pointer step. +2. Cache хранит статический результат registry diagnostics и binding counts. + Он инвалидируется при изменении marker/config binding lifecycle, registry + revision/authority, active/disabled/removed metadata или наличия state, + влияющего на binding status. +3. Обычное изменение значения уже доступной entity не инвалидирует binding + counts, если её классификация `active/ha_disabled/orphaned/unverified` не + могла измениться. +4. Три корневых атрибута сохраняют текущие имена и значения: + `data-ha-registry-access`, `data-ha-disabled-bindings`, + `data-ha-unverified-bindings`. +5. Публичный `houseplanDiagnostics()` сохраняет форму и redaction. Динамический + `lastSuccessAgeMs` вычисляется в момент вызова поверх cached core; для его + роста не запускается timer и не инвалидируется plan render. +6. После invalidation новый результат вычисляется не более одного раза до + следующей смены dependency, независимо от количества render requests. + +## 8. Контракт lightweight live-interaction слоя + +### 8.1 Граница тяжёлой сцены + +Тяжёлая сцена — room fills/outlines, physical walls, openings, saved decor, +Glow/sun, device faces, room labels, vacuum trails и hidden-iso geometry — не +перешаблонизируется из-за очередного pointermove или camera animation frame. +Её DOM identity сохраняется в пределах interaction, если нет отдельной +структурной причины для rebuild. + +Вызов `HouseplanCard.render()` считается full render независимо от того, дал ли +Lit затем минимальный DOM diff. Рендер отдельного lightweight child/layer full +render карточки не считается. + +### 8.2 Камера + +Pan, pinch, wheel/camera transition, zoom buttons, room focus и double-fit +обновляют все участвующие SVG `viewBox`, HTML overlay projection и зависящие от +камеры размеры согласованно в одном RAF-coalesced live paint. Flat и hidden iso +не расходятся; tooltip, device layer, room labels, locks, measure labels, +vacuum puck и editor chrome остаются совмещены с планом. + +Canonical `_view`/`_zoom` продолжают отражать реально показанный кадр, чтобы +retarget/cancel/persistence контракты #82/#396/#449 не получили stale start. +На terminal state выполняется один full reconciliation render и штатное +сохранение viewport там, где оно происходило раньше. + +### 8.3 Hover + +Принятый владельцем Q2 включает обычный hover: + +- room fill/physical outline и room tooltip сохраняют нынешние content, + visibility, pointer modality и настройку отключения tooltip; +- device tooltip/LQI/temperature/humidity и текущие CSS hover states остаются; +- координата tooltip может обновляться каждый RAF, не вызывая full render; +- leave, touch suppression, mode/space switch, visibility change и removal + очищают lightweight hover без ghost overlay; +- hover никогда не меняет config/layout и не делает websocket writes. + +### 8.4 Редакторские continuous interactions + +Один и тот же принцип применяется ко всем состояниям, которые меняются на +pointermove до commit: + +| Поверхность | Live-содержимое | +|---|---| +| План | wall chain/rubber-band, snap/conflict marker, column/opening preview и dimensions, wall/room resize preview и measurements, physical move/rotate | +| Устройства | marker drag/position preview, align guides и связанный tooltip | +| Подложка | line/shape draft, decor/furniture/image move/resize/rotate, placement preview, backdrop transform и measurements | + +Pointermove меняет только соответствующий lightweight layer или transform. +Pointerup сохраняет тот же canonical результат, history entry и write, что до +#451; cancel восстанавливает то же исходное состояние и не создаёт write. +Pointerdown может выполнить отдельный full render, если действительно меняет +selection/tool UI, но последующие move-события не повторяют его. + +Все movement sources coalesce: между двумя animation frames применяется +последняя позиция, но commit повторно использует последнюю canonical event +coordinate/state, поэтому coalescing не теряет конечную точку. + +## 9. UX, touch и accessibility + +- Внешний вид до/после settled state должен быть pixel-equivalent с текущим + `dev`; намеренных golden diffs нет. +- Mouse, touch и pen сохраняют pointer capture, pan threshold, pinch ownership, + click suppression и `pointercancel` поведение. +- Hover остаётся только на устройствах с fine hover capability; touch не + получает синтетический ghost hover. +- Keyboard/Escape/Ctrl+Z и focus order не меняются. +- Reduced motion сохраняет atomic camera result; forced colours, dark/light, + kiosk, fixed-floor и read-only не получают отдельной ветки поведения. +- Новых текстов, контролов, ARIA-элементов и i18n keys нет. + +## 10. Модель данных, совместимость и безопасность + +- Persisted config/layout и backend payload не меняются; migration/write-back + отсутствуют. +- Старые планы автоматически получают ускорение после обновления frontend. +- Оптимизация не изменяет HA service calls, permission checks, destructive + confirmations, registry redaction или support-report contents. +- Не вводятся worker, network request, dependency или глобальный shared cache + между экземплярами карточки. +- Несколько карточек на dashboard имеют независимые interaction queues, + dependency sets и diagnostics cache. +- Disconnect удаляет RAF/listener/observer и pending interaction state; warm + remount не переносит незавершённый gesture в новый экземпляр. + +## 11. Performance и наблюдаемость + +### 11.1 Новый профиль + +Добавляется отдельный `large-house-interaction-v1` на существующем +детерминированном large-house fixture, чтобы не менять смысл +`large-house-v1`. Один runner/harness обязан оставаться способен измерить base +до #451 и candidate; новые private поля сначала объявляются optional с честным +fallback либо счётчики собираются внешним instrumentation. + +После warm-up профиль выполняет: + +1. 120 View hover moves по room/device/miss; +2. четыре pan drag по 20 moves и terminal pointerup; +3. camera wheel/transition и pinch series; +4. не менее трёх representative editor series: Plan draw/snap, room resize и + Decor furniture/shape transform; targeted smoke покрывает остальную матрицу; +5. 30 нерелевантных HA ticks и один релевантный tick; +6. релевантный HA tick в середине drag с проверкой deferred-last-wins frame. + +### 11.2 Структурные assertions + +Для измерительного окна после начального settled frame: + +- 120 hover moves: **0** full renders; +- каждое pan/pinch/editor move-series: **0** full renders между start и terminal + event и не более **1** terminal full render; +- 30 нерелевантных HA ticks: **0** full renders; +- один релевантный HA tick вне gesture: ровно **1** full render; +- несколько релевантных ticks внутри gesture: **0** промежуточных и ровно + **1** terminal full render с последним состоянием; +- camera/hover/editor windows: **0** полных marker-binding diagnostic scans; +- static heavy-scene node identity/count, config, layout и websocket writes + остаются стабильны; preview commit/cancel checks выполняются отдельно. + +Assertions fail the runner независимо от timing result. RAF paints и renders +выделенного lightweight child считаются отдельными метриками и не маскируются. + +### 11.3 Абсолютные цели + +Для профиля при тех же CI browser/runtime условиях задаются base-relative +пределы и следующие bootstrap `hardMaxMs`, полученные как безопасный порядок +ниже наблюдавшихся 3 432–5 538 мс полного-render времени и согласованные с +существующим `panZoomMs: 500 ms`: + +| Метрика | Абсолютный потолок | +|---|---:| +| 120 View hover moves | 500 мс | +| 4 × 20 pan moves + terminal | 500 мс | +| wheel/pinch camera series | 500 мс | +| 120 representative editor moves + terminal | 750 мс | +| 30 нерелевантных HA ticks | 250 мс | + +Для каждого pointer-series дополнительно: `maxSingleLongTaskMs <= 150`, +`longTaskTotalMs <= 300`, `longTaskCount <= 3`. Эти потолки — bootstrap safety +limits, а не разрешение расходовать весь budget; relative comparison остаётся. +Ослабление существующих budgets запрещено. Первый paired Linux artifact должен +быть описан в review/release evidence; ужесточение после него допустимо отдельной +обоснованной правкой, ослабление — только через новый review. + +## 12. Acceptance criteria + +- **AC1 (`unit` + code review; разработчик/ревьюер):** HA intake отделён от + visual invalidation; нерелевантный snapshot обновляет актуальный `hass` и + lifecycle/history, но не вызывает `HouseplanCard.render()`. +- **AC2 (`unit` + targeted smoke; разработчик):** изменение каждой категории + dependency из §6.2 вызывает актуальный render, а изменение посторонней entity + — нет; unknown classifier path fail-open и не оставляет stale UI. +- **AC3 (`unit` + targeted smoke; разработчик):** activity/vacuum samples, + reconnect/load, registry subscription, permissions и action safety работают + при серии skipped visual ticks; action использует последний `hass`. +- **AC4 (`unit` + smoke; разработчик):** diagnostics binding scan отсутствует + в render/pointer path, cache инвалидируется по §7, три `data-*` атрибута и + redacted public report актуальны, `lastSuccessAgeMs` растёт без repaint timer. +- **AC5 (`smoke` + performance; разработчик):** View hover, pan, pinch, + wheel/camera animation и double-fit соблюдают render-count assertions §11.2; + все SVG/HTML/iso layers остаются совмещены на каждом captured frame. +- **AC6 (`smoke` + performance; разработчик):** Plan draw/snap/opening/column, + physical move/rotate, room resize, device drag, decor draft/move/resize/rotate, + furniture/image placement и backdrop transform не вызывают full render на + каждый move; terminal commit/cancel даёт один согласованный full frame. +- **AC7 (`unit` + smoke; разработчик):** серия relevant HA updates во время + gesture применяет только последний snapshot сразу после всех terminal paths; + irrelevant updates не создают pending render. +- **AC8 (`unit` + smoke; разработчик):** coalescing сохраняет конечную pointer + coordinate, snap/geometry result, history, save count, pointer capture, + cancel/undo и no-extra-write контракты для mouse/touch/pen. +- **AC9 (`golden` + canonical screenshots; разработчик/владелец):** settled + View, hover/tooltip, все три редактора, dark/light, forced colours, kiosk, + fixed-floor и hidden iso не имеют непредусмотренных pixel diffs; screenshots + приложены к handoff/review. +- **AC10 (`performance`; разработчик):** новый interaction profile проходит + все structural, absolute и base-relative checks §11; прежние ordinary, + isometric, plan-snap и Glow profiles проходят без ослабления budgets. +- **AC11 (`typecheck` + `unit` + `build`; разработчик):** implementation-loop + gates зелёные; перед beta зелёные golden, smoke и performance по runbook; + Linux CI остаётся каноном полного HA harness. +- **AC12 (`schema/backend/i18n review`; ревьюер):** persisted model, backend, + import/export, HA API, i18n strings и dependencies не изменены; новые caches + bounded per card и очищаются при disconnect. +- **AC13 (`documentation review`; разработчик/ревьюер):** + `docs/PERFORMANCE.md` и `demo/performance/README.md` описывают новый профиль, + counters, budgets и локальный запуск; оба changelog содержат пользовательский + эффект со ссылкой на #451. + +## 13. План автотестов + +### 13.1 Unit + +- pure dependency classifier: relevant/irrelevant/missing state, attributes, + all source kinds, top-level locale/theme/units/user/connection and fail-open; +- dependency-set rebuild после marker/config/dialog/registry changes; +- HA intake с skipped render: activity terminal edge, vacuum sample, reconnect, + latest action snapshot и no duplicate processing; +- gesture gate state machine: begin/update coalescing/end/cancel/forced cancel, + latest-relevant-wins и irrelevant-no-pending; +- diagnostics cache keys, one scan per invalidation, state-presence transition, + dynamic age и redaction; +- camera live projection: clamp, anchor, retarget, persistence and cleanup. + +### 13.2 Targeted production-bundle smoke + +Инструментировать full render count, lightweight paints, diagnostics scans, +heavy node identity, writes и visible state. На production bundle пройти: + +- room/device/miss hover and leave; +- mouse pan, wheel, pointercancel; touch pan/pinch and lost capture; +- #449 double-fit и interrupted/retargeted camera transition; +- матрицу interactions из AC6 с несколькими move events, commit и cancel; +- relevant/irrelevant HA ticks вне и внутри gesture; +- space/mode switch, visibility hide/show, disconnect/reconnect и warm remount; +- flat/iso, normal/fixed-floor/kiosk/read-only. + +Synthetic events обязаны доходить до production handlers; `0` событий у +handler не принимается как доказательство `0` renders. + +### 13.3 Golden, performance и release + +- implementation loop: `npm run typecheck`, `npm test`, `npm run build`; +- после изменения `src/**` — canonical screenshots по `docs/SCREENSHOTS.md`, + inspection и существующие golden; любой diff исследуется; +- перед beta — полный smoke/golden/performance gate по runbook; +- full performance сравнивает candidate/base на одном Linux runner и публикует + отдельный interaction artifact со structural diagnostics; +- три bundle snapshots после release build должны совпадать. + +## 14. План реализации и затрагиваемые файлы + +Ожидаемые точки изменения: + +1. `src/houseplan-card.ts` — HA intake/visual invalidation, diagnostics cache, + gesture terminal reconciliation и подключение live layers. +2. Новый либо существующий узкий `src/*render*`/`src/*interaction*` module — + pure dependency classifier, bounded state machine и RAF scheduler; точные + имена не являются контрактом. +3. `src/houseplan-editor-runtime.ts` — маршрутизация continuous editor previews + в lightweight invalidation без изменения canonical commit logic. +4. Unit tests для classifier/cache/controller и regression tests существующих + camera/editor controllers. +5. Targeted `demo/smoke_*.mjs` — production-bundle render-count и interaction + matrix; обновление smoke runner/package scripts при необходимости. +6. `demo/benchmark_large_house.mjs`, `demo/performance/card-contract.mjs`, новый + budget/profile и evaluator/report plumbing — `large-house-interaction-v1`. +7. `docs/PERFORMANCE.md`, `demo/performance/README.md`, оба changelog. + +Реализация сначала выделяет pure contracts и diagnostics cache, затем HA +filter, затем camera/hover live path, затем редакторские consumers. Нельзя +массово снять `state: true`, пока у каждого изменяемого состояния нет +проверенного live paint и terminal reconciliation path. + +## 15. Release-артефакты + +Изменение пользовательски заметно как исправление зависаний, поэтому основной +implementation commit имеет `User-Visible: yes` и в том же коммите обновляет: + +- `docs/CHANGELOG.md`; +- `docs/CHANGELOG.ru.md`. + +Также обновляются: + +- `docs/PERFORMANCE.md`; +- `demo/performance/README.md`; +- budget/profile и machine-readable performance report; +- canonical screenshots/golden evidence без ожидаемого визуального изменения; +- targeted smoke artifacts и full performance comparison. + +Новые user-guide/i18n/security artifacts не требуются. Выпуск — обычной beta +по разделу 8 release runbook; issue не закрывается автором реализации. + +## 16. Риски и снижение + +| Риск | Вероятность / ущерб | Снижение | +|---|---|---| +| Фильтр ошибочно считает relevant HA tick посторонним | средняя / высокий | единый dependency authority, fail-open, матрица всех consumers и dialog states | +| Пропуск render одновременно пропускает activity/vacuum/reconnect intake | высокая / высокий | отдельный intake contract и тесты серий skipped ticks | +| Lightweight DOM расходится с canonical state | средняя / высокий | один state machine, last-event commit и обязательный terminal reconciliation | +| Несколько SVG/HTML/iso слоёв получают разные camera frames | средняя / высокий | один atomic projection writer и frame screenshots | +| Gesture оставляет pending HA/RAF после cancel/disconnect | средняя / высокий | единый terminal cleanup и lifecycle matrix | +| Child render формально скрывает прежнюю тяжёлую работу | средняя / средний | отдельные full/light counters, heavy-node identity и timing gate | +| Новый harness ломает сравнение со старым base | средняя / высокий | новый profile id, optional contract/fallback и тест candidate/base | +| Слишком мягкий timing budget снова пропускает проблему | средняя / высокий | structural assertions обязательны; absolute + relative gates, paired artifact review | + +## 17. Откат + +Откат — revert implementation commit(ов) #451 и нового interaction profile. +Данные и backend не требуют rollback/migration. Если дефект найден только в +одном live interaction, допускается временно вернуть этому interaction прежний +reactive path отдельным аварийным commit, не отключая безопасный diagnostics +cache и HA filter, но такой fallback обязан иметь issue и regression proof. + +## 18. Принято предположительно, поменять свободно + +Ниже технические решения, не являющиеся продуктовым выбором владельца: + +- dependency projection может переиспользовать `RenderDeviceSnapshot`, но имеет + один источник истины и включает зависимости всего сохранённого плана; +- lightweight слой может быть Lit child component, imperative DOM projection + или их комбинацией, если full/light counters и cleanup contracts соблюдены; +- public diagnostics складывается из cached static core и динамического age; +- точные private field/helper names и разбиение unit/smoke файлов свободны; +- representative editor cases живут в performance profile, полная матрица — в + deterministic smoke, чтобы timing fixture не превращался в функциональный + e2e-комбайн. + +Продуктовые решения владельца не предположительны: Q1 — relevant HA updates во +время жеста применяются последним итоговым кадром; Q2 — обычный hover входит в +#451 и сохраняет текущие эффекты через lightweight слой. diff --git a/docs/specs/README.md b/docs/specs/README.md index 8c9de736..7a8b54a3 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -177,6 +177,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#447](https://github.com/Matysh/houseplan-card/issues/447) Наружная грань для мебели и сдвиг декора стрелками | [447-exterior-furniture-snap-keyboard-nudge.md](447-exterior-furniture-snap-keyboard-nudge.md) | | [#448](https://github.com/Matysh/houseplan-card/issues/448) Единый бессрочный переключатель `hp_alpha` | [448-alpha-switch.md](448-alpha-switch.md) | | [#449](https://github.com/Matysh/houseplan-card/issues/449) Двойной клик/тап по свободному фону вписывает весь план | [449-double-fit-all.md](449-double-fit-all.md) | +| [#451](https://github.com/Matysh/houseplan-card/issues/451) Фильтрация render и лёгкий live-слой взаимодействий | [451-render-performance.md](451-render-performance.md) | ## P3