Scope long-form pin Mode flags to the pin's provider - #590
Conversation
A long-form pin's Mode checkboxes (input/output/pullup/pulldown/
open_drain) were offered in full for every pin, but an external provider
allows only a subset: an I2C expander like pca9554 permits input/output,
a shift register sn74hc595 only output. The editor let users tick flags
ESPHome rejects.
Fetch the backend's {provider_key: [allowed_modes]} map once per session
(components/get_pin_registry_modes, cached + shared), thread it onto
RenderCtx, and in the pin renderer detect the provider key in the pin
value and narrow the Mode group's flag children to the allowed set.
A native pin (no provider key), an unknown provider, or a missing map
keeps every flag, so there's no regression.
PR Review — Scope long-form pin Mode flags to the pin's providerSolid, well-scoped bugfix — merge-ready. All prior Copilot/Kōan review points are addressed in the current diff.
Checklist
|
There was a problem hiding this comment.
Pull request overview
Scopes the long-form pin mode flag checkboxes in the visual config editor to the selected pin provider’s allowed modes (e.g., expander/shift-register pins), preventing users from selecting combinations ESPHome rejects.
Changes:
- Add
ESPHomeAPI.getPinRegistryModes()to fetch and sanitize the backend-provided{provider_key: allowed_modes[]}map. - Introduce a session-scoped cache for the pin-registry modes map and thread it through
RenderCtx. - Narrow the rendered
modeflag children in the pin renderer based on the provider key present in the pin value, with runtime tests covering provider/native/fallback behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/api/esphome-api.ts |
Adds getPinRegistryModes() WS wrapper with payload-shape filtering. |
src/util/pin-registry-modes-cache.ts |
Implements a session-scoped cache + subscription mechanism for the modes map. |
src/components/device/config-entry-form.ts |
Consumes apiContext, triggers the fetch, and passes cached modes into RenderCtx. |
src/components/device/config-entry-renderers-shared.ts |
Extends RenderCtx with optional pinRegistryModes. |
src/components/device/config-entry-pin-renderer.ts |
Detects provider key in pin value and scopes mode flag children accordingly. |
test/util/pin-registry-modes-cache.test.ts |
Adds unit tests for cache memoization, subscriber notification, and failure behavior. |
test/components/device/config-entry-pin-renderer-runtime.test.ts |
Adds runtime tests validating mode-flag scoping and graceful fallback cases. |
- getPinRegistryModes omits providers whose mode list filters to empty, so an empty allow-list can't scope the Mode group to zero checkboxes. - providerAllowedModes uses an own-property check (not `in`) so a pin value key like `toString` can't match a prototype member, and treats an empty allowed list as no scoping (show every flag). - The pin-registry-modes cache logs a failed fetch, isolates listener calls in try/catch, and clears listeners on reset (test isolation). - Move the wa-select-sync JSDoc back onto _syncSelectValues; the new connectedCallback gets its own terse doc.
|
All review points addressed in 521115d (backend #1163 is now merged): Copilot
Kōan
Also opened #592 to extract a shared fetch-once session-blob cache helper as a follow-up (the boilerplate |
Address review: scoping must not hide a mode flag the value already sets (a legacy/invalid config on an expander) or the user can't untick it to repair it. scopeModeChildren now keeps allowed flags plus any flag the current value sets (presentModeFlags expands a scalar shorthand or reads the object keys). Also kick the registry-modes fetch once when the api context lands rather than on every render.
Build the map (and the failure-fallback empty map) with Object.create(null) so an untrusted __proto__ / constructor key in the WS payload can't pollute the prototype when assigned. Addresses review on #590.
|
Addressed in cc2a4b3: The other inline threads (own-property check, empty-list omit, listener isolation, reset clears listeners, JSDoc relocation, legacy disallowed-but-present flag visibility + its test, fetch-once) were resolved in the earlier commits 521115d / fc62120 / c393365. |
What does this implement/fix?
The long-form pin Mode checkboxes (
input/output/pullup/pulldown/open_drain) were offered in full for every pin, but an external pin provider allows only a subset: an I2C expander likepca9554permitsinput/output, a shift registersn74hc595onlyoutput. The editor let users tick flags ESPHome rejects ([pullup] is an invalid option for [mode]).This is the frontend half of #584; the backend (esphome/device-builder#1163) derives a
{provider_key: [allowed_modes]}map from ESPHome'sPIN_SCHEMA_REGISTRYand ships it overcomponents/get_pin_registry_modes.Changes
ESPHomeAPI.getPinRegistryModes()— typed wrapper + payload-shape filter, mirroringgetIntegrationDocs.src/util/pin-registry-modes-cache.ts— session-scoped cache (fetched once, shared across forms, subscribe-to-populate; a failed fetch caches{}so it doesn't retry-storm).config-entry-formconsumesapiContext, kicks the fetch when the context lands, and threads the map ontoRenderCtx.pinRegistryModes(re-rendering when it populates).config-entry-pin-rendererdetects the provider key present in the pin value (pca9554) and narrows the Mode group's flag children to the allowed set before rendering. A native pin (no provider key), an unknown provider, or a missing map keeps every flag — no regression.Verification
Unit tests cover the scoping (pca9554 hides pullup; native keeps all; unknown provider / empty map fall back). Verified end-to-end in the browser against the backend branch: with the pca9554 switch the Mode group shows only Input / Output, while the native GPIO33 binary_sensor still shows all five.
Related issue or feature (if applicable):
components/get_pin_registry_modescommand + artefact); degrades gracefully to "show every flag" without it.Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesChecklist
npm run lintpasses.npm run testpasses.