diff --git a/docs/specs/044-filter-grouping-policy.md b/docs/specs/044-filter-grouping-policy.md index 7f8842dc..1cf385af 100644 --- a/docs/specs/044-filter-grouping-policy.md +++ b/docs/specs/044-filter-grouping-policy.md @@ -3,8 +3,8 @@ - Issue: https://github.com/Matysh/houseplan-card/issues/44 - Приоритет: P2, tech-debt; полный трек (discovery/UI/settings/i18n/tests — решение аналитики 2026-08-15) -- Ревизия: 2 (2026-08-30) — policy v1 принята владельцем (вход в работу без - возражений к записанной политике); конкретизация под инбокс v1.69 +- Ревизия: 3 (2026-08-30) — по SPEC-REVIEW-44-r1 (H1, H2); policy v1 принята + владельцем, конкретизация под инбокс v1.69 - Связано: device inbox #29, field registry/манифест #33 (паспорта `allow-extra` обоих ключей уже выданы), персист-паттерн #377 @@ -20,10 +20,11 @@ ## Что человек увидит до и после -**До**: группировка света и исключение интеграций происходят молча; понять -«почему этого устройства нет в списке» нельзя. **После**: обе настройки видны -и объяснимы; у кандидата, скрытого фильтром, в инбоксе появляется причина -«Исключена интеграция X»; ничего не меняется само — только по Сохранить. +**До**: группировка света и исключение интеграций происходят молча; причина +`excluded_integration` в инбоксе уже существует (#29), но безлична +(«Интеграция исключена фильтрами»). **После**: обе настройки видны и +управляемы; текст причины называет интеграцию по имени («Исключена +интеграция {integration}»); ничего не меняется само — только по Сохранить. ## Проблема @@ -66,13 +67,15 @@ discovery `devices.ts:1491`). Отмена — no-op. Undo — штатный именованный снапшот конфига (паттерн существующих правок настроек). -### 2. Причина «Исключена интеграция X» в инбоксе +### 2. Причина с именем интеграции (H1 r1: факт исправлен) -Кандидат, отфильтрованный исключением, при показе на вкладке «Скрытые» -несёт `DeviceInboxReason` нового значения `excluded_integration` с текстом -«Исключена интеграция {integration}». (Сегодня такие устройства не попадают -в инбокс вовсе — они появляются на «Скрытых» именно с этой причиной; -объём выдачи ограничен существующей механикой вкладки.) +Причина `excluded_integration` УЖЕ существует и рендерится на вкладке +«Доступны» (#29, `houseplan-editor-runtime.ts:7637`, +`device_inbox.reason_excluded_integration`) — но текст обобщённый. Скоуп +этой задачи: тексту добавляется плейсхолдер `{integration}` с фактическим +platform-именем кандидата; вкладка НЕ меняется (перенос категории — отдельный +продуктовый вопрос, здесь не открывается). Значение reason и его механика не +трогаются. ### 3. Registry/доки @@ -93,6 +96,13 @@ discovery `devices.ts:1491`). 1. Конфиг без ключей → поведение байт-в-байт сегодняшнее (default-путь не меняется ни в одном потребителе). +1a. (H2 r1) `roomClimateMap` (`devices.ts:1491`) — единственное место в + `src/**`, хардкодящее `EXCLUDED_DOMAINS` мимо настройки, — переводится на + тот же резолвер действующего набора исключений, что и discovery: климат + комнаты следует за настройкой пользователя, а не за старым жёстким + списком (иначе UI создаёт новый «третий вариант»). Явный climate-opt-in + (галочка «этот датчик меряет воздух комнаты») по-прежнему сильнее + исключения — существующая ветка `optClimate` не меняется. 2. Explicit-маркер (живой или tombstone) НИКОГДА не исчезает из-за смены фильтров — фильтры влияют только на автоматических кандидатов и seed. 3. Оба значения читаются из одного источника (`_settings`) всеми @@ -125,8 +135,13 @@ issue требует видимости и policy, не пере-схемиза список; «Вернуть рекомендуемые» + Сохранить → ключа нет. - **AC3** (смок): у явного маркера с binding исключённой интеграции ничего не меняется (живой остаётся на плане, tombstone не трогается). -- **AC4** (смок): скрытый фильтром кандидат виден на «Скрытых» с причиной - «Исключена интеграция X». +- **AC4** (смок): кандидат исключённой интеграции виден на «Доступны» с + причиной, называющей интеграцию по имени («Исключена интеграция demo_x»); + вкладка и значение reason прежние (регресс-ветка). +- **AC4b** (юнит, H2): `roomClimateMap` с настроенным `exclude_integrations` + фильтрует по НЕМУ (интеграция вне списка даёт климат; продуктовая, вручную + включённая в список — не даёт); без ключа — байт-в-байт сегодняшний + результат; явный climate-opt-in побеждает исключение (регресс-ветка). - **AC5** (юнит): резолвер действующего набора: unset→продуктовый список, []→пусто (валидная «ничего не исключать»), список→список; тумблер: unset→true. @@ -139,8 +154,8 @@ issue требует видимости и policy, не пере-схемиза ## План автотестов -- Юниты: резолверы (AC5), превью-контракт (AC6), регресс дефолта (AC7) — - test/devices.test.mjs + test/device-inbox.test.mjs. +- Юниты: резолверы (AC5), превью-контракт (AC6), регресс дефолта (AC7), + климат-фильтр (AC4b) — test/devices.test.mjs + test/device-inbox.test.mjs. - Смок `demo/smoke_discovery_filters.mjs` (новый): AC1–AC4 на демо-стенде с моковым config/set (паттерн #377-смока). - Мутанты: м1 — превью-счётчики от копии логики (подмена вызова общего @@ -155,6 +170,9 @@ issue требует видимости и policy, не пере-схемиза - Реason-вкладка «Скрытые» получает новый класс записей — объём ограничен существующим механизмом вкладки; смок проверяет отсутствие дублей. - Гонка вкладок — штатный conflict #340. +- Перевод roomClimateMap на резолвер (H2) меняет климат ТОЛЬКО при заданном + пользователем ключе — default-путь передаёт тот же продуктовый список + (AC4b регресс-ветка + существующие климат-юниты #317 не слабеют). ## Откат