UX-13 P3: Simple view and Batteries screen #150

Merged
admin merged 2 commits from UX-13-p3-batteries into release/1.0 2026-09-28 23:44:27 +02:00
Owner

UX-13 phase P3: the Simple view and the Batteries screen. Base: release/1.0 at ed739d4.

What changed

  • Batteries screen. One card per battery, replacing the OPT-11 P1 minimal cards. Each card has:
    • an On/Off switch and a mode list (Solar first, Leftover solar);
    • "Cheap hours only" and "Sell in expensive hours", shown greyed out until OPT-11 P3;
    • the level, and Now / Today / Why lines. homeview.battery_why() builds them from what the add-on knows in this cycle: off, off giving way to the peak limit, a refusal hold, another battery refusing, maintenance, the peak limit, shadow mode, stopped or alarm, and the mode order.
    • Today lists the battery's real plan blocks. If there are none, it says "Not planned yet" (modes on). Nothing is invented.
  • F2 from the OPT-11 P1 test. An Off battery that the peak limit uses reads "Off, but gives 0.5 kW for the peak limit.", and its Why says "The peak limit comes before Off." The Off help no longer names 0 W. A test checks that no line says 0 W next to power that is moving. The site-level "held back by the peak limit" Why row is in main.py and messages.py, which are out of scope, so it is left as a follow-up.
  • Home plan. An Off battery gets no house, trade or monthly-cycle blocks. It keeps the peak block.
  • Settings, Simple is now the default view. It shows:
    • the fields the layout marks simple (peak limit with this month's peak, and shadow mode), each with an "Applies right away" or "Applies after a restart" chip;
    • the battery switches and modes.
    • The Installer view is one tap away. The Installer rows stay built, so the save bar counts every change.
  • The code uses the existing /action values pack_use and pack_mode and the existing store keys. No control, allocator, arbiter or command code changed: git diff ed739d4 -- app/{main,allocator,arbiter,control,packmode,inverter,peakguard,arbitrage,maintenance,messages,hass,web}.py is empty.
  • All text is in English and Dutch in app/ui_text. DOCS gets a short section, DECISIONS UX-13 gets one P3 bullet, and the screenshot README is updated.
  • Screenshots. 7 new PNGs (batteries ×5, settings-simple ×2). The 2 settings-installer PNGs also changed, because the view switch is now in them. The other PNGs were not committed.

Tests

  • New test_batteries_screen.py (CI) covers:
    • the six states (modes off, modes on, Off, Off plus peak, refusal, maintenance) in EN and NL;
    • the F2 rules and the plan leaving Off batteries out;
    • 18 battery_why branches;
    • /action round trips over the real web app, as app.js posts them, including the NL status and 400 refusals;
    • the page wiring;
    • that homeview.py writes nothing (AST check plus an unchanged store and unchanged power requests).
    • A mutation that removes the Off wording fails 3 checks.
  • The full suite passed locally in goodwe-addon-checks:1 (48 files).
  • Headless Chromium check: CHECK=1 sh goodwe_controller/tools/ux13_screenshots.sh runs tools/ux13_browser_check.py.
    • It covered 96 views: 6 states × Batteries and Simple × 390 and 1000 px × light and dark × EN and NL. There were 0 page errors, 0 console errors and no sideways scroll.
    • Clicks: the switch and the list post {action, unit, value}, the cards update, focus is kept, and Simple toggles in NL, phone width, dark. The month peak note and the modes-off button opening Installer also pass.

Not done / follow-ups

  • The site Why row (messages.py/main.py) still says "held back by the peak limit" while the peak limit pushes power out (F2, first half).
  • The solar forecast card on Simple waits for the pv_forecast_entity option (OPT-11 P4).
  • Selling (pack_sell) and cheap-hours charging wait for OPT-11 P3.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV

UX-13 phase P3: the Simple view and the Batteries screen. Base: release/1.0 at ed739d4. ## What changed - **Batteries screen.** One card per battery, replacing the OPT-11 P1 minimal cards. Each card has: - an On/Off switch and a mode list (Solar first, Leftover solar); - "Cheap hours only" and "Sell in expensive hours", shown greyed out until OPT-11 P3; - the level, and **Now / Today / Why** lines. `homeview.battery_why()` builds them from what the add-on knows in this cycle: off, off giving way to the peak limit, a refusal hold, another battery refusing, maintenance, the peak limit, shadow mode, stopped or alarm, and the mode order. - Today lists the battery's real plan blocks. If there are none, it says "Not planned yet" (modes on). Nothing is invented. - **F2 from the OPT-11 P1 test.** An Off battery that the peak limit uses reads "Off, but gives 0.5 kW for the peak limit.", and its Why says "The peak limit comes before Off." The Off help no longer names 0 W. A test checks that no line says 0 W next to power that is moving. The site-level "held back by the peak limit" Why row is in `main.py` and `messages.py`, which are out of scope, so it is left as a follow-up. - **Home plan.** An Off battery gets no house, trade or monthly-cycle blocks. It keeps the peak block. - **Settings, Simple** is now the default view. It shows: - the fields the layout marks `simple` (peak limit with this month's peak, and shadow mode), each with an "Applies right away" or "Applies after a restart" chip; - the battery switches and modes. - The **Installer** view is one tap away. The Installer rows stay built, so the save bar counts every change. - The code uses the existing `/action` values `pack_use` and `pack_mode` and the existing store keys. No control, allocator, arbiter or command code changed: `git diff ed739d4 -- app/{main,allocator,arbiter,control,packmode,inverter,peakguard,arbitrage,maintenance,messages,hass,web}.py` is empty. - All text is in English and Dutch in `app/ui_text`. DOCS gets a short section, DECISIONS UX-13 gets one P3 bullet, and the screenshot README is updated. - **Screenshots.** 7 new PNGs (batteries ×5, settings-simple ×2). The 2 settings-installer PNGs also changed, because the view switch is now in them. The other PNGs were not committed. ## Tests - New `test_batteries_screen.py` (CI) covers: - the six states (modes off, modes on, Off, Off plus peak, refusal, maintenance) in EN and NL; - the F2 rules and the plan leaving Off batteries out; - 18 `battery_why` branches; - `/action` round trips over the real web app, as app.js posts them, including the NL status and 400 refusals; - the page wiring; - that homeview.py writes nothing (AST check plus an unchanged store and unchanged power requests). - A mutation that removes the Off wording fails 3 checks. - The full suite passed locally in `goodwe-addon-checks:1` (48 files). - Headless Chromium check: `CHECK=1 sh goodwe_controller/tools/ux13_screenshots.sh` runs `tools/ux13_browser_check.py`. - It covered 96 views: 6 states × Batteries and Simple × 390 and 1000 px × light and dark × EN and NL. There were 0 page errors, 0 console errors and no sideways scroll. - Clicks: the switch and the list post `{action, unit, value}`, the cards update, focus is kept, and Simple toggles in NL, phone width, dark. The month peak note and the modes-off button opening Installer also pass. ## Not done / follow-ups - The site Why row (`messages.py`/`main.py`) still says "held back by the peak limit" while the peak limit pushes power out (F2, first half). - The solar forecast card on Simple waits for the `pv_forecast_entity` option (OPT-11 P4). - Selling (`pack_sell`) and cheap-hours charging wait for OPT-11 P3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
admin added 1 commit 2026-09-28 22:40:03 +02:00
UX-13 P3: Simple view and Batteries screen
checks / unit (pull_request) Successful in 11m9s
infra-pin / fresh (pull_request) Successful in 2s
2d2b6afb07
- Batteries: one card per battery with On/Off, the mode list (cheap hours
  and selling greyed out until OPT-11 P3), level, and Now / Today / Why
  lines built in homeview.py. Replaces the OPT-11 P1 minimal cards and uses
  the existing pack_use / pack_mode actions and store keys.
- An Off battery the peak limit still needs says so with its figure; no line
  shows 0 W beside moving power (OPT-11 P1 test F2). Today says "Not planned
  yet" until OPT-11 P3/P4.
- Home's plan leaves Off batteries out, except for the peak limit.
- Settings opens on Simple: the layout's simple fields with an applies chip
  and a note, plus the battery switches and modes. Installer is one tap.
- English and Dutch words in app/ui_text; test_batteries_screen.py; browser
  check tools/ux13_browser_check.py (CHECK=1 ux13_screenshots.sh); new and
  changed screenshots only.

No control, allocator, arbiter or command module changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
admin added 1 commit 2026-09-28 23:20:37 +02:00
UX-13 P3 review: truthful peak lines, a clock-proof test, Simple stays default
checks / unit (pull_request) Successful in 11m10s
infra-pin / fresh (pull_request) Successful in 2s
a9f7d78527
- The peak limit's Why and plan block need a real power request (floor above
  0 W), not a blind or floor-limited guard. An Off battery gets the peak
  block only while it gives power. Why no longer says every battery gives.
