From ada3538449b56537d56c2c321f794928d668758b Mon Sep 17 00:00:00 2001 From: Casey Date: Fri, 5 Jun 2026 10:49:40 +0200 Subject: [PATCH] Rename range sliders to lower/upper + friendlier new-profile defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Device page sorts entities alphabetically by name, so 'Min/Max …' always listed max above min ('a' < 'i'). Rename the four range numbers to 'Brightness lower/upper' and 'Color temp lower/upper' so each quantity's lower bound sorts first. Pin suggested_object_id to the field key so new profiles keep slugging number._min_brightness etc., identical to profiles created before the rename; existing entity_ids are registry- preserved via unchanged unique_ids. New-profile defaults: brightness 5-100% -> 10-90% (5% proved too dim on gloomy days; 90% leaves manual-boost headroom), min color temp 2200K -> 2000K (deeper warm tone won in practice). Existing profiles unaffected — saved options and restored slider values take precedence. Specs synced: runtime-range-controls naming table + scenarios, output-sensors collision references. Two new tests cover the slug pin and the lower/upper friendly-name composition. --- custom_components/adaptive_lighting/const.py | 26 ++++++++++------- custom_components/adaptive_lighting/number.py | 10 +++++++ .../adaptive_lighting/strings.json | 8 ++--- .../adaptive_lighting/translations/en.json | 8 ++--- openspec/specs/output-sensors/spec.md | 4 +-- openspec/specs/runtime-range-controls/spec.md | 13 +++++---- tests/test_color_and_brightness.py | 2 +- tests/test_number_platform.py | 29 +++++++++++++++++++ 8 files changed, 73 insertions(+), 27 deletions(-) diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index 93c86217..6144849e 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -51,20 +51,22 @@ DOCS[CONF_INITIAL_TRANSITION] = "Fade time when a light first turns on, in secon CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90 DOCS[CONF_INTERVAL] = "How often to recompute and re-apply the curve, in seconds." -CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 100 +# CDiT default: 90 instead of upstream's 100. Leaves headroom so a manual +# "brighter please" bump above the curve is always possible. +CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 90 DOCS[CONF_MAX_BRIGHTNESS] = "Brightness at the peak of the day, in percent." CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP = "max_color_temp", 5500 DOCS[CONF_MAX_COLOR_TEMP] = "Color temperature at the peak of the day, in Kelvin." -# CDiT default: 5 instead of upstream's 1. 1% reads as off on most bulbs; -# 5% is the dim-but-visible floor. -CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS = "min_brightness", 5 +# CDiT default: 10 instead of upstream's 1. 1% reads as off on most bulbs +# (and 5% proved too dim on gloomy days); 10% is the usable floor. +CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS = "min_brightness", 10 DOCS[CONF_MIN_BRIGHTNESS] = "Brightness during the night, in percent." -# CDiT default: 2200 K instead of upstream's 2000 K. 2000 K reads as -# sodium-vapor orange; 2200 K is a softer warm-lamp tone. -CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2200 +# CDiT default: 2000 K (matches upstream). Earlier the fork used 2200 K +# ("sodium-vapor orange" worry), but in practice the deeper warm tone won. +CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2000 DOCS[CONF_MIN_COLOR_TEMP] = "Color temperature during the night, in Kelvin." CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR = "prefer_rgb_color", False @@ -165,12 +167,14 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" # `field_key` becomes the entity's unique-id suffix; the `conf_key` ties # the entity back to its initial-seed value in `entry.options`. # Design decisions 5, 6, 11 — see add-runtime-range-controls/design.md. +# Naming: "lower/upper" (not "min/max") so the device page — which sorts +# entities alphabetically by name — lists min before max for each quantity. RANGE_ENTITIES: list[dict[str, Any]] = [ { "field_key": "min_brightness", "conf_key": CONF_MIN_BRIGHTNESS, "default": DEFAULT_MIN_BRIGHTNESS, - "name": "Min brightness", + "name": "Brightness lower", "native_min": 1, "native_max": 100, "step": 1, @@ -181,7 +185,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [ "field_key": "max_brightness", "conf_key": CONF_MAX_BRIGHTNESS, "default": DEFAULT_MAX_BRIGHTNESS, - "name": "Max brightness", + "name": "Brightness upper", "native_min": 1, "native_max": 100, "step": 1, @@ -192,7 +196,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", + "name": "Color temp lower", "native_min": 1000, "native_max": 10000, "step": 100, @@ -203,7 +207,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", + "name": "Color temp upper", "native_min": 1000, "native_max": 10000, "step": 100, diff --git a/custom_components/adaptive_lighting/number.py b/custom_components/adaptive_lighting/number.py index b5e0a92c..04c79ec0 100644 --- a/custom_components/adaptive_lighting/number.py +++ b/custom_components/adaptive_lighting/number.py @@ -100,6 +100,16 @@ class AdaptiveRangeNumber(RestoreNumber): # async_added_to_hass overrides with a restored or just-saved value. self._attr_native_value = self._options_value() + @property + def suggested_object_id(self) -> str | None: + """Pin entity_id slugs to the field key (e.g. ``_min_brightness``). + + The display names use "lower/upper" wording (device-page sort order), + but entity_ids stay on the min/max field keys so new profiles slug + identically to ones created before the rename. + """ + return self._field_key + @property def device_info(self) -> DeviceInfo: """Group with the profile's switches under one device.""" diff --git a/custom_components/adaptive_lighting/strings.json b/custom_components/adaptive_lighting/strings.json index 78a23427..d5a94352 100644 --- a/custom_components/adaptive_lighting/strings.json +++ b/custom_components/adaptive_lighting/strings.json @@ -259,16 +259,16 @@ "entity": { "number": { "min_brightness": { - "name": "Min brightness" + "name": "Brightness lower" }, "max_brightness": { - "name": "Max brightness" + "name": "Brightness upper" }, "min_color_temp": { - "name": "Min color temp" + "name": "Color temp lower" }, "max_color_temp": { - "name": "Max color temp" + "name": "Color temp upper" } }, "sensor": { diff --git a/custom_components/adaptive_lighting/translations/en.json b/custom_components/adaptive_lighting/translations/en.json index badb49c8..68ab99b9 100644 --- a/custom_components/adaptive_lighting/translations/en.json +++ b/custom_components/adaptive_lighting/translations/en.json @@ -247,16 +247,16 @@ "entity": { "number": { "min_brightness": { - "name": "Min brightness" + "name": "Brightness lower" }, "max_brightness": { - "name": "Max brightness" + "name": "Brightness upper" }, "min_color_temp": { - "name": "Min color temp" + "name": "Color temp lower" }, "max_color_temp": { - "name": "Max color temp" + "name": "Color temp upper" } }, "sensor": { diff --git a/openspec/specs/output-sensors/spec.md b/openspec/specs/output-sensors/spec.md index ff5a5f61..f020ba7c 100644 --- a/openspec/specs/output-sensors/spec.md +++ b/openspec/specs/output-sensors/spec.md @@ -139,7 +139,7 @@ The resulting friendly names SHALL follow this table for a profile named `Dining | Output-color-temp sensor | `"Output color temp"` | `Dining MVP Output color temp` | | Sun-elevation sensor | `"Sun elevation"` | `Dining MVP Sun elevation` | -The chosen role labels SHALL NOT collide with any existing entity's `_attr_name` on the same device (specifically: not `"Brightness"`, which is the adapt-brightness switch's role per `add-runtime-range-controls`, and not `"Min color temp"` / `"Max color temp"`, which are the range-number roles). +The chosen role labels SHALL NOT collide with any existing entity's `_attr_name` on the same device (specifically: not `"Brightness"`, which is the adapt-brightness switch's role per `add-runtime-range-controls`, and not `"Color temp lower"` / `"Color temp upper"`, which are the range-number roles). #### Scenario: Friendly names compose from device name + sensor role @@ -155,7 +155,7 @@ The chosen role labels SHALL NOT collide with any existing entity's `_attr_name` - **WHEN** all ten entities' friendly names are inspected - **THEN** no two entities SHALL share the same friendly name - **AND** specifically the adapt-brightness switch ("Dining MVP Brightness") and the output-brightness sensor ("Dining MVP Output brightness") SHALL be distinguishable strings -- **AND** the output-color-temp sensor ("Dining MVP Output color temp") SHALL be distinguishable from the "Min color temp" and "Max color temp" number entities +- **AND** the output-color-temp sensor ("Dining MVP Output color temp") SHALL be distinguishable from the "Color temp lower" and "Color temp upper" number entities ### Requirement: Ambient lux sensor mirrors the configured lux sensor's reading diff --git a/openspec/specs/runtime-range-controls/spec.md b/openspec/specs/runtime-range-controls/spec.md index 9bab9328..e0bca606 100644 --- a/openspec/specs/runtime-range-controls/spec.md +++ b/openspec/specs/runtime-range-controls/spec.md @@ -116,10 +116,12 @@ The resulting friendly names SHALL follow this table for a profile named `Dining | Master switch | `None` | `Dining MVP` | | Adapt-brightness switch | `"Brightness"` | `Dining MVP Brightness` | | Adapt-color switch | `"Color"` | `Dining MVP Color` | -| Min brightness number | `"Min brightness"` | `Dining MVP Min brightness` | -| Max brightness number | `"Max brightness"` | `Dining MVP Max brightness` | -| Min color temp number | `"Min color temp"` | `Dining MVP Min color temp` | -| Max color temp number | `"Max color temp"` | `Dining MVP Max color temp` | +| Min brightness number | `"Brightness lower"` | `Dining MVP Brightness lower` | +| Max brightness number | `"Brightness upper"` | `Dining MVP Brightness upper` | +| Min color temp number | `"Color temp lower"` | `Dining MVP Color temp lower` | +| Max color temp number | `"Color temp upper"` | `Dining MVP Color temp upper` | + +The range numbers use "lower/upper" wording (not "Min/Max") so the HA device page — which sorts entities alphabetically by friendly name — lists each quantity's lower bound before its upper bound. The number entities SHALL additionally pin `suggested_object_id` to their field key (`min_brightness`, `max_brightness`, `min_color_temp`, `max_color_temp`) so newly created profiles slug the same entity_ids as profiles created before the rename. Existing `unique_id`s SHALL remain unchanged; the entity registry SHALL preserve existing `entity_id`s for any deployed install. @@ -130,7 +132,8 @@ Existing `unique_id`s SHALL remain unchanged; the entity registry SHALL preserve - **THEN** the master switch's friendly name SHALL be exactly "Dining MVP" - **AND** the adapt-brightness switch's friendly name SHALL be exactly "Dining MVP Brightness" - **AND** the adapt-color switch's friendly name SHALL be exactly "Dining MVP Color" -- **AND** the four range number entities' friendly names SHALL be "Dining MVP Min brightness", "Dining MVP Max brightness", "Dining MVP Min color temp", "Dining MVP Max color temp" +- **AND** the four range number entities' friendly names SHALL be "Dining MVP Brightness lower", "Dining MVP Brightness upper", "Dining MVP Color temp lower", "Dining MVP Color temp upper" +- **AND** the four range number entities' entity_ids SHALL slug from the field keys (e.g. `number.dining_mvp_min_brightness`), not from the display names #### Scenario: Existing entity_ids survive the rename diff --git a/tests/test_color_and_brightness.py b/tests/test_color_and_brightness.py index 2b7750a0..a5ba8b3a 100644 --- a/tests/test_color_and_brightness.py +++ b/tests/test_color_and_brightness.py @@ -27,7 +27,7 @@ T_SUNSET = dt.datetime(2026, 3, 21, 18, 0, 0, tzinfo=UTC) @pytest.fixture def settings() -> SunLightSettings: - """Standard CDiT defaults — 5–100 % brightness, 2200–5500 K.""" + """Fixed test range — 5-100 % brightness, 2200-5500 K (explicit, not DEFAULT_*).""" return SunLightSettings( name="test", min_brightness=5, diff --git a/tests/test_number_platform.py b/tests/test_number_platform.py index 4ddd1c9b..d59c6534 100644 --- a/tests/test_number_platform.py +++ b/tests/test_number_platform.py @@ -79,6 +79,35 @@ async def test_four_range_entities_registered(hass) -> None: assert eid.startswith("number.") +# --------------------------------------------------------------------------- +# Naming — "lower/upper" display names, but entity_ids slug from field keys +# (suggested_object_id pin keeps new profiles consistent with pre-rename ones) +# --------------------------------------------------------------------------- + + +async def test_entity_ids_slug_from_field_keys(hass) -> None: + """Renamed display names must not leak into entity_id slugs.""" + entry = await _setup_entry(hass) + for field_key in FIELD_KEYS: + eid = _resolve_entity_id(hass, entry, field_key) + assert eid == f"number.{PROFILE_NAME}_{field_key}" + + +async def test_friendly_names_use_lower_upper_wording(hass) -> None: + """Device page sorts alphabetically; lower/upper puts min above max.""" + expected_roles = { + "min_brightness": "Brightness lower", + "max_brightness": "Brightness upper", + "min_color_temp": "Color temp lower", + "max_color_temp": "Color temp upper", + } + entry = await _setup_entry(hass) + for field_key, role in expected_roles.items(): + eid = _resolve_entity_id(hass, entry, field_key) + state = hass.states.get(eid) + assert state.attributes["friendly_name"] == f"{PROFILE_NAME} {role}" + + # --------------------------------------------------------------------------- # 7.3 — Same device as the profile's switches # ---------------------------------------------------------------------------