Files
houseplan-card/docs/specs/448-alpha-switch.md
2026-09-04 16:20:34 +03:00

267 lines
21 KiB
Markdown
Raw Permalink 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.
# #448 — Единый бессрочный переключатель `hp_alpha`
- Issue: [#448](https://github.com/Matysh/houseplan-card/issues/448)
- Приоритет: P1
- Тип: feature
- Решения владельца: 2026-09-04, Q1 — default; Q2 — без миграции прежнего `iso`
## Сценарий
Продвинутый пользователь или тестировщик House Plan открывает полную карточку
на своём обычном Home Assistant origin и один раз включает скрытый alpha-режим
через URL. После перезагрузок, перезапусков Home Assistant и обновлений House
Plan ему по-прежнему доступны все экспериментальные функции этой сборки. Сейчас
такой функцией является существующий объёмный вид плана.
## Что человек увидит до и после
До изменения прежний ключ `iso` истёк на версии 1.65, поэтому в актуальной
сборке объёмный вид невозможно включить. После изменения `hp_alpha=1` один раз
возвращает существующий переключатель «Плоский / 3-D» без срока действия;
`hp_alpha=0` выключает весь экспериментальный набор. Старое включение `iso`
автоматически не переносится — режим нужно активировать заново.
## Проблема
`src/labs.ts` сейчас предоставляет внешний реестр отдельных feature-id с
`since`/`expires`, читает операции `hp-labs=iso` и хранит массив id в
`houseplan_card_labs_v1`. Единственный зарегистрированный `iso` истекает перед
версией 1.65.0, поэтому на линии 1.71 он всегда отфильтровывается, хотя
изометрический renderer, выбор вида по пространству и его тестовые сценарии
остались в продукте.
Такой контракт также требует придумывать пользователю новый внешний ключ и
продлевать окно при каждой следующей экспериментальной функции. Решение
владельца заменяет его одним browser-local переключателем способности.
## Скоуп
- Один внешний URL-параметр `hp_alpha` со значениями `1` и `0`.
- Один бессрочный persisted switch на текущем browser/HA-origin.
- Включение одним switch всех экспериментальных функций, присутствующих в
текущей сборке; внутренние идентификаторы функций допустимы только как деталь
реализации и диагностики.
- Возврат существующего объёмного вида и его переключателя в полной карточке.
- Сохранение текущих ограничений объёмного вида: редакторы и
`houseplan-space-card` остаются плоскими, публичного UI включения нет.
- Адаптация unit, browser smoke, golden и performance fixtures к новому
внешнему контракту.
- Актуализация документации Labs/изометрии и двух changelog.
## Не-скоуп
- Публичный запуск 3-D или обычная настройка alpha в интерфейсе.
- Реализация Stage 3 из #160 либо изменение визуального результата Stage 1/2.
- Отдельные пользовательские ключи для `iso` или будущих экспериментов.
- Серверное хранение, синхронизация между браузерами или HA-пользователями.
- Изменение схемы плана, backend API, HA-запросов, сохранения конфигурации,
действий устройств или статической карточки пространства.
- Перенос ранее сохранённого `iso` в новый переключатель.
## Контракт поведения
### Единый внешний переключатель
1. `?hp_alpha=1` включает alpha для текущего browser/HA-origin и запоминает
включение.
2. `#hp_alpha=1&space=<id>` делает то же, не ломая существующий выбор
пространства из hash.
3. `hp_alpha=0` в query или hash выключает alpha и запоминает выключение.
4. URL не переписывается после чтения параметра.
5. Если параметр указан и в query, и в hash, операции применяются в порядке
query → hash: последнее распознанное значение в hash сильнее query. Внутри
одной части URL последнее распознанное значение сильнее предыдущего.
6. Только точные строки `1` и `0` распознаются. Явное иное значение не может
включить alpha: для текущего разрешения оно даёт выключенное состояние и не
перезаписывает storage. Отсутствие параметра читает сохранённое состояние.
7. Отсутствующие или повреждённые persisted-данные дают выключенное состояние.
8. Версия карточки не участвует в решении. Нет `since`, `expires` и
автоматического выключения после beta/stable update.
### Доступные эксперименты
9. При включённом alpha внутренний runtime активирует весь известный этой
сборке экспериментальный набор. В первой поставке набора ровно одна функция:
`iso`.
10. Потребители могут продолжать различать функции внутренними id, но URL,
storage и пользовательская документация не позволяют выбирать их по одной.
11. При выключенном alpha effective projection всегда Flat, даже если
per-space preference хранит `iso`.
12. Выключение alpha не стирает `houseplan_card_view_v1`: повторное включение
возвращает ранее выбранную проекцию пространства. Это состояние вида, а не
второй экспериментальный ключ.
13. В полной карточке View включённый alpha возвращает существующий
переключатель Flat ↔ 3-D. В kiosk применяется сохранённая проекция, но новый
служебный контрол не появляется. В любом редакторе и
`houseplan-space-card` объёмная проекция отсутствует.
14. Любой сбой разрешения alpha безопасно оставляет публичный Flat и не влияет
на данные или HA-команды.
## URL, storage и совместимость
- Новый storage key: `houseplan_card_alpha_v1`.
- Канонические значения storage: строка `1` для enabled и строка `0` для
disabled. Любое другое содержимое считается выключенным.
- Legacy URL `hp-labs=iso`, `hp-labs=-iso`, `hp-labs=off` больше не управляет
функциями.
- Legacy key `houseplan_card_labs_v1` не читается, не мигрируется и сам по себе
не удаляется. Даже точное прежнее значение `["iso"]` не включает alpha.
- Старому тестировщику необходимо один раз открыть карточку с `hp_alpha=1`.
- Per-space preference `houseplan_card_view_v1` сохраняет существующий формат и
не мигрируется.
- Состояние локально для origin и профиля браузера; private mode/quota ошибки
не роняют карточку, но сохранение между сессиями в таком окружении не
гарантируется.
## UX и диагностика
- Обычный пользователь не видит новой настройки, баннера или подсказки.
- Способ включения остаётся намеренно скрытым и документируется только в
материалах разработчика/изометрии.
- Когда alpha включён и экспериментальный кадр действительно отрисован,
dev-console один раз на фактический набор сообщает, что активен `hp_alpha`, и
перечисляет внутренние функции с issue, но без срока истечения.
- Когда alpha выключен либо не удалось разобрать, служебный лог не обещает
активный эксперимент.
- Диагностический browser hook должен однозначно позволять smoke-тесту отличить
alpha enabled от disabled и увидеть фактически активный внутренний набор.
## Модель данных и миграция
План, backend и пользовательская конфигурация не меняются. Новое состояние —
один локальный boolean-like переключатель браузера. Автоматической миграции нет
по прямому решению владельца: legacy storage остаётся неактивным входом, а
пользователь заново подтверждает участие явным `hp_alpha=1`.
Внутренняя модель может сохранить snapshot со списком активных возможностей,
если это уменьшает дифф потребителей. Его источник обязан быть один: при alpha
off список пуст, при alpha on он равен полному набору сборки. Внутренний список
не является persisted или URL API.
## i18n и accessibility
Новых публичных строк и интерактивных элементов нет. Существующие
локализованные названия Flat/3-D, `aria-label` и keyboard contract
переключателя сохраняются без изменений. Kiosk и static card не получают новых
focus targets.
Влияния на touch-контракт нет: в полном View снова становится достижимой уже
принятая в #89/#122 кнопка `projection-toggle`, и её существующие tap,
keyboard и focus-поведение не меняются. Kiosk по-прежнему не получает ни эту
кнопку, ни новую touch-цель; для View и kiosk не допускается деградация
требований `docs/TOUCH-SUPPORT.md`.
## Критерии приёмки
- **AC1.** `hp_alpha=1` в query и hash включает alpha, сохраняет `1` и не
переписывает URL. Доказательство: unit resolver/storage + browser smoke после
reload.
- **AC2.** `hp_alpha=0` выключает alpha, сохраняет `0`, немедленно возвращает
Flat и убирает переключатель 3-D. Доказательство: unit + browser smoke.
- **AC3.** Query применяется раньше hash; последнее распознанное значение
детерминированно побеждает. Доказательство: табличный unit-тест конфликтов.
- **AC4.** Отсутствующий storage, повреждённый storage и явное неизвестное
значение не включают alpha и не вызывают исключений. Доказательство: unit.
- **AC5.** Поведение одинаково на текущей beta, stable и произвольной будущей
semver: version expiry отсутствует. Доказательство: unit с несколькими
версиями и source-contract без `since`/`expires` в решении.
- **AC6.** `houseplan_card_labs_v1: ["iso"]` и любой `hp-labs=...` не включают
alpha и не переносятся. Доказательство: unit + browser smoke чистого профиля.
- **AC7.** При alpha on существующий полный View предлагает Flat ↔ 3-D и
отображает Stage 2; при alpha off публичный View остаётся Flat.
Доказательство: targeted isometric smoke и существующие golden.
- **AC8.** Сохранённая per-space проекция переживает alpha off/on, но без alpha
никогда не отрисовывается. Доказательство: unit/browser smoke.
- **AC9.** Редакторы и `houseplan-space-card` остаются плоскими, kiosk не
получает служебный контрол. Доказательство: contract unit + smoke.
- **AC10.** Все isometric golden-сценарии явно включают общий alpha и падают,
если фактически получили Flat. Доказательство: matrix unit +
`npm run golden:verify`.
- **AC11.** Isometric performance profile включает alpha новым контрактом и не
использует legacy storage/URL. Доказательство: source-contract + профильный
запуск.
- **AC12.** Диагностика однозначно показывает switch и активный набор без
`expires`; повторный одинаковый кадр не спамит console. Доказательство: unit
или targeted browser smoke.
- **AC13.** Alpha не участвует в HA data/request paths, schema и writes.
Доказательство: static contract tests и чтение границ импорта.
## План автотестов
### Unit и contract
- Переписать таблицу `test/labs.test.mjs`: `1`/`0`, query/hash precedence,
повторные значения, storage persistence, corruption, future versions,
private-storage failures и отсутствие legacy migration.
- Зафиксировать один внешний URL/storage contract; запретить возвращение
`hp-labs`, persisted массива и expiry как активирующих входов.
- Обновить `test/isometric-contract.test.mjs` и golden-matrix contract: full
card использует alpha, secondary card не знает про него, isometric scenarios
объявляют его явно.
### Browser и visual
- Обновить `demo/smoke_isometric_contract.mjs`: clean off → URL on → reload →
3-D → URL off → Flat → повторное on восстанавливает preference.
- Обновить `demo/smoke_isometric_live_touch.mjs` и все fixtures, которые сейчас
подменяют `hp-labs`/legacy storage напрямую.
- Прогнать выбранные `smoke-select` сценарии и весь isometric smoke-контракт.
- Прогнать `npm run golden:verify`; новые эталоны не принимаются, если пиксели
Stage 1/2 не изменились.
### Производительность и основные гейты
- `npm run benchmark:large-house-isometric` с alpha fixture и существующим
бюджетом; задача меняет gate, а не геометрию/рендер, поэтому новый бюджет не
вводится.
- Перед код-ревью: typecheck, unit, build + bundle-tree/budget,
`check-docs`, `no-new-any`, выбранные smokes и golden.
## Затронутые файлы и модули
Ожидаемо: `src/labs.ts`, потребители Labs в `src/houseplan-card.ts`, unit и
isometric browser/golden/performance harnesses, `docs/DEVELOPMENT.md`,
`docs/ISOMETRIC.md`, архитектурные/ADR ссылки при необходимости, оба changelog.
Backend, schema плана и secondary-card renderer изменяться не должны.
## Риски
- Legacy фикстура может продолжить принудительно подставлять `['iso']` и дать
ложнозелёный тест, не проверяющий новый пользовательский вход.
- Стирание projection preference при `hp_alpha=0` превратит временное
выключение экспериментов в потерю пользовательского выбора.
- Параллельные module instances могут зарегистрировать несколько listeners;
существующий single-listener contract нужно сохранить.
- Ошибка precedence query/hash сделает включение зависимым от порядка роутера HA.
- Слишком широкая замена термина `iso` может затронуть внутренний renderer;
меняется capability gate, а не имя проекции.
## Откат
Откат изменения возвращает старый истёкший Labs resolver; публичный Flat при
этом остаётся безопасным. Новый storage key можно оставить неиспользуемым: он
не является данными плана и не требует destructive cleanup. Нельзя откатывать
через продление `iso` expiry или возвращение нескольких внешних ключей — это
восстановит проблему задачи.
## Release-артефакты
- Записи со ссылкой на #448 в `docs/CHANGELOG.ru.md` и `docs/CHANGELOG.md` в
коммите пользовательского изменения.
- Актуальные `docs/DEVELOPMENT.md` и `docs/ISOMETRIC.md`; при изменении
архитектурной границы — соответствующие ADR/`docs/ARCHITECTURE.md`.
- Обновлённые unit, smoke, golden matrix и isometric performance fixtures.
- Обычные синхронные release bundles; новая картинка документации или новый
golden baseline не ожидаются, пока пиксельный результат Stage 1/2 не менялся.
## Принятые предположения — можно менять на ревью без решения владельца
- Storage key — `houseplan_card_alpha_v1`, значения — строки `1`/`0`.
- Явное неизвестное URL-значение выключает alpha только для текущего разрешения
и не портит ранее сохранённое значение; распознанные `1`/`0` записываются.
- Legacy storage не удаляется автоматически: он просто перестаёт быть входом.
- Внутренние feature-id можно сохранить для маршрутизации и диагностики, если
снаружи существует только один alpha switch.
- Отключение alpha скрывает эксперимент, но не стирает per-space projection
preference.