- test_batteries_screen pins the fixture clock: unpinned it failed every
  morning. New checks cover the trade and cycle filters, the blind guard and
  an Off battery that A covers alone.
- Settings keeps only a picked view; a link to Installer is not remembered.
- Browser check: badges, unsaved change across views, locked value.

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

Review of PR #150 (UX-13 P3). Fixes pushed as a9f7d78

Blocker, fixed: the new test failed every morning. test_batteries_screen.py built the plan at the real time of day, but the fixture's prices belong to the day it was built. From 00:00 to about 20:00 UTC the fixture has trades and a monthly cycle still ahead, so Today was not "Not planned yet". I faked the clock to 08:00 UTC and 4 checks failed (modes on, modes off, maintenance, shadow/alarm). CI was green only because it ran in the evening. Fix: the test now pins the fixture clock (built at 00:15 Brussels time), looks at 23:30 for part 1 and at 00:30 for part 3. With the clock faked to 00, 08, 12, 21 and 23 UTC, all checks pass.

1. No control change: OK. git diff ed739d4..HEAD -- goodwe_controller/app touches only homeview.py, uilayout.py, uipage.py, static/ and ui_text/. Nothing in main, allocator, arbiter, control, packmode, inverter, peakguard, arbitrage, maintenance, messages, hass, web, store or mqtt changed. homeview only reads, and c.pack_mode() is a read of the store (packmode.read). The page makes 4 fetches: status, action (twice) and ha/entities. The only actions it posts are pack_use and pack_mode, and web.py's validation handles them (the 400s are tested in part 5).

