Skip to content

Edit inline automations on flat singleton components (sun:, mqtt:)#1139

Merged
bdraco merged 1 commit into
mainfrom
fix-flat-singleton-automations
Jun 2, 2026
Merged

Edit inline automations on flat singleton components (sun:, mqtt:)#1139
bdraco merged 1 commit into
mainfrom
fix-flat-singleton-automations

Conversation

@bdraco
Copy link
Copy Markdown
Member

@bdraco bdraco commented Jun 2, 2026

What does this implement/fix?

The automation editor's parse / upsert / delete only handled list-domain
component instances (switch:, time: — YAML lists). Flat singleton
components that host inline on_*: triggers — sun: (on_sunrise/on_sunset),
mqtt: (on_message/on_connect/…) — are single mappings, and were silently
skipped: their inline automations couldn't be shown, edited, or deleted in the
Visual Editor, even though the wizard already offered sun.on_sunrise as
addable (the available surface and the parse/write surface disagreed).

Changes:

  • Parser walks both list instances and flat singleton mappings; a singleton
    keys on its declared id: or the domain name when id-less.
  • Locator routes a flat-mapping body to a singleton span (the whole block)
    instead of hunting for - list items, so upsert and delete both work.
  • Domain recovery resolves 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 AvailableComponentInstance
    entries so the available surface agrees with parse/write.

No new location kind is needed — ComponentOnLocation(..., index=None) already
models the single-handler form. writing_lists.py is untouched: flat singletons
are always single-handler (index=None) and route through the inline path, never
the 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-conf
normaliser.

Related issue or feature (if applicable):

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Frontend coordination

  • No frontend change needed
  • Companion frontend PR: esphome/device-builder-frontend#

This backend PR makes flat singletons parse/edit/delete and surfaces them in
get_available. A coordinated frontend PR will add the affordance for adding
a new automation to a flat singleton (frontend #552 deliberately left flat
singletons out of scope pending this backend support).

Checklist

  • The code change is tested and works locally.
  • Pre-commit hooks pass (ruff, codespell, yaml/json/python checks).
  • Tests have been added or updated under tests/ where applicable.
  • components.index.json / definitions/components/*.json have not been hand-edited (regenerate via script/sync_components.py if a sync is needed).
  • Architecture-level changes are reflected in docs/ARCHITECTURE.md and/or docs/API.md.

@github-actions github-actions Bot added the bugfix Bug fix label Jun 2, 2026
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jun 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.38%. Comparing base (6f0805c) to head (cdd8725).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1139   +/-   ##
=======================================
  Coverage   99.38%   99.38%           
=======================================
  Files         209      209           
  Lines       15376    15399   +23     
=======================================
+ Hits        15282    15305   +23     
  Misses         94       94           
Flag Coverage Δ
py3.12 99.37% <100.00%> (+<0.01%) ⬆️
py3.14 99.38% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...vice_builder/controllers/automations/controller.py 100.00% <100.00%> (ø)
..._device_builder/controllers/automations/parsing.py 100.00% <100.00%> (ø)
..._device_builder/controllers/automations/writing.py 98.92% <100.00%> (+<0.01%) ⬆️
esphome_device_builder/helpers/yaml/component.py 100.00% <100.00%> (ø)
esphome_device_builder/helpers/yaml/inline.py 100.00% <100.00%> (ø)
esphome_device_builder/helpers/yaml/scalar.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jun 2, 2026

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing fix-flat-singleton-automations (cdd8725) with main (6f0805c)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@bdraco bdraco requested a review from Copilot June 2, 2026 07:15
@bdraco bdraco force-pushed the fix-flat-singleton-automations branch from d14c18e to 6b05109 Compare June 2, 2026 07:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_available scoping to surface singleton component blocks as targetable AvailableComponentInstances, 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.

@esphbot
Copy link
Copy Markdown
Contributor

esphbot commented Jun 2, 2026

PR Review — Edit inline automations on flat singleton components (sun:, mqtt:)

Clean, well-scoped bugfix — flat singletons now parse/edit/delete consistently with get_available. Merge-ready.

  • block_body_is_list extraction is behavior-preserving vs the old inline scan in _normalize_multi_conf_block (first non-blank/non-comment line decides; empty body → not-a-list → same fall-through).
  • Domain recovery for id-less singletons is correctly placed after the literal id: scan and before the ambiguous catalog guess; id'd singletons still resolve via the id-scan, so mqtt.on_connect vs wifi.on_connect attaches under the configured domain.
  • No double-parsing of esphome.on_*: those are device-level and excluded from _component_trigger_domains, handled by the separate _parse_device_level path.
  • _locate_singleton_instance mirrors the existing stale-id refusal pattern (returns None → clean not-found), and the unknown-id path is tested.
  • Two minor, non-blocking suggestions: a possible non-display name: for singletons, and an optional multi-handler delete test.

🟢 Suggestions

1. Consider a delete test on a multi-handler singleton (`tests/test_automations_writer.py`, L264-272)

test_delete_inline_handler_on_sun_block deletes from the single-handler inline_sun_on_sunrise.yaml. The inline_sun_idd.yaml fixture already has both on_sunrise and on_sunset, so a delete test against it would pin the more interesting edge: that removing one handler leaves the sibling handler and the block's config keys intact (the handler_end scan in remove_inline_handler should stop at on_sunset's column).

Line coverage is already 100%, so this is purely a behavioral-edge nicety, not a gap that blocks merge.


Checklist

  • List-vs-mapping detection refactor preserves behavior
  • No double-parsing of device-level esphome triggers
  • Domain recovery ordering correct (id-scan → synthetic → domain-name → catalog)
  • Stale/unknown id refused cleanly
  • Tests verify observable behavior, not source text
  • Edge case coverage (empty body, bodyless singleton, ambiguous keys)
  • No injection / unsafe deserialization (SafeLoader via make_yaml)

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.
@bdraco bdraco force-pushed the fix-flat-singleton-automations branch from 6b05109 to cdd8725 Compare June 2, 2026 07:27
@bdraco bdraco marked this pull request as ready for review June 2, 2026 07:27
@bdraco bdraco merged commit 148d82c into main Jun 2, 2026
15 checks passed
@bdraco bdraco deleted the fix-flat-singleton-automations branch June 2, 2026 07:28
bdraco added a commit to esphome/device-builder-frontend that referenced this pull request Jun 2, 2026
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.
bdraco added a commit to esphome/device-builder-frontend that referenced this pull request Jun 2, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Visual Editor: inline automations on flat singleton components (sun:, mqtt:) aren't parsed or editable

4 participants