UX-15: setup-only mode, and wizard fixes F1, F2, meter entity, step 1 #147

Merged
admin merged 4 commits from UX-15-setup-only into release/1.0 2026-09-28 15:27:28 +02:00
Owner

Superseded: the banner text and the TEST-23 status below are out of date. See review comment 1896 for the real live run and the final banner text.

Card UX-15 (6aba0053d27a32cb3f210dcc). Record: DECISIONS/UX-15-setup-only-mode.md.

Setup-only mode

  • Rule (setuponly.applies): the options name no inverter at all (no flat setpoint_entity, no inverters entry), and rule 1's refusal is the gate's only refusal. That is exactly the shipped defaults.
  • Still exit 78: a complete configuration that is refused, no power request plus any other refusal, and a list entry with a blank power request entity. These are configurations someone edited. The Configuration tab stays their recovery surface (UX-02 §4, amended).
  • Behaviour: amain() branches before it builds the broker, the Controller, the meter task or the tariff task. SetupOnly serves the page: a Home banner ("Setup: the add-on controls nothing until setup is done"), /status setup_only: true, and the wizard opens with no_power_request. It writes no Store file, so the first-run rule still holds.
  • Cannot command, structurally:
    • HomeAssistant(read_only=True): call() refuses before it sends anything. That covers every service call, including number.set_value and notifications, and the flag has no setter.
    • SetupOnly refuses a writable HomeAssistant, and Controller refuses a read-only one.
    • /action answers 409.
    • No MQTT client is created.

Wizard fixes

  • F1: /config/validate converts what it can (ui_to_options_partial). A conversion error blocks its own step beside its field, and the live checks still run. A save of such a model returns 422 with steps.
  • F2: saves send base, the model the page opened. Only the keys that differ from it are written, onto the blob stored now (uipage.only_edits).
  • Meter entity: a meter entity that Home Assistant does not have is red on the last step (it stays a warning in the Installer view).
  • Step 1: after a fall followed by a skip, step 1 says the reading went down.

Evidence (on 63e7e8a)

  • Full suite in the CI image: 45/45.
  • test_setup_only.py boots the real main() against a fake Supervisor and Home Assistant over HTTP and logs every request:
    • Shipped defaults, also with auto_start on, a Store holding auto: true, and Start plus the battery cycle pressed: served by SetupOnly on a read-only HomeAssistant, 0 service calls, no MQTT request, both actions answered 409, no state.json before the save.
    • The wizard then saves, and the next boot passes the normal gate. The wizard does not reopen, and every write, after Start too, is set_value 0.0.
    • The complete-but-refused config and the blank-plus-refused config exit 78 and serve nothing.
  • test_setup.py part 9:
    • F1: step 4 is blocked and the live checks still run.
    • F2 with two tabs: A saves peak 4.0. B, which edited only the timeout, gets the 409, and its second press keeps 4.0; its re-save moves only price_timeout_s.
    • The missing meter entity is red.
    • Step 1's wording after the skip.
    • The STYLE 25-word scan covers the new strings.
  • Mutations, 11 of 11 red on named checks:
    • applies always false, and always true
    • the read-only refusal removed
    • the Controller check removed
    • a writable HomeAssistant in setup mode
    • /action's 409 removed
    • the return after serve() removed
    • F2 merge removed
    • F1 partial conversion removed
    • the meter check back to a warning
    • setup mode writing the Store
  • Supervisor 2026.08.0 AppOptions, offline: 116 of 116 wizard blobs accepted, 78 keys each. That includes 28 from the shipped defaults (the new empty start).

TEST-23

Not run: permission to start a Supervisor stand-in container on test23-net was denied. See the agent report. The rig was restored unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

