UX-13 P4: the setup wizard for installers
checks / unit (pull_request) Successful in 10m22s
infra-pin / fresh (pull_request) Successful in 1s

The Setup tab draws the Installer view's page model one step at a time,
from each field's `wizard` step in app/uilayout.py: meter (with the kettle
test), inverters and batteries, solar (optional), prices, peak limit
(battery modes: "comes in a later update", OPT-11), check and start.
It reuses P2's controls and saves through /config/validate and
/config/save. No route was added.

- First run: /config/model says `setup_needed` when the stored options name
  no power request entity, or on a new install (no `setup_done` and no
  `auto` in the Store, control not started). The page opens #setup only
  when opened with no route. The wizard's save writes `setup_done`.
- Kettle test: GET ha/entities?entity= reads one entity now, in W. The
  change decides: up keeps meter_invert off, down turns it on; a net or
  import/export meter that goes down blocks the step with what to check.
- Last step: validate.setup_checks() beside the refusals: the meter reads,
  each power request entity exists in HA (bad if not, SAFETY-77), battery
  levels and power with direction, solar sign, prices. Saves with
  shadow_mode on by default, then a confirmed restart naming the 0 W.
- P2 test finding (a): a stale save lists only the keys the other surface
  moved (the blob handed out on open, by fingerprint, against stored now).
- test_setup.py; tools/ux13_wizard_blobs.py makes the 88 blobs the wizard
  can save; all accepted by Supervisor 2026.08.0 AppOptions (offline).
- DECISIONS/UX-13-setup-wizard.md; DOCS "Setup, for a new install".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
This commit is contained in:
2026-09-28 05:31:18 +02:00
co-authored by Claude Opus 5.5
parent 515c11b380
commit be57b87c34
19 changed files with 1885 additions and 59 deletions
@@ -0,0 +1,93 @@
# UX-13 P4 — the setup wizard, and when it opens by itself
**Status: DECIDED 2026-09-28 (P4 dev; the design and mockup were approved
2026-09-27).** Card 6ab979852f3eca02fb40f69e. Builds on
[[UX-13-one-layout-table]]. Design note: `goodwe-session/UX-13-design.md` §1, §3.6.
## 1. What the wizard is
The Setup tab draws the Installer view's page model one step at a time: a
field is on step *n* when its `uilayout.Field` says `wizard=n`. It uses P2's
controls, `/config/validate` and `/config/save`. No route was added.
`uilayout.py` holds what a field cannot say: the kettle table (`KETTLE`), the
optional step and the shadow switch (`SHADOW`). Step 5 has no battery modes
yet (OPT-11); it says they come later.
A step moves on when no refusal is anchored to one of its shown fields
(`uipage.step_problems`). A refusal on a hidden field, a field on no step or
no field at all blocks the last step, which lists every refusal.
## 2. When it opens by itself (`uipage.setup_needed`)
The page opens `#setup` only when it is opened with no route, and only when
`/config/model` gives a reason:
- **`no_power_request`**: the stored options name no inverter to control.
- **`first_start`**: the Store has no `setup_done` and no `auto` key, and
control is not started. `auto` is the key Start/Stop writes, and
config.yaml ships `auto_start` off (the same "fresh install" reading as
app/shadow.py's `auto` default), so this is a site that has never run or
been stopped by hand and never finished the wizard.
A site that runs control, or ever started or stopped it, is never sent to the
wizard. Without a Store the answer is no. The wizard's save writes
`setup_done` (an ISO time) to the Store. A save from the Installer view does
not.
**Not decided here, for the owner:** the add-on exits 78 before it serves the
page when the stored options name no power request entity, which is what a
brand-new install ships (config.yaml's `setpoint_entity: ""`). So on a truly
new install the installer fills in "Inverter setpoint entity" on the
Configuration tab first, and the wizard opens on the next page visit.
Serving a setup-only page from a refused configuration would change the CFG-01
startup gate and UX-02 §4 ("the panel is not available when it is most
needed"), so it is left as an open question.
## 3. The kettle test and the sign checks
Step 1 reads the meter's entities now (`GET ha/entities?entity=`, one
`hass.state()` read, in W) before and after a kettle is switched on. The
change, not the sign, decides: a rise of at least 500 W keeps `meter_invert`
off, a fall turns it on. For a net entity or import and export, a fall cannot
be fixed by an option, so the step is not passed and the page says what to
check. A meter the add-on reads itself (HomeWizard, MQTT) cannot be read by
the page, so the test is skipped and Home shows the direction after the
restart. The installer may skip the test; the last step then warns.
The battery and solar signs cannot be forced from the page, which never
commands an inverter. The last step reports each battery's reading with its
direction ("reads 800 W charging now, so check the inverter shows the same")
and warns when a solar entity reads below zero without its "reads negative"
switch.
## 4. The last step
`validate.setup_checks()` sits beside the rules because it names option keys.
It reads nothing itself: `web.py` passes a Home Assistant entity list at most
5 s old, the running options, the running meter reading and today's price
count. Tiers are ok, info, warn and bad. A power request entity missing from
Home Assistant is **bad**, because a write to it is answered 200 (SAFETY-77)
and only the entity list can tell. The wizard does not save while anything is
bad. It then saves through P2's path (`setup: "done"`) and asks for the
restart as the second, confirmed request, with a dialog that names the 0 W.
The shadow switch is on by default and writes `shadow_mode: on`, which holds
every inverter at 0 W after a restart even if control is started. Off keeps the
stored value, or `auto` if that was `on`.
## 5. The stale save lists only what moved (P2 test, finding a)
`/config/model` remembers the last 16 blobs it handed out, by fingerprint. On a
stale save the add-on compares that blob with what is stored now, so the banner
lists only the other surface's changes. `changed` (stored against the posted
blob) is unchanged for the classic page's "theirs → mine" list. When the
fingerprint was not handed out by this process, it falls back to `changed`.
## 6. How it is held
- test_setup.py: the steps and tables, the kettle logic (and the page's copy of
it), step blocking, the first-run rule on new and TEST-23 sites, the live
checks, the save through the P2 path, the stale fix, and the words.
- tools/ux13_wizard_blobs.py produces every blob the wizard can save from a new
install and three TEST-23 sites (88 accepted by the shipped validator). At P4
all 88 were accepted by Supervisor 2026.08.0's `AppOptions`, offline, 78
keys each.