Skip to content

Consume schema-author visibility hints + cascade strictness through nested groups#350

Merged
bdraco merged 4 commits into
mainfrom
feature/consume-advanced-yaml-only-flags
May 11, 2026
Merged

Consume schema-author visibility hints + cascade strictness through nested groups#350
bdraco merged 4 commits into
mainfrom
feature/consume-advanced-yaml-only-flags

Conversation

@bdraco
Copy link
Copy Markdown
Member

@bdraco bdraco commented May 6, 2026

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.Visibility StrEnum) to cv.Optional / cv.Required so 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.py to honour the schema's intent and apply a strictness-cascade to nested structures.

Schema-driven flag mapping

Schema visibility Catalog flag
absent name-based heuristic decides advanced (existing behaviour)
"advanced" advanced=True
"yaml_only" hidden=True (the frontend already filters hidden)

The hand-curated _ADVANCED_BASE_KEYS / _IMPORTANT_KEYS classifier 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_priority on core.COMPONENT_SCHEMA (yaml_only); update_interval on the time platform via cv.polling_component_schema(visibility=Visibility.ADVANCED) (advanced). Sensors and other polling components stay un-flagged so their update_interval still 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_cascade walks the catalog tree once at the top of _extract_config_entries and pushes parent strictness down. The strictness ordering is YAML_ONLY > ADVANCED > unset; a child can be stricter than its parent, never less strict. Without the cascade:

  • An advanced parent block would render under a disclosure while its inner fields surfaced on the main form (leaky disclosure UX).
  • A yaml_only parent would leak controls into the editor with no surrounding context.

The cascade also enforces the per-entry strictness ordering: a field with hidden=True is also forced advanced=True, since every yaml-only field is at-least advanced by definition.

Tests

tests/test_sync_components_ui_hints.py covers:

  • Schema-driven mapping in both directions ("advanced"advanced=True, "yaml_only"hidden=True)
  • Heuristic fallback when visibility is absent
  • Forward-compat: an unrecognised visibility value falls back to the heuristic (so a future upstream level like "deprecated" doesn't silently disappear from the form)
  • Cascade rule: parent-ADVANCED pushes to unset children; parent-YAML_ONLY hides every descendant; inner YAML_ONLY under an ADVANCED parent stays hidden
  • Multi-level descent (grandparent forces leaf strictness)
  • Cascade no-op on a tree with no strict markers
  • config_entries: None tolerated without traversal

Activation

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-derived advanced values 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

  • 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

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.json has 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.

@bdraco bdraco added the enhancement Improvement to an existing feature label May 6, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 6, 2026

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing feature/consume-advanced-yaml-only-flags (e73ac04) with main (52a98fa)

Open in CodSpeed

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 6, 2026

Codecov Report

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

Additional details and impacted files

Impacted file tree graph

@@            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              
Flag Coverage Δ
py3.12 99.18% <ø> (-0.01%) ⬇️
py3.14 99.20% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

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.py to read advanced from the schema and apply it to the generated catalog entries.
  • Teach script/sync_components.py to read yaml_only from the schema and map it to the existing catalog hidden flag.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread script/sync_components.py Outdated
Comment thread script/sync_components.py Outdated
bdraco added a commit that referenced this pull request May 6, 2026
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).
@bdraco bdraco changed the title Consume schema-author UI hints (advanced, yaml_only) from upstream esphome Consume schema-author visibility hints + cascade strictness through nested groups May 6, 2026
@bdraco bdraco requested a review from Copilot May 6, 2026 03:58
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

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.

bdraco added 3 commits May 11, 2026 11:14
…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).
@bdraco bdraco force-pushed the feature/consume-advanced-yaml-only-flags branch from 867652e to fe71b04 Compare May 11, 2026 16:17
@bdraco bdraco marked this pull request as ready for review May 11, 2026 16:24
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``.
@bdraco bdraco merged commit 046e6d0 into main May 11, 2026
13 checks passed
@bdraco bdraco deleted the feature/consume-advanced-yaml-only-flags branch May 11, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants