Edit inline automations on flat singleton components (sun:, mqtt:)#1139
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1139 +/- ##
=======================================
Coverage 99.38% 99.38%
=======================================
Files 209 209
Lines 15376 15399 +23
=======================================
+ Hits 15282 15305 +23
Misses 94 94
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
Comparing Footnotes
|
d14c18e to
6b05109
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes the automations editor’s inability to parse/edit/delete inline on_* handlers declared under flat singleton component blocks (e.g. sun:, mqtt:), bringing parse/write behavior in line with what get_available and the trigger catalog already expose.
Changes:
- Extend automation parsing to walk both list-based component instances and singleton mapping blocks, keying singleton instances by
id:(or by domain when id-less). - Update inline YAML instance location to treat singleton blocks as a single editable span (so upsert/delete work reliably).
- Update
get_availablescoping to surface singleton component blocks as targetableAvailableComponentInstances, and add coverage for parse/write/available behavior.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
esphome_device_builder/controllers/automations/parsing.py |
Parse inline triggers from both list instances and singleton mapping blocks; add shared instance-trigger parser helper. |
esphome_device_builder/controllers/automations/writing.py |
Improve domain recovery for id-less singleton instances (domain-name keyed) before falling back to catalog guessing. |
esphome_device_builder/controllers/automations/controller.py |
Include singleton component instances in get_available and refactor instance construction to a helper. |
esphome_device_builder/helpers/yaml/inline.py |
Detect non-list domain bodies and locate singleton blocks as a single instance span for inline handler edits. |
esphome_device_builder/helpers/yaml/scalar.py |
Add block_body_is_list() helper to centralize list-vs-mapping detection for YAML blocks. |
esphome_device_builder/helpers/yaml/component.py |
Reuse block_body_is_list() to simplify multi-conf normalization’s list-body detection. |
tests/test_automations_parse.py |
Add parsing tests for singleton inline triggers (sun, mqtt) including trigger-params handling. |
tests/test_automations_writer.py |
Add upsert/delete/round-trip tests for singleton inline handlers, including sibling key preservation. |
tests/test_automations_controller.py |
Add get_available test coverage ensuring singleton instances are surfaced as targets. |
tests/fixtures/automation_yamls/inline_sun_on_sunrise.yaml |
New fixture: id-less sun: with on_sunrise. |
tests/fixtures/automation_yamls/inline_sun_idd.yaml |
New fixture: id’d sun: with on_sunrise/on_sunset. |
tests/fixtures/automation_yamls/inline_sun_empty.yaml |
New fixture: sun: block with config keys only (no handlers). |
tests/fixtures/automation_yamls/inline_mqtt_singleton.yaml |
New fixture: singleton mqtt: with on_message trigger params and on_connect. |
PR Review — Edit inline automations on flat singleton components (sun:, mqtt:)Clean, well-scoped bugfix — flat singletons now parse/edit/delete consistently with
🟢 Suggestions1. Consider a delete test on a multi-handler singleton (`tests/test_automations_writer.py`, L264-272)
Line coverage is already 100%, so this is purely a behavioral-edge nicety, not a gap that blocks merge. Checklist
Automated review by Kōan6b05109 |
The automation editor only handled list-domain component instances (switch:/time: are YAML lists). Flat singleton components that host inline on_* triggers — sun: (on_sunrise/on_sunset), mqtt: (on_message /on_connect/…) — are single mappings, so the parser skipped them and the writer's locator couldn't place or delete their handlers. - Parser walks both list instances and flat singleton mappings; a singleton keys on its declared id or the domain name when id-less. - The inline locator routes a flat mapping body to a singleton span (the whole block) instead of hunting for list items. - Resolve an id-less singleton's domain before the ambiguous catalog guess, so a shared trigger key (mqtt.on_connect vs wifi.on_connect) attaches under the configured domain. - get_available surfaces singletons as targetable instances so the available surface and the parse/write surface agree. Hoist the shared "block body is a list?" check into block_body_is_list, replacing the duplicated inline scan in the multi-conf normaliser.
6b05109 to
cdd8725
Compare
The backend now edits inline on_* handlers on flat singleton components (sun:, mqtt:) addressed by `id or domain` (esphome/device-builder#1139). The frontend deliberately hid them — `instanceComponentId` returned null for a flat block (no `parentKey`), so `_shortcutTarget` offered no shortcut and the parser left the handler `unscoped`. Mirror the backend's `singleton_component_id`: a flat block resolves to its declared `id:` or the domain name. `parseYamlAutomations` now scopes a flat block's direct on_* to that id (carrying the domain in `parentKey` so the trigger catalog resolves the pretty name), and `_shortcutTarget` offers the per-section automations list / "+ Add automation" for it — like a list-item instance. List components are unchanged.
The backend now edits inline on_* handlers on flat singleton components (sun:, mqtt:) addressed by `id or domain` (esphome/device-builder#1139). The frontend deliberately hid them — `instanceComponentId` returned null for a flat block (no `parentKey`), so `_shortcutTarget` offered no shortcut and the parser left the handler `unscoped`. Mirror the backend's `singleton_component_id`: a flat block resolves to its declared `id:` or the domain name. `parseYamlAutomations` now scopes a flat block's direct on_* to that id (carrying the domain in `parentKey` so the trigger catalog resolves the pretty name), and `_shortcutTarget` offers the per-section automations list / "+ Add automation" for it — like a list-item instance. List components are unchanged.
What does this implement/fix?
The automation editor's parse / upsert / delete only handled list-domain
component instances (
switch:,time:— YAML lists). Flat singletoncomponents that host inline
on_*:triggers —sun:(on_sunrise/on_sunset),mqtt:(on_message/on_connect/…) — are single mappings, and were silentlyskipped: their inline automations couldn't be shown, edited, or deleted in the
Visual Editor, even though the wizard already offered
sun.on_sunriseasaddable (the available surface and the parse/write surface disagreed).
Changes:
keys on its declared
id:or the domain name when id-less.instead of hunting for
-list items, so upsert and delete both work.catalog guess, so a shared trigger key (
mqtt.on_connectvswifi.on_connect)attaches under the configured domain.
AvailableComponentInstanceentries so the available surface agrees with parse/write.
No new location kind is needed —
ComponentOnLocation(..., index=None)alreadymodels the single-handler form.
writing_lists.pyis untouched: flat singletonsare always single-handler (
index=None) and route through the inline path, neverthe list-index path.
DRY: the shared "is this block body a list?" check is hoisted into
block_body_is_list, replacing a duplicated inline scan in the multi-confnormaliser.
Related issue or feature (if applicable):
Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesFrontend coordination
This backend PR makes flat singletons parse/edit/delete and surfaces them in
get_available. A coordinated frontend PR will add the affordance for addinga new automation to a flat singleton (frontend #552 deliberately left flat
singletons out of scope pending this backend support).
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.