Skip to content

Cherry-pick upstream #1462 + #1379 (expand_light_groups + race-condition regression test)#12

Merged
florianhorner merged 6 commits into
mainfrom
florianhorner/jean-claude
Apr 19, 2026
Merged

Cherry-pick upstream #1462 + #1379 (expand_light_groups + race-condition regression test)#12
florianhorner merged 6 commits into
mainfrom
florianhorner/jean-claude

Conversation

@florianhorner
Copy link
Copy Markdown
Owner

Summary

Batch cherry-pick from upstream basnijholt/adaptive-lighting while Bas is focused on other projects.

Merged

Skipped

Two PRs from the original batch proposal were found to be incompatible with the current fork state:

Test plan

  • Conflicts resolved, no merge markers remain
  • JSON translation files validate
  • Full pytest suite run against core@2026.2.2: 35 pre-existing failures (unchanged from fork/main baseline), 0 new regressions, 1 intentional xfail for the documented upstream bug
  • Manual smoke test on HA (optional; no runtime-critical changes in this batch)

Generated with Claude Code.

basnijholt and others added 5 commits January 11, 2026 10:15
Regression test for basnijholt#1373

This test verifies that when `separate_turn_on_commands: true` is enabled
and a light is turned off between split commands, the second command
should be skipped.

The bug occurs because the proactive adaptation context is never cleared
when `light.turn_off` is called, causing the off-check to be bypassed
in `_execute_adaptation_calls`.

This test currently FAILS (demonstrating the bug) and will PASS after
the fix is implemented.
Some light group entities act as a proxy that must receive a single combined
`light.turn_on` call to function correctly — for example virtual mixers like
<https://github.com/mion00/color-temperature-light-mixer> for instance,
that blend a warm and a cold white channel into one entity. In such setups the
individual member entities only expose `ColorMode.BRIGHTNESS`, so sending
separate per-member commands bypasses the mixing logic.

Setting `expand_light_groups: false` keeps the group entity in `self.lights`
instead of expanding it to its members. Adaptation commands go to the group,
and the interceptor no longer skips group entities for that switch.

Default is `true` — no behaviour change for existing configurations.
Conflicts resolved:
- const.py: keep both CONF_ENABLE_DIAGNOSTIC_SENSORS (fork) and
  new CONF_EXPAND_LIGHT_GROUPS validator entries
- strings.json / en.json / de.json: keep fork's 5-step layout;
  add expand_light_groups strings alongside existing entries
…mmands race condition (basnijholt)

