Files
houseplan-card/docs/specs/124-isometric-view-toggle-performance.md
T
2026-08-15 03:50:34 +03:00

13 KiB
Raw Blame History

Issue #124 — isometric view toggle выполняет Stage 1 performance budget

  • Issue: https://github.com/Matysh/houseplan-card/issues/124
  • Статус документа: готово к будущей реализации; issue остаётся на S3-spec
  • Приоритет: P2
  • Тип: bug/performance, обычный трек
  • Пользовательское изменение: нет; скрытый Labs UI и pixels сохраняются

1. Симптом и доказательство

В exact-SHA Full Performance для v1.63.0-beta.1 (3270e039…) профиль large-house-isometric-v1 измерил:

  • viewToggleMs.median: 192.8 ms;
  • pre-#89 baseline: 31.7 ms;
  • limit: 131.7 ms (base + 20% + 100 ms);
  • все семь candidate samples: 189.2–213.4 ms.

Одно переиспользование wall union в экспериментальном 2576d9d дало около 10 ms и не закрыло долг. Точная доминирующая причина не доказана. Полная Lit/DOM смена структурных шаблонов — гипотеза, а не готовое решение.

#156 не является дублем: его regression run имел зелёный viewToggleMs на другой сравнительной линии и прямо исключил #124.

2. Цели

  1. Профилированием установить стоимость Flat → Iso и Iso → Flat.
  2. Уложить canonical profile в неизменённый budget.
  3. Сохранить Flat/Iso geometry, live layers, actions, fallback и viewport.
  4. Не увеличить cache/heap и не перенести стоимость в соседнюю метрику.
  5. Получить воспроизводимое exact-SHA доказательство закрытия исходного долга.

3. Не входит в задачу

  • ослабление budget/noise/minimum samples;
  • публичное включение изометрии;
  • новая Stage 3 геометрия/материалы/камера;
  • изменение Flat pixels или editors/static card;
  • скрытие live layers на время toggle;
  • async loading/network request;
  • замена renderer architecture без измеренной причины;
  • переоткрытие #89 или поглощение #156.

4. Канонический benchmark

demo/benchmark_large_house.mjs --profile=large-house-isometric-v1 остаётся authority. viewToggleMs включает полный user-observable цикл:

  1. _setProjection('flat');
  2. updateComplete;
  3. _setProjection('iso');
  4. updateComplete.

Нельзя завершить metric до painted DOM либо заменить его pure geometry timing. Minimum — 7 samples на Linux CI с обычной environment/provenance проверкой.

Для закрытия нужны два сравнения:

  • debt closure: candidate против pre-#89 SHA 316ee76a2913066f8ccd86d27fdf7b17dabaff48, который использовался как историческая граница до merge #89;
  • current regression: тот же exact candidate против актуального main/stable обычным workflow, чтобы оптимизация не ухудшила v1.64 соседние paths.

Если automation не умеет сохранить pinned comparison artifact, допускается manual workflow input с exact SHA и ссылка на run в review. Один зелёный push candidate-vs-parent, где product code одинаков, не закрывает #124.

5. Обязательное профилирование до изменения продукта

До выбора реализации author публикует в issue/review:

  • отдельные Flat commit и Iso commit timings;
  • main-thread flame/trace либо эквивалентные измеренные call stacks;
  • Lit performUpdate/template commit долю;
  • geometry/cache hit/miss и _isoScene() build count;
  • DOM node add/remove count по классам слоёв;
  • layout/style/paint долю;
  • минимум три главных cost centres с абсолютным временем.

Профилирование выполняется на production bundle и той же fixture. Dev build, один sample или ручное ощущение не являются доказательством.

Diagnostic instrumentation:

  • живёт в benchmark/harness либо за test-only hook;
  • не создаёт production console noise;
  • не меняет timing path в финальном closure run;
  • удаляется либо остаётся как дешёвый выключенный counter с documented owner.

6. Допустимые направления оптимизации

Конкретное направление выбирается только по §5. Допустимы:

  • стабилизация shared DOM subtree и переключение минимального structural layer;
  • reuse уже вычисленной scene/union/index geometry;
  • разделение structural и live templates, чтобы HA layers не пересоздавались;
  • memoization чистых template inputs с bounded identity;
  • уменьшение синхронного layout/measurement после projection commit;
  • использование части 2576d9d, если trace подтверждает её вклад.

Запрещено:

  • держать два полноценных интерактивных дерева, если это дублирует tab order, actions, ids или нарушает heap budget;
  • CSS screenshot/bitmap вместо живого плана;
  • пропуск updateComplete/paint в benchmark;
  • debounce, который делает UI быстрым только до позднего тяжёлого commit;
  • global cache без лимита;
  • UA-specific fast path.

7. Render и DOM invariants

  • Labs off/expired: iso DOM, geometry work, storage/network path отсутствуют;
  • Flat остаётся reference и не получает Stage 2 DOM/classes/filters;
  • editors и houseplan-space-card всегда Flat;
  • Iso использует одну canonical scene/projection snapshot;
  • room fills/hover, Glow/spill, sun, decor/backdrop, vacuum, devices, labels и opening live panels сохраняются;
  • DOM/tab order и actions совпадают по semantic targets;
  • no duplicate ids, focus targets или pointer consumers;
  • toggle сохраняет logical center и zoom по #89;
  • failure latch и explicit retry остаются работоспособными.

