Add color helper integration - #177605
Conversation
Replaces the earlier storage-collection input_color port with the config-entry helper pattern. Stores a color (chromatic or white CCT, optional brightness) as a restorable helper entity with derived representations, a color_params attribute splattable into light.turn_on, and scene reproduce support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Color conversion, scene restoration, persisted-data handling, icon clearing, and quality-scale compliance have unresolved issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds a config-entry-based Color helper for storing reusable color values, brightness, and derived representations.
Changes:
- Adds color normalization, persistence, services, scenes, and config/options flows.
- Adds UI metadata, translations, generated registrations, and ownership.
- Adds comprehensive integration and unit tests.
File summaries
| File | Description |
|---|---|
.strict-typing |
Enables strict typing. |
CODEOWNERS |
Adds integration ownership. |
homeassistant/components/color/__init__.py |
Registers entities and services. |
homeassistant/components/color/color_math.py |
Implements color conversion. |
homeassistant/components/color/config_flow.py |
Adds configuration flows. |
homeassistant/components/color/const.py |
Defines integration constants. |
homeassistant/components/color/entity.py |
Implements state and persistence. |
homeassistant/components/color/icons.json |
Defines service icons. |
homeassistant/components/color/manifest.json |
Declares integration metadata. |
homeassistant/components/color/reproduce_state.py |
Adds scene reproduction. |
homeassistant/components/color/services.yaml |
Defines service selectors. |
homeassistant/components/color/strings.json |
Adds user-facing strings. |
homeassistant/generated/config_flows.py |
Registers the helper flow. |
homeassistant/generated/integrations.json |
Adds generated metadata. |
script/hassfest/quality_scale.py |
Adds quality-scale exemptions. |
tests/components/color/__init__.py |
Initializes the test package. |
tests/components/color/test_color_math.py |
Tests color conversion. |
tests/components/color/test_config_flow.py |
Tests configuration flows. |
tests/components/color/test_init.py |
Tests setup and unloading. |
tests/components/color/test_reproduce_state.py |
Tests scene restoration. |
tests/components/color/test_services.py |
Tests service behavior. |
tests/components/color/test_tricky_paths.py |
Tests restoration and edge cases. |
Review details
- Files reviewed: 20/22 changed files
- Comments generated: 6
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
- Set quality_scale internal in the manifest (hassfest new-integration gate; matches threshold/derivative/schedule) - Use a typed HassKey instead of hass.data[DOMAIN] (pylint W7405) - Reject zero-intensity color inputs, which have no chromaticity - Validate restored xy payloads (length, finite) and kind - Prefer the canonical xy attribute over the derived hex state when reproducing scene snapshots - Store the options-flow icon explicitly so clearing it does not resurrect the creation-time icon Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Entities lack config-entry registry association, and the chromatic default fails its selector schema.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (6)
homeassistant/components/color/color_math.py:207
- Document that this preserves the exact sRGB value, not the exact input bytes. The function deliberately canonicalizes the result to an uppercase
#RRGGBB, so the current wording promises provenance that the returned value cannot retain.
For inputs that map cleanly to a single sRGB triple (hex/rgb/hs/
color_name) we echo back the exact bytes the user supplied without losing
them to the xy gamut round-trip. For xy/kelvin inputs there is no
canonical "source hex" so we return None.
homeassistant/components/color/color_math.py:43
- Strip at most one leading
#so malformed hex values are rejected.lstrip("#")accepts inputs such as##FF0000, even though the action contract is an optional single prefix followed by six hexadecimal digits.
This issue also appears on line 204 of the same file.
stripped = hex_value.strip().lstrip("#")
homeassistant/components/color/config_flow.py:63
- Use an RGB list as the
ColorRGBSelectordefault. The selector validates a three-byte list, so submitting this step without changing the field applies"#FFFFFF"as the default and fails schema validation instead of creating the helper.
vol.Required(
CONF_INITIAL_COLOR, default=DEFAULT_HEX
): selector.ColorRGBSelector(),
script/hassfest/quality_scale.py:230
- Classify this new internal helper under
NO_QUALITY_SCALEinstead of the grandfathered missing-file list.validate_iqs_fileexplicitly directs new internal integrations there atscript/hassfest/quality_scale.py:2133-2135, and analogous internal helpers such asinput_numberare listed inNO_QUALITY_SCALE.
"color",
script/hassfest/quality_scale.py:1165
- Remove this
INTEGRATIONS_WITHOUT_SCALEexemption when moving the internal helper toNO_QUALITY_SCALE. This list is only consulted when a quality-scale file exists (script/hassfest/quality_scale.py:2174-2199), while this integration intentionally has no such file.
"color",
homeassistant/components/color/const.py:27
- Describe
source_hexas a normalized sRGB equivalent rather than an exact-byte echo. The implementation strips the prefix/whitespace and uppercases hex values, while RGB, HS, and color-name inputs do not contain source bytes to echo.
# Exact-byte echo of the user's input when it had a clear hex equivalent
# (hex/rgb/hs/color_name). Null for xy/kelvin inputs.
- Files reviewed: 20/22 changed files
- Comments generated: 1
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Entity registry association, selector defaults, black input handling, and restored-data validation need correction.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (4)
homeassistant/components/color/init.py:130
- Add the entity through a config-entry-backed
EntityPlatform. Calling the root component'sasync_add_entitiesuses the domain platform, whoseconfig_entryisNone;entity_platform.py:1014-1021therefore registers this entity withoutconfig_entry_id, so config-entry cleanup cannot remove its registry entry and the helper leaves an orphan when deleted.
await component.async_add_entities([entity])
homeassistant/components/color/entity.py:100
- Reject restored payloads that violate the canonical value ranges. This check accepts out-of-range
xy, brightness, and Kelvin values (and ignores the stored version), so corrupted restore data such as brightness-1is exposed incolor_paramseven though the public action schemas reject it, breaking the promised directly-splattable payload instead of falling back to the configured initial value.
if not (isfinite(x) and isfinite(y)) or kind not in (
KIND_CHROMATIC,
KIND_WHITE,
):
return None
homeassistant/components/color/config_flow.py:131
- Reject pure black in the chromatic flow instead of silently creating a white helper.
ColorRGBSelectoraccepts[0, 0, 0],_coerce_color_inputturns it into#000000, and_initial_canonicallater catchesnormalize's pure-black error and falls back toDEFAULT_HEX, leaving the user with a different color (and an inconsistentsource_hex).
CONF_INITIAL_COLOR: _coerce_color_input(
user_input.get(CONF_INITIAL_COLOR)
),
homeassistant/components/color/config_flow.py:63
- Use an RGB list as the
ColorRGBSelectordefault. The selector validates values as three-element integer lists (helpers/selector.py:657-660), so accepting this"#FFFFFF"default without changing the field fails schema validation instead of creating the helper.
This issue also appears on line 129 of the same file.
vol.Required(
CONF_INITIAL_COLOR, default=DEFAULT_HEX
): selector.ColorRGBSelector(),
- Files reviewed: 21/23 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
Routes entity setup through EntityComponent.async_setup_entry so the entity registry entry is linked to its config entry and cleaned up automatically on entry removal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Entity-registry association, selector defaults, and malformed-state handling contain blocking correctness issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
homeassistant/components/color/reproduce_state.py:69
- Catch validation failures from malformed snapshot color attributes and continue reproducing brightness. An old or manually defined snapshot can contain an invalid Kelvin or xy value, causing this blocking service call to raise and abort
asyncio.gather, so this state and potentially other scene states are not reproduced despite the documented malformed-snapshot resilience.
await hass.services.async_call(
homeassistant/components/color/entity.py:100
- Reject restored payloads that violate the canonical invariants before applying them. This check accepts out-of-range xy/brightness/Kelvin values, a white value with no Kelvin, and any schema version; such cache data then exposes invalid attributes (for example brightness
999) and an invalidcolor_paramspayload instead of falling back to the configured initial value.
if not (isfinite(x) and isfinite(y)) or kind not in (
KIND_CHROMATIC,
KIND_WHITE,
):
return None
- Files reviewed: 22/24 changed files
- Comments generated: 2
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Restore validation, malformed scene handling, and the RGB selector default contain correctness issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (16)
homeassistant/components/color/entity.py:100
- Validate restored fields against the canonical invariants before applying them. A payload with
kind="white"and no Kelvin, out-of-range xy, or brightness outside 0–255 is currently accepted and can expose inconsistent or invalidcolor_paramsafter restart.
if not (isfinite(x) and isfinite(y)) or kind not in (
KIND_CHROMATIC,
KIND_WHITE,
):
return None
homeassistant/components/color/entity.py:95
- Catch
OverflowErrorwhile decoding restored numeric fields. A malformed stored Kelvin or brightness such asInfinityreachesint(...)and currently escapes this fallback path, causing entity setup to fail instead of using the initial value.
except KeyError, TypeError, ValueError:
return None
homeassistant/components/color/config_flow.py:63
- Use an RGB sequence as the
ColorRGBSelectordefault. The selector validates only three-item lists, so omitting the field applies"#FFFFFF"and then fails schema validation instead of creating the helper with its advertised default.
vol.Required(
CONF_INITIAL_COLOR, default=DEFAULT_HEX
): selector.ColorRGBSelector(),
homeassistant/components/color/entity.py:80
- Reject restore payloads whose schema version does not match. The payload writes
STATE_SCHEMA_VERSIONbut never reads it, so missing or future incompatible schemas are silently interpreted as version 1 instead of falling back to the configured initial value.
try:
homeassistant/components/color/color_math.py:43
- Strip at most one optional
#prefix. Usinglstrip("#")accepts malformed values such as##FF0000as valid hex input instead of raisingColorInputError.
stripped = hex_value.strip().lstrip("#")
homeassistant/components/color/reproduce_state.py:58
- Validate or isolate malformed color attributes before calling the service. A two-item but invalid
xy_color(or an invalid white Kelvin) reaches the service schema, whose exception propagates throughgather, prevents the later brightness restoration, and can abort reproduction of the whole scene despite this function's stated malformed-snapshot resilience.
if attrs.get(ATTR_KIND) == KIND_WHITE and attrs.get(ATTR_COLOR_TEMP_KELVIN):
color_data = {FIELD_KELVIN: attrs[ATTR_COLOR_TEMP_KELVIN]}
elif isinstance(xy, (list, tuple)) and len(xy) == 2:
# Canonical xy beats the derived hex state (hex -> xy is lossy).
color_data = {FIELD_XY: list(xy)}
homeassistant/components/color/reproduce_state.py:79
- Skip malformed snapshot brightness values instead of letting them abort scene reproduction. Values outside the service schema (for example 999 or a non-numeric string) currently raise from
async_calland propagate throughgatherafter the color has already been changed.
if ATTR_BRIGHTNESS in attrs:
snapshot_brightness = attrs[ATTR_BRIGHTNESS]
if snapshot_brightness is None:
homeassistant/components/color/color_math.py:98
- Convert overflowed Kelvin inputs into
ColorInputErroras well.int(math.inf)raisesOverflowError, so a corrupted config entry can escape_initial_canonical's fallback and fail entity setup.
try:
value = int(kelvin)
except (TypeError, ValueError) as err:
raise ColorInputError(
f"color_temp_kelvin must be an int, got {kelvin!r}"
) from err
homeassistant/components/color/entity.py:151
- Treat overflowed initial brightness as invalid instead of failing setup. A corrupted entry containing an infinite numeric value reaches
int(brightness), which raisesOverflowErrorand bypasses the intendedNonefallback.
try:
value = int(brightness)
except TypeError, ValueError:
return None
homeassistant/components/color/const.py:27
- Describe
source_hexas normalized rather than byte-exact.compute_source_hexuppercases the digits, adds#, and converts non-hex shapes, so this comment currently promises preservation the implementation does not provide.
# Exact-byte echo of the user's input when it had a clear hex equivalent
# (hex/rgb/hs/color_name). Null for xy/kelvin inputs.
homeassistant/components/color/color_math.py:208
- Describe the result as a normalized hex equivalent rather than the user's exact bytes. The return expression always rebuilds and uppercases the value, so case, prefix, and original representation are not preserved.
"""Return the literal hex equivalent of the user's input, if one exists.
For inputs that map cleanly to a single sRGB triple (hex/rgb/hs/
color_name) we echo back the exact bytes the user supplied without losing
them to the xy gamut round-trip. For xy/kelvin inputs there is no
canonical "source hex" so we return None.
tests/components/color/test_tricky_paths.py:313
- Describe this as preserving the color in normalized hex form, not preserving the user's exact bytes. Normalization can change case and add the
#prefix.
"""The source_hex echoes the user's bytes exactly, normalized to uppercase."""
tests/components/color/test_config_flow.py:7
- Exercise color coercion through the config flow instead of importing its private helper. Cases such as tuples, dictionaries, and
Nonecannot passColorRGBSelectorvalidation, so these tests couple to unreachable internal fallback behavior rather than the user-facing contract.
from homeassistant.components.color.config_flow import _coerce_color_input
tests/components/color/test_color_math.py:61
- Replace the per-channel branch with parameterized expected bounds. Home Assistant's test convention avoids conditions in test bodies because each parameter should encode its complete expected outcome and fail independently.
for i, got in enumerate(derive_rgb(canonical)):
if i == dominant:
assert got > 200, f"dominant channel too low: {got}"
else:
assert got < 30, f"off-channel too high: {got}"
tests/components/color/test_tricky_paths.py:312
- Rename the test to reflect normalized hex preservation rather than byte-exact preservation. The implementation canonicalizes case and prefix, so
exactdescribes a stronger contract than the assertion verifies.
async def test_source_hex_exact_for_hex_input(hass: HomeAssistant) -> None:
tests/components/color/test_tricky_paths.py:12
- Remove the duplicated test inventory from the module docstring. It restates test names, requires manual synchronization, and is already incomplete relative to the behaviors covered later in this file.
These tests target paths where regressions would silently corrupt user data
or break promised semantics:
- Full restart round-trip via mock_restore_cache_with_extra_data
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
The ColorRGBSelector validates a three-element list, so the chromatic step's default must be an RGB triplet rather than a hex string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Restore payload validation and quality-scale registration contain unresolved correctness and policy issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (7)
script/hassfest/quality_scale.py:230
- Move
colortoNO_QUALITY_SCALEinstead of adding it to the legacy exemption lists.validate_iqs_fileatscript/hassfest/quality_scale.py:2133-2139explicitly directs newquality_scale: internalintegrations there; this entry and the one at line 1165 bypass that new-integration policy.
"color",
homeassistant/components/color/entity.py:101
- Validate the complete restored canonical payload before accepting it. For example,
kind="white"withkelvin=None, orbrightness=999, passes this check and exposes an internally inconsistent white entity or invalidcolor_params; malformed snapshots should fall back to the configured initial value.
if not (isfinite(x) and isfinite(y)) or kind not in (
KIND_CHROMATIC,
KIND_WHITE,
):
return None
return cls(canonical, brightness, source_hex)
tests/components/color/test_config_flow.py:7
- Exercise color coercion through the config flow instead of importing its private helper. Home Assistant integration tests should avoid internal implementation details; these direct cases couple the suite to
_coerce_color_input, and several inputs cannot be produced byColorRGBSelector.
from homeassistant.components.color.config_flow import _coerce_color_input
homeassistant/components/color/entity.py:81
- Reject restore payloads with a missing or unsupported schema version.
as_dict()writesSTATE_SCHEMA_VERSION, butfrom_dict()never reads it, so an older or newer payload with the current key names is silently interpreted as the current schema instead of falling back safely.
try:
x, y = (float(value) for value in data["xy"])
homeassistant/components/color/entity.py:7
- Use the already imported local attribute constants and drop this light-component import.
ATTR_BRIGHTNESS,ATTR_COLOR_TEMP_KELVIN, andATTR_XY_COLORalready define the same payload keys, so importing the full light entity component solely for these names creates unnecessary cross-integration coupling.
from homeassistant.components import light
homeassistant/components/color/const.py:28
- Describe this as a normalized hex equivalent rather than an exact-byte echo.
compute_source_hexstrips formatting and emits uppercase, so lowercase or hashless input is not preserved byte-for-byte.
# Exact-byte echo of the user's input when it had a clear hex equivalent
# (hex/rgb/hs/color_name). Null for xy/kelvin inputs.
ATTR_SOURCE_HEX: Final = "source_hex"
homeassistant/components/color/color_math.py:207
- Document the normalized result instead of claiming the original bytes are echoed. Hex input is parsed and re-emitted with a leading hash and uppercase digits, so this description currently promises semantics the function does not provide.
For inputs that map cleanly to a single sRGB triple (hex/rgb/hs/
color_name) we echo back the exact bytes the user supplied without losing
them to the xy gamut round-trip. For xy/kelvin inputs there is no
canonical "source hex" so we return None.
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Input validation currently accepts malformed hex syntax and unsafe chromaticity values.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
homeassistant/components/color/color_math.py:86
- Reject xy values with a denominator too small for
color_xy_to_RGB()to handle safely. Tiny positiveyvalues pass this predicate, but the conversion divides byy, can produce infinities/NaNs, and then raises while converting to integers; the service has already assigned that unusable canonical value to the entity.
return isfinite(x) and isfinite(y) and 0.0 <= x <= 1.0 and y > 0.0 and x + y <= 1.0
homeassistant/components/color/color_math.py:44
- Strip at most one leading
#when validating a hex value.lstrip("#")accepts malformed values such as##FF0000or any number of prefixes, so theset_coloraction silently treats invalid hex syntax as valid.
stripped = hex_value.strip().lstrip("#")
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
The entity component lacks shutdown registration, and the quality-scale exemptions use the wrong lists.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (3)
script/hassfest/quality_scale.py:1165
- Remove
colorfromINTEGRATIONS_WITHOUT_SCALEand list this no-file internal integration inNO_QUALITY_SCALEinstead. The validator's no-file path at lines 2124-2141 namesNO_QUALITY_SCALEas the required destination, while this list only tracks integrations that have a quality-scale file but no declared tier.
"color",
script/hassfest/quality_scale.py:230
- Move
colortoNO_QUALITY_SCALEinstead of grandfathering it in the legacy exemption lists. The validator atscript/hassfest/quality_scale.py:2124-2141explicitly requires new integrations markedinternalto be listed inNO_QUALITY_SCALE; adding it here and toINTEGRATIONS_WITHOUT_SCALEbypasses that new-integration check and gives these legacy lists another permanent exception.
This issue also appears on line 1165 of the same file.
"color",
homeassistant/components/color/init.py:94
- Register the entity component's shutdown callback before returning from setup. This config-entry-only component never calls
EntityComponent.async_setup(), so withoutregister_shutdown()its entity platforms are not shut down on Home Assistant stop; the established config-entry-only pattern ishomeassistant/components/wake_word/__init__.py:60-63, andEntityComponent.register_shutdown()explicitly exists for this case.
component = EntityComponent[ColorEntity](_LOGGER, DOMAIN, hass)
hass.data[DATA_COMPONENT] = component
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
From an internal adversarial review pass: - Non-finite numbers in service schemas and kelvin validation raise vol.Invalid/ColorInputError instead of leaking OverflowError - Non-numeric rgb/hs/xy components raise ColorInputError instead of bare ValueError, keeping normalize()'s single-error-path contract - Scene restore validates snapshot xy before calling set_color (corrupt xy no longer aborts scene activation), restores via source_hex when it matches the snapshot chromaticity so the attribute survives round-trips, and logs white snapshots missing kelvin - Restore rejects payloads with a kelvin on chromatic colors - Follow helper conventions: has_entity_name, strings.json title, and recorder exclusion for attributes derived from the state Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
NO_QUALITY_SCALE is where the hassfest no-file path directs internal integrations; the legacy exemption lists were the wrong destination. The config-entry-only EntityComponent also needs an explicit register_shutdown() since async_setup() never runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
The new integration bypasses quality-scale checks and accepts inconsistent persisted source-color data.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
script/hassfest/quality_scale.py:230
- Use the supported quality-scale path instead of grandfathering this new integration. These two lists are legacy exemptions; the validator directs new
internalintegrations toNO_QUALITY_SCALE, or otherwise requires aquality_scale.yamland at least Bronze (script/hassfest/quality_scale.py:2122-2149, 2185-2199), so addingcolorhere bypasses both new-integration checks.
"color_extractor",
homeassistant/components/color/entity.py:92
- Reject invalid or inconsistent
source_hexvalues during restore. Unlike every other persisted field, this value is accepted by stringifying arbitrary data, so a corrupted payload can restore a white 2700 K color withsource_hex="#FF0000"(or expose a non-hex string) and then persist those contradictory attributes indefinitely; validate its type/format and require it to be absent for white colors.
source_hex = data.get("source_hex")
if source_hex is not None:
source_hex = str(source_hex)
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Persisted infinite numeric values can currently crash helper setup instead of triggering the intended fallback behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
homeassistant/components/color/entity.py:171
- Handle overflowing initial brightness like the other malformed entry values. A corrupted config entry containing an infinite brightness makes
int(brightness)raiseOverflowError, preventing the helper from being set up rather than treating brightness as unset.
try:
value = int(brightness)
except TypeError, ValueError:
return None
homeassistant/components/color/entity.py:93
- Treat overflowing numeric values as malformed restore data.
int(math.inf)raisesOverflowError, so a corrupted savedversion,kelvin, orbrightnesscurrently aborts entity setup instead of returningNoneand falling back to the configured initial value.
This issue also appears on line 168 of the same file.
except KeyError, TypeError, ValueError:
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
Systematic sweep of every int()/float() on externally-sourced data: - _validate_rgb catches OverflowError so infinite components raise ColorInputError like every other malformed input - Scene restore validates snapshot kelvin and brightness before the service call; corrupt values fall back or are skipped with a debug log instead of aborting scene activation, and overflowing ints in xy comparisons are treated as invalid - The config flow color coercion falls back to the default color on non-numeric or overflowing components Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Malformed scene attributes can currently abort reproduction of every state.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (6)
homeassistant/components/color/reproduce_state.py:119
- Catch validation errors from the color service and skip the malformed color. A syntactically valid snapshot such as
#000000, or a truthy invalid white Kelvin, reaches this call and raisesServiceValidationError/vol.Invalid; that escapesasyncio.gatherand aborts scene reproduction for every state.
color_data = {FIELD_KELVIN: int(attrs[ATTR_COLOR_TEMP_KELVIN])}
elif _source_hex_matches(source_hex, xy):
# The canonical value was derived from this exact hex, so restoring
# via hex loses nothing and preserves the source_hex attribute.
color_data = {FIELD_HEX: source_hex}
homeassistant/components/color/reproduce_state.py:138
- Validate or catch invalid snapshot brightness before dispatching it. An out-of-range or non-numeric attribute raises
vol.Invalidhere, which escapesasyncio.gatherand aborts the entire scene reproduction instead of leaving brightness untouched.
)
if color_data is not None:
await hass.services.async_call(
DOMAIN,
tests/components/color/test_tricky_paths.py:12
- Shorten this module docstring to state only the module's purpose. The bullet list repeats the test functions below and will become stale as cases are added or renamed.
"""Tests for non-trivial behaviors: restore, snapshots, options reload.
These tests target paths where regressions would silently corrupt user data
or break promised semantics:
- Full restart round-trip via mock_restore_cache_with_extra_data
homeassistant/components/color/const.py:27
- Describe
source_hexas a normalized sRGB equivalent rather than an exact-byte echo. The implementation uppercases hex input and adds#, while RGB/HS/name inputs do not supply literal hex bytes at all.
# Exact-byte echo of the user's input when it had a clear hex equivalent
# (hex/rgb/hs/color_name). Null for xy/kelvin inputs.
homeassistant/components/color/color_math.py:231
- Document the normalized pre-xy sRGB value instead of claiming the user's exact bytes are echoed.
compute_source_hexcanonicalizes hex syntax and computes bytes for RGB, HS, and named-color inputs, so the current contract is inaccurate.
"""Return the literal hex equivalent of the user's input, if one exists.
For inputs that map cleanly to a single sRGB triple (hex/rgb/hs/
color_name) we echo back the exact bytes the user supplied without losing
them to the xy gamut round-trip. For xy/kelvin inputs there is no
tests/components/color/test_color_math.py:63
- Parameterize the expected channel bounds instead of branching in the test body. Repository test guidance requires splitting or parametrizing test cases without conditions, and declarative expected bounds make each case's assertion explicit.
for i, got in enumerate(derive_rgb(canonical)):
if i == dominant:
assert got > 200, f"dominant channel too low: {got}"
else:
assert got < 30, f"off-channel too high: {got}"
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
A snapshot value that passes the pre-dispatch guards but fails service validation (e.g. a #000000 state string) no longer aborts reproduction of every state in the scene; it is logged and skipped. Also corrects the source_hex documentation (the value is the normalized pre-xy sRGB hex, not an exact-byte echo) and reworks two tests per repo guidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Non-finite HS values can trigger an unhandled internal service failure.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
homeassistant/components/color/color_math.py:86
- Reject non-finite HS components before conversion.
NaNpasses these range checks, thencolor_hs_to_RGB()raises aValueErroroutsideColorInputError, socolor.set_colorfails internally instead of returning the translated validation error.
if not 0 <= hue <= 360:
raise ColorInputError("hs_color hue must be 0-360")
if not 0 <= sat <= 100:
raise ColorInputError("hs_color saturation must be 0-100")
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
HS and xy inputs can leak OverflowError instead of producing validation errors.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (5)
homeassistant/components/color/color_math.py:105
- Catch
OverflowErrorwhen converting xy components. Extremely large integer input makesfloat()raiseOverflowError, sonormalize()leaks an unexpected exception rather than rejecting the color withColorInputError.
except (TypeError, ValueError) as err:
homeassistant/components/color/init.py:79
- Use an overflow-safe float coercer for xy service data. An extremely large integer raises
OverflowErrorinsidevol.Coerce(float), bypassing both schema validation and the integration's translatedServiceValidationErrorpath.
[vol.All(vol.Coerce(float), vol.Range(min=0.0, max=1.0))],
homeassistant/components/color/color_math.py:79
- Catch
OverflowErrorwhen converting HS components. A caller can supply an integer such as10**400, for whichfloat()raisesOverflowErrorinstead of the documentedColorInputError, bypassing the translated service-validation path.
This issue also appears on line 105 of the same file.
except (TypeError, ValueError) as err:
tests/components/color/test_config_flow.py:8
- Exercise color coercion through the config flow instead of importing the private
_coerce_color_inputhelper. Directly testing this implementation detail conflicts with the integration-testing guideline and couples the suite to an internal helper that the selector already guards.
from homeassistant.components.color.config_flow import _coerce_color_input
homeassistant/components/color/init.py:74
- Use an overflow-safe float coercer for HS service data.
vol.Coerce(float)does not translateOverflowError, so an input such as[10**400, 50]escapes schema validation and fails the service call unexpectedly beforenormalize()can return the translated color error.
This issue also appears on line 79 of the same file.
[vol.Coerce(float)],
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
float() raises OverflowError for arbitrarily large ints (unlike inf, which it accepts), so hs/xy inputs like 10**400 leaked an unexpected exception past both the service schema and normalize()'s error path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3
There was a problem hiding this comment.
🟡 Not ready to approve
Extreme valid xy input can break state writes, while invalid initial black data produces inconsistent source metadata.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
homeassistant/components/color/color_math.py:96
- Reject xy values with a y component too close to zero. The current
y > 0.0check accepts subnormal values such as5e-324;color_xy_to_RGB()then overflows while dividing byy, and deriving the entity state raises instead of completing a validset_colorcall (also leaving the in-memory canonical value poisoned).
return isfinite(x) and isfinite(y) and 0.0 <= x <= 1.0 and y > 0.0 and x + y <= 1.0
homeassistant/components/color/entity.py:182
- Clear
source_hexwhen the configured initial color is rejected. For a corrupted/custom entry containing#000000,_initial_canonical()falls back toDEFAULT_HEX, but this method still returns#000000, leaving the entity permanently advertising a source value that never produced its canonical color.
initial = entry.data.get(CONF_INITIAL_COLOR)
if not initial:
return None
return compute_source_hex({FIELD_HEX: initial})
- Files reviewed: 22/24 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Proposed change
Adds a new
colorhelper integration: a reusable, named color value stored as a helper entity, addressing a decade of feature requests for a color equivalent ofinput_number/input_datetime(see the proposal in the discussion linked below).Design summary:
integration_type: helper, config flow + options flow), matching the pattern ofthreshold/derivative/template. Zero external dependencies.xy+kind(chromatic|white) +kelvin(when white) + optionalbrightness. All other representations (hex,rgb_color,hs_color,color_temp_kelvin) are derived state attributes; state is the hex string.color_paramsattribute: a dict splattable directly intolight.turn_on(xy_colororcolor_temp_kelvin, plusbrightnesswhen stored) so automations need no kind/capability branching — in the spirit oflight.turn_on'sprofile:parameter.set_color(exactly one of six input shapes, validated at schema level withServiceValidationErrortranslation keys),set_brightness,clear_brightness. Deliberately no action that commands other entities.async_reproduce_states; persistence viaRestoreEntity+ versionedExtraStoredData.config_flow.py,__init__.py, andreproduce_state.py; hassfest, ruff, and mypy strict pass.Field-tested as a HACS custom integration: https://github.com/kkilchrist/ha-color-ext
Type of change
Additional information
getConfigFlowHandlers)Per the AI policy: Claude (Claude Code) was used to assist with the port, tests, and documentation. The design, implementation decisions, and review are mine.
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests:
🤖 Generated with Claude Code
https://claude.ai/code/session_01DSnLnMp43HRSiDXyq8RGY3