feat: discovery filters become visible, explained and previewable (#44)

Light grouping and the excluded-integrations list move from hidden keys
to a Discovery-filters section on the catalog's Available tab: a toggle
(unset = on, the legacy behaviour), searchable integration chips with a
Restore-recommended reset (defaults stored as key absence), and
appear/disappear counters computed by diffing the REAL
seedHiddenBindings/buildDevices outputs — no second copy of the filter.
Saving writes settings once over the ordinary expected_rev path. Every
excluded candidate now names its integration in the catalog. Room
climate follows the same user exclusions through the single
effectiveExcludedIntegrations resolver (spec H2); explicit climate
opt-in stays stronger. The field registry passports both keys as
current supported settings.

User-Visible: yes
Issue: #44
This commit is contained in:
Codex
2026-08-30 14:03:35 +03:00
parent d14cf769d5
commit 8d431d6ddf
18 changed files with 515 additions and 31 deletions
+15 -13
View File
@@ -44,14 +44,15 @@ export const CONFIG_FIELD_REGISTRY = Object.freeze([
selector: { path: ['settings', 'group_lights'] },
storage: 'House Plan server config',
type: 'boolean',
default: 'unset',
default: 'unset (= true: room lights grouped)',
level: 'global',
ui: 'hidden',
ui: 'device inbox → Discovery filters (#44)',
runtime: 'device discovery/materialisation',
introduced: 'legacy',
status: 'decision-required',
migration: 'decide supported UI versus fixed discovery rule before removal',
compatibility: 'preserve unknown owner intent; never delete automatically yet',
introduced: 'legacy; supported UI since #44',
status: 'current',
migration: 'none — a supported setting; true is stored as key absence',
compatibility: 'unset keeps grouping on, exactly the legacy behaviour',
enforcedBy: 'demo/smoke_discovery_filters.mjs (#44 AC1)',
},
{
id: 'settings.exclude_integrations',
@@ -59,14 +60,15 @@ export const CONFIG_FIELD_REGISTRY = Object.freeze([
selector: { path: ['settings', 'exclude_integrations'] },
storage: 'House Plan server config',
type: 'string[]',
default: 'unset',
default: 'unset (= the product EXCLUDED_DOMAINS list)',
level: 'global',
ui: 'hidden',
runtime: 'device discovery filter',
introduced: 'legacy',
status: 'decision-required',
migration: 'decide supported UI versus fixed exclusion rules before removal',
compatibility: 'preserve and continue applying',
ui: 'device inbox → Discovery filters (#44)',
runtime: 'device discovery, materialisation seed, room climate (#44 H2) — one resolver',
introduced: 'legacy; supported UI since #44',
status: 'current',
migration: 'none — a supported setting; a present list REPLACES the product list wholesale',
compatibility: 'unset keeps the product list; [] is a valid "exclude nothing"',
enforcedBy: 'effectiveExcludedIntegrations (devices.ts) + demo/smoke_discovery_filters.mjs (#44 AC2)',
},
{
id: 'settings.weather_entity',
+22
View File
@@ -735,6 +735,28 @@ const MUTANT_DEFINITIONS = [
replace: " this._persistDecorStyle();\n }, 0);",
}],
},
{
id: 'discovery-preview-copies-the-filter',
guard: 'node --test test/devices.test.mjs',
because: 'the preview must diff the REAL seedHiddenBindings/buildDevices outputs — a '
+ 'hand-rolled platform check would drift from the production filter (#44 AC6)',
patches: [{
file: 'src/houseplan-editor-runtime.ts',
find: " const seededOf = (settings: object, excluded: ReadonlySet<string>) => new Set(\n seedHiddenBindings({ ...ctx, settings, excluded } as never));",
replace: " const seededOf = (settings: object, excluded: ReadonlySet<string>) => new Set(\n [...excluded].filter((platform) => platform && EXCLUDED_DOMAINS.has(platform)));",
}],
},
{
id: 'discovery-reset-writes-a-copy',
guard: 'node demo/smoke_discovery_filters.mjs',
because: 'Restore recommended must store the default as ABSENCE of the key — a written '
+ 'copy of the product list would freeze it against future product updates (#44 AC2)',
patches: [{
file: 'src/houseplan-editor-runtime.ts',
find: " if (draft.usesProductList) delete settings.exclude_integrations;\n else settings.exclude_integrations = draft.excluded;",
replace: " settings.exclude_integrations = draft.excluded;",
}],
},
{
id: 'schema-manifest-enum-drift',
guard: 'node --test test/config-schema-parity.test.mjs',