2. Truthful UI: 3 defects, fixed.

  • (a) peak_engaged() was pg.engaged. But a blind or floor-limited guard is also engaged, with a 0 W floor that only forbids charging (peakguard.py:745 and :807). Blind happens after every start (OPT-11 P1 test, rig note). In that state every On battery read "The peak limit needs power, so every battery gives", a peak block was drawn for every battery on Home and Batteries, and an Off battery still ramping to 0 W read "Off, but gives … for the peak limit". Now it needs floor_w > 0. This also fixes the P1 Home peak block, which had the same test.
  • (b) An Off battery that A covers alone rests, per the owner's rule (Off gives only the shortfall, 1907). Its card still showed "Gives power to stay under 3.5 kW" under "Off. Resting now.", and A's Why said "every battery gives". Now an Off battery gets the peak block only while it gives power. The Why text is now "The peak limit needs power from the batteries." (NL: "…vraagt vermogen van de batterijen.").
  • (c) The Why used "moving" (giving or charging) for off_peak, while Now used giving. The peak limit never charges an Off battery (1906), so both now use giving.
  • "Not planned yet" appears only when there is no non-house block. This is now tested in the morning, when trades and a cycle are ahead.
  • Plan filter: an Off battery gets no house, trade or cycle blocks, and no "waits its turn" line. This matches the controller: P_ARBITRAGE 30 < P_MODE 35, and Off aborts or skips maintenance. The real peak action stays.
  • The Off wording matches DOCS. It gives no figure for the target, so it stays true when SAFETY-79 adds the margin.

3. Simple and Installer: OK, one fix.

  • Unsaved changes survive the view switch: render() rebuilds from V, and the save bar dedupes by key. I added a browser check: 4.2 kW on Simple → Installer → Simple keeps the value, and the save bar shows 1.
  • The badges are right in code. Options are read once at start (main.py:6120, load_options()), test_uilayout asserts that every field is RESTART, and pack_* write the store at once. I added a browser check on the three chips.
  • Locked values still need "Change" in Installer (checked: no input, one Change button).
  • Fix: every jump to Installer was saved to localStorage, including the modes-off link and firstBad(). One tap on "Change inverter settings" made Settings open on Installer for good. Now only the person's own pick of a view is kept. DOCS says so.

