mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 21:58:56 +00:00
Stage 5 of #780. - Import summary: «Strips left unbound after import: {n}» from the backend `unbound_led_strips` count; «Optimize plans» reports strips passing through walls per space and edits none (AC16). - Linear field for long strips (ТЗ §13.2): pieces of at most the radius along the polyline, emitters thinned to r/4, each piece clipped to the visibility fans of its own emitters as separate clipPath children (no boolean pass per piece), one floor clip for the whole layer, no fan at all where nothing blocks within the radius; a grid index of body faces and boxed inside tests; unchanged fields skip re-diffing. 50×50 on the large house: first stable frame ~1.4 s, warm space ~1.1 s locally. - led-strips-v1 profile: demo/benchmark_led_strips.mjs with the derived large-house fixture (10×5, 50×50, none), absolute limits of the ТЗ table in demo/performance/budgets-led-strips.json, exact counters (zero recomputes on HA ticks/camera/colour, ≤50 cache entries, no growth over 20 cycles); added to the full performance workflow. - Bundle: LAZY_LED_GZIP_CEILING 10 KiB, LAZY_LED_EDITOR_GZIP_CEILING 11 KiB (measured + 10 %, rounded up); overlaps with the initial and editor graphs refused; the lazy editor graph stays inside its ceiling. - Smokes smoke_led_strip_draw/bind/glow, linked in smoke-links; 13 mutants in the registry (7 browser guards in the inventory); config field registry entry `spaces[].led_strips`. - Golden: five new scenes on the `golden-led` space of the visual fixture (`ledStrips` option, the designer's four strips on #868D94), matrix v71. - Docs: LIGHT, DEVICE-PRESENTATION, USER-GUIDE (en/ru), UX-MODES, ARCHITECTURE, ISOMETRIC, CONFIG-COMPATIBILITY, TOUCH-SUPPORT, demo/stand README, performance README; docs/design/led-strips with the unchanged designer archive, two paired frames and ACCEPTANCE.md; both changelogs. Issue: #780 User-Visible: yes
572 lines
25 KiB
JavaScript
572 lines
25 KiB
JavaScript
/**
|
|
* Registry of persisted fields that need an explicit compatibility decision.
|
|
*
|
|
* This is deliberately data-only so the local auditor, documentation tooling
|
|
* and future frontend/backend parity checks can consume the same source. A
|
|
* selector is a JSON path; `*` visits every array item and `equals` narrows a
|
|
* field whose key is still current but whose old value is compatibility-only.
|
|
*/
|
|
export const CONFIG_FIELD_REGISTRY = Object.freeze([
|
|
{
|
|
id: 'settings.sun_ray_origin',
|
|
enforcedBy: 'sun.ts + validation.py + config-schema parity; #577',
|
|
selector: { path: ['settings', 'sun_ray_origin'] },
|
|
storage: 'House Plan server config',
|
|
type: "'inner' | 'outer'",
|
|
default: "unset (= 'inner', the pre-#577 geometry)",
|
|
level: 'global',
|
|
ui: 'General settings → Sun → Sun rays',
|
|
runtime: 'selects the physical window face used as the source span',
|
|
introduced: '#577',
|
|
status: 'current',
|
|
migration: "none — old configs stay on 'inner'; new settings saves materialise the enum",
|
|
compatibility: "unknown values fail backend writes and fall back to 'inner' in the frontend",
|
|
},
|
|
{
|
|
id: 'settings.summary_panel',
|
|
enforcedBy: 'summary-panel.ts + validation.py change-aware reference validation; #437',
|
|
selector: { path: ['settings', 'summary_panel'] },
|
|
storage: 'House Plan server config',
|
|
type: '{ version: 1, title, show_on_mobile, blocks[] } | bounded future object',
|
|
default: 'unset (= localized read-only defaults; no server write)',
|
|
level: 'global shared presentation',
|
|
ui: 'View → Summary panel settings',
|
|
runtime: 'read-only screen-space overlay; HA entity values remain runtime-only',
|
|
introduced: '#437 / v1.73.0',
|
|
status: 'current',
|
|
migration: 'none — omission derives defaults; explicit empty blocks remain empty',
|
|
compatibility: 'older ordinary config/set preserves the namespace; future versions stay opaque and inert',
|
|
},
|
|
{
|
|
id: 'card.tap_action',
|
|
schema: 'lovelace-card', // #33: Lovelace card option — not in the backend manifest by design
|
|
selector: { path: ['tap_action'] },
|
|
storage: 'Lovelace card config',
|
|
type: "'info' | 'more-info' | 'toggle'",
|
|
default: 'unset',
|
|
level: 'card',
|
|
ui: 'hidden',
|
|
runtime: 'ignored since v1.38.1; marker.tap_action is authoritative',
|
|
introduced: 'legacy',
|
|
status: 'deprecated-read',
|
|
migration: 'remove from Lovelace YAML manually; no visual change',
|
|
compatibility: 'accepted indefinitely until the minimum supported card config is raised',
|
|
},
|
|
{
|
|
id: 'settings.show_all',
|
|
schema: 'allow-extra', // #33: lives only through extra=ALLOW_EXTRA, absent from CONFIG_SCHEMA
|
|
enforcedBy: 'houseplan-card.ts materialisation: seeds hidden markers, then `delete st.show_all`',
|
|
selector: { path: ['settings', 'show_all'] },
|
|
storage: 'House Plan server config',
|
|
type: 'boolean',
|
|
default: 'unset',
|
|
level: 'global',
|
|
ui: 'removed',
|
|
runtime: 'materialisation compatibility only',
|
|
introduced: 'legacy',
|
|
status: 'migrate-on-write',
|
|
migration: 'seed marker visibility, then remove the field',
|
|
compatibility: 'read until all supported configs have passed materialisation once',
|
|
},
|
|
{
|
|
id: 'settings.group_lights',
|
|
schema: 'allow-extra', // #33: lives only through extra=ALLOW_EXTRA, absent from CONFIG_SCHEMA
|
|
selector: { path: ['settings', 'group_lights'] },
|
|
storage: 'House Plan server config',
|
|
type: 'boolean',
|
|
default: 'unset (= true: room lights grouped)',
|
|
level: 'global',
|
|
ui: 'device inbox → Discovery filters (#44)',
|
|
runtime: 'device discovery/materialisation',
|
|
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',
|
|
schema: 'allow-extra', // #33: lives only through extra=ALLOW_EXTRA, absent from CONFIG_SCHEMA
|
|
selector: { path: ['settings', 'exclude_integrations'] },
|
|
storage: 'House Plan server config',
|
|
type: 'string[]',
|
|
default: 'unset (= the product EXCLUDED_DOMAINS list)',
|
|
level: 'global',
|
|
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',
|
|
enforcedBy: 'houseplan-editor-runtime.ts _saveSettingsDialog: `delete settings.weather_entity` on every save',
|
|
selector: { path: ['settings', 'weather_entity'] },
|
|
storage: 'House Plan server config',
|
|
type: 'string | null',
|
|
default: 'unset',
|
|
level: 'global',
|
|
ui: 'removed',
|
|
runtime: 'ignored since sun rays became geometry/elevation-only',
|
|
introduced: 'pre-v1.60.2',
|
|
status: 'migrate-on-settings-save',
|
|
migration: 'drop on the next settings save',
|
|
compatibility: 'backend accepts bounded values so old configs still load',
|
|
},
|
|
{
|
|
id: 'settings.marker_area_snapshot',
|
|
enforcedBy: 'device-area-relocation.ts + validation.py; #126',
|
|
selector: { path: ['settings', 'marker_area_snapshot'] },
|
|
storage: 'House Plan server config',
|
|
type: 'Record<markerId, { binding: device:* | entity:*, area: areaId }>',
|
|
default: 'unset (= one-time safe backfill)',
|
|
level: 'global internal lifecycle metadata',
|
|
ui: 'hidden',
|
|
runtime: 'detect authoritative HA Area changes and supersede stale saved positions',
|
|
introduced: '#126 / v1.70.0',
|
|
status: 'current',
|
|
migration: 'safe point-in-room backfill; cross-source full import drops it',
|
|
compatibility: 'older frontends ignore it; same-source backup preserves it',
|
|
},
|
|
{
|
|
id: 'settings.zigbee_topology',
|
|
schema: 'allow-extra',
|
|
enforcedBy: 'zigbee-topology-settings.ts + explicit admin provider controls; #54',
|
|
selector: { path: ['settings', 'zigbee_topology'] },
|
|
storage: 'House Plan server config',
|
|
type: '{ enabled?: boolean, z2m_base_topics?: string[] }',
|
|
default: 'unset (= disabled; default Zigbee2MQTT base topic is presented in UI)',
|
|
level: 'global',
|
|
ui: 'General settings → Zigbee links',
|
|
runtime: 'allows a mouse-only incident-neighbour overlay after explicit provider refresh',
|
|
introduced: '#54',
|
|
status: 'current',
|
|
migration: 'none — topology snapshots remain memory-only',
|
|
compatibility: 'older frontends ignore it; backend preserves it through allow-extra',
|
|
},
|
|
{
|
|
id: 'settings.radar',
|
|
schema: 'allow-extra',
|
|
enforcedBy: 'radar_validation.py + radar coordinator; #485 stage 1',
|
|
selector: { path: ['settings', 'radar'] },
|
|
storage: 'House Plan server config',
|
|
type: '{ version?: 1, show_live?: boolean, ...future stage fields }',
|
|
default: 'unset (= live presence layer enabled)',
|
|
level: 'global',
|
|
ui: 'General settings → Show presence on the plan',
|
|
runtime: 'global display preference; no source subscription or recording consent by itself',
|
|
introduced: '#485 stage 1',
|
|
status: 'current',
|
|
migration: 'none',
|
|
compatibility: 'older frontends ignore it; known writes preserve future siblings',
|
|
},
|
|
{
|
|
id: 'markers[].radar',
|
|
schema: 'allow-extra',
|
|
enforcedBy: 'radar_validation.py + radar coordinator; #485 stage 1',
|
|
selector: { path: ['markers', '*', 'radar'] },
|
|
storage: 'House Plan server config',
|
|
type: 'versioned presence-radar source, installation and calibration config',
|
|
default: 'unset (= marker is not a configured presence radar)',
|
|
level: 'device marker',
|
|
ui: 'Device editor → Presence on plan',
|
|
runtime: 'exact HA source bindings normalized server-side into bounded live frames',
|
|
introduced: '#485 stage 1',
|
|
status: 'current',
|
|
migration: 'none; virtualized imports drop the hardware-specific block',
|
|
compatibility: 'untouched unknown versions remain inert and lossless',
|
|
},
|
|
{
|
|
id: 'markers[].display=ripple',
|
|
enforcedBy: 'logic.ts normalizeDeviceDisplay (read) + editor-runtime _dropLegacySegments rewrites on every write',
|
|
selector: { path: ['markers', '*', 'display'], equals: 'ripple' },
|
|
storage: 'House Plan server config',
|
|
type: "legacy enum value 'ripple'",
|
|
default: 'badge',
|
|
level: 'device marker',
|
|
ui: 'not offered',
|
|
runtime: "normalizeDeviceDisplay maps it to 'icon_ripple'",
|
|
introduced: 'pre-v1.26.0',
|
|
status: 'deprecated-read',
|
|
migration: "write 'icon_ripple' when the marker is edited",
|
|
compatibility: 'backend and frontend keep reading it during the declared compatibility window',
|
|
},
|
|
{
|
|
id: 'markers[].vacuum.trail',
|
|
selector: { path: ['markers', '*', 'vacuum', 'trail'] },
|
|
storage: 'House Plan server config',
|
|
type: 'boolean | null',
|
|
default: 'unset',
|
|
level: 'device marker',
|
|
ui: 'replaced by trail_mode',
|
|
runtime: 'fallback only; trail_mode wins',
|
|
introduced: 'legacy vacuum model',
|
|
status: 'deprecated-read',
|
|
migration: 'convert to trail_mode when vacuum settings are saved or plans are optimized',
|
|
compatibility: 'read while configs without trail_mode remain supported',
|
|
},
|
|
{
|
|
id: 'markers[].vacuum.room_highlight',
|
|
selector: { path: ['markers', '*', 'vacuum', 'room_highlight'] },
|
|
storage: 'House Plan server config',
|
|
type: 'boolean | null',
|
|
default: 'unset',
|
|
level: 'device marker',
|
|
ui: 'hidden',
|
|
runtime: 'no supported consumer found in v1.60.2',
|
|
introduced: 'legacy vacuum model',
|
|
status: 'decision-required',
|
|
migration: 'confirm external use; then document, migrate or remove',
|
|
compatibility: 'preserve until the decision is recorded',
|
|
},
|
|
{
|
|
id: 'markers[].vacuum.segment_map',
|
|
selector: { path: ['markers', '*', 'vacuum', 'segment_map'] },
|
|
storage: 'House Plan server config',
|
|
type: 'Record<string, string>',
|
|
default: 'unset',
|
|
level: 'device marker',
|
|
ui: 'hidden',
|
|
runtime: 'no supported consumer found in v1.60.2',
|
|
introduced: 'legacy vacuum model',
|
|
status: 'decision-required',
|
|
migration: 'confirm external use; then document, migrate or remove',
|
|
compatibility: 'preserve until the decision is recorded',
|
|
},
|
|
{
|
|
id: 'spaces[].aspect',
|
|
enforcedBy: 'validation.py vol.Remove — the backend drops the field during validation',
|
|
selector: { path: ['spaces', '*', 'aspect'] },
|
|
storage: 'House Plan server config',
|
|
type: 'number',
|
|
default: 'unset',
|
|
level: 'space',
|
|
ui: 'removed',
|
|
runtime: 'backend drops it; plan_aspect is canonical',
|
|
introduced: 'legacy backdrop model',
|
|
status: 'drop-on-validation',
|
|
migration: 'removed by backend validation because its coordinate basis is unsafe',
|
|
compatibility: 'stale clients may submit it without failing the whole save',
|
|
},
|
|
{
|
|
id: 'spaces[].segments',
|
|
enforcedBy: 'validation.py vol.Remove — the backend drops the field during validation',
|
|
selector: { path: ['spaces', '*', 'segments'] },
|
|
storage: 'House Plan server config',
|
|
type: 'unknown[]',
|
|
default: 'unset',
|
|
level: 'space',
|
|
ui: 'removed',
|
|
runtime: 'backend drops it; walls derive from room contours',
|
|
introduced: 'pre-v1.19.0',
|
|
status: 'drop-on-validation',
|
|
migration: 'remove on backend validation/write',
|
|
compatibility: 'stale clients may submit it without failing the whole save',
|
|
},
|
|
{
|
|
id: 'spaces[].plan_scale',
|
|
selector: { path: ['spaces', '*', 'plan_scale'] },
|
|
storage: 'House Plan server config',
|
|
type: 'number | null',
|
|
default: '1',
|
|
level: 'space',
|
|
ui: 'replaced by per-axis transform',
|
|
runtime: 'read fallback for plan_scale_x and plan_scale_y',
|
|
introduced: 'pre-v1.58.0',
|
|
status: 'deprecated-read',
|
|
migration: 'materialize equal plan_scale_x/plan_scale_y on explicit backdrop edit or optimization',
|
|
compatibility: 'keep reading to preserve old backdrop geometry exactly',
|
|
},
|
|
{
|
|
id: 'spaces[].settings.fill_mode=glow',
|
|
selector: { path: ['spaces', '*', 'settings', 'fill_mode'], equals: 'glow' },
|
|
storage: 'House Plan server config',
|
|
type: "legacy enum value 'glow'",
|
|
default: 'none + Glow off',
|
|
level: 'space',
|
|
ui: 'replaced by fill_mode plus glow_enabled',
|
|
runtime: 'projects to data fill none and Glow on unless explicit glow_enabled wins',
|
|
introduced: 'pre-v1.61.0',
|
|
status: 'deprecated-read',
|
|
migration: "materialize fill_mode:'none' and effective glow_enabled on space edit or explicit optimization",
|
|
compatibility: 'backend and frontend accept the legacy token indefinitely for old dashboard bundles',
|
|
},
|
|
{
|
|
id: 'spaces[].rooms[].settings.fill_mode=glow',
|
|
selector: { path: ['spaces', '*', 'rooms', '*', 'settings', 'fill_mode'], equals: 'glow' },
|
|
storage: 'House Plan server config',
|
|
type: "legacy enum value 'glow'",
|
|
default: 'inherit data fill and space Glow',
|
|
level: 'room',
|
|
ui: 'replaced by inherited fill plus room glow override',
|
|
runtime: 'projects to inherited data fill and Glow on unless explicit room glow wins',
|
|
introduced: 'legacy/manual config',
|
|
status: 'deprecated-read',
|
|
migration: 'remove the fill token and materialize effective room glow on room edit or explicit optimization',
|
|
compatibility: 'backend and frontend accept the legacy token indefinitely',
|
|
},
|
|
{
|
|
id: 'spaces[].rooms[].settings.temp_min',
|
|
selector: { path: ['spaces', '*', 'rooms', '*', 'settings', 'temp_min'] },
|
|
storage: 'House Plan server config',
|
|
type: 'finite number | null',
|
|
default: 'unset/null (= inherit the lower comfort bound from the space)',
|
|
level: 'room',
|
|
ui: 'Room settings when the effective fill is Temperature',
|
|
runtime: 'roomTempRangeOf; affects temperature floor fill and opening tunnel fill only',
|
|
introduced: 'v1.73.0',
|
|
status: 'current',
|
|
migration: 'none — each absent/null bound inherits independently',
|
|
compatibility: 'older clients ignore the field and may erase it when rewriting complete room settings',
|
|
},
|
|
{
|
|
id: 'spaces[].rooms[].settings.temp_max',
|
|
selector: { path: ['spaces', '*', 'rooms', '*', 'settings', 'temp_max'] },
|
|
storage: 'House Plan server config',
|
|
type: 'finite number | null',
|
|
default: 'unset/null (= inherit the upper comfort bound from the space)',
|
|
level: 'room',
|
|
ui: 'Room settings when the effective fill is Temperature',
|
|
runtime: 'roomTempRangeOf; affects temperature floor fill and opening tunnel fill only',
|
|
introduced: 'v1.73.0',
|
|
status: 'current',
|
|
migration: 'none — each absent/null bound inherits independently',
|
|
compatibility: 'older clients ignore the field and may erase it when rewriting complete room settings',
|
|
},
|
|
{
|
|
id: 'spaces[].open_spans',
|
|
selector: { path: ['spaces', '*', 'open_spans'] },
|
|
storage: 'House Plan server config',
|
|
type: 'Array<{ a: Point; b: Point }>',
|
|
default: 'unset',
|
|
level: 'space',
|
|
ui: 'removed',
|
|
runtime: 'read-only compatibility projection to cm:0 contour atoms',
|
|
introduced: 'legacy virtual-boundary model',
|
|
status: 'migrate-on-write',
|
|
migration: 'convert covered wall_segments to cm:0 and remove after a structural write, Optimize or import',
|
|
compatibility: 'accepted only below model v9; canonical v9 rejects it',
|
|
},
|
|
{
|
|
id: 'spaces[].rooms[].open_to',
|
|
selector: { path: ['spaces', '*', 'rooms', '*', 'open_to'] },
|
|
storage: 'House Plan server config',
|
|
type: 'string[] | null',
|
|
default: 'unset',
|
|
level: 'room',
|
|
ui: 'removed',
|
|
runtime: 'read-only fallback projected to cm:0 when explicit open_spans are absent',
|
|
introduced: 'legacy virtual-boundary model',
|
|
status: 'deprecated-read',
|
|
migration: 'convert the proven shared wall_segments to cm:0 and remove after a structural write, Optimize or import',
|
|
compatibility: 'accepted only below model v9; canonical v9 rejects it',
|
|
},
|
|
{
|
|
id: 'spaces[].zero_wall_style',
|
|
selector: { path: ['spaces', '*', 'zero_wall_style'] },
|
|
storage: 'House Plan server config',
|
|
type: "'dashed' | 'solid'",
|
|
default: 'dashed',
|
|
level: 'space',
|
|
ui: 'space settings: zero-thickness walls',
|
|
runtime: 'dashed transmits Glow/sun; solid blocks both without adding a wall body',
|
|
introduced: 'model v9 / issue #306',
|
|
status: 'current',
|
|
migration: 'never materialized implicitly; missing and unknown values read as dashed',
|
|
compatibility: 'older clients ignore the field and do not understand the unified cm:0 light contract',
|
|
},
|
|
{
|
|
id: 'spaces[].wall_segments',
|
|
selector: { path: ['spaces', '*', 'wall_segments'] },
|
|
storage: 'House Plan server config',
|
|
type: 'Array<{ id: string; a: Point; b: Point; cm: number }>',
|
|
default: '[] after the first v8 structural write; required in model v9',
|
|
level: 'space',
|
|
ui: 'internal canonical wall identity',
|
|
runtime: 'authoritative contour-wall identity and thickness; cm:0 is a zero-thickness wall in model v9',
|
|
introduced: 'model v8 / issue #282',
|
|
status: 'current',
|
|
migration: 'materialize atomically on a structural write, Optimize or import',
|
|
compatibility: 'legacy walls[] remains a validated generated projection',
|
|
},
|
|
{
|
|
id: 'spaces[].rooms[].wall_ids',
|
|
selector: { path: ['spaces', '*', 'rooms', '*', 'wall_ids'] },
|
|
storage: 'House Plan server config',
|
|
type: 'string[]',
|
|
default: 'one id per poly edge in model v8',
|
|
level: 'room',
|
|
ui: 'internal canonical wall ownership',
|
|
runtime: 'maps every room polygon edge to one stable wall segment',
|
|
introduced: 'model v8 / issue #282',
|
|
status: 'current',
|
|
migration: 'materialize together with wall_segments',
|
|
compatibility: 'absent v7 rooms remain readable until a structural write',
|
|
},
|
|
{
|
|
id: 'spaces[].room_drafts[].segments[].id',
|
|
selector: { path: ['spaces', '*', 'room_drafts', '*', 'segments', '*', 'id'] },
|
|
storage: 'House Plan server config',
|
|
type: 'string',
|
|
default: 'deterministic id in model v8',
|
|
level: 'draft wall segment',
|
|
ui: 'internal draft identity',
|
|
runtime: 'preserves segment identity while an unfinished contour is continued',
|
|
introduced: 'model v8 / issue #282',
|
|
status: 'current',
|
|
migration: 'materialize together with wall_segments',
|
|
compatibility: 'optional on legacy draft reads',
|
|
},
|
|
{
|
|
id: 'spaces[].openings[].host=wall',
|
|
selector: { path: ['spaces', '*', 'openings', '*', 'host', 'kind'], equals: 'wall' },
|
|
storage: 'House Plan server config',
|
|
type: "{ kind: 'wall'; id: string; t: number }",
|
|
default: 'materialized by migration (#316 §3); may persist unhosted after degraded migration',
|
|
level: 'opening',
|
|
ui: 'internal opening ownership',
|
|
runtime: 'binds an opening to a stable contour-wall segment',
|
|
introduced: 'model v8 / issue #282',
|
|
status: 'current',
|
|
migration: 'auto-resolved per #316 §3.1-3.3; unhosted is the degraded terminal state',
|
|
compatibility: 'legacy unhosted openings remain readable before migration',
|
|
},
|
|
{
|
|
id: 'spaces[].decor[].width',
|
|
selector: { path: ['spaces', '*', 'decor', '*', 'width'] },
|
|
storage: 'House Plan server config',
|
|
type: 'number',
|
|
default: 'shape default',
|
|
level: 'decor object',
|
|
ui: 'replaced by width_cm',
|
|
runtime: 'read fallback for pixel-identical old rendering',
|
|
introduced: 'legacy decor model',
|
|
status: 'deprecated-read',
|
|
migration: 'convert to width_cm on object edit or explicit plan optimization',
|
|
compatibility: 'keep reading until migrated; canonical width_cm always wins',
|
|
},
|
|
{
|
|
id: 'spaces[].decor[text].size',
|
|
selector: { path: ['spaces', '*', 'decor', '*', 'size'], parent: { key: 'kind', equals: 'text' } },
|
|
storage: 'House Plan server config',
|
|
type: "'s' | 'm' | 'l'",
|
|
default: 'm',
|
|
level: 'decor text',
|
|
ui: 'removed',
|
|
runtime: 'legacy font multiplier fallback',
|
|
introduced: 'legacy decor text model',
|
|
status: 'deprecated-read',
|
|
migration: 'convert to size_cm on text edit or explicit plan optimization',
|
|
compatibility: 'keep reading so old labels remain pixel-identical',
|
|
},
|
|
{
|
|
id: 'spaces[].decor[text].scale',
|
|
selector: { path: ['spaces', '*', 'decor', '*', 'scale'], parent: { key: 'kind', equals: 'text' } },
|
|
storage: 'House Plan server config',
|
|
type: 'number',
|
|
default: '1',
|
|
level: 'decor text',
|
|
ui: 'replaced by size_cm',
|
|
runtime: 'legacy font multiplier fallback; explicit scale wins over size',
|
|
introduced: 'legacy decor text model',
|
|
status: 'deprecated-read',
|
|
migration: 'convert to size_cm on text edit or explicit plan optimization',
|
|
compatibility: 'keep reading so old labels remain pixel-identical',
|
|
},
|
|
...['entity', 'attr', 'unit'].map((field) => ({
|
|
id: `spaces[].decor[text].${field}`,
|
|
selector: { path: ['spaces', '*', 'decor', '*', field], parent: { key: 'kind', equals: 'text' } },
|
|
storage: 'House Plan server config',
|
|
type: field === 'entity' ? 'string | null' : 'string | null',
|
|
default: 'unset',
|
|
level: 'decor text',
|
|
ui: 'replaced by inline {entity[:attribute]} tokens',
|
|
runtime: 'legacy live-text fallback',
|
|
introduced: 'v1.59.0 beta-era live text',
|
|
status: 'deprecated-read',
|
|
migration: 'embed the reference into text on text edit or explicit plan optimization',
|
|
compatibility: 'backend accepts bounded values while old labels remain supported',
|
|
})),
|
|
{
|
|
id: 'settings.decor_default_style',
|
|
selector: { path: ['settings', 'decor_default_style'] },
|
|
storage: 'House Plan server config',
|
|
type: '{ color?, opacity?, width_cm?, fill?, fill_color?, fill_opacity? }',
|
|
default: 'unset (absence IS the factory default)',
|
|
level: 'global',
|
|
ui: 'Background editor main-toolbar picker',
|
|
runtime: 'seeds the editor default decor style once per page (#377)',
|
|
introduced: 'v1.69.0',
|
|
status: 'current',
|
|
migration: 'none — reset to default removes the key',
|
|
compatibility: 'optional; partial keys inherit the factory default per field',
|
|
},
|
|
{
|
|
id: "spaces[].decor[]<kind='furniture'>",
|
|
selector: { path: ['spaces', '*', 'decor', '*', 'kind'], equals: 'furniture' },
|
|
storage: 'House Plan server config',
|
|
type: "decor item variant: symbol + box (x, y, w, h, angle)",
|
|
default: 'n/a',
|
|
level: 'space',
|
|
ui: 'Background editor furniture tool (#159/#359)',
|
|
runtime: 'plan-art rendering, placement preview, stroke zoom (#361)',
|
|
introduced: 'v1.69.0',
|
|
status: 'current',
|
|
migration: 'none',
|
|
compatibility: 'unknown symbols render as generic box; assets ship with the bundle',
|
|
},
|
|
{
|
|
id: "spaces[].decor[]<kind='image'>",
|
|
selector: { path: ['spaces', '*', 'decor', '*', 'kind'], equals: 'image' },
|
|
storage: 'House Plan server config + content-addressed houseplan/assets store',
|
|
type: 'decor item variant: asset_id + box (x, y, w, h, angle, opacity, flip_h, flip_v)',
|
|
default: 'n/a',
|
|
level: 'space',
|
|
ui: 'Background editor Image palette (#51)',
|
|
runtime: 'full/static decor projection through verified signed content',
|
|
introduced: 'v1.71.0',
|
|
status: 'current',
|
|
migration: 'none — additive record; file bytes are never embedded in config',
|
|
compatibility: 'capability-gated writes; missing bytes keep an editor repair placeholder',
|
|
},
|
|
{
|
|
id: "spaces[].openings[].host=partition",
|
|
selector: { path: ['spaces', '*', 'openings', '*', 'host', 'kind'], equals: 'partition' },
|
|
storage: 'House Plan server config',
|
|
type: "{ kind: 'partition', id, t }",
|
|
default: 'n/a',
|
|
level: 'space',
|
|
ui: 'Plan editor: openings hosted on partitions (model v9, v1.68)',
|
|
runtime: 'opening geometry, junction limits, glow cuts',
|
|
introduced: 'v1.68.0',
|
|
status: 'current',
|
|
migration: 'v9 migration resolves opening-vs-zero-wall conflicts (#316)',
|
|
compatibility: 'v8 configs without partition hosts stay valid',
|
|
},
|
|
{
|
|
id: 'spaces[].led_strips',
|
|
selector: { path: ['spaces', '*', 'led_strips'] },
|
|
storage: 'House Plan server config',
|
|
type: "Array<{ id, points: [x, y][], marker: string | null, active?: boolean }>",
|
|
default: 'absent = no strips',
|
|
level: 'space',
|
|
ui: 'Devices editor: LED strip tool, LED tray, device dialog «Show as LED strip / icon» (#780)',
|
|
runtime: 'LED stripe and linear Glow field (lazy chunks), anchor of the bound marker, static card',
|
|
introduced: 'v1.79.0',
|
|
status: 'current',
|
|
migration: 'none: optional array, no model_version bump; orphan links normalise to unbound geometry on write',
|
|
compatibility: 'older clients keep the unknown array; deleting a bound marker unbinds the strip instead of failing',
|
|
},
|
|
]);
|
|
|
|
export const CONFIG_FIELD_STATUSES = Object.freeze([
|
|
'current',
|
|
'decision-required',
|
|
'deprecated-read',
|
|
'migrate-on-write',
|
|
'migrate-on-settings-save',
|
|
'drop-on-validation',
|
|
]);
|