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).
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
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
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
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.
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.
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.
- 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>
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).
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.
Latch clearing text: test_messages now checks all four latch rows. Dropping "A restart does not clear this" → 1 FAIL.
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.
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.
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.
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.02026-09-27 03:19:55 +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-08 phase 2b (Trello UX-08). The status rows and page banners follow STYLE.md.
app/messages.py(37row_*and 7banner_*functions).test_messages.pyrenders 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.ClampWatchdognow countsover_ratingandboundseparately. After an episode, each count shows under its own alarm's title.id, so the tests pin meaning, not wording.DECISIONS/UX-08-short-notifications.md(phase 2b).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 nowbattery 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 changesStuck-meter check: off, it only works with the HomeWizard local meter sourceNOT 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
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 inchecks()/status_payload()against release/1.0, and ran the full suite ingoodwe-addon-checks:1(36/36 PASS atd017fc6).Safety: meaning and evidence
elif _cw.activebranch, 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,bugis 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, becausebugis sticky within an episode, bound cycles can be happening now, and "not now" would then be false.row_direction_tripleaves 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.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_beforeends 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) andpeak guard: no decision yet - the first control cycle has not run. They are moved intomessages.pyasrow_estop_fitted,row_no_peak_forecastandrow_peak_guard_waiting, and sampled.Checked, no change needed
ok/warnpair matches release/1.0, and so does everyifthat 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.over_rating/boundare pure counters innote(). Return values,_speak,REASSERT_S, the escalation andnotification_ids are untouched, so the cadence is unchanged. Mutation: swapping the two increments → test_clamp 2 FAIL.pack_permissionsnaming ascheck_peak_floor_limited().headline()only affectsmessagesrows andNOT COMMANDABLEnotes, and those have no sentence break, so no banner is truncated mid-fact.row_pv_stray:stray_inverts()returns stripped entries, and_inv_listis stripped the same way, so.index()cannot raise.DECISIONS/TEL-12-several-pv-inverters.md, so its 2/5 claim is not read as still standing.For UX-09, not fixed here (no new class, but new instances):
row_power_too_highrow says "sent X W", which is untrue in shadow mode, as in the notification.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.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).pv_entitiesdescription (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.row_estop_fitted("Emergency stop: fitted"),row_no_peak_forecast("No peak forecast set, so maintenance charging is not kept under the peak limit") androw_peak_guard_waiting("Peak guard: no decision yet, control has not run"). The colours and conditions are unchanged, and all three are sampled.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.