4. Tests bite. Each mutation below was killed, with the check that caught it:

  • Why order (peak before refusal): killed by the "18 cases" check.
  • off_peak without the giving test: killed by the "18 cases" check and the "covered by A alone" check.
  • Off wording changed, or the Off branch in battery_now removed: killed by the "Off giving way…" check.
  • shadow_mode badge set to NOW: killed by test_uilayout, "everything applies after a restart".
  • The batteries chip set to restart: killed by the browser check "Simple's badges".
  • Plan filters, as first submitted, 2 of 4 survived. Removing the trade filter and removing the maintenance filter both passed, because at 23:07 nothing was planned. Now:
    • house filter: killed by 3 checks;
    • trade filter: killed by the morning check;
    • cycle filter: killed by the morning check;
    • peak filter: killed by the "covered by A alone" check;
    • "Not planned yet" branch removed: killed by the "modes on" check.
  • Floor test removed from peak_engaged: killed by the blind/floor-limited check.
  • View switch:
    • no render() on switch: killed by "unsaved peak limit survives";
    • always remember: killed by "jump … not remembered";
    • #simple never hidden: killed (Installer click times out).
  • Headless check re-run on a9f7d78: 96 views with 0 page errors, 0 console errors and no sideways scroll, plus 14 click checks, all pass.

5. STYLE and Dutch: OK, one fix. "De piekgrens gaat voor Uit" is now "…gaat voor op Uit". The new strings are short and plain. The only new NL value equal to EN is {name}: {mode}., which is a pattern. What stays English on the screenshots (the pill "Control: starting", validator messages) is in the accepted UX-17 areas.

6. Screenshots: OK. 7 new PNGs and 2 changed, 59 to 183 KB. They show only fixture names, with no IPs, entity values or tokens. I regenerated batteries-off-peak.png for the new Why text and committed only that file.

7. Records. DOCS and DECISIONS are concise. I corrected two lines: DECISIONS said "until then Today says Not planned yet" (now "when no block is planned"), and DOCS says Settings opens on the view last picked. I did not re-run the PR's claim that "a mutation removing the Off wording fails 3 checks". Changing the wording fails 1 check.

Follow-ups (cards):

  • F2, first half: the site Why row in messages.py and main.py still says "held back by the peak limit".
  • NL number fields show "3.5" (a decimal point) on Settings. This is P2 code, and shown() is unchanged here.
  • The screenshots and the browser check still use the real clock, so the Today lines differ by hour. Pin the demo clock too.
