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

255 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 изометрии остаётся отдельной будущей задачей.