Conflicts resolved:
- tests/test_switch.py: kept fork's test_automation_turn_on_from_off_not_marked_as_manual_control
  (from basnijholt#1378 fix) and appended upstream's test_separate_turn_on_commands_respects_light_off_state
  at end of file so both regression tests coexist.
The test documents an unfixed bug (proactive adaptation context not
cleared on light.turn_off, causing second split command to be sent
to an off light). Upstream basnijholt#1379 added the test without the fix, so
we xfail it until the bug is addressed.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7fe675a3-821c-4993-9e01-f45f07f5c488

📥 Commits

Reviewing files that changed from the base of the PR and between bc69d6f and 5ceffa0.

📒 Files selected for processing (1)
  • custom_components/adaptive_lighting/const.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build
  • GitHub Check: Run pytest (2025.12.5, 3.13.2)
  • GitHub Check: Run pytest (2025.11.3, 3.13.2)
  • GitHub Check: Run pytest (dev, 3.14.2)
  • GitHub Check: Run pytest (2025.8.3, 3.13.2)
  • GitHub Check: Run pytest (2026.2.2, 3.13.2)
  • GitHub Check: Run pytest (2025.7.4, 3.13.2)
  • GitHub Check: Run pytest (2026.1.3, 3.13.2)
  • GitHub Check: Run pytest (2025.6.3, 3.13.2)
  • GitHub Check: Run pytest (2024.12.5, 3.12.0)
  • GitHub Check: Run pytest (2025.3.4, 3.13.0)
  • GitHub Check: Run pytest (2025.2.5, 3.13.0)
  • GitHub Check: Run pytest (2025.10.4, 3.13.2)
  • GitHub Check: Run pytest (2025.1.4, 3.12.0)
  • GitHub Check: Run pytest (2025.4.4, 3.13.0)
  • GitHub Check: Run pytest (2025.9.4, 3.13.2)
  • GitHub Check: Run pytest (2025.5.3, 3.13.2)
  • GitHub Check: markdown-code-runner
  • GitHub Check: build (linux/arm64)
  • GitHub Check: build (linux/amd64)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit configuration file

Focus on: error handling, type safety, async correctness, and missing edge cases. Flag any hardcoded credentials or API keys.

Files:

  • custom_components/adaptive_lighting/const.py
🔇 Additional comments (3)
custom_components/adaptive_lighting/const.py (3)

330-335: expand_light_groups constant and docs are correctly introduced.

The option is clearly defined with a safe default (True) and explicit behavior documentation for both modes.


471-471: Validation wiring is complete for the new option.

Adding the bool tuple ensures consistent schema validation and default handling.


598-598: Options-flow exposure is correctly wired.

This closes the configuration UX gap by making expand_light_groups configurable in the 5-step flow.


📝 Walkthrough

Walkthrough

Introduces a new boolean configuration option expand_light_groups (default true) and wires it through constants, localization, switch logic (conditional group expansion and manager skip behavior), plus an xfail regression test covering turn-on behavior when lights are turned off.

Changes

Cohort / File(s) Summary
Configuration & Constants
custom_components/adaptive_lighting/const.py
Added CONF_EXPAND_LIGHT_GROUPS = "expand_light_groups" and DEFAULT_EXPAND_LIGHT_GROUPS = True; registered as a boolean in validation tuples and included in step options.
Localization & UI
custom_components/adaptive_lighting/strings.json, custom_components/adaptive_lighting/translations/en.json, custom_components/adaptive_lighting/translations/de.json
Added expand_light_groups option labels and descriptions under options.step.init.data and options.step.init.data_description explaining expand vs. control-as-group behavior.
Switch & Core Logic
custom_components/adaptive_lighting/switch.py
Added self._expand_light_groups_flag from config in _set_changeable_settings(); modified _expand_light_groups() to respect the flag (skip expansion when false); updated manager logic so light-group entities are only excluded when expansion is enabled.
Tests
tests/test_switch.py
Added test_separate_turn_on_commands_respects_light_off_state() (marked xfail) that asserts proactive split turn-on adaptation does not issue turn_on calls after the light is immediately turned off and that the light remains off.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title exceeds the 72-character limit (89 characters) and does not follow conventional commits format (feat:, fix:, chore:, docs:, refactor:). Reformat the title to follow conventional commits (e.g., 'feat: add expand_light_groups option and regression test') and keep it under 72 characters.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive, directly related to the changeset, and clearly documents the cherry-picked upstream PRs, conflicts resolved, and test implications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch florianhorner/jean-claude

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
custom_components/adaptive_lighting/strings.json (1)

26-27: ⚠️ Potential issue | 🟠 Major

Remove the raw URL from this description.

The current hassfest failure is for this field: strings.json descriptions cannot contain URLs. Move the link to description placeholders or remove it, otherwise the validation job will keep failing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/adaptive_lighting/strings.json` around lines 26 - 27, The
"description" value under the "Essentials" entry in strings.json contains a raw
URL which violates hassfest; remove the raw link from the description (or
replace it with a neutral phrase like "see our web app for interactive graphs"
using no URL) and, if you need to preserve the link, move it into a separate
description placeholder or resources field instead of the user-facing
"description" string so the strings.json entry (key "description" for title
"Essentials") no longer contains an inline URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/adaptive_lighting/const.py`:
- Around line 330-335: The option CONF_EXPAND_LIGHT_GROUPS (with
DEFAULT_EXPAND_LIGHT_GROUPS) is defined but not exposed in the options flow; add
CONF_EXPAND_LIGHT_GROUPS to the STEP_OPTIONS["init"] configuration so it appears
in the 5-step options UI (ensure it follows the same pattern as other boolean
options in STEP_OPTIONS["init"] and uses the same label/translation key used in
DOCS). Locate STEP_OPTIONS["init"] in the options flow code and insert a
reference to CONF_EXPAND_LIGHT_GROUPS alongside the other option keys so the UI
renders and persists this setting.

In `@custom_components/adaptive_lighting/switch.py`:
- Around line 955-956: The code mutates self.lights during expansion so toggling
_expand_light_groups_flag and calling _expand_light_groups() loses the original
configured group entities; preserve the original configured light IDs by adding
and using a separate attribute (e.g. self._configured_lights or
self._original_lights) that is set once from the incoming config and never
overwritten by expansions, update _expand_light_groups() to derive expanded
all_lights from that preserved list instead of self.lights, and ensure
change_switch_settings (the path that toggles _expand_light_groups_flag) and any
code that updates manager.lights use the preserved configured list to restore
group entities when expansion is turned off.
- Around line 2003-2008: The intercept was updated to allow group entities when
switch._expand_light_groups_flag is False, but callers still resolve switches
with the default expand_light_groups=True, causing group-targeted calls to miss
the configured switch; update handle_apply, handle_set_manual_control,
set_light_status_for_switches, and reset to pass the switch's
_expand_light_groups_flag (or False when referenced) into calls to
_switch_with_lights() and _switches_with_lights() so they resolve group entities
consistently with the switch configuration, ensuring group-targeted service
calls find the correct switch and avoid NoSwitchFoundError or skipped updates.

In `@custom_components/adaptive_lighting/translations/de.json`:
- Around line 37-38: Add the missing German helper text for the translation key
options.step.init.data_description.expand_light_groups by adding the appropriate
German string to the translations JSON (the key currently missing in
translations/de.json); locate the expand_light_groups entry used alongside
room_preset in the same JSON and insert the German equivalent of the English
helper (explaining that expand_light_groups controls whether group entities are
expanded to individual member entities (`true`) or the group entity is
controlled directly (`false`)), ensuring proper JSON quoting and comma placement
to match surrounding entries.

In `@tests/test_switch.py`:
- Around line 3039-3049: The variable name turn_off_time is misleading because
it's used as a boolean flag; rename it to after_turn_off everywhere it's
declared and referenced (including its initial assignment and within the async
function track_turn_on_calls where it sets "after_turn_off": turn_off_time is
not None), and update any other uses (e.g., places that set it to True/False or
check it) so that the flag semantics match the new name and the test list
turn_on_calls continues to record the "after_turn_off" value correctly.

---

Outside diff comments:
In `@custom_components/adaptive_lighting/strings.json`:
- Around line 26-27: The "description" value under the "Essentials" entry in
strings.json contains a raw URL which violates hassfest; remove the raw link
from the description (or replace it with a neutral phrase like "see our web app
for interactive graphs" using no URL) and, if you need to preserve the link,
move it into a separate description placeholder or resources field instead of
the user-facing "description" string so the strings.json entry (key
"description" for title "Essentials") no longer contains an inline URL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f33d395b-0f67-4739-aa8f-80046c912046

📥 Commits

Reviewing files that changed from the base of the PR and between 5951fc6 and bc69d6f.

📒 Files selected for processing (6)
  • custom_components/adaptive_lighting/const.py
  • custom_components/adaptive_lighting/strings.json
  • custom_components/adaptive_lighting/switch.py
  • custom_components/adaptive_lighting/translations/de.json
  • custom_components/adaptive_lighting/translations/en.json
  • tests/test_switch.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Run pytest (2026.2.2, 3.13.2)
  • GitHub Check: Run pytest (dev, 3.14.2)
  • GitHub Check: Run pytest (2025.8.3, 3.13.2)
  • GitHub Check: Run pytest (2025.9.4, 3.13.2)
  • GitHub Check: Run pytest (2025.6.3, 3.13.2)
  • GitHub Check: Run pytest (2025.11.3, 3.13.2)
  • GitHub Check: Run pytest (2025.12.5, 3.13.2)
  • GitHub Check: Run pytest (2025.7.4, 3.13.2)
  • GitHub Check: Run pytest (2025.3.4, 3.13.0)
  • GitHub Check: Run pytest (2024.12.5, 3.12.0)
  • GitHub Check: Run pytest (2026.1.3, 3.13.2)
  • GitHub Check: Run pytest (2025.5.3, 3.13.2)
  • GitHub Check: Run pytest (2025.10.4, 3.13.2)
  • GitHub Check: Run pytest (2025.4.4, 3.13.0)
  • GitHub Check: Run pytest (2025.2.5, 3.13.0)
  • GitHub Check: Run pytest (2025.1.4, 3.12.0)
  • GitHub Check: build
  • GitHub Check: pre-commit
  • GitHub Check: markdown-code-runner
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/arm64)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

⚙️ CodeRabbit configuration file

Focus on: error handling, type safety, async correctness, and missing edge cases. Flag any hardcoded credentials or API keys.

Files:

  • custom_components/adaptive_lighting/const.py
  • custom_components/adaptive_lighting/switch.py
  • tests/test_switch.py
**/tests/**

⚙️ CodeRabbit configuration file

Prioritize missing edge case coverage and regression risks over style nits.

Files:

  • tests/test_switch.py
🪛 GitHub Actions: Validate with hassfest
custom_components/adaptive_lighting/strings.json

[error] 1-1: hassfest TRANSLATIONS: Invalid strings.json for integration 'adaptive_lighting' (/github/workspace/custom_components/adaptive_lighting/). The string should not contain URLs. Replace the URL with description placeholders for dictionary value @ data['options']['step']['init']['description']. Got 'Pick your lights and set brightness and color ranges. Most users only need this page. For interactive graphs, visit this web app.'

🔇 Additional comments (3)
tests/test_switch.py (3)

2998-3018: LGTM! Well-documented regression test.

The xfail(strict=False) marker is appropriate for documenting an unresolved upstream bug. The docstring clearly explains the race condition: proactive adaptation context isn't cleared on light.turn_off, causing the second split command to bypass the off-check in switch.py:1378-1400 (as shown in the relevant code snippets).


3066-3104: LGTM! Correctly captures the race condition timing.

The test flow properly exercises the bug scenario:

  1. Turn on with tracked context (starts proactive adaptation with split commands)
  2. Verify proactive context is set
  3. Immediately turn off (before second split command completes)
  4. Wait for all adaptation tasks via asyncio.gather
  5. Check if proactive context persists (root cause indicator)

The use of blocking=True for service calls combined with asyncio.gather for adaptation tasks correctly captures the race condition window.


3107-3144: LGTM! Comprehensive bug verification with clear failure messages.

The assertions check all manifestations of the documented bug:

  1. Proactive context remaining set after turn_off (root cause)
  2. Light state being ON after turn_off was called
  3. Any light.turn_on calls occurring after turn_off

The entity ID check correctly handles both single-entity and multi-entity service call formats. The detailed pytest.fail() messages clearly explain the expected fix per the relevant code snippets (clearing proactive context when light.turn_off is detected).

Comment thread custom_components/adaptive_lighting/const.py
Comment on lines +955 to 956
self._expand_light_groups_flag = data[CONF_EXPAND_LIGHT_GROUPS]
self._expand_light_groups() # updates manual control timers
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve the original configured light list when toggling this flag.

_expand_light_groups() now derives the next all_lights from self.lights, but self.lights has already been mutated by prior expansions. After one expand_light_groups=True pass, switching it back to false via change_switch_settings can no longer restore the group entity, and the old member IDs stay registered in manager.lights.

As per coding guidelines, focus on missing edge cases.

Also applies to: 1040-1043

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/adaptive_lighting/switch.py` around lines 955 - 956, The
code mutates self.lights during expansion so toggling _expand_light_groups_flag
and calling _expand_light_groups() loses the original configured group entities;
preserve the original configured light IDs by adding and using a separate
attribute (e.g. self._configured_lights or self._original_lights) that is set
once from the incoming config and never overwritten by expansions, update
_expand_light_groups() to derive expanded all_lights from that preserved list
instead of self.lights, and ensure change_switch_settings (the path that toggles
_expand_light_groups_flag) and any code that updates manager.lights use the
preserved configured list to restore group entities when expansion is turned
off.

Comment on lines +2003 to +2008
# Never adapt on light groups when expanding, because HA will make a separate light.turn_on
or (
switch._expand_light_groups_flag
and (e := self.hass.states.get(entity_id))
and _is_light_group(e)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Group-preserving mode still breaks group-targeted service resolution.

Allowing group entities through this intercept path is only half of the change. handle_apply, handle_set_manual_control, set_light_status_for_switches, and reset still resolve switches through _switch_with_lights() / _switches_with_lights() with the default expand_light_groups=True. For a switch configured with expand_light_groups=False, a call that targets the group entity no longer matches its switch and can raise NoSwitchFoundError or skip reset/status updates.

As per coding guidelines, focus on missing edge cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/adaptive_lighting/switch.py` around lines 2003 - 2008, The
intercept was updated to allow group entities when
switch._expand_light_groups_flag is False, but callers still resolve switches
with the default expand_light_groups=True, causing group-targeted calls to miss
the configured switch; update handle_apply, handle_set_manual_control,
set_light_status_for_switches, and reset to pass the switch's
_expand_light_groups_flag (or False when referenced) into calls to
_switch_with_lights() and _switches_with_lights() so they resolve group entities
consistently with the switch configuration, ensuring group-targeted service
calls find the correct switch and avoid NoSwitchFoundError or skipped updates.

Comment on lines +37 to +38
"room_preset": "Room preset",
"expand_light_groups": "expand_light_groups: Lichtgruppen auf einzelne Mitgliedsentitäten erweitern (`true`) oder die Gruppenentität direkt steuern (`false`)."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add the German helper text for expand_light_groups.

strings.json and translations/en.json both define options.step.init.data_description.expand_light_groups, but translations/de.json still omits it. German users will get a missing or fallback-English helper text for the new option.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/adaptive_lighting/translations/de.json` around lines 37 -
38, Add the missing German helper text for the translation key
options.step.init.data_description.expand_light_groups by adding the appropriate
German string to the translations JSON (the key currently missing in
translations/de.json); locate the expand_light_groups entry used alongside
room_preset in the same JSON and insert the German equivalent of the English
helper (explaining that expand_light_groups controls whether group entities are
expanded to individual member entities (`true`) or the group entity is
controlled directly (`false`)), ensuring proper JSON quoting and comma placement
to match surrounding entries.

Comment thread tests/test_switch.py
Comment on lines +3039 to +3049
turn_off_time = None

async def track_turn_on_calls(event: Event) -> None:
if (
event.data.get("domain") == LIGHT_DOMAIN
and event.data.get("service") == SERVICE_TURN_ON
):
turn_on_calls.append(
{"event": event, "after_turn_off": turn_off_time is not None},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: turn_off_time is misleading variable name.

The variable is used as a boolean flag (True) but is named as if it stores a timestamp. Consider renaming to after_turn_off for clarity.

💡 Suggested rename for clarity
-    turn_off_time = None
+    turn_off_happened = False

     async def track_turn_on_calls(event: Event) -> None:
         if (
             event.data.get("domain") == LIGHT_DOMAIN
             and event.data.get("service") == SERVICE_TURN_ON
         ):
             turn_on_calls.append(
-                {"event": event, "after_turn_off": turn_off_time is not None},
+                {"event": event, "after_turn_off": turn_off_happened},
             )

And at line 3086:

-    turn_off_time = True
+    turn_off_happened = True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_switch.py` around lines 3039 - 3049, The variable name
turn_off_time is misleading because it's used as a boolean flag; rename it to
after_turn_off everywhere it's declared and referenced (including its initial
assignment and within the async function track_turn_on_calls where it sets
"after_turn_off": turn_off_time is not None), and update any other uses (e.g.,
places that set it to True/False or check it) so that the flag semantics match
the new name and the test list turn_on_calls continues to record the
"after_turn_off" value correctly.

@basnijholt
Copy link
Copy Markdown

Hi Florian, you’re right. I’m kind of distracted by other projects. It’s also that I get a little overwhelmed when looking at all the PRs. Some of them change 4,000 lines, whereas the entire codebase is approximately that size. Even though I use AI myself, I’m a little skeptical about hitting merge without looking at it in detail. After all, 50,000 to 100,000 people use the code.

Without this, the option is defined in VALIDATION_TUPLES but invisible
in the 5-step options flow UI — users can't toggle it and it stays at
the default True forever. Caught by /plan-eng-review on PR #12.
@florianhorner florianhorner merged commit e3aeb33 into main Apr 19, 2026
5 of 26 checks passed
@florianhorner florianhorner deleted the florianhorner/jean-claude branch April 19, 2026 23:09
florianhorner added a commit that referenced this pull request Apr 20, 2026
…ning (#13)

* test: full coverage for expand_light_groups option

Adds 5 unit tests filling the P2 gap from /plan-eng-review on PR #12.
The new option's non-default branch had zero coverage; these lock in
behavior before users start flipping it (e.g. for Lightener, Hue
rooms, or Zigbee2MQTT groups).

- test_expand_light_groups_const_wiring: default True, VALIDATION_TUPLES,
  STEP_OPTIONS[workarounds] UI wiring, DOCS entry — catches the exact
  class of bug that required the follow-up STEP_OPTIONS fix in #12.
- test_expand_light_groups_false_keeps_group_entity: group stays in
  manager.lights, members are NOT tracked individually.
- test_expand_light_groups_true_expands_to_members: default behavior
  still produces the member-level tracking, preventing accidental
  regression.
- test_expand_light_groups_false_non_group_unaffected: no-op for
  non-group lights — safe for users who flip the flag without groups.
- test_expand_light_groups_false_group_turn_on_reaches_group: the
  core behavioral guarantee — a user turn_on on a group entity must
  reach the group, not get silently rerouted to members. This is
  what unblocks Lightener.

Full suite: 35 pre-existing failures (unchanged baseline), 0 new
regressions, 1 intentional xfail from PR basnijholt#1379.

* feat: warn on silent group expansion + adapt-cycle/manual-control tests

Given the Lightener curve card work depends on expand_light_groups=False
being honored everywhere, this commit closes three silent-failure gaps
from the follow-up review on PR #13:

Gap 1 — adapt-cycle dispatch test
  test_expand_light_groups_false_adapt_cycle_dispatches_to_group
  Spies on _adapt_light to lock in that the periodic adaptation cycle
  dispatches to the GROUP entity_id, not its members, when flag=False.
  Without this, the intercept path could behave correctly while the
  time-interval-driven adaptation silently bypasses Lightener curves on
  every adapt cycle.

Gap 2 — manual-control attribution test
  test_expand_light_groups_false_no_false_manual_control
  With take_over_control=True + flag=False, internal member state
  changes (Lightener fan-out, Hue bridge sync) must NOT populate
  manager.manual_control. Otherwise every adapt cycle would mark
  members as manually controlled and the next cycle would skip them
  entirely — a slow death spiral with curves that eventually never fire.

Gap 3 — one-time warning when AL expands a light group
  switch.py: AdaptiveSwitch._expand_light_groups now emits a WARNING
  (debounced via manager.expand_light_groups_warned set) pointing the
  user at the expand_light_groups: false flag. Fires only from the
  switch-configured code path; utility expansions in _switches_with_lights
  stay silent so matching doesn't spam the log.

  test_expand_light_groups_true_emits_warning_once asserts the warning
  fires exactly once per entity per HA run.
  test_expand_light_groups_false_does_not_warn asserts the warning
  stays silent for users who already opted in to the fix.

Full suite: 35 pre-existing failures (unchanged baseline), 0 new
regressions. 9/9 new expand_light_groups tests passing. 1 intentional
xfail from basnijholt#1379 untouched.

---------

Co-authored-by: Florian Horner <florianhorner@macbook-pro-von-florian.tail6f28f8.ts.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants