Repository navigation
Conversation
|
@esphbot review |
Merging this PR will not alter performance
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2858 +/- ##
=======================================
Coverage 99.75% 99.75%
=======================================
Files 296 296
Lines 25618 25650 +32
=======================================
+ Hits 25556 25588 +32
Misses 62 62
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
PR Review — Mark in the listing an automation whose name more than one item carriesSolid, well-scoped change. The listing flags now come from the same count the refusal uses. No blocking issues. The refactor of I checked that the refactor keeps the old behaviour:
The
🟢 Suggestions
1. Response shape column omits `unnamed_script_ids`
|
|
Closing without merging. The change is not worth what it adds.
The guard from #2854 stays as it is. |
esphbot
left a comment
There was a problem hiding this comment.
Tip
No blocking issues found — ready to merge.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Address the mapping-valued script guard gap and document unnamed_script_ids in the API response shape.
Review effort: Lite
Findings: 1
Open (2)
What changed in this PR
Adds ambiguity metadata to automation listings so callers can identify names whose writes will be refused.
Changes:
- Adds
shared_nameandunnamed_script_idsresponse fields. - Updates listing logic, write guards, tests, and API documentation.
| File | Summary |
|---|---|
tests/test_automations_ambiguous_location.py |
Tests shared-name markers and script IDs. |
esphome_device_builder/models/automations.py |
Adds response fields. |
esphome_device_builder/controllers/automations/controller.py |
Applies listing metadata. |
esphome_device_builder/controllers/automations/addressing.py |
Counts names and guards writes; mapping-valued scripts need handling. |
docs/API.md |
Documents API responses; unnamed_script_ids needs inclusion in the response shape. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if not isinstance(scripts, list): | ||
| return [] | ||
| return [ | ||
| item | ||
| (instance_id("script", item, idx, is_list=True), item) |
| | `automations/get_filters` | `{}` | `[Filter]` | Full sensor / binary_sensor / text_sensor filter catalog. Each entry's `applies_to` lists the component domains the filter is valid on; the REGISTRY_LIST renderer uses it to scope the per-row picker. | | ||
| | `automations/get_available` | `{configuration, yaml?}` | `{triggers, actions, conditions, scripts, devices}` | Scoped catalog for a single device. Optional `yaml` scopes off the editor's unsaved draft instead of disk, so a component just added through the wizard exposes its triggers before the global save. `triggers` / `actions` / `conditions` filtered to the components present in the YAML, matched by the catalog's canonical `<domain>.<platform>` form (e.g. an action with `domain == "switch.template"` only surfaces when a switch with `platform: template` is configured); `core` items (control flow, lambda, combinators) and device-level triggers are always included. `scripts` lists declared `script: id`s with their `parameters:` map. `devices` lists every configured component instance with its `id` / `name` for id-picker dropdowns, plus `title` — the component's catalog display name (e.g. `wifi` → "WiFi Component"), shown when `name` is unset so a nameless singleton reads as a human name instead of the raw key. Each instance carries `has_explicit_id`: false means `id` is the parser/writer round-trip identity synthesized for an id-less block (the domain for a singleton, `<domain>_<idx>` positional, `<parent_instance_id>_<sub_key>` for sub-entities), not a YAML-referenceable id — the frontend must never write it into an action's reference param (#2208). A multi-entity platform component (e.g. `sensor: - platform: aht10`) also lists each configured nested sub-entity (`temperature` / `humidity`) as its own instance keyed on the bare sub-domain (`component_id: "sensor"`) with `parent_id` set to the container — on its declared `id:`, or on the synthetic `<parent_instance_id>_<sub_key>` id when the sub-block has none — and always marks the platform item `is_entity_container: true` (even with no sub-blocks configured) so the frontend offers entity triggers on the sub-entities and never on the container. | | ||
| | `automations/parse` | `{configuration, yaml?}` | `[ParsedAutomation]` | Walk the device YAML and return every recognised automation (top-level `script:` / `interval:`, a mapping-form block counting as its one entry at index 0 or the script's id, `api.actions:`, device-level `esphome.on_*`, inline component `on_*:`, light `effects:` entries). An unknown *condition* id (or a misrouted / malformed body) flags only its own automation (`error` set, empty tree); siblings still parse. An uncatalogued single-key **action** (from an `external_components` source, or a typo) instead decomposes to an opaque passthrough `ActionNode` — `unknown: true` with the body on `raw_body` (tags carried via the passthrough tag sentinel above) — so its catalogued siblings stay editable; the frontend renders that one node read-only. A body the tree cannot represent (an oversized LVGL `*.update`, a tagged condition gate such as `condition: !include`) additionally sets `unsupported: true`, so the editor shows the neutral "edit in YAML" hint instead of an error alert. A YAML that won't load at all raises `INVALID_ARGS`. Optional `yaml` parses the unsaved draft instead of disk. | | ||
| | `automations/get_available` | `{configuration, yaml?}` | `{triggers, actions, conditions, scripts, devices}` | Scoped catalog for a single device. Optional `yaml` scopes off the editor's unsaved draft instead of disk, so a component just added through the wizard exposes its triggers before the global save. `triggers` / `actions` / `conditions` filtered to the components present in the YAML, matched by the catalog's canonical `<domain>.<platform>` form (e.g. an action with `domain == "switch.template"` only surfaces when a switch with `platform: template` is configured); `core` items (control flow, lambda, combinators) and device-level triggers are always included. `scripts` lists declared `script: id`s with their `parameters:` map. `devices` lists every configured component instance with its `id` / `name` for id-picker dropdowns, plus `title` — the component's catalog display name (e.g. `wifi` → "WiFi Component"), shown when `name` is unset so a nameless singleton reads as a human name instead of the raw key. Each instance carries `has_explicit_id`: false means `id` is the parser/writer round-trip identity synthesized for an id-less block (the domain for a singleton, `<domain>_<idx>` positional, `<parent_instance_id>_<sub_key>` for sub-entities), not a YAML-referenceable id — the frontend must never write it into an action's reference param (#2208). A multi-entity platform component (e.g. `sensor: - platform: aht10`) also lists each configured nested sub-entity (`temperature` / `humidity`) as its own instance keyed on the bare sub-domain (`component_id: "sensor"`) with `parent_id` set to the container — on its declared `id:`, or on the synthetic `<parent_instance_id>_<sub_key>` id when the sub-block has none — and always marks the platform item `is_entity_container: true` (even with no sub-blocks configured) so the frontend offers entity triggers on the sub-entities and never on the container. An instance, and a script, carries `shared_name: true` when more than one item is listed under its `id`. `unnamed_script_ids` lists the ids the scripts without an `id:` are listed under, which are not free for a new script. | |


What does this implement/fix?
Since #2854 a write aimed at a name more than one item carries is refused. The listing gave no sign of it, so a caller found out only from the refusal. The dashboard's editor saves as the user types, so it showed that refusal again on every pause.
The listing now says so up front:
automations/parse: a row carriesshared_name: truewhen more than one item carries the name of its location. The field is absent otherwise, so a listing without such a pair reads as before.automations/get_available: a component instance and a script carryshared_name: truethe same way.automations/get_availablealso returnsunnamed_script_ids, the ids the scripts without anid:are listed under. They are not free for a new script, andscriptskeeps listing declared scripts only, since it feeds the pickers ofscript.execute.The count behind the flags is the one the refusal uses, so the two cannot disagree. A test checks for every automation that it is marked exactly when a write to it is refused.
The MCP tool
list_automationsforwards the listing, so a model seesshared_nameon a row it cannot write before it tries. Nothing a caller sends changes.Checked against 57 hand written configs, 302 automations: no row is marked, and what
parseandget_availablereturn for them is the same as on main, apart from the newunnamed_script_idslist.#2853, a new form for the made up ids, is closed as not planned. This and the frontend change below replace it.
Related issue or feature (if applicable):
Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesFrontend coordination
Checklist
ruff,codespell, yaml/json/python checks).tests/where applicable.components.index.json/definitions/components/*.jsonhave not been hand-edited (regenerate viascript/sync_components.pyif a sync is needed).docs/ARCHITECTURE.mdand/ordocs/API.md.