UX-08 phase 2a: short, plain notifications, and a ratchet for the rest #131

Merged
admin merged 2 commits from UX-08-plain-notifications into release/1.0 2026-09-27 01:29:47 +02:00
Owner

UX-08 phase 2a: every Home Assistant notification the add-on raises is short and plain. These are the owner's decisions of 2026-09-27: a title of at most 5 everyday words and a one-sentence body. A red safety alarm may use up to 3 sentences. Titles carry no "GoodWe:" prefix, and alarms never show entity ids.

What changed

  • STYLE.md (repository root) states the rule: shape, numbers, severity, one word per thing, and the banned terms.
  • app/messages.py holds all 7 notifications: severity, title, body, and a Details line that opens the add-on documentation page and names the section. main.py now only decides when to raise them.
  • DOCS.md has a new section, Notifications, with one heading per notification. It holds everything that moved out of the bodies.
  • DECISIONS/UX-08-short-notifications.md lists the choices made (summarised below).
  • test_messages.py, with an allowlist of 200 entries (see below).

Before → after (as landed)

was now
GoodWe: the control loop is at its integrator bound (60 words) Battery is not keeping up - "The battery is not following the controller; short moments are normal, repeated ones need a check."
GoodWe: a computed setpoint exceeded the rating ("… BUG SIGNAL …", 75 words) Power request too high - "The controller asked for 7 412 W, above the inverter rating, and sent 5 000 W instead. Control is still running. This is a software fault, so please report it to your installer."
GoodWe: the inverters are pushing against each other (161 words) Inverters working against each other - "garage was discharging while shed was charging, and your meter cannot see this. Control is stopped and every inverter is set to 0 W; if the grid power does not change now, switch the inverters off at the isolator. A restart does not clear this: start control on the add-on page once you know why."
GoodWe: the capacity-peak guard has run out of battery (153 words) Battery at its peak reserve - "The battery of inverter-b is at its peak reserve, so this 15-minute period will go over your 5 000 W peak limit and raise this month's peak. Switch off large loads, or lower the peak reserve if the battery allows it. The reserve only limits peak shaving: normal home use can still discharge the battery below it, down to its cut-out level."
GoodWe: B is not delivering its share (231 words) inverter-b is not delivering - 3 sentences, which keep "nothing was changed", the three checks in order, and "a restart does not clear this".
GoodWe: the capacity-peak guard has run out of inverter / GoodWe: the P1 meter may be frozen Load too high for battery / Meter reading may be stuck

Safety. No notification_id, trigger, edge or re-announce cadence changed, and no log line or status row changed. The choices, with detail in the DECISIONS file:

  1. "Not delivering" (SAFETY-42) is an alarm (3 sentences): its log line is ERROR and the alarm is latched. The page banner stays amber.
  2. Figures left the frozen-meter, circulating and peak bodies, because none is needed to act. They stay in the reason text that the log and status row carry, and the tests pin that. The frozen card now quotes no rail, so it cannot quote a wrong one (SAFETY-20).
  3. The over-rating alarm keeps asked, sent and which limit cut it, via ClampWatchdog.facts. That field is kept under the same rule as reason, so the words and the numbers come from one tick, asked first (SAFETY-40).
  4. SAFETY-25 stays as the last sentence of the peak-reserve alarm, including "down to its cut-out level". Each branch now gets its own action (lower, set, or check the sensor).
  5. SAFETY-32 names the pack inside the first sentence. At a roster of one, the body is byte-identical minus the name.
  6. "The meter cannot see this" stays, as the owner asked.

Tests re-pinned from prose to meaning. Six files touched:

  • test_clamp: title constants, which limit, asked-then-sent and its transposition.
  • test_freeze: title, "still running", no wrong rail.
  • test_watchdog: the three things, both directions of the attribution, and the figures in the reason.
  • test_delivery: "nothing was changed".
  • test_peakguard: the SAFETY-25 scope, the SAFETY-32 name, and the N=1 identity.
  • test_messages: new.

