Skip to content

Add UI config flow to Universal media player - #177656

Draft
gmihovics wants to merge 11 commits into
home-assistant:devfrom
gmihovics:feature/universal-media-player-config-flow
Draft

Add UI config flow to Universal media player#177656
gmihovics wants to merge 11 commits into
home-assistant:devfrom
gmihovics:feature/universal-media-player-config-flow

Conversation

@gmihovics

@gmihovics gmihovics commented Jul 30, 2026

Copy link
Copy Markdown

Proposed change

Adds a UI config flow to the Universal media player integration, which was previously YAML-only. YAML configuration continues to work unchanged; the config flow is an additional way to create new Universal media players through the UI, consistent with how every other comparable helper integration (filter, generic_thermostat, threshold, min_max, derivative, statistics) keeps YAML and the config flow as independent, parallel paths rather than migrating one into the other.

  • Adds async_setup_entry/async_unload_entry and a 4-step config flow: name + children, command routing (13 commands via ActionSelector: turn_on, turn_off, volume_up, volume_down, volume_mute, volume_set, select_source, select_sound_mode, media_play, media_pause, media_stop, media_next_track, media_previous_track), attribute overrides (state, is_volume_muted, volume_level, source, source_list, sound_mode, sound_mode_list), and advanced options (device_class, browse_media_entity, active_child_template, state_template).
  • Adds a matching options flow so existing UI-created entries can be reconfigured, with options_flow_reloads = True so edits take effect immediately instead of requiring a restart.
  • The UI intentionally covers the common/documented cases; fully arbitrary command/attribute overrides (beyond the above) remain YAML-only, consistent with how filter and generic_thermostat split UI vs. YAML configuration.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

- Add config_flow.py with SchemaConfigFlowHandler providing a two-step
  wizard: basic setup (name + child players) then command routing (turn_on,
  turn_off, volume_up, volume_down, volume_mute, select_source via
  ActionSelector)
- Add options flow so users can reconfigure child players and command
  overrides after initial setup
- Add async_step_import to migrate YAML configs that carry a unique_id
  into UI-managed config entries automatically on startup
- Add async_setup_entry / async_unload_entry to __init__.py forwarding to
  the media_player platform
- Add async_setup_entry to media_player.py; existing UniversalMediaPlayer
  entity class is unchanged
- Update manifest.json with config_flow: true
- Add config/options flow strings to strings.json and regenerate
  translations/en.json
Also fixes the config and options flows, marks the integration as a
helper so the config flow may include a name field, and preserves the
original unique_id when migrating a YAML entry with a unique_id to a
config entry so the entity registry entry is not orphaned.
Copilot AI review requested due to automatic review settings July 30, 2026 14:48

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @gmihovics

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

YAML migration loses supported behavior, action sequences are truncated, and option changes are not applied until reload.

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 UI-based configuration, options editing, and YAML migration for Universal media players.

Changes:

  • Adds config-entry setup and unloading.
  • Adds user, options, and YAML-import flows.
  • Adds tests, UI strings, and generated metadata.
File summaries
File Description
homeassistant/components/universal/__init__.py Adds config-entry lifecycle handling.
homeassistant/components/universal/config_flow.py Implements configuration, options, and import flows.
homeassistant/components/universal/manifest.json Enables config flow and helper classification.
homeassistant/components/universal/media_player.py Adds config-entry entity setup and YAML migration.
homeassistant/components/universal/strings.json Adds flow labels and descriptions.
homeassistant/generated/config_flows.py Registers the helper config flow.
homeassistant/generated/integrations.json Updates generated integration metadata.
tests/components/universal/test_config_flow.py Tests user, options, and import flows.
tests/components/universal/test_media_player.py Tests config-entry entity behavior and commands.
Review details

Comments suppressed due to low confidence (2)

