UX-08 phase 2b: short, plain status rows and page banners #133

Merged
admin merged 3 commits from UX-08-status-rows into release/1.0 2026-09-27 03:19:55 +02:00
Owner

UX-08 phase 2b (Trello UX-08). The status rows and page banners follow STYLE.md.

  • Every Commissioning row and banner is now in app/messages.py (37 row_* and 7 banner_* functions). test_messages.py renders each one and checks it by colour. A row is 1 sentence and at most 15 words. A red row may use 3 sentences and 30 words, so a latch still says how it clears. A banner is at most 12 words. Semicolons, jargon and entity ids are refused.
  • No row shows an HA entity id. Sensors are named by their setting, and solar sources by their place in the list. Reason codes and limits stay on the ERROR/WARNING log line and in the event list. Rows keep the numbers a reader acts on.
  • UX-07 item 1: ClampWatchdog now counts over_rating and bound separately. After an episode, each count shows under its own alarm's title.
  • No colour, visibility or banner-order change. Rows get an id, so the tests pin meaning, not wording.
  • Allowlist: 200 → 168, all 26 row and 6 banner entries removed, and the cap equals the list length. DOCS.md has a new "Status page" section. The choices made are in DECISIONS/UX-08-short-notifications.md (phase 2b).
Before After
METER MAY BE FROZEN - METER_FROZEN: the meter has answered 600 s without its measurement changing … The loop is STILL FOLLOWING it … Meter reading may be stuck. Control still follows it. Check the meter, and stop control if it is stuck.
SIGN-DISAGREEMENT FAILSAFE - SIGN_DISAGREEMENT: commanded … The inverter is held at 0 W. … Restarting the add-on does NOT clear it. Command and meter point opposite ways, so the inverter is set to 0 W. Find out why, then start control. A restart does not clear this.
output clamp: not engaged now, but it has engaged 48 time(s) in 12 episode(s) since start - see the event log (integrator-bound only) Battery is not keeping up: 48 times since start, not now
battery power (inverter-b): no usable value this cycle from sensor.batt_b - and this row cannot tell which of two things that is: … (61 words) Battery power for inverter-b: no usable reading. The sensor may be missing, or its reading is held back as too old. Check it in Developer Tools before replacing it.
freeze detector: NOT COVERED on meter_source ha_dsmr - only homewizard_local sees whether the meter's own document changes Stuck-meter check: off, it only works with the HomeWizard local meter source
Banner NOT DELIVERING — inverter-b is standing at a share it is not producing. The site is still running and short by it; nothing … inverter-b is not delivering. Control keeps running.

Full suite green locally in goodwe-addon-checks:1 (36/36).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

