Drop house-mode-modes and add-lovelace-card change proposals

Both fail the "will the maintainer actually use this?" test for a
single-household fork:

- house-mode-modes: state-driven switch flips are what Node-RED already
  does in the CDiT stack. Pushing them into the integration competes
  with Node-RED's purpose and adds HA-core-churn maintenance forever.
- add-lovelace-card: maintainer wouldn't use a custom Lit + TypeScript +
  Vite + HACS plugin card. Stock tile + apexcharts cards in Lovelace
  YAML cover the same need without a frontend ecosystem in a Python repo.

Integration scope stays: sun curve + runtime entities + options flow.
This commit is contained in:
Casey 2026-05-17 22:21:12 +02:00
commit 70983a82f4
5 changed files with 0 additions and 296 deletions

View file

@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-16

View file

@ -1,211 +0,0 @@
## Context
Once `cdit-config-redesign` and `add-runtime-range-controls` are in, each AL profile exposes:
- 3 switches: `switch.adaptive_lighting_<name>`, `_adapt_color`, `_adapt_brightness`.
- 4 `number` entities: `_min_brightness`, `_max_brightness`, `_min_color_temp`, `_max_color_temp`.
- 2 sensor inputs (sun timing): user-configured `sunrise_entity`, `sunset_entity`.
- Internal state via the master switch's `attributes`: `current_brightness`, `current_color_temp`, `sun_position`.
These are scattered across the device page and reachable individually via dashboard cards (`entity` card, `entities` card, etc.), but no single card composes them. Users with multiple AL profiles end up either reading dense entity lists or hand-building entity-card stacks that age poorly.
The integration itself is HA-backend Python. A Lovelace card is HA-frontend code (custom element, TypeScript, runs in the browser). Different runtime, different repo conventions, different build pipeline. The card is *not* a Python module.
Home Assistant's recommended custom-card stack is **Lit** (because HA core is Lit; design tokens and helper types are first-class). Distribution is via **HACS frontend resources**: HACS reads `hacs.json`, fetches the built JS from a release, registers the URL with HA's frontend resource loader. Users install once via HACS and the card is available in the Lovelace card picker.
## Goals / Non-Goals
**Goals:**
- One card per profile that reads "at a glance" — current brightness, current K, current position on the curve, mode (if configured).
- All interactions (toggle, drag) route to HA services. No card-internal state machine.
- Visual editor so dashboard users do not edit YAML to add the card.
- Theming follows HA's surface/text variables; typography and accents are the card's own.
- HACS-installable. Optional one-toggle auto-registration when the integration is configured.
- Accessible: full keyboard reachability, ARIA labels, prefers-reduced-motion respected.
- Bundle size under 100 KB gzipped — this is a single-profile card, not an app.
**Non-Goals:**
- Multi-profile mode (one card showing N profiles in tabs/columns). Stack multiple cards in the dashboard if you need that.
- Editing the AL profile's *configuration* from the card. Configuration stays in the integration's options dialog. The card edits *runtime values* only (the 4 number entities, the 3 switches).
- A standalone web app outside HA. The card runs inside HA's dashboard; the upstream simulator at `webapp/` is a separate concern.
- Server-side rendering, SSR pre-paint, or HA Cast support — out of scope for v1.
- Theme system beyond honoring HA's CSS variables. We do not ship a theme picker.
- Localization beyond English in the first release. Strings will be externalized so other locales can be added later without code changes.
## Decisions
### Decision 1: Card is additive, not a replacement for the device page or entity cards
**What we chose:** The card consumes existing entities. The integration's device page, the individual switches, and the four `number` entities remain available and unaffected. Users who never enable the card see no difference in their setup.
**Why:** Replacement-style cards (hide all the underlying entities, surface only "the card UX") are fragile — a card bug means the user has no fallback. Keeping the underlying entities accessible means the card is a *view*, not a *driver*.
**Alternatives considered:**
- **Replace the device page with the card.** Rejected — outside our reach; HA core renders the device page.
- **Replace the four number entities with card-internal sliders.** Rejected — card state would not survive a frontend reload and would not be automatable.
### Decision 2: Stack is Lit + TypeScript + Vite
**What we chose:** `lit` for the component layer, full TypeScript including HA's `custom-card-helpers` and `home-assistant-js-websocket` types, **Vite** for the build (bundler + dev server + HMR). Single output: a bundled UMD/ESM JS file plus an inline `lovelace-card.js.map`.
**Why:**
- **Lit**: HA core is Lit; using it means the card's `<ha-*>` element usage (e.g., `<ha-switch>`, `<ha-slider>`, `<ha-icon>`) works without translation, and theming via CSS custom properties is native.
- **TypeScript**: HA's frontend types (`HomeAssistant`, `HassEntity`, `LovelaceCard`, `LovelaceCardConfig`) are well-typed; building without TS gives up that safety net for no reason.
- **Vite**: fast dev iteration (HMR against a running HA instance via the frontend dev mode), good tree-shaking, simple `vite build` produces a single bundle without manual Rollup config.
**Alternatives considered:**
- **Preact + Vite.** Rejected — Preact components do not natively render HA's `<ha-*>` elements; you would translate every interaction.
- **Vanilla custom elements (no framework).** Rejected — too much boilerplate; Lit's templating saves real time.
- **Rollup only, no Vite.** Rejected — slower dev loop; the build is no smaller.
### Decision 3: Same repo, separate build pipeline (`lovelace-card/`)
**What we chose:** New top-level directory `lovelace-card/` with its own `package.json`, `node_modules`, `vite.config.ts`, and `tsconfig.json`. The Python integration ignores this directory (already covered by `.gitignore` patterns for `node_modules`); CI gains a `lovelace-card-build` job.
**Why:** Atomic releases — when we bump the integration's version, the card builds in the same release tag. HACS reads both sections of `hacs.json` from the same repo. Splitting into a sibling repo (`cdit-works/lovelace-adaptive-lighting`) would force cross-repo version synchronization for every release.
**Alternatives considered:**
- **Sibling repo.** Rejected — release coordination overhead.
- **Built artifacts committed to git under `custom_components/adaptive_lighting/frontend/`.** Rejected — pollutes the Python package with frontend assets; HACS treats integrations and plugins as separate categories.
### Decision 4: One card = one profile (no multi-profile mode)
**What we chose:** The card config takes exactly one `entry_id` (the AL profile's config entry ID). To show N profiles, the user stacks N cards using HA's standard grid or vertical-stack layouts.
**Why:** Lovelace's composition primitives already solve multi-card layout. Building tabs/columns/grid into the card itself reimplements `stack-in-card`, `vertical-stack`, and `grid-layout`. Cards are best when they are a single coherent unit.
**Alternatives considered:**
- **`profiles: [<id1>, <id2>, ...]` with a tab strip.** Rejected — leaks responsibility into the card.
### Decision 5: Auto-discovery of related entities from the device
**What we chose:** Card config has one user-facing field: `entry` (the AL profile's config entry ID, picked from a dropdown in the visual editor). The card resolves all relevant entities at render time by:
1. Looking up the device tied to that config entry.
2. Reading all entities owned by that device.
3. Classifying them by `unique_id` suffix (`_min_brightness`, `_adapt_color`, etc.).
**Why:** Users do not hand-wire seven entity IDs per card. If we rename an entity in a future integration version, the card finds the new name automatically.
**Alternatives considered:**
- **Explicit `entities: { switch_master: ..., number_min_brightness: ..., ... }` config.** Rejected — verbose, fragile, and pushes integration internals onto the dashboard user.
### Decision 6: Honor HA theme variables, override typography and accents
**What we chose:**
- **Inherit from HA theme**: `--card-background-color`, `--primary-text-color`, `--secondary-text-color`, `--divider-color` — the card's outer surface and base text colors come from the user's chosen HA theme so the card sits naturally on any dashboard.
- **Override locally**: typography stack (display: an editorial sans-serif like *Mona Sans* or *Söhne*; body: a paired sans; numerals: tabular monospace `Söhne Mono` or `Berkeley Mono`), spacing scale, and a single accent color that follows the current color temperature output (warm for low K, neutral for ~4000 K, cool for high K).
- **Fonts loaded from a self-hosted woff2 in the bundle** so cards do not hit a third-party font CDN.
**Why:** HA themes are diverse — pure-white, pure-black, dramatic accent-color themes, etc. Inheriting structural colors means the card fits in. Overriding typography is what makes the card *the* card instead of "another HA card."
**Alternatives considered:**
- **Full custom palette ignoring HA theme.** Rejected — the card would look out of place on a user's themed dashboard.
- **Pure inheritance (no typography override).** Rejected — defeats the purpose of building a distinctive card.
### Decision 7: Curve rendered as static SVG with a single animated marker
**What we chose:** The 24-hour brightness curve is precomputed once per minute by the card from the user's `min/max_brightness` numbers and the sun-event timestamps (via the integration's exposed attributes), drawn as a single SVG `<path>`. The "you are here" marker is a small filled `<circle>` whose `cx` updates every 30 seconds via a property change.
**Why:** SVG path is cheaper than a `<canvas>` redraw, and a tiny CSS transition on the marker's `cx` is smoother than re-rendering. Curves at this scale (~150 pts) are trivial.
**Alternatives considered:**
- **Canvas-based curve.** Rejected — overkill; SVG handles this without breaking a sweat.
- **No curve, just numbers.** Rejected — the curve is the card's signature visual.
### Decision 8: Slider drags debounced 300 ms before calling `number.set_value`
**What we chose:** Each of the four range sliders tracks an internal "draft" value during drag. The HA service call (`number.set_value`) fires 300 ms after the user releases the slider (or after 300 ms of pointer-still during a continuous drag).
**Why:** Without debouncing, dragging a slider from 1% to 80% emits ~50 service calls. With debouncing, it emits 12. Lower bus traffic, less state churn, and the card still feels responsive because the visual position updates instantly from the draft value.
**Alternatives considered:**
- **Fire on every change event.** Rejected — wastes service calls and trips up automations watching the entity.
- **Fire only on `change` (full release).** Rejected — feels laggy when the user adjusts mid-drag.
### Decision 9: HACS frontend resource distribution, with optional integration auto-registration
**What we chose:**
- **Primary distribution**: `hacs.json` declares the card as a frontend plugin alongside the integration. HACS users install via "Frontend → Adaptive Lighting (CDiT) Card."
- **Optional auto-register**: in the integration's *global* options (Diagnostics section), a single toggle `register_card_resource` (default OFF). When the user enables it, on next setup the integration calls `lovelace.resources.async_create_item(...)` with the card's expected URL, so the user does not need to manually add a resource entry in Lovelace.
- **Resource URL**: `/hacsfiles/adaptive-lighting/cdit-adaptive-lighting-card.js` — matches HACS's standard file serving path.
**Why:** HACS users get a one-click install. Power users who do not use HACS or who keep frontend resources Strictly Managed™ can leave auto-register off and add the resource themselves. The toggle is off by default because silently registering frontend resources during a backend install crosses a layer the user might not expect.
**Alternatives considered:**
- **Always auto-register.** Rejected — too magical; the integration touching `lovelace.resources` without permission feels invasive.
- **Never auto-register, document only.** Rejected — the manual-add path is a known HACS UX wart; if we can save the click, we should.
### Decision 10: Visual direction is "editorial-instrument"
**What we chose:** A specific aesthetic point of view, not a stack of generic UI primitives:
```
Layout idea (one card, ~360 wide):
┌──────────────────────────────────────────────────────┐
│ KITCHEN │ ← profile name, small caps display font
│ ────── │ ← thin rule, theme-divider color
│ │
│ 72 % · 3 240 K │ ← hero numerals, tabular mono, ~28pt
│ brightness color temperature │ ← micro caption, body sans, ~11pt
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ╭───────────────╮ │ │
│ │ ● ╲ │ │ ← static SVG curve; only the
│ │ you-are ╲ ●now dot moves
│ │ ──╯ here ╰── │
│ │ 04:51 19:34 │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ Adaptive ● Color ● Brightness ● │ ← switches as toggles
│ │
│ ── 5 % ──────────────────────────── 90 % ── │ ← double-ended sliders
│ ── 2 200 K ──────────────────────── 4 800 K ── │
│ │
│ House mode: Day → full adaptive │ ← optional, shown only if house-mode-modes configured
└──────────────────────────────────────────────────────┘
```
**Why:** Calm typography + a single curve + restrained color is more "instrument panel" than "dashboard widget" — the card communicates *what the lights are doing*, not *what knobs you can turn*. The card itself shifts subtly in tone with the current color temperature (background tint warms after sunset, cools mid-day), which makes the dashboard feel alive without animation noise.
**Alternatives considered:**
- **Skeuomorphic dimmer/dial UI.** Rejected — feels gimmicky after the first day.
- **Pure data card (numbers in a grid, no curve).** Rejected — loses the "at a glance" property; the curve is the differentiator.
- **Card-shifts-color too aggressively (full background = current K).** Rejected — fights the user's chosen dashboard theme; subtle accent tint only.
## Risks / Trade-offs
- **[Lit + Vite + npm in a Python repo]** → Adds a JS toolchain to the repo. Anyone working on the integration without touching the card can ignore it (no Python dependency on `lovelace-card/`), but CI gains a parallel build job. Mitigation: dedicated `lovelace-card-build` workflow, isolated from the integration's test workflow.
- **[HA frontend API breaking changes]** → HA frontend has historically broken minor APIs (especially around `LovelaceCard` lifecycle). Mitigation: pin `home-assistant-js-websocket` and `custom-card-helpers` to known-good versions; the card declares its minimum HA version (matches the integration's, pinned to 2025.1+).
- **[`<ha-slider>` and `<ha-switch>` internals]** → We use HA's built-in elements, which means we are coupled to whatever HA ships. Mitigation: if HA renames or restyles one of these, the card breaks visually but not behaviorally — failure mode is cosmetic and recoverable.
- **[Bundle size]** → Lit + helpers is ~30 KB gzipped baseline. Plus our code. Plus fonts. Mitigation: keep one display font and one body font; subset to the glyphs we use; lazy-load nothing because there is only one screen.
- **[Resource auto-registration races with HACS install]** → If the user enables auto-register before the card file exists at the URL, HA logs a 404 every time it tries to load Lovelace resources. Mitigation: integration writes the resource only when the card file is present on disk (check via the static path); document the order (install card via HACS first, then enable auto-register).
- **[Accessibility for the curve]** → A pretty SVG curve is not screen-reader-accessible without explicit `aria-label` text describing current values. Mitigation: a visually hidden `<div aria-live="polite">` mirrors the hero numerals; the curve gets `role="img"` with an accessible name like "Brightness curve, currently 72% at 3240 K."
- **[Long-press disambiguation on mobile]** → Long-press to open more-info conflicts with HA's standard tap behavior. Mitigation: follow HA's `action-handler` convention exactly; let the dashboard's tap/hold/double-tap actions config override card defaults.
## Migration Plan
- **Install order**: this change is a net-new resource. No migration of existing data.
- **First-time install (HACS path)**:
1. User installs the CDiT integration via HACS as normal.
2. User installs the "Adaptive Lighting (CDiT) Card" frontend resource via HACS.
3. User adds a card to a dashboard; the visual editor lets them pick an AL profile.
- **First-time install (no HACS, manual)**:
1. Copy `dist/cdit-adaptive-lighting-card.js` from the release tarball into `<HA config>/www/`.
2. Add a resource entry: `url: /local/cdit-adaptive-lighting-card.js, type: module`.
3. Restart frontend, add card to dashboard.
- **Optional auto-register flow**:
1. After HACS install, user opens the integration's global options.
2. Enables `register_card_resource` toggle in Diagnostics.
3. On next reload, the integration writes the resource entry. User no longer manages it manually.
**Rollback:** Remove the card from any dashboards using it; uninstall via HACS (or delete from `www/`). The integration and all entities remain functional — the card is purely additive.
## Open Questions
- **Font choice**: Mona Sans + tabular Mona Sans Mono, or Söhne + Söhne Mono, or a freer choice (Inter Display + JetBrains Mono)? Söhne is licensed; Mona Sans is open. Lean Mona Sans for license cleanness in an open-source plugin.
- **Curve refresh cadence**: 30 seconds (smooth-feeling) vs 60 seconds (cheaper). At 30 s the marker glides; at 60 s it ticks. Lean 30 s.
- **Card width breakpoints**: at ~360 px the layout above works. At narrower (mobile column dashboards, ~280 px), the curve gets short. Drop the time labels under the curve? Or scale down the hero numerals? Lean: drop time labels at ≤300 px width.
- **Bundle distribution**: ship one combined bundle (~80 KB) or split into `card.js` + `editor.js` so the editor only loads in dashboard-edit mode (~50 KB + 30 KB)? Lean split — the editor lazy-loads behind dashboard-edit-mode anyway.
- **Should the card show forecast** (where the curve will be in N hours) on hover? Could be a nice "I am thinking about my house" gesture. Lean: defer to v1.1.
- **House-mode badge interaction**: tap to cycle the source `input_select`? Or read-only? Lean read-only for v1 — cycling modes is a house-wide concern, not a per-profile one.

View file

@ -1,40 +0,0 @@
## Why
The CDiT-redesigned Adaptive Lighting integration exposes its surface through HA's standard mechanisms: a device page, three switches per profile, four `number` entities once `add-runtime-range-controls` lands, and an options dialog. That surface is *functional* but not *unified* — the user assembles their own mental picture from rows in the entity list, scattered slider entities, and a config dialog they only see during setup. Day-to-day, the question "what is the kitchen actually doing right now?" requires reading three or four entities and computing.
This change adds a **dedicated Lovelace card** that places all of an AL profile's state on one screen: current brightness and color temperature, position on the 24-hour curve, the three runtime switches, the four range sliders, and (if configured) the active house mode. The card is **additive** — it does not replace the device page or the existing entities; it composes them into a single visual home for a profile.
> **Status**: proposal + design complete. Specs and tasks deferred until prerequisites (`cdit-config-redesign`, `add-runtime-range-controls`) land.
## What Changes
- **New top-level directory `lovelace-card/`** in this repo. Self-contained TypeScript + Lit + Vite frontend project. Builds to a single bundled JS file (`cdit-adaptive-lighting-card.js`) that registers a `<cdit-adaptive-lighting-card>` custom element with HA's `customCards` registry.
- **One card per Adaptive Lighting profile.** Card config takes a single field: the profile's config entry ID (auto-discovered from the user's installed AL configs in the visual editor). The card finds all related entities (switches, numbers, sun-time sensors) by walking the device.
- **Sections rendered**: header with profile name, "Now: <brightness>% · <K> K" hero, 24-hour curve with current position marker, three switches as inline toggles, four range sliders, optional house-mode badge with current mode value.
- **Interactions are HA-native services**: switch toggles call `switch.toggle`, slider drags call `number.set_value` (debounced 300 ms after drag end), long-press opens HA's standard more-info dialog for the underlying entity.
- **Editor**: the card ships a Lovelace visual editor (`<cdit-adaptive-lighting-card-editor>`) so dashboard users don't hand-edit YAML.
- **Distribution: HACS frontend resource**, plus optional auto-registration. When the integration is installed and the user enables a config-flow toggle "Register Lovelace card resource automatically", the integration writes the Lovelace resource entry via `lovelace.resources` so the user does not have to add it manually.
- **Bundled with the integration release**`manifest.json` of the integration declares the card's HACS metadata so HACS treats them as one package.
- **Theming**: card respects HA's CSS theme variables for surface and text colors but overrides typography (a specific display + body pairing) and adds its own warm/cool accent that follows the current color-temperature output.
## Capabilities
### New Capabilities
- `lovelace-card`: dashboard card for a single Adaptive Lighting profile. Covers card configuration model, auto-discovery of related entities, the rendered visual layout, interaction-to-service mapping, the visual editor, theming behavior, accessibility surface, and the distribution / auto-registration flow.
### Modified Capabilities
None. This is purely additive — no existing capability changes its requirements.
## Impact
- **`lovelace-card/`** (new directory): TypeScript source under `src/`, Vite build config, package.json with Lit + HA-frontend-types dependencies, README with install instructions, a `dist/` output.
- **`hacs.json`** at repo root: extend with a `"plugin"` section so HACS treats the built card as an installable frontend resource alongside the integration.
- **`custom_components/adaptive_lighting/__init__.py`**: optional `async_register_lovelace_resource()` helper that writes the resource URL into HA's lovelace storage when the auto-register option is enabled.
- **`custom_components/adaptive_lighting/config_flow.py`**: one new field in the integration's *global* (not per-entry) options — `register_card_resource` boolean — sitting in the Diagnostics section. Default off.
- **`custom_components/adaptive_lighting/const.py`**: const for the resource URL (`/hacsfiles/adaptive-lighting/cdit-adaptive-lighting-card.js`) plus an off-by-default `CONF_REGISTER_CARD_RESOURCE`.
- **Tests**: card has its own test setup (Vitest + Playwright for visual regression on key states); integration tests for the auto-register helper.
- **No changes to existing entities or services.** The card consumes the public HA state machine.
**Sequencing**: depends on `cdit-config-redesign` (3-switch model, sun-entity sensors) and `add-runtime-range-controls` (the four `number` entities the sliders bind to). Strictly later than both. Independent of and parallel-able with `house-mode-modes`.

View file

@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-16

View file

@ -1,41 +0,0 @@
## Why
A CDiT house already organizes itself around a "house mode" (`input_select.house_mode` or similar) with values like `morning`, `day`, `evening`, `night`, `away`, `guest`. Adaptive Lighting today is mode-blind: enabling AL only during `morning` / `day` / `evening` while disabling adaptation during `away` and forcing fixed brightness in `guest` requires N HA automations per AL × M modes, with no single place to read the truth.
This change makes the house mode binding native to each AL config: pick the house mode source entity once, fill in a per-mode behavior matrix, and the integration drives its three runtime switches (`master`, `adapt_color`, `adapt_brightness`) on every `state_changed` event from the source entity.
> **Status**: stub proposal. Specs, design, and tasks to be written when work on this change starts. Depends on `cdit-config-redesign` landing first; can land in parallel with `add-runtime-range-controls`.
## What Changes
- **New "House mode" section in the options flow** (added to the layout established by `cdit-config-redesign`). Lives between "Sun schedule" and "Light control". Collapsed by default if no source entity is configured, expanded once one is selected.
- **New field `house_mode_entity`** — entity selector. Accepts any entity (`input_select`, `sensor`, `input_text`) — strict typing isn't appropriate because users build mode entities from many sources.
- **Per-mode behavior matrix** — dynamically rendered once a source entity is selected. For each mode value (auto-discovered from `input_select.options` when available; manually enumerable for other entity types), three booleans: `active`, `adapt_color`, `adapt_brightness`.
- **State listener** — integration subscribes to `state_changed` events for the configured `house_mode_entity`. On change, looks up the new state value in the matrix and asserts the three runtime switches accordingly.
- **Strict semantics** (TBD in design): every mode change re-asserts the table; manual user overrides between mode transitions are not preserved across the next transition. Alternative "sticky" semantics deferred until design.
- **Graceful degradation**:
- Source entity unavailable → no switch changes, log a warning once.
- State value not in the matrix → log an info entry, leave switches untouched.
- Matrix is empty (no `house_mode_entity` configured) → integration behaves as if this feature does not exist.
- **No new runtime control entities** — this change operates only on the existing three switches created per AL config.
## Capabilities
### New Capabilities
- `house-mode-binding`: per-AL coupling between an external HA mode entity and the three runtime switches. Covers entity-source configuration, mode discovery, behavior-matrix data model, state-change handling, override semantics (strict by default), and degradation under missing/unavailable source entities.
### Modified Capabilities
- `options-flow`: gains a new "House mode" section with a driver field (`house_mode_entity`) and a conditional matrix renderer. Spec delta will capture this as ADDED Requirements + a MODIFIED Requirement on the section layout list.
## Impact
- **`custom_components/adaptive_lighting/config_flow.py`** — new section, new field, dynamic matrix rendering based on the discovered mode list of the selected entity.
- **`custom_components/adaptive_lighting/__init__.py`** — `async_setup_entry` subscribes to `state_changed` for the configured `house_mode_entity` via `hass.helpers.event.async_track_state_change_event`.
- **`custom_components/adaptive_lighting/switch.py`** — `AdaptiveSwitch` (the master class) gains an `_apply_house_mode(mode_value)` method that flips the three runtime switches per the configured matrix.
- **`custom_components/adaptive_lighting/const.py`** — `CONF_HOUSE_MODE_ENTITY`, `CONF_HOUSE_MODE_MATRIX` constants; default matrix shape (empty dict).
- **`tests/test_house_mode_binding.py`** — new file. Tests: mode change flips switches per matrix; unknown mode value is logged but doesn't crash; entity unavailable triggers warn-once behavior; empty matrix is a no-op; mode-discovery from `input_select.options`; manual mode list for non-select entities.
- **No new runtime dependencies.**
**Sequencing**: depends on `cdit-config-redesign`. May land in parallel with `add-runtime-range-controls` (no overlap). Open architectural question for design: strict vs sticky override semantics.