Если shared subtree остаётся смонтированным между projections, его inertness, ARIA и pointer policy должны быть доказаны, а не основаны только на opacity.

8. Cache и lifecycle

  • _isoGeometryCache cap остаётся 8;
  • cacheGrowth.isoGeometry после profile — 0;
  • repeated Flat ↔ Iso не добавляет новые entries при том же fingerprint;
  • HA-only state update не rebuild-ит structural geometry;
  • theme/locale/live state не меняют geometry key;
  • config geometry change инвалидирует правильную scene;
  • Labs expiry/removal немедленно делает effective Flat и не удерживает active iso DOM вне bounded cache;
  • warm remount не усыновляет viewport другой projection.

9. Соседние performance budgets

Оптимизация не считается успешной, если viewToggleMs зелёный, но ухудшены:

  • model ready / first stable render;
  • space switch;
  • HA state update;
  • pan/zoom и resize preview;
  • switch cycle;
  • long-task count/max/total;
  • heap growth;
  • Flat large-house-v1.

Все лимиты читаются из текущих versioned budget files; hardcoded threshold в production/test не добавляется.

10. Touch, accessibility и actions

  • projection toggle остаётся hidden Labs control;
  • touch/kiosk gestures имеют прежний safety contract;
  • активный dialog/tooltip/focus не дублируется между trees;
  • tap/long press на room/device/opening вызывает тот же outcome в Flat/Iso;
  • locks/security и service target не меняются;
  • keyboard tab order не получает скрытых duplicate nodes;
  • reduced motion не относится к мгновенному projection toggle и не меняет benchmark semantics.

11. Acceptance criteria

  1. Profiling evidence на production fixture публикует измеренную root cause.
  2. Pinned pre-#89 exact comparison зелёный по неизменённому viewToggleMs budget с ≥7 samples.
  3. Current-main exact comparison также зелёный.
  4. Все остальные timing/long-task/heap/cache checks профиля зелёные.
  5. Flat profile остаётся в budget.
  6. isoGeometry cap/growth равны 8/0, repeated toggle — cache hit.
  7. Flat/Iso golden и live/touch/action smokes зелёные без несвязанной переакцептации.
  8. Labs off не создаёт iso work/DOM.
  9. HA state update не rebuild-ит geometry и не переносит cost из toggle.
  10. Viewport/fallback/warm-remount contract #89/#122 не регрессирует.

12. План тестирования

Unit/source contract

  • cache key/cap/hit/invalidation;
  • effective projection/fallback;
  • shared DOM ownership и отсутствие duplicate consumers;
  • Labs-off source contract;
  • logical center conversion unchanged.

Browser smoke

  • repeated Flat ↔ Iso с layer/action parity;
  • room/device/opening/vacuum interactions;
  • Glow/sun/room fills remain mounted and state-stable;
  • theme/state update during/after toggle;
  • editor round-trip always Flat;
  • kiosk/touch and warm remount;
  • injected geometry exception and explicit retry.

Golden

  • все existing Flat и Iso baselines verify;
  • никакой baseline не принимается только ради performance fix;
  • если measured solution намеренно меняет DOM без pixels, expected diff = 0.

Performance

  • local 1-sample diagnostic допустим только для итерации;
  • closure evidence — два Linux exact-SHA runs по §4, ≥7 samples;
  • artifacts содержат raw samples, summaries, provenance и cache counters.

13. План реализации

  1. Добавить/выполнить profiling instrumentation и опубликовать вывод.
  2. Выбрать минимальный measured optimization.
  3. Зафиксировать structural/live DOM и cache invariants тестами.
  4. Реализовать без visual/action diff.
  5. Прогнать typecheck, unit и build в цикле.
  6. Перед beta прогнать targeted smoke/golden и оба performance comparison.

14. Документация и release-артефакты

  • changelog не нужен при чистой performance-починке скрытой функции (User-Visible: no);
  • docs/ISOMETRIC.md обновляется measured architecture/root cause;
  • ADR меняется, только если renderer ownership действительно изменён;
  • docs/TESTING.md/performance README фиксируют pinned closure evidence;
  • user guide и i18n не меняются;
  • performance artifacts и review links обязательны;
  • golden обновлять запрещено без отдельного visual rationale.

15. Риски и откат

Риск Мера
Cost просто отложен после metric painted completion + trace
Два дерева дублируют actions DOM/ARIA/pointer contract smoke
Cache растёт cap/growth exact assertions
Flat подорожал отдельный flat profile
Решение основано на гипотезе profiling до product diff

Откат возвращает прежний render path. Cache/storage schema не меняются. Если оптимизация ломает Flat/actions, Labs kill switch немедленно даёт effective Flat, но это mitigation, а не критерий принятия красного кода.

16. Принятые технические предположения

  • историческая debt-граница — exact pre-#89 SHA 316ee76…;
  • benchmark продолжает измерять полный Flat → Iso → Flat/Iso user cycle;
  • measured solution может не использовать 2576d9d;
  • public activation изометрии остаётся отдельной будущей задачей.