Skip to content

44 โ€” Onboarding and First Run

Scope. Getting from installed to usable: the config flow that asks for almost nothing, the per-map onboarding state that is computed rather than stepped, and the sidebar panel registered per vacuum. The ongoing step machine and its drift signal are 31 โ€” The Setup Layer; these own a different part of the store.

One decision runs through all three: nothing here blocks. Every step can be deferred, and completeness is something the system reports rather than something it enforces.


1. The config flow accepts a blank vacuum

config_flow.py::EufyVacuumConfigFlow collects the vacuum entity, an optional tested-model string and free-text notes โ€” and the vacuum picker is optional. Leaving it blank still creates the config entry; the user fills it in later through config_flow.py::EufyVacuumOptionsFlow.

That matters more than it looks. The vacuum this integration manages is owned by a different integration, and on a cold install that integration may not have finished setting up โ€” the same race 39 ยง2 documents at the adapter layer. A required picker would mean the install fails for a reason the user cannot act on and would not understand, at the one moment they have the least context.

Accepting an empty entry converts a hard failure into a deferred step.


2. Onboarding state is computed, and reading it creates nothing

onboarding/manager.py::OnboardingManager owns the onboarding subtree and is one of the three subsystems constructed without a manager reference (33 ยง1) โ€” it takes the data dict and hass, and nothing else.

onboarding/manager.py::get_onboarding_state derives completeness from stored flags plus live map data, and its docstring carries the important half: creates nothing. A status read does not mint the record it is reporting on.

That is the same discipline as 36 ยง3 and 38 ยง5, applied one layer down. A read that creates state on the way past makes "has this been onboarded?" unanswerable, because asking makes it true.

Two consequences of computing rather than storing completeness:

  • Only enabled rooms need a floor type. Disabling a room removes it from the requirement without touching the onboarding record, because the requirement is derived from the live room set each time it is asked.
  • New rooms are detected from the vacuum's own attributes (onboarding/manager.py::check_for_new_rooms), not from a stored expectation, so a map that gains a room reopens onboarding without a migration.

onboarding/manager.py::confirm_floor_type records a floor type as explicitly confirmed by the user, which is distinct from a floor type that merely has a value. That distinction is what lets 23 ยง3 treat floor type as collected for the map render and the onboarding gate โ€” and not as licence to pick a water level nobody asked for.

onboarding/manager.py::get_rooms_onboarding_summary aggregates across maps, and onboarding/manager.py::reset_onboarding clears one map โ€” per map, because a second floor is a separate onboarding.


3. One panel per vacuum, with a name the user owns

panels.py::async_register_vacuum_panel registers one sidebar panel per managed vacuum, at a url derived from the vacuum's object id, all pointing at the same web component.

The title is per-vacuum and user-settable, stored on the managed-vacuum record and defaulting to the product name when unset. The reason is concrete: before that, two vacuums produced two identical sidebar entries, and the sidebar is the one place a user cannot disambiguate by hovering over an entity id.

A default that is right for one instance and useless for two is a common shape, and the fix is not a cleverer default โ€” it is making the field editable and defaulting it.

panels.py is also deliberately the single source of truth for that registration, because three separate paths reach it: startup, adding a vacuum at runtime, and a rename. Panels registered for an entry are tracked in a ledger so unload can remove them by name (39 ยง4) โ€” the panel helper offers no unregister call of its own, so whatever was registered has to be remembered.


4. Common wrong assumptions

assumption reality
setup requires choosing a vacuum the picker is optional and the entry is created regardless โ€” ยง1
onboarding is a wizard with steps completeness is computed from live data every time it is asked โ€” ยง2
reading onboarding state initialises it it explicitly creates nothing โ€” ยง2
every room needs a floor type only enabled ones, and the requirement is re-derived, not stored โ€” ยง2
a floor type with a value is confirmed user confirmation is a separate fact, and only it gates onboarding โ€” ยง2
the sidebar entry is named after the integration the title is per-vacuum and user-settable, because two vacuums produced two identical entries โ€” ยง3

Registries

00b-invariants.md ยท 00c-replicas.md