Add experience-level and remote-compute onboarding preferences#1445
Add experience-level and remote-compute onboarding preferences#1445bdraco wants to merge 21 commits into
Conversation
Introduces two persistent, user-editable preferences that the onboarding redesign drives off of: - experience_level (beginner / ui / yaml), nullable until chosen - remote_compute_only The onboarding step list is now environment- and preference-aware: the use-case step appears only on non-HA installs and the Wi-Fi step is dropped when remote_compute_only is set. A startup migration defaults pre-existing installs (prior onboarding completed, or device YAML already on disk) to the yaml experience and acknowledges onboarding so the wizard never auto-pops for them.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1445 +/- ##
=======================================
Coverage 99.50% 99.50%
=======================================
Files 222 222
Lines 17253 17305 +52
=======================================
+ Hits 17168 17220 +52
Misses 85 85
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
|
esphbot
left a comment
There was a problem hiding this comment.
Blocking issues found — see the review comment above.
There was a problem hiding this comment.
Pull request overview
Adds two new onboarding-related user preferences (experience level and remote-compute-only) and updates the onboarding flow to conditionally include/skip steps based on environment (HA add-on vs non-HA) and preferences. It also migrates pre-existing installs to the YAML experience at startup so existing users aren’t re-prompted by the new onboarding version.
Changes:
- Introduce
ExperienceLevelandremote_compute_onlyinUserPreferences, and bump onboarding flow version to2. - Make
onboarding/get_statereturn an environment-/preference-aware step list (use-case + experience steps, and optional Wi-Fi step). - Add startup migration to default pre-existing installs to the YAML experience and mark onboarding acknowledged; expand tests and API docs accordingly.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_onboarding_controller.py |
Expands coverage for the new onboarding step list and migration behavior. |
esphome_device_builder/models/preferences.py |
Adds ExperienceLevel enum and new preference fields (experience_level, remote_compute_only). |
esphome_device_builder/models/onboarding.py |
Adds new step IDs and bumps ONBOARDING_VERSION to 2. |
esphome_device_builder/device_builder.py |
Runs onboarding migration during startup before controllers begin serving requests. |
esphome_device_builder/controllers/onboarding.py |
Computes step list based on on_ha_addon + preferences; implements migration for pre-existing installs. |
docs/API.md |
Documents new preference fields and the updated onboarding step semantics. |
The migration's config detection globbed only *.yaml, so a .yml-only install that never acknowledged onboarding was misclassified as fresh and got the wizard popped, the opposite of the migration's intent. Switch to esphome.util.list_yaml_files so the extension, secrets, and dotfile rules match the device scanner and can't drift. Log the scan OSError instead of silently swallowing it, and add .yml + unreadable-dir tests.
|
Addressed the review in aeb219f.
Left the |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
A missing config dir is a genuinely fresh install (no configs, return False). A dir that exists but can't be read now returns True instead, so a transient read error can't reclassify a real install as fresh and re-pop the wizard, which is the failure direction the migration is meant to avoid. FileNotFoundError keeps the fresh-install path; other OSError assumes pre-existing and logs.
|
Addressed the silent-failure note in 1800546. |
mark_acknowledged and the pre-existing migration bumped onboarding_completed_version with the same max() expression and the same comment. Extract _acknowledge_current_version so the monotonic rule lives in one place; mark_acknowledged now passes it straight to mutate_preferences instead of wrapping a closure.
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
- Move the ExperienceLevel multi-line docstring summary to the line after the opening quotes, per the repo docstring convention. - Annotate the _step test helper as OnboardingStepStatus | None to match its next(..., None) behaviour.
|
Addressed the two style nits in e720453: the |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
The migration acknowledged onboarding for every pre-existing install, which also silenced the Wi-Fi prompt. Now it only acknowledges installs that already completed an earlier onboarding (their prior Wi-Fi save or decline stands); an install known only by its device YAML is migrated to the YAML experience but left un-acknowledged, so a missing-Wi-Fi prompt still surfaces.
The frontend gates first-paint UI on the experience level and remote-compute flag, so pushing them in the subscription snapshot lets the client paint without a separate config/get_preferences round-trip whose failure would otherwise uniquely break device creation. Read sync in _send_initial per the hot-path rule; the metadata sidecar is small.
|
Added user preferences to the |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
Drop the parenthetical examples and the redundant tail; the docs carry the full rationale.
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
A corrupt or schema-incompatible prefs blob was defaulted silently and the source then erased: the migration stripped the sidecar _preferences key and the next save overwrote a corrupt dedicated file. Make _decode strict (propagate), preserve a corrupt dedicated file by renaming it .corrupt, and leave an undecodable legacy sidecar blob in place (skip the strip); both log before falling back to defaults. Drops the now-unused _prefs_from_data.
|
The three silent-failure items are the same data-loss concern (a corrupt/incompatible blob silently defaulted and the source destroyed) — fixed in 7d50de9 by preserving undecodable data rather than erasing it:
Both paths Re the daemon-lifecycle checklist item: confirmed present in |
Solid fix. One residual gap. |
|
|
esphbot
left a comment
There was a problem hiding this comment.
Blocking issues found — see the review comment above.
|
Good catch — |
|
|
esphbot
left a comment
There was a problem hiding this comment.
Blocking issues found — see the review comment above.
|
Fixed in 4c09b8c. Important #1 (strip after unconfirmed write — real data-loss path): confirmed against Silent Failure #2 (corrupt dedicated file skips sidecar recovery): folded in. Both the normal first-run path and the corrupt-file path now route through one Tests: added Silent Failure (acknowledged-before-durable write): intended. This is the RAM-canonical Store contract shared with the peer-link and device-metadata stores — |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
|
Fixed in 762a147 — One correction worth flagging: Added |
|
Both items addressed (this review was at 4c09b8c; HEAD is now 97c956a). Suggestion #1 (update() returns canonical ref): fixed in 762a147. MEDIUM (failed preserve can cascade into destroying the corrupt file): real gap in the "preserved, never destroyed" guarantee — fixed in 97c956a. When Test |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
|
Suggestion #1 (-O-strippable assert): fixed in 2f25f43. Swapped the MEDIUM (fire-and-forget persistence): keeping as the documented RAM-canonical design, as the review notes is acceptable — same contract as the peer-link and device-metadata stores; durability rides the shutdown flush and the background write already logs failures (storage.py:215). Surfacing per-write durability to the WS caller would mean abandoning the debounce the pattern exists for. If we later want a signal, the right shape is a store-health event rather than a synchronous flush-confirm on the hot write path; out of scope here. |
PR Review — Add experience-level and remote-compute onboarding preferencesSolid, well-defended feature — merge-ready. One minor, pre-existing input-validation nuance worth a glance. Strengths (verified against the branch tip 2f25f43, not just the diff):
🟢 Suggestions1. set_preferences surfaces InvalidFieldValue / silently coerces non-bool fields (`esphome_device_builder/controllers/config/controller.py`, L118-122)
Why it's only a suggestion: the frontend is the sole caller and the onboarding flow sends well-typed JSON ( If you want to harden the boundary, validating/coercing the known fields in Checklist
Silent Failure Analysis🟡 **MEDIUM** — silent persistence drop with success return (`esphome_device_builder/controllers/config/_preferences_store.py:138-142`)Risk: After a corrupt-file rename fails and Fix: Surface the persistence-disabled state to callers (e.g. raise or flag a Automated review by Kōan (Claude) |
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
|
Both deferred as out-of-scope for this PR. Suggestion #1 (set_preferences input validation): pre-existing mashumaro behaviour for the existing enum/bool fields ( MEDIUM (silent persistence drop when |
…-levels # Conflicts: # docs/API.md # esphome_device_builder/controllers/config/_preferences_store.py # esphome_device_builder/controllers/onboarding.py # esphome_device_builder/device_builder.py # tests/controllers/config/test_preferences_store.py # tests/test_onboarding_controller.py # tests/test_subscribe_events_cleanup.py
What does this implement/fix?
Adds two onboarding preferences, an experience level (beginner, ui, yaml) and a remote-compute-only flag, and makes the onboarding steps aware of them. The use-case question only appears outside the Home Assistant add-on, and the Wi-Fi step is skipped when the install is remote-compute-only. Existing users (YAML configs on disk, or a previously completed onboarding) are silently defaulted to the "Prefer YAML" experience and never see the wizard, so they aren't re-prompted.
The dashboard reads these on every connect, so preferences now ride the
subscribe_eventsinitial_statesnapshot rather than a separateconfig/get_preferencesround-trip. To serve that snapshot synchronously without blocking the event loop, preferences moved to a RAM-canonicalPreferencesStore(the same per-file debouncedStoreas the device-metadata and peer-link settings): loaded once at startup, migrated out of the shared.device-builder.jsonsidecar into their own.device-builder-preferences.jsonon first run, mutated in RAM with a debounced write. That storage conversion also closes the read-vs-write race the old sidecar read-modify-write had, where two concurrentset_preferenceswrites could read the same baseline and clobber each other's field.Related issue or feature (if applicable):
Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesFrontend coordination
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.