> **Superseded:** the banner text and the TEST-23 status below are out of date. See review comment 1896 for the real live run and the final banner text. Card UX-15 (6aba0053d27a32cb3f210dcc). Record: `DECISIONS/UX-15-setup-only-mode.md`. ## Setup-only mode - **Rule** (`setuponly.applies`): the options name no inverter at all (no flat `setpoint_entity`, no `inverters` entry), and rule 1's refusal is the gate's **only** refusal. That is exactly the shipped defaults. - **Still exit 78:** a complete configuration that is refused, no power request plus any other refusal, and a list entry with a blank power request entity. These are configurations someone edited. The Configuration tab stays their recovery surface (UX-02 §4, amended). - **Behaviour:** `amain()` branches before it builds the broker, the Controller, the meter task or the tariff task. `SetupOnly` serves the page: a Home banner ("Setup: the add-on controls nothing until setup is done"), `/status` `setup_only: true`, and the wizard opens with `no_power_request`. It writes no Store file, so the first-run rule still holds. - **Cannot command, structurally:** - `HomeAssistant(read_only=True)`: `call()` refuses before it sends anything. That covers every service call, including `number.set_value` and notifications, and the flag has no setter. - `SetupOnly` refuses a writable HomeAssistant, and `Controller` refuses a read-only one. - `/action` answers 409. - No MQTT client is created. ## Wizard fixes - **F1:** `/config/validate` converts what it can (`ui_to_options_partial`). A conversion error blocks its own step beside its field, and the live checks still run. A save of such a model returns 422 with `steps`. - **F2:** saves send `base`, the model the page opened. Only the keys that differ from it are written, onto the blob stored now (`uipage.only_edits`). - **Meter entity:** a meter entity that Home Assistant does not have is red on the last step (it stays a warning in the Installer view). - **Step 1:** after a fall followed by a skip, step 1 says the reading went down. ## Evidence (on 63e7e8a) - **Full suite in the CI image:** 45/45. - **`test_setup_only.py`** boots the real `main()` against a fake Supervisor and Home Assistant over HTTP and logs every request: - Shipped defaults, also with `auto_start` on, a Store holding `auto: true`, and Start plus the battery cycle pressed: served by `SetupOnly` on a read-only HomeAssistant, **0 service calls**, no MQTT request, both actions answered 409, no `state.json` before the save. - The wizard then saves, and the next boot passes the normal gate. The wizard does not reopen, and every write, after Start too, is `set_value 0.0`. - The complete-but-refused config and the blank-plus-refused config exit 78 and serve nothing. - **`test_setup.py` part 9:** - F1: step 4 is blocked and the live checks still run. - F2 with two tabs: A saves peak 4.0. B, which edited only the timeout, gets the 409, and its second press keeps 4.0; its re-save moves only `price_timeout_s`. - The missing meter entity is red. - Step 1's wording after the skip. - The STYLE 25-word scan covers the new strings. - **Mutations, 11 of 11 red on named checks:** - `applies` always false, and always true - the read-only refusal removed - the Controller check removed - a writable HomeAssistant in setup mode - `/action`'s 409 removed - the `return` after `serve()` removed - F2 merge removed - F1 partial conversion removed - the meter check back to a warning - setup mode writing the Store - **Supervisor 2026.08.0 `AppOptions`, offline:** 116 of 116 wizard blobs accepted, 78 keys each. That includes 28 from the shipped defaults (the new `empty` start). ## TEST-23 Not run: permission to start a Supervisor stand-in container on test23-net was denied. See the agent report. The rig was restored unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 2 commits 2026-09-28 09:05:42 +02:00
A new install names no inverter. When that is the startup gate's only
refusal, the add-on now serves the setup wizard in setup-only mode instead
of exiting 78. It builds no controller, MQTT client or meter task, its
HomeAssistant is read-only (every service call refuses before sending),
Controller refuses a read-only HomeAssistant, and /action answers 409.
Any other refusal still exits 78. DECISIONS/UX-15-setup-only-mode.md.

Wizard fixes from the UX-13 P4 test:
- F1: /config/validate converts what it can, so a conversion error blocks
  its own step and the live checks still run; save 422 carries steps.
- F2: saves send the model the page opened (base); only keys this page
  changed are written onto the blob stored now.