test_messages.py has five parts:

  • Every notification is rendered and checked against the limits for its severity.
  • An AST scan fails any persistent_notification call not built by app/messages.py.
  • A ratchet covers status rows, banners, refusals, WARNING+ logs, and option names and descriptions. Each string obeys its limit or is listed in ux08_allowlist.txt. A new violation is red, a stale entry is red, and the list is capped (ALLOWLIST_MAX = 200).
  • DOCS has a heading for every Details line.
  • 13 mutations.

Allowlist remaining: 200 (row 26, banner 6, refusal 34, log 43, option-name 28, option-desc 63). They are for phases 2b, 2c and 2d.

Not in this PR: the test_translations.py citation relaxation (the manager default). No option description changes in 2a, so it lands with 2c, when the citations move to DOCS.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

UX-08 phase 2a: every Home Assistant notification the add-on raises is short and plain. These are the owner's decisions of 2026-09-27: a title of at most 5 everyday words and a one-sentence body. A red safety alarm may use up to 3 sentences. Titles carry no "GoodWe:" prefix, and alarms never show entity ids. **What changed** - `STYLE.md` (repository root) states the rule: shape, numbers, severity, one word per thing, and the banned terms. - `app/messages.py` holds all 7 notifications: severity, title, body, and a Details line that opens the add-on documentation page and names the section. `main.py` now only decides *when* to raise them. - `DOCS.md` has a new section, **Notifications**, with one heading per notification. It holds everything that moved out of the bodies. - `DECISIONS/UX-08-short-notifications.md` lists the choices made (summarised below). - `test_messages.py`, with an allowlist of 200 entries (see below). **Before → after (as landed)** | was | now | |---|---| | GoodWe: the control loop is at its integrator bound (60 words) | **Battery is not keeping up** - "The battery is not following the controller; short moments are normal, repeated ones need a check." | | GoodWe: a computed setpoint exceeded the rating ("… BUG SIGNAL …", 75 words) | **Power request too high** - "The controller asked for 7 412 W, above the inverter rating, and sent 5 000 W instead. Control is still running. This is a software fault, so please report it to your installer." | | GoodWe: the inverters are pushing against each other (161 words) | **Inverters working against each other** - "garage was discharging while shed was charging, and your meter cannot see this. Control is stopped and every inverter is set to 0 W; if the grid power does not change now, switch the inverters off at the isolator. A restart does not clear this: start control on the add-on page once you know why." | | GoodWe: the capacity-peak guard has run out of battery (153 words) | **Battery at its peak reserve** - "The battery of inverter-b is at its peak reserve, so this 15-minute period will go over your 5 000 W peak limit and raise this month's peak. Switch off large loads, or lower the peak reserve if the battery allows it. The reserve only limits peak shaving: normal home use can still discharge the battery below it, down to its cut-out level." | | GoodWe: B is not delivering its share (231 words) | **inverter-b is not delivering** - 3 sentences, which keep "nothing was changed", the three checks in order, and "a restart does not clear this". | | GoodWe: the capacity-peak guard has run out of inverter / GoodWe: the P1 meter may be frozen | **Load too high for battery** / **Meter reading may be stuck** | **Safety.** No `notification_id`, trigger, edge or re-announce cadence changed, and no log line or status row changed. The choices, with detail in the DECISIONS file: 1. "Not delivering" (SAFETY-42) is an **alarm** (3 sentences): its log line is ERROR and the alarm is latched. The page banner stays amber. 2. Figures left the frozen-meter, circulating and peak bodies, because none is needed to act. They stay in the reason text that the log and status row carry, and the tests pin that. The frozen card now quotes no rail, so it cannot quote a wrong one (SAFETY-20). 3. The over-rating alarm keeps asked, sent and which limit cut it, via `ClampWatchdog.facts`. That field is kept under the same rule as `reason`, so the words and the numbers come from one tick, asked first (SAFETY-40). 4. SAFETY-25 stays as the last sentence of the peak-reserve alarm, including "down to its cut-out level". Each branch now gets its own action (lower, set, or check the sensor). 5. SAFETY-32 names the pack inside the first sentence. At a roster of one, the body is byte-identical minus the name. 6. "The meter cannot see this" stays, as the owner asked. **Tests re-pinned from prose to meaning.** Six files touched: - `test_clamp`: title constants, which limit, asked-then-sent and its transposition. - `test_freeze`: title, "still running", no wrong rail. - `test_watchdog`: the three things, both directions of the attribution, and the figures in the reason. - `test_delivery`: "nothing was changed". - `test_peakguard`: the SAFETY-25 scope, the SAFETY-32 name, and the N=1 identity. - `test_messages`: new. **`test_messages.py`** has five parts: - Every notification is rendered and checked against the limits for its severity. - An AST scan fails any `persistent_notification` call not built by `app/messages.py`. - A ratchet covers status rows, banners, refusals, WARNING+ logs, and option names and descriptions. Each string obeys its limit or is listed in `ux08_allowlist.txt`. A new violation is red, a stale entry is red, and the list is capped (`ALLOWLIST_MAX = 200`). - DOCS has a heading for every Details line. - 13 mutations. **Allowlist remaining: 200** (row 26, banner 6, refusal 34, log 43, option-name 28, option-desc 63). They are for phases 2b, 2c and 2d. Not in this PR: the `test_translations.py` citation relaxation (the manager default). No option description changes in 2a, so it lands with 2c, when the citations move to DOCS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 01:01:01 +02:00
UX-08 phase 2a: short, plain notifications, and a ratchet for the rest
checks / unit (pull_request) Successful in 8m54s
infra-pin / fresh (pull_request) Successful in 1s
50ccea3dd7
Every HA notification the add-on raises now has a title of at most 5
everyday words and a one-sentence body; a red alarm may use three so no
action is lost. The texts live in app/messages.py, the rule in STYLE.md,
and the detail that moved out in DOCS.md "Notifications", which each body
links to. No notification_id, trigger or cadence changed.