homeassistant/components/universal/config_flow.py:125

  • Preserve all supported YAML settings when creating the imported entry. This options mapping drops device_class, active_child_template, and state_template, even though async_setup_entry reads those values, so imported players lose their configured class and template-driven behavior.
        options: dict[str, Any] = {
            CONF_NAME: import_data[CONF_NAME],
            CONF_CHILDREN: import_data.get(CONF_CHILDREN, []),
            # Preserve JSON-serialisable advanced options so they survive the
            # round-trip through config entry storage unchanged.
            CONF_BROWSE_MEDIA_ENTITY: import_data.get(CONF_BROWSE_MEDIA_ENTITY),

homeassistant/components/universal/config_flow.py:137

  • Preserve YAML command overrides outside EXPOSED_COMMANDS. The platform schema accepts mappings such as volume_set, media_play, play_media, and shuffle_set, and the entity implements those overrides, but this loop discards them during migration and changes the entity's supported features and routing.
        for cmd in EXPOSED_COMMANDS:
            options[cmd] = [yaml_commands[cmd]] if cmd in yaml_commands else []
  • Files reviewed: 7/9 changed files
  • Comments generated: 7
  • Review effort level: Medium

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +141 to +145
if config.get(CONF_UNIQUE_ID):
# Migrate YAML entries that carry a unique_id to a config entry so they
# become manageable via the UI. The entity is created by async_setup_entry
# once the import flow completes; return early to avoid a duplicate.
hass.async_create_task(
Comment on lines +178 to +180
actions = options.get(cmd, [])
if isinstance(actions, list) and actions:
commands[cmd] = actions[0]
"domain": "universal",
"name": "Universal media player",
"codeowners": [],
"config_flow": true,
Comment on lines +101 to +102
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
Comment on lines +117 to +119
# Commands in YAML are single service-call dicts; wrap in a list to
# match the ActionSelector storage format used by the UI flow.
yaml_commands: dict[str, Any] = import_data.get(CONF_COMMANDS, {})
"turn_off": "Action to run when the player is turned off.",
"turn_on": "Action to run when the player is turned on (e.g. power on an AV receiver or run a script).",
"volume_down": "Action to run when volume down is pressed.",
"volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
Comment on lines +141 to +144
"Universal media player '%s' is configured via YAML, which is "
"deprecated. Remove it from your configuration.yaml and recreate "
"it through the UI (Settings → Devices & services → Add integration "
"→ Universal media player)"
Adds two more config/options flow steps: attribute overrides (state,
is_volume_muted, volume_level, source, source_list, sound_mode,
sound_mode_list) and advanced options (device_class,
browse_media_entity, active_child_template, state_template). Also
expands command routing from 6 to 13 commands, matching the most
commonly used media_player commands documented for YAML.

Also fixes async_step_import, which silently dropped device_class,
active_child_template, and state_template when migrating a YAML
config with a unique_id.
Copilot AI review requested due to automatic review settings July 30, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Migration can lose commands, while options reload and action-sequence handling are incomplete.

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)

homeassistant/components/universal/media_player.py:175

  • Trigger the import flow for YAML entries without a unique_id as well. This guard bypasses async_step_import and its yaml_ fallback during real YAML setup, so those entries remain YAML-only despite the stated migration behavior; the direct flow test does not exercise this setup path.
    if config.get(CONF_UNIQUE_ID):
        # Migrate YAML entries that carry a unique_id to a config entry so they
        # become manageable via the UI.  The entity is created by async_setup_entry
        # once the import flow completes; return early to avoid a duplicate.
        hass.async_create_task(

homeassistant/components/universal/media_player.py:203

  • Execute the complete action sequence returned by ActionSelector. It explicitly represents a script sequence, so selecting only actions[0] silently drops later actions and fails when the first item is a non-service action such as a delay or condition; use a Script-based runner for the stored sequence instead.
        if isinstance(actions, list) and actions:
            commands[cmd] = actions[0]

homeassistant/components/universal/strings.json:70

  • Document the actual mute variable name. _async_call_service passes ATTR_MEDIA_VOLUME_MUTED, whose value is is_volume_muted, so the documented media_player_volume_muted variable is undefined in the configured action.
          "volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",

homeassistant/components/universal/config_flow.py:192

  • Tell users that the entry has already been imported instead of asking them to recreate it. Following the current warning creates a second UI entry even though async_create_entry below has already persisted the migrated one.
                "Universal media player '%s' is configured via YAML, which is "
                "deprecated. Remove it from your configuration.yaml and recreate "
                "it through the UI (Settings → Devices & services → Add integration "
                "→ Universal media player)"

homeassistant/components/universal/config_flow.py:143

  • Enable automatic reloads when the options flow finishes. SchemaConfigFlowHandler defaults options_flow_reloads to False, so these updated children, commands, and attributes are only written to storage while the loaded entity keeps its old configuration until a manual reload or restart.
    options_flow = OPTIONS_FLOW

homeassistant/components/universal/strings.json:147

  • Add the missing options-flow label and description for the media_play command. The options form exposes the same command but currently has no translation references for it.
          "media_pause": "[%key:component::universal::config::step::commands::data::media_pause%]",

homeassistant/components/universal/strings.json:62

  • Add the missing config-flow label and description for the media_play command. EXPOSED_COMMANDS adds this field to the form, but these translation mappings omit it.
          "media_pause": "Pause",
  • Files reviewed: 7/9 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.

Comment on lines +184 to +185
for cmd in EXPOSED_COMMANDS:
options[cmd] = [yaml_commands[cmd]] if cmd in yaml_commands else []
DEVICE_CLASSES_SCHEMA coerces device_class to a MediaPlayerDeviceClass
StrEnum, and CMD_SCHEMA compiles templated command data/target values
into Template objects; storing either directly in config entry options
crashed the config entries store since neither is JSON-serialisable.

Both are now flattened to plain strings on import, and
async_setup_entry recompiles command data/target templates via
SERVICE_SCHEMA so they still render at call time, since
async_call_from_config is invoked with validate_config=False.
Copilot AI review requested due to automatic review settings July 30, 2026 15:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Action sequences are mishandled, option edits are not reloaded, and YAML migration can skip or lose configuration.

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 (5)

homeassistant/components/universal/config_flow.py:209

  • Preserve all supported YAML command overrides during import. Restricting this loop to EXPOSED_COMMANDS drops existing overrides such as play_media, media_play_pause, toggle, clear_playlist, repeat_set, and shuffle_set; the migrated entity then silently changes behavior because async_setup_entry also reads only this tuple.
        for cmd in EXPOSED_COMMANDS:
            options[cmd] = (
                [_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
            )

homeassistant/components/universal/config_flow.py:216

  • Tell users that the YAML configuration was imported instead of asking them to recreate it. This warning is emitted immediately before creating the config entry, so recreating it manually can produce a duplicate; instruct users only to remove the migrated YAML.
                "Universal media player '%s' is configured via YAML, which is "
                "deprecated. Remove it from your configuration.yaml and recreate "
                "it through the UI (Settings → Devices & services → Add integration "
                "→ Universal media player)"

homeassistant/components/universal/media_player.py:171

  • Run the import for YAML entries that lack unique_id too. This guard prevents the yaml_<name> fallback in async_step_import from being reached during real platform setup, so those players are not migrated as described; the direct flow test does not exercise this path.
    if config.get(CONF_UNIQUE_ID):

homeassistant/components/universal/config_flow.py:161

  • Reload the config entry after an options flow is saved. SchemaConfigFlowHandler defaults options_flow_reloads to False, so the running player keeps its old children, commands, attributes, and templates until a restart or manual reload.

This issue also appears in the following locations of the same file:

  • line 206
  • line 213
    config_flow = CONFIG_FLOW
    options_flow = OPTIONS_FLOW

homeassistant/components/universal/strings.json:70

  • Document the actual mute-action variable name. async_mute_volume passes ATTR_MEDIA_VOLUME_MUTED, whose value is is_volume_muted, so templates following this description receive an undefined media_player_volume_muted variable.
          "volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
  • Files reviewed: 7/9 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.

Comment on lines +205 to +208
for cmd in EXPOSED_COMMANDS:
actions = options.get(cmd, [])
if isinstance(actions, list) and actions:
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
media_play was left out of strings.json's commands step (both data and
data_description, in config and options), so it rendered as the raw
key name instead of "Play" in the command routing form.
Copilot AI review requested due to automatic review settings July 30, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Valid UI actions, option updates, and some migrated YAML configurations currently produce incorrect runtime 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 (6)

homeassistant/components/universal/config_flow.py:161

  • Enable automatic reloads for this options flow. SchemaConfigFlowHandler.options_flow_reloads defaults to False, so saving new children, commands, or attributes updates storage but leaves the loaded entity using its old configuration until a manual reload or restart; set this flag to True as similar helper flows do.
    config_flow = CONFIG_FLOW
    options_flow = OPTIONS_FLOW

homeassistant/components/universal/media_player.py:208

  • Execute the full action sequence accepted by ActionSelector. The selector accepts arbitrary script actions and multiple actions, but this keeps only the first item and validates it as a service call, so later actions are silently discarded and valid actions such as delay, choose, or device actions make config-entry setup fail; run the stored sequence through the script engine or restrict and validate the flow input as one service call.
    for cmd in EXPOSED_COMMANDS:
        actions = options.get(cmd, [])
        if isinstance(actions, list) and actions:
            commands[cmd] = cv.SERVICE_SCHEMA(actions[0])

homeassistant/components/universal/config_flow.py:209

  • Preserve YAML command overrides that are not exposed in the UI. Import currently copies only EXPOSED_COMMANDS, so automatically migrated players lose supported overrides such as play_media, clear_playlist, shuffle_set, repeat_set, media_play_pause, and toggle; retain hidden commands in config-entry options and merge them during setup, as is already done for hidden attributes.
        for cmd in EXPOSED_COMMANDS:
            options[cmd] = (
                [_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
            )

homeassistant/components/universal/media_player.py:175

  • Start the import flow for YAML players without a unique_id too. This guard makes the yaml_ fallback in async_step_import unreachable during real YAML setup, so those players remain YAML entities despite the stated migration behavior; the current test only invokes the import flow directly and does not exercise this platform path.
    if config.get(CONF_UNIQUE_ID):
        # Migrate YAML entries that carry a unique_id to a config entry so they
        # become manageable via the UI.  The entity is created by async_setup_entry
        # once the import flow completes; return early to avoid a duplicate.
        hass.async_create_task(

homeassistant/components/universal/config_flow.py:216

  • Tell users that the player was already imported instead of asking them to recreate it. This warning is emitted immediately before creating the config entry, so following its current instruction creates a duplicate UI player after the YAML entry is removed.
                "Universal media player '%s' is configured via YAML, which is "
                "deprecated. Remove it from your configuration.yaml and recreate "
                "it through the UI (Settings → Devices & services → Add integration "
                "→ Universal media player)"

homeassistant/components/universal/strings.json:72

  • Document the actual mute template variable. async_mute_volume passes ATTR_MEDIA_VOLUME_MUTED, whose value is is_volume_muted, so media_player_volume_muted is undefined when this action renders.
          "volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
  • Files reviewed: 7/9 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.

Copilot AI review requested due to automatic review settings July 30, 2026 16:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

YAML migration can drop command overrides, options do not reload, and action sequences are handled incorrectly.

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 (5)

homeassistant/components/universal/media_player.py:208

  • Execute the complete action sequence selected by the user. ActionSelector accepts a script sequence, but indexing [0] silently discards subsequent actions, and validating that item with SERVICE_SCHEMA also makes valid non-service actions such as delays or conditions fail config-entry setup.
        if isinstance(actions, list) and actions:
            commands[cmd] = cv.SERVICE_SCHEMA(actions[0])

homeassistant/components/universal/config_flow.py:209

  • Preserve every supported YAML command when migrating the entity. Iterating only EXPOSED_COMMANDS drops existing overrides such as play_media, media_play_pause, clear_playlist, shuffle_set, repeat_set, and toggle; because the YAML platform returns early after import, those commands stop working immediately even while the YAML remains present.
        for cmd in EXPOSED_COMMANDS:
            options[cmd] = (
                [_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
            )

homeassistant/components/universal/media_player.py:171

  • Start the import flow for YAML entries without unique IDs as well. The yaml_ fallback in async_step_import is otherwise unreachable during normal YAML setup, so such entities remain YAML-only despite the stated migration behavior and the new fallback test.
    if config.get(CONF_UNIQUE_ID):

homeassistant/components/universal/config_flow.py:216

  • Tell the user to remove the YAML configuration without recreating the entry. This warning is emitted after async_create_entry has already imported the player, so instructing users to recreate it is inaccurate and can discard the entity-registry-preserving migration this flow just performed.
                "Universal media player '%s' is configured via YAML, which is "
                "deprecated. Remove it from your configuration.yaml and recreate "
                "it through the UI (Settings → Devices & services → Add integration "
                "→ Universal media player)"

homeassistant/components/universal/config_flow.py:161

  • Reload the config entry after the options flow completes. SchemaConfigFlowHandler defaults options_flow_reloads to False, so these edits are persisted but the existing media-player entity continues using its old children, commands, and attributes until Home Assistant restarts.
    options_flow = OPTIONS_FLOW
  • Files reviewed: 7/9 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.

Without options_flow_reloads, editing children/commands/attributes
through Configure persisted to storage but the running entity kept
its old in-memory configuration until Home Assistant restarted.
Every comparable helper (threshold, min_max, derivative,
generic_thermostat) already sets this.
YAML's commands: accepts arbitrary slug keys (play_media, toggle,
repeat_set, shuffle_set, clear_playlist, etc.), not just the 13
exposed in the UI. async_step_import previously only carried over
EXPOSED_COMMANDS, silently turning any other override into delegation
to the active child. They're now preserved under a CONF_COMMANDS key
and merged back in by async_setup_entry.
Copilot AI review requested due to automatic review settings July 30, 2026 16:54
The YAML entry has already been migrated to a config entry by this
point; telling users to "recreate" it through the UI could lead them
to create a second, duplicate Universal player instead of managing
the migrated one.
The entity passes ATTR_MEDIA_VOLUME_MUTED (whose value is
is_volume_muted), not media_player_volume_muted, as the mute state
variable for the overriding action.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Action sequences, self-referential selections, YAML migration, and user-facing guidance contain unresolved functional 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 (6)

homeassistant/components/universal/media_player.py:208

  • Execute the complete action sequence rather than validating only actions[0]. ActionSelector accepts script sequences, so this drops later actions and makes valid actions such as delay or choose fail setup under SERVICE_SCHEMA; run the sequence as a Script or constrain the input to one service call.
            commands[cmd] = cv.SERVICE_SCHEMA(actions[0])

homeassistant/components/universal/media_player.py:171

  • Import YAML entries without a unique_id through the flow as well. This guard leaves those entries in legacy YAML setup, so the documented yaml_ fallback in async_step_import is never reached during normal startup.
    if config.get(CONF_UNIQUE_ID):

homeassistant/components/universal/config_flow.py:76

  • Exclude the entry's own media-player entity from the options child selector. Selecting it makes the universal player observe and delegate services to itself, which can recurse; use a dynamic schema with entity_selector_without_own_entities, as in homeassistant/components/group/config_flow.py:52-65.
_OPTIONS_BASIC_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_CHILDREN, default=[]): _CHILDREN_SELECTOR,
    }

homeassistant/components/universal/config_flow.py:228

  • Tell users that the entry was already imported instead of asking them to recreate it. This flow immediately creates the config entry, so following the current warning can create a duplicate universal player.
                "Universal media player '%s' has been migrated from YAML to a "
                "config entry. Remove it from your configuration.yaml; you can "
                "now manage it from Settings → Devices & services"
            ),

homeassistant/components/universal/strings.json:72

  • Document the actual mute template variable. The command receives is_volume_muted from ATTR_MEDIA_VOLUME_MUTED, so the advertised name renders undefined.
          "volume_mute": "Action to run when muting or unmuting. Receives variable `is_volume_muted` (boolean).",

homeassistant/components/universal/strings.json:15

  • Describe the default active-child selection accurately. The implementation chooses the child with the highest-priority playback state, not simply the first child that is on.
          "active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
  • Files reviewed: 7/9 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.

Comment on lines +65 to +67
vol.Optional(CONF_BROWSE_MEDIA_ENTITY): selector.EntitySelector(
selector.EntitySelectorConfig(domain="media_player")
),
Copilot AI review requested due to automatic review settings July 30, 2026 17:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

YAML migration remains incomplete, and valid multi-action selections are truncated or can prevent setup.

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)