UX-08 phase 2b (Trello UX-08). The status rows and page banners follow STYLE.md. - Every Commissioning row and banner is now in `app/messages.py` (37 `row_*` and 7 `banner_*` functions). `test_messages.py` renders each one and checks it by colour. A row is 1 sentence and at most 15 words. A red row may use 3 sentences and 30 words, so a latch still says how it clears. A banner is at most 12 words. Semicolons, jargon and entity ids are refused. - No row shows an HA entity id. Sensors are named by their setting, and solar sources by their place in the list. Reason codes and limits stay on the ERROR/WARNING log line and in the event list. Rows keep the numbers a reader acts on. - **UX-07 item 1:** `ClampWatchdog` now counts `over_rating` and `bound` separately. After an episode, each count shows under its own alarm's title. - No colour, visibility or banner-order change. Rows get an `id`, so the tests pin meaning, not wording. - Allowlist: **200 → 168**, all 26 row and 6 banner entries removed, and the cap equals the list length. DOCS.md has a new "Status page" section. The choices made are in `DECISIONS/UX-08-short-notifications.md` (phase 2b). | Before | After | |---|---| | `METER MAY BE FROZEN - METER_FROZEN: the meter has answered 600 s without its measurement changing … The loop is STILL FOLLOWING it …` | `Meter reading may be stuck. Control still follows it. Check the meter, and stop control if it is stuck.` | | `SIGN-DISAGREEMENT FAILSAFE - SIGN_DISAGREEMENT: commanded … The inverter is held at 0 W. … Restarting the add-on does NOT clear it.` | `Command and meter point opposite ways, so the inverter is set to 0 W. Find out why, then start control. A restart does not clear this.` | | `output clamp: not engaged now, but it has engaged 48 time(s) in 12 episode(s) since start - see the event log` (integrator-bound only) | `Battery is not keeping up: 48 times since start, not now` | | `battery power (inverter-b): no usable value this cycle from sensor.batt_b - and this row cannot tell which of two things that is: …` (61 words) | `Battery power for inverter-b: no usable reading. The sensor may be missing, or its reading is held back as too old. Check it in Developer Tools before replacing it.` | | `freeze detector: NOT COVERED on meter_source ha_dsmr - only homewizard_local sees whether the meter's own document changes` | `Stuck-meter check: off, it only works with the HomeWizard local meter source` | | Banner `NOT DELIVERING — inverter-b is standing at a share it is not producing. The site is still running and short by it; nothing …` | `inverter-b is not delivering. Control keeps running.` | Full suite green locally in `goodwe-addon-checks:1` (36/36). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 02:12:28 +02:00
UX-08 phase 2b: short, plain status rows and page banners
checks / unit (pull_request) Successful in 8m47s
infra-pin / fresh (pull_request) Failing after 1s
d017fc67bf
Every Commissioning row and every page banner is now worded in
app/messages.py (row_*, banner_*): one sentence of at most 15 words, a red
row up to three sentences so a latch still says how it clears, banners at
most 12 words. No row names a Home Assistant entity: sensors by their
setting, solar sources by their place in the list. Reason codes and limits
stay on the log line and event list; rows keep the numbers a reader acts on.

UX-07 item 1: ClampWatchdog counts over-rating and integrator-bound
engagements separately, and after an episode each count shows under its own
alarm's title instead of both under "output clamp".

No row colour, visibility or banner order changed. Rows carry an "id" so the
tests pin meaning rather than wording. The row and banner kinds leave the
allowlist (200 -> 168, cap equal). DOCS.md gains "Status page"; choices are in
DECISIONS/UX-08-short-notifications.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
Author
Owner

Review (UX-08 phase 2b): findings

I read every row and banner as a home owner would (all 63 renderings of status_samples()), checked each call site in checks()/status_payload() against release/1.0, and ran the full suite in goodwe-addon-checks:1 (36/36 PASS at d017fc6).

Safety: meaning and evidence

  1. Over-rating evidence is hidden while a bound episode runs. After a "Power request too high" episode clears, a later "Battery is not keeping up" episode takes the elif _cw.active branch, and the over-rating count disappears from the page until that episode ends. The count is the software-fault evidence (UX-07 item 1 is about showing it under its own title). While the bound episode is active, bug is False, so every over-rating count comes from an earlier episode and "…since start, not now" is true. Fix: also show the over-rating history row in that branch. The reverse case (bound history during a bug episode) is not added, because bug is sticky within an episode, bound cycles can be happening now, and "not now" would then be false.
  2. The latch rows' clearing sentence is not pinned. Mutation: dropping "A restart does not clear this" from row_direction_trip leaves test_freeze, test_inverter, test_messages and test_watchdog all green. Fix: a test_messages check that each latch row (direction ×2, site direction, circulating, not delivering) says how it clears.
  3. A false claim is left in an option description. pv_entities (en + nl) still says "The status page names which entity is missing, why, and what the others reported". Since this PR it names the source's place in the list. Fix: reword that sentence (en and nl), and update the allowlist hash for that option-desc line. The rest of that description is phase 2c.

Readability (textual, pushing myself)
4. banner_direction(True) "Inverter and own power point opposite ways. It is at 0 W." is hard to parse. It becomes "One inverter's power opposes its command. That inverter is at 0 W."
5. row_before ends in "not now", which reads like a refusal. It becomes "…since start, fine now". The DOCS table is updated to match.
6. Three raw rows were left in checks() and are not on the allowlist, because they pass the scan: e-stop fitted, no peak forecast: maintenance charging is not capacity-capped ("capacity-capped" is jargon) and peak guard: no decision yet - the first control cycle has not run. They are moved into messages.py as row_estop_fitted, row_no_peak_forecast and row_peak_guard_waiting, and sampled.

Checked, no change needed

  • Colour, visibility and banner order: every ok/warn pair matches release/1.0, and so does every if that decides whether a row shows, and the cascade order. The only difference is the UX-07 split, which gives two amber rows where there was one. Amber rows do not feed the banner.
  • Clamp counters: over_rating/bound are pure counters in note(). Return values, _speak, REASSERT_S, the escalation and notification_ids are untouched, so the cadence is unchanged. Mutation: swapping the two increments → test_clamp 2 FAIL.
  • Mutations that were caught: dropping the SAFETY-25 sentence from the row → test_peakguard 2 FAIL. PV partial position off by one → test_pv 2 FAIL. Making not-delivering red → test_delivery 3 FAIL.
  • The peak-reserve row uses the same three branches and pack_permissions naming as check_peak_floor_limited(). headline() only affects messages rows and NOT COMMANDABLE notes, and those have no sentence break, so no banner is truncated mid-fact.
  • row_pv_stray: stray_inverts() returns stripped entries, and _inv_list is stripped the same way, so .index() cannot raise.
  • Choice 1 (red row 3 sentences / 30 words): agreed. It mirrors the red-alarm rule the owner set, and every red row that uses it needs the room for its state and how it clears.
  • Choice 2 (numbers): agreed. What an operator acts on stays (stall age, meter age and limit, asked/sent, peak limit, live counts).
  • Choice 3 (TEL-12): justified, and it does not need the owner. TEL-12's safety claim is about the control document (3/5, 4/5), and that is untouched. The byte-identical status row at one entity was a continuity courtesy for 0.11.0 installs, and the owner's newer instruction (no entity ids in status rows) overrides it directly. I'm adding a one-line pointer in DECISIONS/TEL-12-several-pv-inverters.md, so its 2/5 claim is not read as still standing.
  • Choices 4 and 5 (banner derivation, clearing texts): agreed.
  • The DOCS "Status page" section is short and correct, apart from the "not now" wording (item 5).

For UX-09, not fixed here (no new class, but new instances):

  • "Controller" is now the first word of 3 rows and the DOCS table (it replaces "control loop").
  • The row_power_too_high row says "sent X W", which is untrue in shadow mode, as in the notification.
  • The peak-reserve row and banner use "peak reserve" and "cut-out level".
  • More row nouns that don't match the UI labels: "reversed-sign list" (UI: PV entities whose sign is reversed), "single sensor setting" (PV production entity), "meter source" (Where the meter reading comes from), "grid power sensor" (Grid power entity), and "Solar production" against the UI's "PV".

Follow-ups outside this card: shadow.banner() (all caps, "FAILSAFE LATCH") and the liveness notes (NOT COMMANDABLE, NO SETPOINT ENTITY) are still shouting on the page. DECISIONS item 8 defers them, but no card or allowlist line tracks them.

## Review (UX-08 phase 2b): findings I read every row and banner as a home owner would (all 63 renderings of `status_samples()`), checked each call site in `checks()`/`status_payload()` against release/1.0, and ran the full suite in `goodwe-addon-checks:1` (36/36 PASS at d017fc6). **Safety: meaning and evidence** 1. **Over-rating evidence is hidden while a bound episode runs.** After a "Power request too high" episode clears, a later "Battery is not keeping up" episode takes the `elif _cw.active` branch, and the over-rating count disappears from the page until that episode ends. The count is the software-fault evidence (UX-07 item 1 is about showing it under its own title). While the bound episode is active, `bug` is False, so every over-rating count comes from an earlier episode and "…since start, not now" is true. **Fix:** also show the over-rating history row in that branch. The reverse case (bound history during a bug episode) is not added, because `bug` is sticky within an episode, bound cycles can be happening *now*, and "not now" would then be false. 2. **The latch rows' clearing sentence is not pinned.** Mutation: dropping "A restart does not clear this" from `row_direction_trip` leaves test_freeze, test_inverter, test_messages and test_watchdog all green. **Fix:** a test_messages check that each latch row (direction ×2, site direction, circulating, not delivering) says how it clears. 3. **A false claim is left in an option description.** `pv_entities` (en + nl) still says "The status page names which entity is missing, why, and what the others reported". Since this PR it names the source's place in the list. **Fix:** reword that sentence (en and nl), and update the allowlist hash for that option-desc line. The rest of that description is phase 2c. **Readability (textual, pushing myself)** 4. `banner_direction(True)` "Inverter and own power point opposite ways. It is at 0 W." is hard to parse. It becomes "One inverter's power opposes its command. That inverter is at 0 W." 5. `row_before` ends in "not now", which reads like a refusal. It becomes "…since start, fine now". The DOCS table is updated to match. 6. Three raw rows were left in `checks()` and are not on the allowlist, because they pass the scan: `e-stop fitted`, `no peak forecast: maintenance charging is not capacity-capped` ("capacity-capped" is jargon) and `peak guard: no decision yet - the first control cycle has not run`. They are moved into `messages.py` as `row_estop_fitted`, `row_no_peak_forecast` and `row_peak_guard_waiting`, and sampled. **Checked, no change needed** - Colour, visibility and banner order: every `ok`/`warn` pair matches release/1.0, and so does every `if` that decides whether a row shows, and the cascade order. The only difference is the UX-07 split, which gives two amber rows where there was one. Amber rows do not feed the banner. - Clamp counters: `over_rating`/`bound` are pure counters in `note()`. Return values, `_speak`, `REASSERT_S`, the escalation and `notification_id`s are untouched, so the cadence is unchanged. Mutation: swapping the two increments → test_clamp 2 FAIL. - Mutations that were caught: dropping the SAFETY-25 sentence from the row → test_peakguard 2 FAIL. PV partial position off by one → test_pv 2 FAIL. Making not-delivering red → test_delivery 3 FAIL. - The peak-reserve row uses the same three branches and `pack_permissions` naming as `check_peak_floor_limited()`. `headline()` only affects `messages` rows and `NOT COMMANDABLE` notes, and those have no sentence break, so no banner is truncated mid-fact. - `row_pv_stray`: `stray_inverts()` returns stripped entries, and `_inv_list` is stripped the same way, so `.index()` cannot raise. - Choice 1 (red row 3 sentences / 30 words): agreed. It mirrors the red-alarm rule the owner set, and every red row that uses it needs the room for its state and how it clears. - Choice 2 (numbers): agreed. What an operator acts on stays (stall age, meter age and limit, asked/sent, peak limit, live counts). - Choice 3 (TEL-12): **justified, and it does not need the owner.** TEL-12's safety claim is about the *control* document (3/5, 4/5), and that is untouched. The byte-identical *status row* at one entity was a continuity courtesy for 0.11.0 installs, and the owner's newer instruction (no entity ids in status rows) overrides it directly. I'm adding a one-line pointer in `DECISIONS/TEL-12-several-pv-inverters.md`, so its 2/5 claim is not read as still standing. - Choices 4 and 5 (banner derivation, clearing texts): agreed. - The DOCS "Status page" section is short and correct, apart from the "not now" wording (item 5). **For UX-09, not fixed here (no new class, but new instances):** - "Controller" is now the first word of 3 rows and the DOCS table (it replaces "control loop"). - The `row_power_too_high` row says "sent X W", which is untrue in shadow mode, as in the notification. - The peak-reserve row and banner use "peak reserve" and "cut-out level". - More row nouns that don't match the UI labels: "reversed-sign list" (UI: *PV entities whose sign is reversed*), "single sensor setting" (*PV production entity*), "meter source" (*Where the meter reading comes from*), "grid power sensor" (*Grid power entity*), and "Solar production" against the UI's "PV". **Follow-ups outside this card:** `shadow.banner()` (all caps, "FAILSAFE LATCH") and the liveness notes (`NOT COMMANDABLE`, `NO SETPOINT ENTITY`) are still shouting on the page. DECISIONS item 8 defers them, but no card or allowlist line tracks them.
admin added 2 commits 2026-09-27 03:10:27 +02:00
Brings in the infra pin bump (#132) so infra-pin / fresh runs against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UX-08 phase 2b review: keep over-rating evidence, pin latch clearing, plainer rows
checks / unit (pull_request) Successful in 8m48s
infra-pin / fresh (pull_request) Successful in 1s
571b884315
- During a bound episode an earlier over-rating count stays on the page (amber,
  under its own title); test_clamp pins it.
- test_messages pins that every latch row says how it clears.
- pv_entities description no longer claims the page names the entity (en, nl);
  allowlist hash updated for that option-desc line.
- Plainer texts: multi-inverter direction banner, "fine now" after an episode,
  and the e-stop, peak-forecast and peak-guard-waiting rows move to messages.py.
- TEL-12 DECISIONS points to UX-08 item 3 for the retired row rule.

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

All points in comment 1781 are resolved. The fixes are in 571b884, after merging release/1.0 in the normal way (696142b, which brings the #132 pin).

  1. Over-rating evidence during a bound episode: the history row is now shown in that branch too. test_clamp pins it, and removing the branch → 1 FAIL.
  2. Latch clearing text: test_messages now checks all four latch rows. Dropping "A restart does not clear this" → 1 FAIL.
  3. pv_entities description (en, nl): it now says "The status page says which one is missing, by its place in this list." The allowlist line is re-hashed (313bdd035b0b → 0b7ea32cf8d0), and the count is unchanged at 168.
  4. The multi-inverter direction banner now reads "An inverter's own power opposes its command. It is at 0 W." It keeps "own power", which test_watchdog pins.
  5. "not now" → "fine now" (row, DOCS table, test_clamp).
  6. The new rows are row_estop_fitted ("Emergency stop: fitted"), row_no_peak_forecast ("No peak forecast set, so maintenance charging is not kept under the peak limit") and row_peak_guard_waiting ("Peak guard: no decision yet, control has not run"). The colours and conditions are unchanged, and all three are sampled.
  7. TEL-12 DECISIONS has a one-line "superseded in part" pointer.

The full suite is 36/36 locally. CI on 571b884: checks / unit run 910 green, infra-pin / fresh run 911 green. Merging with head pinned.

All points in comment 1781 are resolved. The fixes are in 571b884, after merging release/1.0 in the normal way (696142b, which brings the #132 pin). 1. Over-rating evidence during a bound episode: the history row is now shown in that branch too. test_clamp pins it, and removing the branch → 1 FAIL. 2. Latch clearing text: test_messages now checks all four latch rows. Dropping "A restart does not clear this" → 1 FAIL. 3. `pv_entities` description (en, nl): it now says "The status page says which one is missing, by its place in this list." The allowlist line is re-hashed (313bdd035b0b → 0b7ea32cf8d0), and the count is unchanged at 168. 4. The multi-inverter direction banner now reads "An inverter's own power opposes its command. It is at 0 W." It keeps "own power", which test_watchdog pins. 5. "not now" → "fine now" (row, DOCS table, test_clamp). 6. The new rows are `row_estop_fitted` ("Emergency stop: fitted"), `row_no_peak_forecast` ("No peak forecast set, so maintenance charging is not kept under the peak limit") and `row_peak_guard_waiting` ("Peak guard: no decision yet, control has not run"). The colours and conditions are unchanged, and all three are sampled. 7. TEL-12 DECISIONS has a one-line "superseded in part" pointer. The full suite is 36/36 locally. CI on 571b884: checks / unit run 910 green, infra-pin / fresh run 911 green. Merging with head pinned.
admin merged commit d57767fe38 into release/1.0 2026-09-27 03:19:55 +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#133