/** * 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: 'card.tap_action', 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', 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', selector: { path: ['settings', 'group_lights'] }, storage: 'House Plan server config', type: 'boolean', default: 'unset', level: 'global', ui: 'hidden', 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', }, { id: 'settings.exclude_integrations', selector: { path: ['settings', 'exclude_integrations'] }, storage: 'House Plan server config', type: 'string[]', default: 'unset', 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', }, { id: 'settings.weather_entity', 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: 'markers[].display=ripple', 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', 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', 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', 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[].open_to', selector: { path: ['spaces', '*', 'rooms', '*', 'open_to'] }, storage: 'House Plan server config', type: 'string[] | null', default: 'unset', level: 'room', ui: 'replaced by boundary spans', runtime: 'read fallback when explicit open_spans are absent', introduced: 'legacy virtual-boundary model', status: 'deprecated-read', migration: 'materialize exact open_spans before geometry changes', compatibility: 'keep reading until every supported config can represent the same geometry losslessly', }, { 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', })), ]); export const CONFIG_FIELD_STATUSES = Object.freeze([ 'decision-required', 'deprecated-read', 'migrate-on-write', 'migrate-on-settings-save', 'drop-on-validation', ]);