homeassistant/components/universal/media_player.py:171

  • Import every YAML definition, not only those with a unique_id. This guard bypasses async_step_import for configurations without one, so its yaml_ fallback is never used and those players remain YAML-only despite the migration promised by this PR.
    if config.get(CONF_UNIQUE_ID):

homeassistant/components/universal/media_player.py:208

  • Execute the complete action sequence instead of coercing only its first item to a service call. ActionSelector accepts arbitrary multi-step script syntax, so this drops later actions and makes config-entry setup fail when the first valid action is a delay, condition, or device action; use the script execution pattern used by template entities.
            commands[cmd] = cv.SERVICE_SCHEMA(actions[0])

homeassistant/components/universal/strings.json:15

  • Describe the fallback as selecting the child with the highest-priority active state. The implementation ranks states through playing and only preserves child order for ties, so it does not simply pick the first child that is on.
          "active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
  • Files reviewed: 7/9 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.

No other comparable helper integration (threshold, min_max,
derivative, filter, generic_thermostat, statistics) migrates YAML to
a config entry; YAML and the UI live in permanent parallel for all of
them, with the config flow only used for new UI-created instances.
Since the config flow doesn't yet cover every option YAML supports,
following that same pattern avoids forcing a YAML user into a config
entry that can't fully replicate their existing setup.