- A meter entity missing from Home Assistant is red on the last step.
- Step 1 says the reading went down after a skip that followed a fall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UX-15: mutations fail named checks, not crashes
checks / unit (pull_request) Successful in 10m38s
infra-pin / fresh (pull_request) Successful in 1s
63e7e8a133
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
admin added 2 commits 2026-09-28 15:15:53 +02:00
The Home banner title was ten words; UX-08 asks for about six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UX-15 review: DECISIONS states the TEST-23 run that happened (PR #147)
checks / unit (pull_request) Successful in 11m8s
infra-pin / fresh (pull_request) Successful in 1s
39e6c260fe
It said the PR had TEST-23 evidence, but the PR says TEST-23 was not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review (PR #147, reviewer agent)

Reviewed head 63e7e8a. I pushed two fixes, so the head is now 39e6c26.

Point Outcome
"Cannot command" is structural and tested Holds. HomeAssistant(read_only=True) refuses in call() before sending. SetupOnly refuses a writable one, Controller refuses a read-only one, and /action answers 409. I ran my own mutations in the CI image, and each one turned test_setup_only.py red on named checks: read-only guard removed (2 FAIL), amain() passing the writable hass (4), return after serve() removed (2), /action 409 removed (2), and applies() without the only-refusal equality (2). Before the branch, amain() only does GETs, and limits() is skipped when setpoint_entity is empty.
Precise rule: complete-but-invalid still exits 78 Holds. applies() needs no writers, no roster entries, and problems == [rule 1]. Part 4 boots main() on the complete-but-refused and blank-plus-refused configs, and both exit 78 with nothing served.
Store.fresh first run Holds. Setup-only writes no Store file; the mutation "Store file created at construction" is red. test_setup.py still covers fresh. The wizard save writes setup_done, so the next boot does not reopen the wizard.
F1, F2, meter entity, step-1 wording each tested Holds. Mutations are red: F1 partial conversion removed (3 FAIL), F2 only_edits removed (3), meter check back to setpoint_entity only (2). The step-1 wording has a check, but it is a source-text check: there is no JS runtime in CI.
Supervisor 2026.08.0 validator Holds. I re-dumped the blobs and ran them offline through AppOptions: 116 of 116 accepted, 28 of them from the new empty start. The live save below also went through the real AppOptions.
STYLE scan Holds. The suite in the CI image gave 44 of 45. The one failure is test_freeze.py, which needs the ../goodwe hwsim sibling that CI mounts and my run did not; CI itself is green. Fixed (30cba1e): the Home banner title was 10 words. It is now "Setup: nothing is controlled yet" (UX-08 asks for about 6), and DOCS is updated to match.
DECISIONS and DOCS Fixed (39e6c26): UX-15-setup-only-mode.md §4 said "TEST-23: a real container … (the PR has the evidence)", but the PR body says TEST-23 was not run. That claim was false. It now states the run below. The UX-02 amendment and the UX-13 §2 history are one short paragraph each, which is fine. The PR body's banner text and its "TEST-23: Not run" are now out of date; this comment supersedes them.

TEST-23 live check (image test23-goodwe-addon:30cba1e, 15:07–15:14 CEST)

  • Setup: soak paused, then test23-addon and test23-addon-arb stopped. Both volumes were tarred with sha256 and owner/mode manifests, including tariff.db. up.sh was not run.
  • Stand-in: a Supervisor stand-in in amd64-hassio-supervisor:2026.08.0, at 10.77.34.60. It serves /addons/self/* with the real AppOptions/UiOptions and forwards /core/* through the rig shim, so both logs record the traffic. /services/mqtt answers 404, so the rig's MQTT entities stay untouched.
  • Boot: the add-on ran on an empty temp volume with the shipped defaults (setpoint_entity: ""). The log shows "starting fresh", then the setup WARNING. /status gave setup_only: true with banner "Setup: nothing is controlled yet", and /config/model gave setup_needed: no_power_request. auto_toggle and maint_start both got 409. There was no state.json before the save.
  • No writes: from boot at 15:08:07 to the restart at 15:11:53, the stand-in logged 0 POST /core/api/services/* and 0 MQTT requests (14 requests in all). The rig shim log also shows 0 service POSTs in that window.
  • Wizard: I drove its HTTP calls: validate (setup) returned 200 with no red; the live checks read the meter at 388 W, found number.test23_setpoint_w, and read the battery at 52 %. save with base returned 200. The stand-in's AppOptions accepted 78 keys (shadow_mode: on), and setup_done was written. restart returned 200.
  • After the restart: setup-only stopped cleanly on SIGTERM. The normal gate passed and the add-on ran in shadow ("Shadow mode is on…"), with setup_needed: null. Start returned 200. Over 102 s, 11 of 11 service calls were number/set_value 0.0, and the shim agrees: 11. The add-on log had 0 ERROR lines.
  • Restore: the temp containers and volume are deleted. Both volumes match the manifests (unchanged). test23-addon is a5a20d236eb6 on :16c796f, options fa602196…6f5b, state aa4848a9…b530; test23-addon-arb is e86cfefa on :88c59a9, 8837dbed…/cd6dbdf2…. The soak is unpaused, there are 0 ERROR lines after the restart, and HA has 45 entities with the same counts and the setpoint at 0.0.

Nothing touched .158, .221 or .167. I will merge once CI is green on 39e6c26.

## Review (PR #147, reviewer agent) Reviewed head 63e7e8a. I pushed two fixes, so the head is now **39e6c26**. | Point | Outcome | |---|---| | "Cannot command" is structural and tested | **Holds.** `HomeAssistant(read_only=True)` refuses in `call()` before sending. `SetupOnly` refuses a writable one, `Controller` refuses a read-only one, and `/action` answers 409. I ran my own mutations in the CI image, and each one turned `test_setup_only.py` red on named checks: read-only guard removed (2 FAIL), `amain()` passing the writable `hass` (4), `return` after `serve()` removed (2), `/action` 409 removed (2), and `applies()` without the only-refusal equality (2). Before the branch, `amain()` only does GETs, and `limits()` is skipped when `setpoint_entity` is empty. | | Precise rule: complete-but-invalid still exits 78 | **Holds.** `applies()` needs no writers, no roster entries, and `problems == [rule 1]`. Part 4 boots `main()` on the complete-but-refused and blank-plus-refused configs, and both exit 78 with nothing served. | | Store.fresh first run | **Holds.** Setup-only writes no Store file; the mutation "Store file created at construction" is red. `test_setup.py` still covers `fresh`. The wizard save writes `setup_done`, so the next boot does not reopen the wizard. | | F1, F2, meter entity, step-1 wording each tested | **Holds.** Mutations are red: F1 partial conversion removed (3 FAIL), F2 `only_edits` removed (3), meter check back to `setpoint_entity` only (2). The step-1 wording has a check, but it is a source-text check: there is no JS runtime in CI. | | Supervisor 2026.08.0 validator | **Holds.** I re-dumped the blobs and ran them offline through `AppOptions`: 116 of 116 accepted, 28 of them from the new `empty` start. The live save below also went through the real `AppOptions`. | | STYLE scan | **Holds.** The suite in the CI image gave 44 of 45. The one failure is `test_freeze.py`, which needs the `../goodwe` hwsim sibling that CI mounts and my run did not; CI itself is green. **Fixed (30cba1e):** the Home banner title was 10 words. It is now "Setup: nothing is controlled yet" (UX-08 asks for about 6), and DOCS is updated to match. | | DECISIONS and DOCS | **Fixed (39e6c26):** `UX-15-setup-only-mode.md` §4 said "TEST-23: a real container … (the PR has the evidence)", but the PR body says TEST-23 was not run. That claim was false. It now states the run below. The UX-02 amendment and the UX-13 §2 history are one short paragraph each, which is fine. The PR body's banner text and its "TEST-23: Not run" are now out of date; this comment supersedes them. | ### TEST-23 live check (image `test23-goodwe-addon:30cba1e`, 15:07–15:14 CEST) - **Setup:** soak paused, then `test23-addon` and `test23-addon-arb` stopped. Both volumes were tarred with sha256 and owner/mode manifests, including `tariff.db`. `up.sh` was not run. - **Stand-in:** a Supervisor stand-in in `amd64-hassio-supervisor:2026.08.0`, at 10.77.34.60. It serves `/addons/self/*` with the real `AppOptions`/`UiOptions` and forwards `/core/*` through the rig shim, so both logs record the traffic. `/services/mqtt` answers 404, so the rig's MQTT entities stay untouched. - **Boot:** the add-on ran on an empty temp volume with the shipped defaults (`setpoint_entity: ""`). The log shows "starting fresh", then the setup WARNING. `/status` gave `setup_only: true` with banner "Setup: nothing is controlled yet", and `/config/model` gave `setup_needed: no_power_request`. `auto_toggle` and `maint_start` both got **409**. There was no `state.json` before the save. - **No writes:** from boot at 15:08:07 to the restart at 15:11:53, the stand-in logged **0 POST `/core/api/services/*`** and **0 MQTT requests** (14 requests in all). The rig shim log also shows **0** service POSTs in that window. - **Wizard:** I drove its HTTP calls: `validate` (setup) returned 200 with no red; the live checks read the meter at 388 W, found `number.test23_setpoint_w`, and read the battery at 52 %. `save` with `base` returned 200. The stand-in's `AppOptions` accepted 78 keys (`shadow_mode: on`), and `setup_done` was written. `restart` returned 200. - **After the restart:** setup-only stopped cleanly on SIGTERM. The normal gate passed and the add-on ran in shadow ("Shadow mode is on…"), with `setup_needed: null`. Start returned 200. Over 102 s, **11 of 11 service calls were `number/set_value` 0.0**, and the shim agrees: 11. The add-on log had 0 ERROR lines. - **Restore:** the temp containers and volume are deleted. Both volumes match the manifests (unchanged). `test23-addon` is a5a20d236eb6 on `:16c796f`, options `fa602196…6f5b`, state `aa4848a9…b530`; `test23-addon-arb` is e86cfefa on `:88c59a9`, `8837dbed…`/`cd6dbdf2…`. The soak is unpaused, there are 0 ERROR lines after the restart, and HA has 45 entities with the same counts and the setpoint at 0.0. Nothing touched .158, .221 or .167. I will merge once CI is green on 39e6c26.
admin merged commit 8c34647d05 into release/1.0 2026-09-28 15:27:28 +02:00
Author
Owner

Merged as 8c34647 with head_commit_id pinned to 39e6c26. CI checks / unit was green on 39e6c26 (run 989). git ls-remote shows release/1.0 at 8c34647.

Merged as 8c34647 with head_commit_id pinned to 39e6c26. CI `checks / unit` was green on 39e6c26 (run 989). `git ls-remote` shows release/1.0 at 8c34647.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#147