From ed0fdd485958b31dd64992f25c34c9b8b28da25b Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Fri, 4 Sep 2026 15:59:07 +0300 Subject: [PATCH] docs: specify persistent alpha switch Issue: #448 User-Visible: no --- docs/specs/448-alpha-switch.md | 260 +++++++++++++++++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 261 insertions(+) create mode 100644 docs/specs/448-alpha-switch.md diff --git a/docs/specs/448-alpha-switch.md b/docs/specs/448-alpha-switch.md new file mode 100644 index 00000000..ba9b8e5f --- /dev/null +++ b/docs/specs/448-alpha-switch.md @@ -0,0 +1,260 @@ +# #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=` делает то же, не ломая существующий выбор + пространства из 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. + +## Критерии приёмки + +- **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. diff --git a/docs/specs/README.md b/docs/specs/README.md index b5a9127f..0f5137a8 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -174,6 +174,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#440](https://github.com/Matysh/houseplan-card/issues/440) Полиш аудита v1.71.0-beta.2 | [440-v171-beta2-polish.md](440-v171-beta2-polish.md) | | [#445](https://github.com/Matysh/houseplan-card/issues/445) Магнит мебели к физической поверхности стены | [445-furniture-wall-face-snap.md](445-furniture-wall-face-snap.md) | | [#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) | ## P3