mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -3,8 +3,10 @@
|
||||
- Issue: [#157](https://github.com/Matysh/houseplan-card/issues/157)
|
||||
- Приоритет: P2
|
||||
- Ветка: `issue/157-open-passage`
|
||||
- Статус документа: полное ТЗ подготовлено; issue остаётся в `S3-spec`, review не запущено по прямому указанию владельца
|
||||
- Основание: issue владельца от 2026-08-14 и аналитика от 2026-08-15
|
||||
- Статус документа: замечания первого раунда review учтены; issue остаётся в
|
||||
`S3-spec` по прямому указанию владельца
|
||||
- Основание: issue владельца от 2026-08-14, аналитика от 2026-08-15 и review от
|
||||
2026-08-17
|
||||
|
||||
## 1. Пользовательская проблема и результат
|
||||
|
||||
@@ -73,6 +75,9 @@
|
||||
10. Для света `passage` прозрачен только когда проба по обе стороны стены
|
||||
попадает на пол комнаты. Внешний либо неопределённый край остаётся
|
||||
fail-dark.
|
||||
Любой неизвестный будущий тип проёма также считается непрозрачным: переход
|
||||
от прежнего неявного `type !== 'window'` к явному allowlist — осознанное
|
||||
ужесточение forward-совместимости ради защиты от утечки света.
|
||||
11. `passage` отличается от open span наличием кладки по сторонам и физических
|
||||
откосов. Open span по-прежнему означает отсутствие кладки на всём участке и
|
||||
показывается пунктиром только в Plan.
|
||||
@@ -227,9 +232,14 @@ validate_opening_passages(
|
||||
новой семантикой и проверяется полностью, даже когда значение binding-поля
|
||||
текстово не изменилось.
|
||||
|
||||
Стабильный public error code: `invalid_passage_fields`. Сообщение содержит id
|
||||
пространства, id проёма и отсортированный список запрещённых полей, но не
|
||||
значения entity ids.
|
||||
Стабильный public error code: `invalid_passage_fields`. Технический payload
|
||||
содержит id пространства, id проёма и отсортированный список запрещённых
|
||||
полей, но не значения entity ids. Frontend локализует ошибку человекочитаемо:
|
||||
`У открытого проёма в комнате «<название>» есть недопустимые параметры: …` /
|
||||
`The open passage in room “<name>” has unsupported settings: …`. Если название
|
||||
комнаты отсутствует, используется id пространства; id проёма остаётся в
|
||||
технических деталях, а не является единственным ориентиром в пользовательском
|
||||
сообщении.
|
||||
|
||||
### 7.3 Точки подключения
|
||||
|
||||
@@ -248,7 +258,7 @@ write запрещён.
|
||||
|
||||
### 8.1 Палитра и размещение
|
||||
|
||||
В submenu «Проём» после двери добавляется пункт:
|
||||
В submenu «Проём» добавляется пункт:
|
||||
|
||||
- label: `Открытый проём` / `Open passage`;
|
||||
- icon: `mdi:arch`;
|
||||
@@ -360,6 +370,8 @@ hit-targets или entity subscriptions для passage Static не создаё
|
||||
|
||||
- `door`, `gate`, `passage` — кандидаты interior passage;
|
||||
- `window` — не passage;
|
||||
- любой неизвестный тип — opaque/fail-dark и не попадает в cuts, пока новый
|
||||
literal не будет явно классифицирован и покрыт тестом;
|
||||
- каждый кандидат проходит одинаковую проверку пола по обе стороны;
|
||||
- только прошедшие кандидаты вычитаются из light masonry и добавляются в cuts;
|
||||
- `passage` не зависит от HA state, `contact` и `invert`, даже если эти поля
|
||||
@@ -407,8 +419,10 @@ best-effort downgrade для frontend v1.64.0:
|
||||
конфигурации, содержащей `passage`.
|
||||
|
||||
Эти ограничения явно записываются в `docs/CONFIG-COMPATIBILITY.md`. Реализация
|
||||
должна подтвердить отсутствие runtime crash source-аудитом/fixture-тестом
|
||||
pre-feature поведения; поддержка редактирования на старой версии не требуется.
|
||||
обязана исполнением загрузить fixture с `passage` в собранный из git tag
|
||||
`v1.64.0` bundle через demo harness и подтвердить отсутствие необработанного
|
||||
исключения в консоли. Source-аудит не считается достаточным доказательством;
|
||||
поддержка редактирования на старой версии не требуется.
|
||||
|
||||
## 12. I18n, accessibility и touch
|
||||
|
||||
@@ -458,8 +472,8 @@ pre-feature поведения; поддержка редактирования
|
||||
opening types сохраняют прежний Static output.
|
||||
7. Скрытая изометрия показывает полноразмерный cut и откосы, без leaf/panel
|
||||
shadow и без crash.
|
||||
8. Внутренний passage пропускает light/Glow независимо от state; наружный или
|
||||
неопределённый passage остаётся fail-dark.
|
||||
8. Внутренний passage пропускает light/Glow независимо от state; наружный,
|
||||
неопределённый passage и неизвестный будущий тип остаются fail-dark.
|
||||
9. Backend принимает канонический passage и отклоняет новую/изменённую запись с
|
||||
любым из пяти запрещённых ключей кодом `invalid_passage_fields`.
|
||||
10. Неизменённый legacy-broken passage проходит несвязанное сохранение; его
|
||||
@@ -468,9 +482,12 @@ pre-feature поведения; поддержка редактирования
|
||||
write при ошибке отсутствует.
|
||||
12. Конфиги с door/window/gate не мигрируют и проходят regression suite без
|
||||
изменений.
|
||||
13. Старый frontend v1.64.0 на passage не падает; точный fallback и ограничение
|
||||
старого backend задокументированы.
|
||||
14. RU/EN parity, unit/backend tests, build, smoke и новые golden evidence
|
||||
13. Demo harness с реально собранным bundle `v1.64.0` загружает fixture с
|
||||
passage без необработанного исключения в консоли; точный fallback и
|
||||
ограничение старого backend задокументированы.
|
||||
14. В `scripts/mutation-gate.mjs` зарегистрированы и проходят пять обязательных
|
||||
executable mutants из §15.6; `--check` подтверждает уникальность их якорей.
|
||||
15. RU/EN parity, unit/backend tests, build, smoke и новые golden evidence
|
||||
проходят в предусмотренный процессом момент.
|
||||
|
||||
## 15. Тест-план
|
||||
@@ -486,7 +503,8 @@ pre-feature поведения; поддержка редактирования
|
||||
- full renderer не создаёт lock badge/info/binding subscription для stale
|
||||
passage fields;
|
||||
- light: две комнаты пропускают, внешний край не пропускает, footprint равен
|
||||
door при одинаковой geometry;
|
||||
door при одинаковой geometry; искусственный тип `xyz` не проходит explicit
|
||||
allowlist и остаётся opaque;
|
||||
- Static fingerprint/output меняется для passage и не меняется для старых типов;
|
||||
- iso basis passage имеет zero leaves, projection/bounds безопасны;
|
||||
- RU/EN parity.
|
||||
@@ -519,7 +537,9 @@ pre-feature поведения; поддержка редактирования
|
||||
7. включить Glow: внутренний проход светится насквозь, наружный — нет;
|
||||
8. проверить скрытую изометрию;
|
||||
9. full и space export/import;
|
||||
10. загрузить downgrade fixture старым frontend и подтвердить отсутствие crash.
|
||||
10. checkout tag `v1.64.0`, собрать его bundle, загрузить fixture с `passage`
|
||||
через demo harness и автоматически подтвердить отсутствие `pageerror` и
|
||||
необработанных console errors; приложить команду и лог к evidence.
|
||||
|
||||
### 15.4 Golden
|
||||
|
||||
@@ -550,6 +570,37 @@ Golden, browser smoke и performance выполняются перед бето
|
||||
runbook. Полный HA harness канонически выполняется в Linux CI; невозможность
|
||||
локального Windows `fcntl` не считается заменой CI.
|
||||
|
||||
### 15.6 Исполняемый мутационный гейт
|
||||
|
||||
Реализация добавляет в `scripts/mutation-gate.mjs` пять реальных entries. Каждый
|
||||
entry содержит стабильный id, патч `find`/`replace` на 2–5 строк с ровно одним
|
||||
якорем, указанный guard и причину ожидаемого падения. Это не список для ручной
|
||||
проверки: runner обязан применить патч в отдельном worktree, собрать bundle,
|
||||
получить non-zero от guard и восстановить чистое состояние.
|
||||
|
||||
| Mutant id | Обязательный патч | Guard |
|
||||
|---|---|---|
|
||||
| `passage-visible-geometry-door-fallback` | удалить/обойти явную `passage`-ветку в `renderOpeningVisibleGeometry()`, чтобы снова сработал дверной fallback | `node --test test/opening-symbol.test.mjs` — отрицательный passage-case обязан обнаружить leaf/arc/gate path |
|
||||
| `passage-light-classifier-removed` | исключить `passage` из явного allowlist interior passages | `node --test test/light-visibility.test.mjs` — внутренний passage перестаёт пропускать свет |
|
||||
| `passage-import-validator-bypassed` | отключить вызов `validate_opening_passages` на import-пути | `node scripts/backend-test-guard.mjs invalid_passage_import` — forged full/space import с запрещённым полем обязан быть отклонён |
|
||||
| `passage-static-door-cut-leak` | расширить passage-only Static cuts/fingerprint до `door`, вернув изменение старого плана | `node --test test/space-geometry.test.mjs` — fixed план без passage обязан сохранить прежние geometry и fingerprint |
|
||||
| `passage-iso-door-fallback` | вернуть `passage` в дверную fallback-ветку `buildIsoOpeningBasis()` | `node --test test/iso-openings.test.mjs` — passage basis обязан иметь zero leaves |
|
||||
|
||||
Обязательные команды перед передачей реализации в code review:
|
||||
|
||||
```text
|
||||
node scripts/mutation-gate.mjs --check
|
||||
node scripts/mutation-gate.mjs --id=passage-visible-geometry-door-fallback
|
||||
node scripts/mutation-gate.mjs --id=passage-light-classifier-removed
|
||||
node scripts/mutation-gate.mjs --id=passage-import-validator-bypassed
|
||||
node scripts/mutation-gate.mjs --id=passage-static-door-cut-leak
|
||||
node scripts/mutation-gate.mjs --id=passage-iso-door-fallback
|
||||
```
|
||||
|
||||
Каждый чистый guard сначала должен быть зелёным, а лог мутанта — содержать его
|
||||
id, guard и ожидаемый non-zero. Ручное редактирование без runner или только
|
||||
`--list`/`--check` acceptance criterion не выполняет.
|
||||
|
||||
## 16. План реализации
|
||||
|
||||
1. Расширить types/schema и добавить чистый semantic validator со всеми call
|
||||
@@ -560,8 +611,9 @@ runbook. Полный HA harness канонически выполняется
|
||||
4. Подключить passage cut/tunnel к Static без изменения output старых типов.
|
||||
5. Сделать light classifier явным и покрыть interior/exterior cases.
|
||||
6. Добавить zero-leaf passage basis в скрытой изометрии.
|
||||
7. Покрыть import/export и downgrade fixture.
|
||||
8. Обновить документацию и release artifacts, пройти implementation gates.
|
||||
7. Покрыть import/export и исполняемый downgrade fixture на bundle `v1.64.0`.
|
||||
8. Зарегистрировать и исполнить пять mutation entries из §15.6.
|
||||
9. Обновить документацию и release artifacts, пройти implementation gates.
|
||||
|
||||
## 17. Release-артефакты
|
||||
|
||||
@@ -569,7 +621,9 @@ runbook. Полный HA harness канонически выполняется
|
||||
|
||||
- `docs/CHANGELOG.md`;
|
||||
- `docs/CHANGELOG.ru.md`;
|
||||
- `docs/USER-GUIDE.ru.md` — таблица passage/open span и поток редактора;
|
||||
- `docs/USER-GUIDE.ru.md` — таблица passage/open span, поток редактора и явное
|
||||
пояснение принятой асимметрии Static: passage разрывает стену, а старые
|
||||
door/window/gate в рамках #157 сохраняют прежний Static-визуал;
|
||||
- `docs/LIGHT.md` — explicit interior-passage rule;
|
||||
- `docs/ISOMETRIC.md` — zero-leaf/full-height-cut поведение скрытого режима;
|
||||
- `docs/CONFIG-COMPATIBILITY.md` — enum, broken-read validator и downgrade;
|
||||
@@ -594,6 +648,7 @@ User-Visible: yes
|
||||
| Import обходит правило | подключить validator к обоим import flows и optimize |
|
||||
| Static меняет старые планы | fingerprint/cuts только для passage, regression snapshots без passage |
|
||||
| Свет утекает наружу | сохранить двусторонний floor probe и exterior test |
|
||||
| Новый неизвестный opening type неявно пропускает свет | explicit allowlist, fail-dark contract и unit с типом `xyz` |
|
||||
| Iso создаёт дверное полотно | zero-leaf basis и golden |
|
||||
| Старый frontend портит новый тип | документировать downgrade как read-only best effort |
|
||||
|
||||
@@ -611,9 +666,11 @@ backend read support для `passage`, отключив только созда
|
||||
Следующие мелкие решения приняты без дополнительного продуктового вопроса и
|
||||
могут быть изменены при реализации, если не нарушаются acceptance criteria:
|
||||
|
||||
- `mdi:arch` — рабочая icon; при отсутствии в поставляемой версии MDI допустим
|
||||
локализованно нейтральный `mdi:door-open` без изменения persisted contract;
|
||||
- passage расположен между door и gate в submenu;
|
||||
- `mdi:arch` не поставляется самим card и разрешается runtime-набором MDI в HA;
|
||||
до реализации его наличие проверяется в поддерживаемой минимальной версии HA
|
||||
через demo/browser fixture. Если glyph отсутствует, нормативный fallback —
|
||||
широко поддерживаемый `mdi:door-open`, без изменения persisted contract;
|
||||
- порядок passage в submenu нормативно задан единожды в §8.1;
|
||||
- inline warning достаточен, отдельный confirm modal не нужен;
|
||||
- draft временно хранит скрытые bindings, чтобы смена radio до Save была
|
||||
обратимой;
|
||||
|
||||
Reference in New Issue
Block a user