test_messages.py enforces the rule on every notification, fails any
notification worded outside app/messages.py, and ratchets the kinds not yet
rewritten (rows, banners, refusals, logs, options) through a shrinking
allowlist of 200. Six tests re-pinned from prose to meaning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 01:18:58 +02:00
UX-08 review: tighter notification texts, and two meaning fixes
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 8m48s
7b65a3e69a
- not delivering: the body said control keeps running and then "start
  control"; clearing it takes stop then start, and it now says so.
- peak reserve: the "reserve not set" branch is really soc_floor None,
  i.e. an inverter's cut-out level missing; it now names that. The title
  is true on all three branches ("Battery stopped limiting the peak").
- no semicolon run-ons in bodies (STYLE.md + test); loop-bound, circulating,
  meter-frozen and multi-unit over-rating bodies rewritten to match.
- allowlist cap is now exact, so a shrink cannot leave room to regrow.
- DOCS names the settings behind "peak reserve" and "cut-out level";
  DECISIONS no longer states the HA anchor behaviour as fact.

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

Review (UX-08 phase 2a) — findings, all fixed in 7b65a3e

Meaning bugs (fixed)

  1. Not delivering (SAFETY-42) said "control keeps running" and then "start control on the add-on page". The latch clears only in the auto_toggle → on branch, so while control is running you have to stop and then start. The body now says "once you know why, stop and start control on the add-on page". DOCS says the same.
  2. Peak reserve, the reserve_unset branch told the user to "set a peak reserve". But g.soc_floor is None comes from binding_soc_floor() when an inverter's bms_soc_floor is missing. peak_soc_floor always has a value. The kwarg is renamed floor_unknown, and the text now says "The cut-out level of a battery is not known … set each battery's cut-out level in the settings". DECISIONS item 4 is corrected.
  3. Peak reserve title: "Battery at its peak reserve" was false on 2 of 3 branches. The title is now "Battery stopped limiting the peak". The id is unchanged, and the DOCS heading and table are updated.

Owner's bar (fixed)
4. Semicolons let the bodies obey the sentence count while breaking its spirit (loop bound, circulating, not delivering, meter frozen, multi-unit over-rating). They are rewritten. STYLE.md now bans semicolons in bodies, and test_messages.py enforces it, with a mutation.
5. "limits peak shaving" is now "applies to keeping the peak down". DOCS names the setting labels behind "peak reserve" and "cut-out level", so the reader can find them.

Ratchet (fixed)
6. The cap was <=. After a shrink that leaves slack, so the list could grow back silently. It is now ==: deleting lines means lowering the cap in the same diff.

