mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
267 lines
21 KiB
Markdown
267 lines
21 KiB
Markdown
# #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.
|