From 61be07c17948bed93e4740f2337da9c7d76a2b6a Mon Sep 17 00:00:00 2001 From: Casey Date: Sun, 31 May 2026 21:41:57 +0200 Subject: [PATCH] Fix new-group defaults + tidy section collapse defaults Thread 2 (bug): runtime range number entities seeded from native_min on a fresh profile (only a name stored at setup), so new groups ran at max_brightness=1% and a 1000-1000K color-temp range until the options dialog was saved once. Each RANGE_ENTITIES row now carries a sensible DEFAULT_* and _options_value() falls back to it. Adds a regression test. Thread 3: collapse 'Sun schedule' and 'Light control' by default (now 2 open / 5 closed): Targets + Daytime curve expanded, the rest collapsed. Existing profiles unaffected (their options are already saved); only newly-created groups get the corrected seed. Bump 2.3.0 -> 2.3.1-cdit.1. --- .../adaptive_lighting/config_flow.py | 4 +-- custom_components/adaptive_lighting/const.py | 4 +++ .../adaptive_lighting/manifest.json | 2 +- custom_components/adaptive_lighting/number.py | 14 ++++++-- tests/test_number_platform.py | 36 +++++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/custom_components/adaptive_lighting/config_flow.py b/custom_components/adaptive_lighting/config_flow.py index 3f3ff68d..f87d447b 100644 --- a/custom_components/adaptive_lighting/config_flow.py +++ b/custom_components/adaptive_lighting/config_flow.py @@ -234,7 +234,7 @@ def _build_options_schema( ): _sun_event_selector(), }, ), - {"collapsed": False}, + {"collapsed": True}, ) # An illuminance EntitySelector rejects an empty string, so we must NOT @@ -278,7 +278,7 @@ def _build_options_schema( ): BooleanSelector(), }, ), - {"collapsed": False}, + {"collapsed": True}, ) advanced_schema: dict[Any, Any] = { diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index 4dbdf62e..93c86217 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -169,6 +169,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [ { "field_key": "min_brightness", "conf_key": CONF_MIN_BRIGHTNESS, + "default": DEFAULT_MIN_BRIGHTNESS, "name": "Min brightness", "native_min": 1, "native_max": 100, @@ -179,6 +180,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [ { "field_key": "max_brightness", "conf_key": CONF_MAX_BRIGHTNESS, + "default": DEFAULT_MAX_BRIGHTNESS, "name": "Max brightness", "native_min": 1, "native_max": 100, @@ -189,6 +191,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [ { "field_key": "min_color_temp", "conf_key": CONF_MIN_COLOR_TEMP, + "default": DEFAULT_MIN_COLOR_TEMP, "name": "Min color temp", "native_min": 1000, "native_max": 10000, @@ -199,6 +202,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [ { "field_key": "max_color_temp", "conf_key": CONF_MAX_COLOR_TEMP, + "default": DEFAULT_MAX_COLOR_TEMP, "name": "Max color temp", "native_min": 1000, "native_max": 10000, diff --git a/custom_components/adaptive_lighting/manifest.json b/custom_components/adaptive_lighting/manifest.json index 728284fd..adb7b660 100644 --- a/custom_components/adaptive_lighting/manifest.json +++ b/custom_components/adaptive_lighting/manifest.json @@ -9,5 +9,5 @@ "iot_class": "calculated", "issue_tracker": "https://github.com/CaseyRo/adaptive-lighting/issues", "requirements": ["ulid-transform"], - "version": "2.3.0-cdit.1" + "version": "2.3.1-cdit.1" } diff --git a/custom_components/adaptive_lighting/number.py b/custom_components/adaptive_lighting/number.py index d08e3694..b5e0a92c 100644 --- a/custom_components/adaptive_lighting/number.py +++ b/custom_components/adaptive_lighting/number.py @@ -49,6 +49,7 @@ async def async_setup_entry( entry=config_entry, field_key=row["field_key"], conf_key=row["conf_key"], + default=row["default"], display_name=row["name"], native_min=row["native_min"], native_max=row["native_max"], @@ -74,6 +75,7 @@ class AdaptiveRangeNumber(RestoreNumber): entry: ConfigEntry, field_key: str, conf_key: str, + default: float, display_name: str, native_min: float, native_max: float, @@ -85,6 +87,7 @@ class AdaptiveRangeNumber(RestoreNumber): self._entry = entry self._field_key = field_key self._conf_key = conf_key + self._default = default self._attr_name = display_name self._attr_translation_key = field_key self._attr_unique_id = f"{entry.entry_id}_{field_key}" @@ -108,10 +111,17 @@ class AdaptiveRangeNumber(RestoreNumber): ) def _options_value(self) -> float: - """Read the seed value from entry.options (typed cast).""" + """Read the seed value from entry.options (typed cast). + + On a freshly-created profile neither ``entry.options`` nor + ``entry.data`` carries the range keys (setup only stores the name), + so fall back to the field's sensible ``DEFAULT_*`` rather than the + slider's ``native_min`` — otherwise a new group would come up at + max_brightness=1% and a 1000-1000K color-temp range. + """ raw = self._entry.options.get(self._conf_key) if raw is None: - raw = self._entry.data.get(self._conf_key, self._attr_native_min_value) + raw = self._entry.data.get(self._conf_key, self._default) return float(raw) async def async_added_to_hass(self) -> None: diff --git a/tests/test_number_platform.py b/tests/test_number_platform.py index 14723fdc..4ddd1c9b 100644 --- a/tests/test_number_platform.py +++ b/tests/test_number_platform.py @@ -25,6 +25,10 @@ from custom_components.adaptive_lighting.const import ( CONF_MIN_BRIGHTNESS, CONF_MIN_COLOR_TEMP, CONFIG_ENTRY_VERSION, + DEFAULT_MAX_BRIGHTNESS, + DEFAULT_MAX_COLOR_TEMP, + DEFAULT_MIN_BRIGHTNESS, + DEFAULT_MIN_COLOR_TEMP, DOMAIN, ) @@ -126,6 +130,38 @@ async def test_number_entity_attributes(hass, field_key, expected) -> None: assert attrs["mode"] == NumberMode.SLIDER +# --------------------------------------------------------------------------- +# 7.4b — Fresh profile (only a name) seeds sensible DEFAULT_*, not native_min +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + ("field_key", "expected_default"), + [ + ("min_brightness", DEFAULT_MIN_BRIGHTNESS), + ("max_brightness", DEFAULT_MAX_BRIGHTNESS), + ("min_color_temp", DEFAULT_MIN_COLOR_TEMP), + ("max_color_temp", DEFAULT_MAX_COLOR_TEMP), + ], +) +async def test_new_profile_seeds_sensible_defaults( + hass, + field_key, + expected_default, +) -> None: + """A profile created with only a name must come up at DEFAULT_*. + + Regression: the seed previously fell back to the slider's native_min, + so a brand-new group ran at max_brightness=1% and a collapsed + 1000-1000K color-temp range until the options dialog was saved once. + """ + entry = await _setup_entry(hass) # data={name}, options={} + eid = _resolve_entity_id(hass, entry, field_key) + state = hass.states.get(eid) + assert state is not None + assert float(state.state) == float(expected_default) + + # --------------------------------------------------------------------------- # 7.5 — async_set_native_value does NOT write to entry.options # ---------------------------------------------------------------------------