Removes async_step_import and the now-unreachable SOURCE_IMPORT
trigger in async_setup_platform, along with the now-dead
config-entry-storage handling that only existed to support import
(the CONF_COMMANDS merge-in for commands outside EXPOSED_COMMANDS,
and the list-shaped CONF_ATTRS/actions.dict branches). YAML keeps
working exactly as before, unchanged.
Copilot AI review requested due to automatic review settings July 30, 2026 18:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

YAML import is missing, action sequences are mishandled, and options editing drops unexposed attributes.

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 (5)

homeassistant/components/universal/media_player.py:195

  • Execute or reject the complete action sequence instead of silently taking its first item. ActionSelector accepts arbitrary script sequences, so valid multi-action inputs lose every action after the first, and valid non-service first actions such as delay or choose make SERVICE_SCHEMA fail during entry setup.
        if isinstance(actions, list) and actions:
            commands[cmd] = cv.SERVICE_SCHEMA(actions[0])

homeassistant/components/universal/config_flow.py:144

  • Add the missing YAML import path before advertising migration. SchemaConfigFlowHandler only generates the four steps in CONFIG_FLOW, so this class has no async_step_import, while async_setup_platform still creates the YAML entity directly; existing YAML is therefore never migrated into a config entry.
class UniversalFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
    """Handle a Universal media player config and options flow."""

    config_flow = CONFIG_FLOW
    options_flow = OPTIONS_FLOW

homeassistant/components/universal/media_player.py:211

  • Preserve the config entry's imported unique ID when creating the entity. Using entry_id unconditionally gives a migrated YAML entity a new registry identity, so its existing entity-registry history is not retained as described.
        CONF_UNIQUE_ID: config_entry.entry_id,

homeassistant/components/universal/config_flow.py:104

  • Merge unexposed stored attributes when updating the UI fields. Replacing CONF_ATTRS with only EXPOSED_ATTRIBUTES deletes every YAML-only override after an options-flow save, contrary to the stated preservation behavior.
    return {
        CONF_ATTRS: {
            attr: user_input[attr]
            for attr in EXPOSED_ATTRIBUTES
            if user_input.get(attr)

homeassistant/components/universal/strings.json:12

  • Describe the default active-child selection accurately. The implementation scans all children and chooses the highest-priority playback state, not simply the first child that is on.
          "active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
  • Files reviewed: 7/9 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants