Consume schema-author visibility hints + cascade strictness through nested groups#350
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #350 +/- ##
==========================================
- Coverage 99.21% 99.20% -0.01%
==========================================
Files 83 83
Lines 10699 10628 -71
==========================================
- Hits 10615 10544 -71
Misses 84 84
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Updates the component catalog sync script to consume new per-field UI hints emitted by the upstream ESPHome schema bundle, allowing schema authors to control whether a field is treated as “advanced” or only configurable via YAML.
Changes:
- Teach
script/sync_components.pyto readadvancedfrom the schema and apply it to the generated catalog entries. - Teach
script/sync_components.pyto readyaml_onlyfrom the schema and map it to the existing cataloghiddenflag.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two real concerns flagged by Copilot: 1. The previous shape (``schema_advanced or _classify_advanced(...)``) was additive — when the upstream dumper one day starts emitting ``advanced: false`` to express "schema author opted out of the advanced section", the OR would still let the heuristic flip it on. Switch to keying off ``"advanced" in raw`` so a present-with- value-False on the schema honours the schema instead. Today the dumper only emits the key when True; this change is forward-prep for the polarity-symmetric case. 2. No test coverage for the new flag mapping. Add ``tests/test_sync_components_ui_hints.py`` with seven cases: schema-True wins over heuristic-False, schema-False overrides heuristic-True, no flag falls back to heuristic, yaml_only maps to hidden, hidden defaults to False when absent, both flags are independent, and advanced=False + yaml_only=True is a legal combination (the flags are distinct dimensions).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…phome Pairs with esphome/esphome#16267, which adds ``advanced`` and ``yaml_only`` kwargs to ``cv.Optional`` / ``cv.Required`` so component authors can mark a field's UI treatment at the schema level. The dumper emits the flags onto the per-field dict in the schema bundle this script consumes. When an entry's raw schema dict carries either flag, honour the schema's intent over the existing name-based heuristic: * ``advanced: true`` → entry's ``advanced`` flag set, regardless of what ``_classify_advanced`` would have decided. * ``yaml_only: true`` → entry's ``hidden`` flag set so the frontend's existing ``hidden`` skip applies. Maps the new schema concept onto the catalog's existing field — no consumer-facing surface change. The heuristic stays as the fallback for the long tail of fields the schema doesn't yet annotate; as more fields opt in upstream, the heuristic's hand-curated key sets (``_ADVANCED_BASE_KEYS``, etc.) can shrink toward zero. Forward-prep: the schema bundle on https://schema.esphome.io won't carry the flags until the upstream esphome PR lands and a new schema is published. Until then, this script change is a no-op; the existing heuristic-derived ``advanced`` values stay.
Two real concerns flagged by Copilot: 1. The previous shape (``schema_advanced or _classify_advanced(...)``) was additive — when the upstream dumper one day starts emitting ``advanced: false`` to express "schema author opted out of the advanced section", the OR would still let the heuristic flip it on. Switch to keying off ``"advanced" in raw`` so a present-with- value-False on the schema honours the schema instead. Today the dumper only emits the key when True; this change is forward-prep for the polarity-symmetric case. 2. No test coverage for the new flag mapping. Add ``tests/test_sync_components_ui_hints.py`` with seven cases: schema-True wins over heuristic-False, schema-False overrides heuristic-True, no flag falls back to heuristic, yaml_only maps to hidden, hidden defaults to False when absent, both flags are independent, and advanced=False + yaml_only=True is a legal combination (the flags are distinct dimensions).
Pairs with the upstream esphome refactor that replaces the two ``advanced`` / ``yaml_only`` booleans with a single ``visibility`` ``StrEnum`` kwarg. The schema dump now emits ``"visibility": "advanced" | "yaml_only"`` instead of two booleans. Two behaviour changes on the consumer side: 1. Read ``visibility`` (string) instead of two booleans. The schema's value drives ``advanced`` / ``hidden`` directly when present; the name-based heuristic stays as the fallback for the long tail of fields the schema doesn't yet annotate. 2. Apply a cascade pass after leaf conversion. ``YAML_ONLY`` is strictly stronger than ``ADVANCED``, which is strictly stronger than no setting; a stricter parent forces every descendant at-least as strict. Without the cascade, an ``advanced`` parent block could render under a disclosure while its inner fields surfaced on the main form (leaky disclosure UX), and a ``yaml_only`` parent could leak controls with no surrounding context. The cascade walks the tree once at the top of ``_extract_config_entries`` and merges per-entry: each field's own ``hidden`` flag also implies ``advanced`` (per the strictness ordering), and parents push their state down through ``config_entries`` recursion. Test coverage matrix in ``tests/test_sync_components_ui_hints.py`` covers schema-driven + heuristic + cascade interactions across flat fields, nested groups, multi-level descent, and edge cases (unrecognised visibility string, ``config_entries: None``, inner-yaml_only-under-advanced-parent).
867652e to
fe71b04
Compare
Upstream esphome (#16267) models the visibility kwarg as a
``cv.Visibility`` ``StrEnum`` and dumps the string form on the
wire. The consumer side here was comparing against two
module-level string constants, which works (the wire shape is a
string either way) but loses the typed-value the upstream
intentionally encoded.
Replace ``_VISIBILITY_ADVANCED`` / ``_VISIBILITY_YAML_ONLY`` with
a ``class Visibility(StrEnum)`` whose member values are exactly
the strings the dumper emits. Compare ``raw.get("visibility")``
against ``Visibility.ADVANCED`` / ``Visibility.YAML_ONLY``
directly — ``StrEnum`` members compare equal to their string
values, so the wire shape doesn't need a parse step and an
unrecognised value still falls through to the heuristic
(forward-compat for a future ``"deprecated"`` level upstream).
No behaviour change; existing tests pass unmodified because they
already pass the raw string form (the wire shape) into
``_convert_field``.
What does this implement/fix?
Consumer-side wire-up for esphome/esphome#16267, which landed upstream in milestone 2026.5.0b1 and added a
visibility=kwarg (cv.VisibilityStrEnum) tocv.Optional/cv.Requiredso the field author can mark a config_var's UI treatment in the schema itself. The dumper emits"visibility": "advanced" | "yaml_only"on the per-field dict in the schema bundle this script consumes; the key is absent when not set so the change is fully backwards-compatible with existing consumers.This PR teaches
script/sync_components.pyto honour the schema's intent and apply a strictness-cascade to nested structures.Schema-driven flag mapping
visibilityadvanced(existing behaviour)"advanced"advanced=True"yaml_only"hidden=True(the frontend already filtershidden)The hand-curated
_ADVANCED_BASE_KEYS/_IMPORTANT_KEYSclassifier stays as the fallback for the long tail of fields the schema doesn't yet annotate; as upstream adoption grows, the heuristic shrinks toward zero.Two real fields ship marked upstream in 2026.5.0b1:
setup_priorityoncore.COMPONENT_SCHEMA(yaml_only);update_intervalon thetimeplatform viacv.polling_component_schema(visibility=Visibility.ADVANCED)(advanced). Sensors and other polling components stay un-flagged so theirupdate_intervalstill surfaces on the main form where users want to tune the polling cadence.Cascade pass
Upstream is explicit that cascading lives on the consumer side; the schema dump records each field's own author-declared intent and nothing more. A new
_apply_visibility_cascadewalks the catalog tree once at the top of_extract_config_entriesand pushes parent strictness down. The strictness ordering isYAML_ONLY>ADVANCED> unset; a child can be stricter than its parent, never less strict. Without the cascade:advancedparent block would render under a disclosure while its inner fields surfaced on the main form (leaky disclosure UX).yaml_onlyparent would leak controls into the editor with no surrounding context.The cascade also enforces the per-entry strictness ordering: a field with
hidden=Trueis also forcedadvanced=True, since every yaml-only field is at-least advanced by definition.Tests
tests/test_sync_components_ui_hints.pycovers:"advanced"→advanced=True,"yaml_only"→hidden=True)visibilityis absentvisibilityvalue falls back to the heuristic (so a future upstream level like"deprecated"doesn't silently disappear from the form)ADVANCEDpushes to unset children; parent-YAML_ONLYhides every descendant; innerYAML_ONLYunder anADVANCEDparent stays hiddenconfig_entries: Nonetolerated without traversalActivation
The next nightly catalog sync against the published 2026.5.0b1 schema (or whichever release first ships with #16267 in the bundle on https://schema.esphome.io) is what flips the two real fields above to their new visibility values in
components.json. Until that schema version is the catalog's pin, this change is a no-op — the existing heuristic-derivedadvancedvalues stay; the cascade only mutates fields that already carry one of the strict flags from somewhere.Related issue or feature (if applicable):
Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesFrontend coordination
Checklist
ruff,codespell, yaml/json/python checks).tests/where applicable.components.jsonhas not been hand-edited (regenerate viascript/sync_components.pyif a sync is needed).docs/ARCHITECTURE.mdand/ordocs/API.md.