## Review of PR #150 (UX-13 P3). Fixes pushed as a9f7d78 **Blocker, fixed: the new test failed every morning.** `test_batteries_screen.py` built the plan at the real time of day, but the fixture's prices belong to the day it was built. From 00:00 to about 20:00 UTC the fixture has trades and a monthly cycle still ahead, so Today was not "Not planned yet". I faked the clock to 08:00 UTC and 4 checks failed (modes on, modes off, maintenance, shadow/alarm). CI was green only because it ran in the evening. Fix: the test now pins the fixture clock (built at 00:15 Brussels time), looks at 23:30 for part 1 and at 00:30 for part 3. With the clock faked to 00, 08, 12, 21 and 23 UTC, all checks pass. **1. No control change: OK.** `git diff ed739d4..HEAD -- goodwe_controller/app` touches only homeview.py, uilayout.py, uipage.py, static/ and ui_text/. Nothing in main, allocator, arbiter, control, packmode, inverter, peakguard, arbitrage, maintenance, messages, hass, web, store or mqtt changed. homeview only reads, and `c.pack_mode()` is a read of the store (`packmode.read`). The page makes 4 fetches: `status`, `action` (twice) and `ha/entities`. The only actions it posts are `pack_use` and `pack_mode`, and web.py's validation handles them (the 400s are tested in part 5). **2. Truthful UI: 3 defects, fixed.** - (a) `peak_engaged()` was `pg.engaged`. But a **blind** or **floor-limited** guard is also engaged, with a 0 W floor that only forbids charging (peakguard.py:745 and :807). Blind happens after every start (OPT-11 P1 test, rig note). In that state every On battery read "The peak limit needs power, so every battery gives", a peak block was drawn for every battery on Home and Batteries, and an Off battery still ramping to 0 W read "Off, but gives … for the peak limit". Now it needs `floor_w > 0`. This also fixes the P1 Home peak block, which had the same test. - (b) An Off battery that A covers alone rests, per the owner's rule (Off gives only the shortfall, 1907). Its card still showed "Gives power to stay under 3.5 kW" under "Off. Resting now.", and A's Why said "every battery gives". Now an Off battery gets the peak block only while it gives power. The Why text is now "The peak limit needs power from the batteries." (NL: "…vraagt vermogen van de batterijen."). - (c) The Why used "moving" (giving **or charging**) for `off_peak`, while Now used giving. The peak limit never charges an Off battery (1906), so both now use giving. - "Not planned yet" appears only when there is no non-house block. This is now tested in the morning, when trades and a cycle are ahead. - Plan filter: an Off battery gets no house, trade or cycle blocks, and no "waits its turn" line. This matches the controller: P_ARBITRAGE 30 < P_MODE 35, and Off aborts or skips maintenance. The real peak action stays. - The Off wording matches DOCS. It gives no figure for the target, so it stays true when SAFETY-79 adds the margin. **3. Simple and Installer: OK, one fix.** - Unsaved changes survive the view switch: `render()` rebuilds from V, and the save bar dedupes by key. I added a browser check: 4.2 kW on Simple → Installer → Simple keeps the value, and the save bar shows 1. - The badges are right in code. Options are read once at start (main.py:6120, `load_options()`), test_uilayout asserts that every field is RESTART, and `pack_*` write the store at once. I added a browser check on the three chips. - Locked values still need "Change" in Installer (checked: no input, one Change button). - Fix: every jump to Installer was saved to localStorage, including the modes-off link and firstBad(). One tap on "Change inverter settings" made Settings open on Installer for good. Now only the person's own pick of a view is kept. DOCS says so. **4. Tests bite.** Each mutation below was killed, with the check that caught it: - Why order (peak before refusal): killed by the "18 cases" check. - `off_peak` without the giving test: killed by the "18 cases" check and the "covered by A alone" check. - Off wording changed, or the Off branch in `battery_now` removed: killed by the "Off giving way…" check. - shadow_mode badge set to NOW: killed by test_uilayout, "everything applies after a restart". - The batteries chip set to restart: killed by the browser check "Simple's badges". - Plan filters, as first submitted, **2 of 4 survived**. Removing the trade filter and removing the maintenance filter both passed, because at 23:07 nothing was planned. Now: - house filter: killed by 3 checks; - trade filter: killed by the morning check; - cycle filter: killed by the morning check; - peak filter: killed by the "covered by A alone" check; - "Not planned yet" branch removed: killed by the "modes on" check. - Floor test removed from `peak_engaged`: killed by the blind/floor-limited check. - View switch: - no `render()` on switch: killed by "unsaved peak limit survives"; - always remember: killed by "jump … not remembered"; - `#simple` never hidden: killed (Installer click times out). - Headless check re-run on a9f7d78: 96 views with 0 page errors, 0 console errors and no sideways scroll, plus 14 click checks, all pass. **5. STYLE and Dutch: OK, one fix.** "De piekgrens gaat voor Uit" is now "…gaat voor op Uit". The new strings are short and plain. The only new NL value equal to EN is `{name}: {mode}.`, which is a pattern. What stays English on the screenshots (the pill "Control: starting", validator messages) is in the accepted UX-17 areas. **6. Screenshots: OK.** 7 new PNGs and 2 changed, 59 to 183 KB. They show only fixture names, with no IPs, entity values or tokens. I regenerated batteries-off-peak.png for the new Why text and committed only that file. **7. Records.** DOCS and DECISIONS are concise. I corrected two lines: DECISIONS said "until then Today says Not planned yet" (now "when no block is planned"), and DOCS says Settings opens on the view last picked. I did not re-run the PR's claim that "a mutation removing the Off wording fails 3 checks". Changing the wording fails 1 check. Follow-ups (cards): - F2, first half: the site Why row in messages.py and main.py still says "held back by the peak limit". - NL number fields show "3.5" (a decimal point) on Settings. This is P2 code, and `shown()` is unchanged here. - The screenshots and the browser check still use the real clock, so the Today lines differ by hour. Pin the demo clock too.
admin merged commit 7995e4df0f into release/1.0 2026-09-28 23:44:27 +02:00
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#150