Checked, no change needed

  • Mutations: swapping asked and sent in output_clamp → test_clamp 3 FAIL. Dropping the SAFETY-25 sentence → test_peakguard 2 FAIL. A new _LOG.warning("the integrator hit its anti-windup bound") → test_messages NEW + FAIL. A 28-word warning → FAIL. An extra allowlist line → stale + cap FAIL.
  • ids, triggers, cadence and the SAFETY-34 re-announce paths (_reannounce_due, _circ_announced, _delivery_announced) are untouched. Removing the figures is fine: they stay in reason, and that reaches the log line and the status row.
  • SAFETY-42 is red while the banner is amber: acceptable. HA shows no colour on a notification, and the two answer different questions. DECISIONS says so now.
  • Details link: naming the section without an anchor is good enough. The "HA does not render anchors" claim was unverified, so DECISIONS no longer states it as fact. Follow-up, not blocking: HOSTNAME is the host's own name if the add-on ever runs host_network: true, and the link then silently degrades to text only.
  • The full suite passes in goodwe-addon-checks:1 (36 files).
## Review (UX-08 phase 2a) — findings, all fixed in 7b65a3e **Meaning bugs (fixed)** 1. **Not delivering (SAFETY-42)** said "control keeps running" and then "start control on the add-on page". The latch clears only in the `auto_toggle` → on branch, so while control is running you have to stop and then start. The body now says "once you know why, stop and start control on the add-on page". DOCS says the same. 2. **Peak reserve, the `reserve_unset` branch** told the user to "set a peak reserve". But `g.soc_floor is None` comes from `binding_soc_floor()` when an inverter's `bms_soc_floor` is missing. `peak_soc_floor` always has a value. The kwarg is renamed `floor_unknown`, and the text now says "The cut-out level of a battery is not known … set each battery's cut-out level in the settings". DECISIONS item 4 is corrected. 3. **Peak reserve title**: "Battery at its peak reserve" was false on 2 of 3 branches. The title is now **"Battery stopped limiting the peak"**. The id is unchanged, and the DOCS heading and table are updated. **Owner's bar (fixed)** 4. Semicolons let the bodies obey the sentence count while breaking its spirit (loop bound, circulating, not delivering, meter frozen, multi-unit over-rating). They are rewritten. STYLE.md now bans semicolons in bodies, and `test_messages.py` enforces it, with a mutation. 5. "limits peak shaving" is now "applies to keeping the peak down". DOCS names the setting labels behind "peak reserve" and "cut-out level", so the reader can find them. **Ratchet (fixed)** 6. The cap was `<=`. After a shrink that leaves slack, so the list could grow back silently. It is now `==`: deleting lines means lowering the cap in the same diff. **Checked, no change needed** - Mutations: swapping asked and sent in `output_clamp` → test_clamp 3 FAIL. Dropping the SAFETY-25 sentence → test_peakguard 2 FAIL. A new `_LOG.warning("the integrator hit its anti-windup bound")` → test_messages NEW + FAIL. A 28-word warning → FAIL. An extra allowlist line → stale + cap FAIL. - ids, triggers, cadence and the SAFETY-34 re-announce paths (`_reannounce_due`, `_circ_announced`, `_delivery_announced`) are untouched. Removing the figures is fine: they stay in `reason`, and that reaches the log line and the status row. - SAFETY-42 is red while the banner is amber: acceptable. HA shows no colour on a notification, and the two answer different questions. DECISIONS says so now. - Details link: naming the section without an anchor is good enough. The "HA does not render anchors" claim was unverified, so DECISIONS no longer states it as fact. Follow-up, not blocking: `HOSTNAME` is the host's own name if the add-on ever runs `host_network: true`, and the link then silently degrades to text only. - The full suite passes in `goodwe-addon-checks:1` (36 files).
Author
Owner

All six points in comment 1768 are resolved in 7b65a3e. CI is green: checks / unit and infra-pin / fresh. Merging with head pinned.

All six points in comment 1768 are resolved in 7b65a3e. CI is green: checks / unit and infra-pin / fresh. Merging with head pinned.
admin merged commit 48ef06ac1f into release/1.0 2026-09-27 01:29:47 +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#131