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.
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
- 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
- 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>
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.02026-09-28 23:44:27 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
UX-13 phase P3: the Simple view and the Batteries screen. Base: release/1.0 at
ed739d4.What changed
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.main.pyandmessages.py, which are out of scope, so it is left as a follow-up.simple(peak limit with this month's peak, and shadow mode), each with an "Applies right away" or "Applies after a restart" chip;/actionvaluespack_useandpack_modeand 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}.pyis empty.app/ui_text. DOCS gets a short section, DECISIONS UX-13 gets one P3 bullet, and the screenshot README is updated.Tests
test_batteries_screen.py(CI) covers:battery_whybranches;/actionround trips over the real web app, as app.js posts them, including the NL status and 400 refusals;goodwe-addon-checks:1(48 files).CHECK=1 sh goodwe_controller/tools/ux13_screenshots.shrunstools/ux13_browser_check.py.{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
messages.py/main.py) still says "held back by the peak limit" while the peak limit pushes power out (F2, first half).pv_forecast_entityoption (OPT-11 P4).pack_sell) and cheap-hours charging wait for OPT-11 P3.🤖 Generated with Claude Code
https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
Review of PR #150 (UX-13 P3). Fixes pushed as
a9f7d78Blocker, fixed: the new test failed every morning.
test_batteries_screen.pybuilt 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/apptouches 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, andc.pack_mode()is a read of the store (packmode.read). The page makes 4 fetches:status,action(twice) andha/entities. The only actions it posts arepack_useandpack_mode, and web.py's validation handles them (the 400s are tested in part 5).2. Truthful UI: 3 defects, fixed.
peak_engaged()waspg.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 needsfloor_w > 0. This also fixes the P1 Home peak block, which had the same test.off_peak, while Now used giving. The peak limit never charges an Off battery (1906), so both now use giving.3. Simple and Installer: OK, one fix.
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.load_options()), test_uilayout asserts that every field is RESTART, andpack_*write the store at once. I added a browser check on the three chips.4. Tests bite. Each mutation below was killed, with the check that caught it:
off_peakwithout the giving test: killed by the "18 cases" check and the "covered by A alone" check.battery_nowremoved: killed by the "Off giving way…" check.peak_engaged: killed by the blind/floor-limited check.render()on switch: killed by "unsaved peak limit survives";#simplenever hidden: killed (Installer click times out).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):
shown()is unchanged here.