Unify Expert Mode with a beginner/expert onboarding experience#818
Conversation
Replaces the single-step Wi-Fi onboarding auto-pop with a short stepped wizard and adds a persistent experience level the dashboard tailors itself to: - A stepped first-run wizard (use-case -> experience -> Wi-Fi). The use-case step appears on non-HA installs; choosing remote-compute drops the Wi-Fi step. The standalone Wi-Fi rotation dialog stays for the kebab entry; both share a wifi-fields helper. - An experience level (beginner / ui / yaml) and a remote-compute-only toggle, both editable any time in a new Settings -> Experience section. Experience is the sole driver of the YAML diff button (beginners off, UI / YAML on) and the editor's first-open layout (YAML users land in the split view), so the manual Settings -> Editor toggle and the command-palette quick-toggle are removed. - Remote-compute-only hides every device-creation entry point (New device, FAB, table Create, Adopt, serial wizard). Coordinated with the device-builder backend PR that adds the experience_level / remote_compute_only preferences and the environment-aware onboarding steps.
|
|
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 a first-run onboarding wizard that captures a user “experience level” and optionally enables a remote-compute-only mode, then uses these preferences to adapt the dashboard/editor UI and hide device-creation entry points when appropriate.
Changes:
- Introduces a stepped onboarding wizard (use-case → experience → Wi‑Fi, with conditional steps) and shared Wi‑Fi input rendering utilities.
- Adds
experience_level+remote_compute_onlypreferences, contexts, and Settings → Experience section to edit them post-onboarding. - Updates dashboard/editor behavior based on experience level and remote-compute-only mode (layout seeding, hiding creation affordances).
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/util/onboarding-gate.test.ts | Adds unit tests for Wi‑Fi-step-specific pending detection. |
| src/util/onboarding-gate.ts | Adds isWifiSetupPending() to scope Wi‑Fi pending logic to the Wi‑Fi step only. |
| src/translations/en.json | Adds new wizard + experience/settings copy; removes old editor/diff-toggle copy. |
| src/pages/device.ts | Seeds first-open editor layout from experience_level when no local layout is saved. |
| src/pages/dashboard.ts | Consumes remoteComputeOnlyContext and blocks serial auto-wizard in remote-compute-only mode. |
| src/context/index.ts | Re-exports new experience/remote-compute contexts. |
| src/context/contexts.ts | Defines experienceLevelContext and remoteComputeOnlyContext. |
| src/components/settings-dialog/types.ts | Adds new Settings section id for “experience”; removes legacy “editor” section. |
| src/components/settings-dialog/experience-section.ts | New Settings UI for experience level selection + remote-compute-only toggle. |
| src/components/settings-dialog/editor-section.ts | Removes legacy YAML-diff-button toggle section. |
| src/components/settings-dialog.ts | Wires new Experience section and icons; removes editor section wiring. |
| src/components/onboarding/wifi-fields.ts | New shared Wi‑Fi SSID/password rendering + validation helper. |
| src/components/onboarding/onboarding-wizard-styles.ts | New shared styles for the onboarding wizard dialog. |
| src/components/onboarding/onboarding-wizard-dialog.ts | New multi-step onboarding wizard dialog (conditional flow + Wi‑Fi save + acknowledge). |
| src/components/onboarding/choice-card.ts | New reusable selectable “choice card” renderer for wizard/settings. |
| src/components/onboarding/choice-card-styles.ts | New shared styling for choice cards across wizard and settings. |
| src/components/onboarding-wifi-dialog.ts | Refactors to use shared renderWifiFields() and isWifiPasswordTooShort(). |
| src/components/dashboard/render-toolbar.ts | Hides “add device” card/FAB when remote-compute-only is enabled. |
| src/components/dashboard/render-content.ts | Hides discovery adopt section and table create action when remote-compute-only is enabled. |
| src/components/command-palette.ts | Removes YAML-diff toggle integration from the command palette. |
| src/components/command-palette-actions.ts | Removes editor/diff-toggle command actions. |
| src/components/app-shell/settings-actions.ts | Adds optimistic persistence for experience level + remote-compute-only with revert+toast on failure. |
| src/components/app-shell/data-load.ts | Loads new preferences + adjusts onboarding pending logic and use-case presence detection. |
| src/components/app-shell.ts | Provides new contexts, mounts wizard dialog, and wires new settings events. |
| src/api/types/system.ts | Adds ExperienceLevel, new preferences fields, and new onboarding step ids. |
Comments suppressed due to low confidence (1)
test/util/onboarding-gate.test.ts:41
stateWith()is typed asReturnType<typeof wifi>[], but the new tests passexperience(...)steps too. This makes the test file fail TypeScript type-checking becauseOnboardingStepId.EXPERIENCE_LEVELisn’t assignable toOnboardingStepId.WIFI_CREDENTIALS. TypestepsasOnboardingState["steps"](orOnboardingStep[]) so the helper matches the real API shape.
const stateWith = (
steps: ReturnType<typeof wifi>[],
current_version = 1,
completed_version = 0
): OnboardingState => ({
current_version,
completed_version,
steps,
});
- Add an in-flight guard so a reconnect mid-write can't reload the pre-write snapshot over an optimistic experience / remote-compute value (mirrors the existing _remoteBuildSetInFlight pattern). - Log preference-load failures instead of swallowing them; experience and remote-compute have no localStorage backing, so a silent failure could leave device creation visible on an install that wanted it hidden. - The wizard now persists its picks (awaited) before acknowledging, so a failed write can't mark onboarding done with experience / remote-compute unset and never re-pop. Drops the fire-and-forget navigation dispatches (a dismissed wizard no longer half-commits) and the app shell refreshes prefs on the acknowledged event. - Label the choice radiogroups and hide the decorative step dots from the accessibility tree. - Back renderWifiFields with a shared style module so .field / .error can't drift between the wizard and the rotation dialog.
|
Addressed the Kōan and Copilot reviews in 7b1f067.
Verified the full wizard flow end to end on a fresh install; it persists |
The wizard and the Settings experience section each rebuilt the same experience-option array in render() and duplicated the level-to-yaml-diff-button rule. Move both to src/util/experience.ts (EXPERIENCE_OPTIONS, yamlDiffForExperience) so the option list, icons, and the diff-button mapping have one source of truth and aren't re-allocated per render.
|
Verified all seven at 7b1f067. Persist-before-ack correct: |
|
|
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
- Remove two pre-existing unused keys (layout.yaml_diff_button, settings.editor) flagged in review; both had zero call sites. - Reword the loadThemePreference catch comment to match behaviour: the last loaded values are kept (not reset), and the failure is logged rather than toasted because it runs on every reconnect.
|
Addressed the cleanup items in 0f1d41b: pruned the two pre-existing orphan keys ( Left as follow-ups: the radiogroup roving-tabindex / arrow-key model (the cards are keyboard-operable today via Tab plus Enter/Space and the groups are labelled, so it is usable; full roving focus would thread focus management through the shared render helper and is cleaner as its own change), and extracting |
Reword matches behavior. Catch keeps last values, resets nothing — sticky after first success. Disagree with prior HIGH. One residual edge: first-ever getPreferences failure leaves remote_compute_only default false, briefly rendering creation UI. Low impact, retries on reconnect. Tabindex deferral reasonable — cards operable via Tab plus Enter/Space, groups labelled. Separate note: _prefsSetInFlight now guards three write paths; consider a counter so overlapping experience/remote-compute writes don't clear the gate early. |
|
|
esphbot
left a comment
There was a problem hiding this comment.
Blocking issues found — see the review comment above.
Split the onboarding auto-show so a fresh install (experience not yet chosen) gets the full wizard, while an existing install that already has an experience but is missing Wi-Fi gets only the standalone Wi-Fi dialog, which carries the "I don't use Wi-Fi" decline. Adds isExperienceChosen to the gate and a _onboardingShowWifi flag the app shell opens.
_prefsSetInFlight was a single boolean guarding two write paths (experience plus yaml-diff, and remote-compute). Two overlapping writes let the first settle and clear the gate while the second was still pending, so a reconnect could reload the pre-write snapshot over it. Replace it with a counter incremented per write and decremented in finally; loadThemePreference now skips while it is greater than zero, so every in-flight write must settle before the gate reopens.
|
Fixed in feb0b9d. Replaced the single On the pref-load suggestion: agreed with your reassessment; the catch keeps the last loaded values (it never resets them) and logs rather than toasting, since |
|
|
esphbot
left a comment
There was a problem hiding this comment.
Blocking issues found — see the review comment above.
Covers the previously-untested frontend logic for this feature: - onSetExperienceLevel / onSetRemoteComputeOnly: optimistic set, the experience-to-yaml-diff seed, and revert + toast on rejection. - The prefs-write in-flight counter: two overlapping writes keep the gate above zero until both settle. - loadOnboardingState routing: fresh installs open the wizard, existing installs missing wifi open the wifi dialog, configured installs open neither, and a session dismissal is respected. - loadThemePreference skips while a write is in flight and otherwise loads experience / remote-compute. - yamlDiffForExperience and EXPERIENCE_OPTIONS.
The wizard's _screens getter inlined the branch logic (use-case only non-HA, wifi dropped for remote-compute), which couldn't be unit-tested without the component. Move it to a pure wizardScreens() helper and pin all four environment/use-case combinations.
Counter approach correct. Overlapping experience + remote-compute writes now both settle before reconnect gate reopens. One gap remains: |
Skip called markOnboardingAcknowledged, which bumped onboarding_completed_version and permanently silenced the Wi-Fi prompt even though secrets.yaml still held the placeholder values. Skip now persists the experience pick but does not acknowledge, and session- dismisses, so the standalone Wi-Fi dialog (with its "I don't use Wi-Fi" decline) re-asks on the next login while Wi-Fi is unconfigured. Only saving credentials or the explicit decline stops the prompt.
|
|
…-levels # Conflicts: # src/translations/en.json
…-levels # Conflicts: # src/translations/en.json
…-levels # Conflicts: # src/api/types/event-subscription.ts # src/components/app-shell.ts # src/components/app-shell/data-load.ts # src/components/app-shell/events.ts # test/components/app-shell/events-initial-state-prefs.test.ts
Agree. Theme non-security-sensitive. |
PR Review — Unify Expert Mode with a beginner/expert onboarding experienceSolid, well-iterated PR. No blocking issues in the reviewed files; per repo policy a maintainer (Steven/Marcel) must merge, and the companion backend PR (esphome/device-builder#1445) must land in lockstep. Specific strengths:
Notes (non-blocking):
Checklist
Automated review by Kōan (Claude) |
esphbot
left a comment
There was a problem hiding this comment.
No blocking issues found.
…el and unused icons
What does this implement/fix?
Unifies the dashboard's Expert Mode with the onboarding experience level into a single
experience_levelpreference (beginnerorexpert).expertis Expert Mode: it unlocks the editor diff view, the device-navigator search, and the YAML content search. There is no separateexpert_modepreference;expertModeContextis derived fromexperience_level === expert, so every consumer keys off the level.Onboarding is a short stepped wizard (use case, experience, then Wi-Fi). The experience step offers Beginner or Expert. A remote-compute-only choice hides every device-creation entry point.
Settings → Appearance carries the Expert Mode toggle (its "what this unlocks" list collapses behind a chevron so the page doesn't grow) and, below it, the remote-compute toggle. There is no separate Experience section; both are editable any time.
Wi-Fi onboarding is unchanged for installs without Wi-Fi configured. If
secrets.yamlhas no Wi-Fi credentials we still prompt for them, since a device created without them generates an unresolvable!secret wifi_ssid. Existing installs are defaulted to the expert experience but are not acknowledged, so a missing-Wi-Fi install still auto-opens the standalone Wi-Fi dialog (not the experience wizard). "Maybe later" re-asks on the next login; only saving credentials or the explicit "I don't use Wi-Fi" decline stops it, exactly as before.Preferences ride the
subscribe_eventsinitial_statesnapshot (the same channel that seeds devices), so device creation gates on that snapshot rather than a separateconfig/get_preferencesround-trip; the snapshot is always present, so the gate can't flash creation UI on a remote-compute install.Companion backend PR: esphome/device-builder#1445
Related issue or feature (if applicable):
Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesChecklist
npm run lintpasses.npm run testpasses.Screenshots
First-run onboarding wizard (desktop / pip, not Home Assistant)
The wizard asks the use case, then the experience level, then Wi-Fi.
Home Assistant add-on flow
No use-case question; the wizard opens at the experience step, then Wi-Fi.
Existing install
Defaulted to the YAML experience on startup, so the wizard never pops.
Settings, Experience
The experience level and the remote-compute toggle are editable any time.


Remote-compute-only
Every device-creation entry point is hidden (no Add-device card, FAB, table Create, Adopt, or discovery). Future PR to add new remote dashboard state/status (out of scope for this PR).
Change Wi-Fi credentials (kebab menu)
Standalone Wi-Fi reconfiguration still flows through its own dialog.
Editor first-open by experience
Beginner and UI open on the navigator; YAML opens the split view with the diff tools ready.