UX-09: Details link for current HA, 'would' in shadow mode, plain terms #134

Merged
admin merged 2 commits from UX-09-2a-fixes into release/1.0 2026-09-27 04:54:42 +02:00
Owner

Trello UX-09 (P2). Fixes from the UX-08 phase 2a post-merge test, plus the scope added by the 2b review.

1. Details link

Finding (from HA source, not guessed):

  • HA 2026.2 added the built-in app panel: core af83fa809a "Add app panel (#157554)", frontend #28245 "Add apps panel as built-in panel". Its add-on pages are /config/app/<slug>/documentation (src/panels/config/apps/ha-config-app-dashboard.ts).
  • HA 2026.5 stopped registering the hassio panel. homeassistant/components/hassio/__init__.py still has frontend_url_path="hassio" at tag 2026.4.0 and no longer has it from 2026.5.0 on. So /hassio/addon/<slug>/documentation is a 404 on 2026.5+ (TEST-23 saw this on 2026.9.1).
  • HA 2026.1 and older have only /hassio/addon/<slug>/....
  • /_my_redirect/supervisor_addon?addon=<slug> works on both, but it opens the Info tab: new → /config/app/<slug>/info (ha-panel-my.ts), old → /hassio/addon?addon= (hassio-my-redirect.ts, frontend 20260107.0). No path reaches the Documentation tab on both.

Fix: at start the add-on reads its slug (GET /addons/self/info) and the HA version (GET /info). Both are on the Supervisor's bypass list for an add-on's own token. It builds /config/app/<slug>/documentation, or /hassio/addon/... for HA < 2026.2. An unknown version gets the new path. The hostname is still the fallback for the slug, which fixes the TEST-23 case where the hostname was a container id. There are no heading anchors, so the line still names the section plainly. test_messages.py pins the full line, the version table and the fallbacks.

2. Shadow mode

output_clamp(), row_power_too_high() and peak_saturated() take shadow=. main.py passes self.shadow. test_messages.py covers both modes on the texts, and test_clamp.py covers both modes through check_output_clamp() and checks(). The id, severity and cadence are the same in both modes.

3. Term mapping (one term per concept)

Concept Texts and DOCS say Setting label (en)
who decides / what it does the add-on / control (rows: Control: …) – (was "controller")
peak_soc_floor peak reserve Peak reserve (%) (was "Peak guard will not discharge below (%)"; nl "Piekreserve (%)")
bms_soc_floor battery cut-out level Battery cut-out level (%) (unchanged)
max_w “Inverter rating” setting Inverter rating (W) (unchanged)
soc_entity battery level / “Battery level entity” Battery level entity (%) (was "Battery charge level …")
pv_entity “Solar production entity” (was "single sensor setting") Solar production entity (W) (was "PV …")
pv_entities “Solar production entities, per inverter” Solar production entities, per inverter (W)
pv_invert_entities “Reversed-sign solar entities” (was "reversed-sign list") Reversed-sign solar entities
meter_source “Where the meter reading comes from” (was "meter source") unchanged
meter_entity “Grid power entity” (was "grid power sensor") unchanged
discharge draw the battery down / give power –
failsafe latch safety stop –

"Controller" became "the add-on" (who decides) and "control" (what it does). The reader sees "the add-on" in HA, "start control" was already in use, and "controller" was easy to confuse with the control board. The glossary is in STYLE.md §4. Option descriptions are phase 2c and were not changed, so some still say "PV".

3b. Shouting

The shadow banner is now messages.banner_shadow(). The inverter link notes are now row_no_setpoint, row_not_controllable and row_commands_refused. All are sampled, so test_messages.py checks them. The liveness row colour now comes from is_live() instead of matching "NOT COMMANDABLE" in the text. The colours are the same as before.

Before / after

  1. Power request too high (shadow): "The controller asked for 3 607 W, above the inverter rating, and sent 2 000 W instead. Control is still running. …" → "The add-on asked for 3 607 W, above the “Inverter rating” setting, and would have sent 2 000 W instead. Shadow mode is on, so the inverter stays at 0 W. This is a software fault, so please report it to your installer."
  2. Battery is not keeping up: "The battery cannot give what the controller asks, which is normal for a moment but needs a check if it keeps happening." → "The battery does less than asked, which is normal briefly, but if this keeps happening, check that it is on and not empty or full."
  3. Load too high for battery (live): "… This is not a fault: the load is bigger than the inverter." → "… This is not a fault: the load is more than the battery can supply." (In shadow mode: "The add-on would ask the battery for all it can give, … Shadow mode is on, so the battery gives nothing and this month's peak may rise. …")
  4. Battery stopped limiting the peak: "Switch off large loads, or lower the peak reserve if the battery allows it. … normal home use can still discharge the battery below it, down to its cut-out level." → "Switch off large loads, or lower the peak reserve. … normal home use can still draw the battery below it, down to the battery cut-out level."
  5. Shadow banner: "SHADOW MODE - the battery is NOT being controlled. … ⚠️ A FAILSAFE LATCH IS STILL UP (inverter-a sign watchdog) and it will still be up when shadow mode is turned off." → "Shadow mode, battery not controlled. A safety stop stays on, see below." (warn: "Shadow mode: the battery is not controlled. Inverters stay at 0 W.")
  6. Inverter notes: "inverter-b: NOT COMMANDABLE - every write rejected for 45 s (limit 30 s)" → "inverter-b cannot be controlled. Every command was refused for 45 seconds, limit 30 seconds." and "inverter-b: NO SETPOINT ENTITY - this controller cannot command it, …" → "inverter-b: “Setpoint entity” is not set, so every inverter stays at 0 W"

Also: "Controller: last ran 2 seconds ago" → "Control: last ran 2 seconds ago"; "so add it to the reversed-sign list" → "add it to “Reversed-sign solar entities”"; "Peak guard: off, it needs a meter source instead of the grid power sensor" → "Peak guard: off, set “Where the meter reading comes from” to anything but off".

4. DOCS

"Notifications" and "Status page" are now written for home owners. Each ends in a "For installers" part, which holds the ids, the SAFETY cards, the option keys, integrator and capaciteitstarief. The notification headings are unchanged, so every Details line still finds its section.

5. Safety

No id, severity, colour, cadence or trigger changed. The latch names left the red shadow banner (12 words cannot carry them). Each latch still has its own red row, which names it and says how it clears, and test_clamp.py now pins that. The choices are recorded in DECISIONS/UX-08-short-notifications.md under "UX-09".

6. Allowlist

168 → 167: the pv_entities option name now fits 6 words. The cap equals the length.

Left for later (not in this card): the "Shadow mode" row of the values table (shadow.words(), "ON THIS CYCLE WOULD HAVE COMMANDED") and the SAFETY-37 stale note still shout. They are not on the ratchet.

Not deployed. The live link check is for the post-merge test on TEST-23 with a Supervisor-style hostname.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

Trello UX-09 (P2). Fixes from the UX-08 phase 2a post-merge test, plus the scope added by the 2b review. ## 1. Details link **Finding (from HA source, not guessed):** - HA **2026.2** added the built-in `app` panel: core `af83fa809a` "Add app panel (#157554)", frontend #28245 "Add apps panel as built-in panel". Its add-on pages are `/config/app/<slug>/documentation` (`src/panels/config/apps/ha-config-app-dashboard.ts`). - HA **2026.5** stopped registering the `hassio` panel. `homeassistant/components/hassio/__init__.py` still has `frontend_url_path="hassio"` at tag 2026.4.0 and no longer has it from 2026.5.0 on. So `/hassio/addon/<slug>/documentation` is a 404 on 2026.5+ (TEST-23 saw this on 2026.9.1). - HA **2026.1 and older** have only `/hassio/addon/<slug>/...`. - `/_my_redirect/supervisor_addon?addon=<slug>` works on both, but it opens the Info tab: new → `/config/app/<slug>/info` (ha-panel-my.ts), old → `/hassio/addon?addon=` (hassio-my-redirect.ts, frontend 20260107.0). No path reaches the Documentation tab on both. **Fix:** at start the add-on reads its slug (`GET /addons/self/info`) and the HA version (`GET /info`). Both are on the Supervisor's bypass list for an add-on's own token. It builds `/config/app/<slug>/documentation`, or `/hassio/addon/...` for HA < 2026.2. An unknown version gets the new path. The hostname is still the fallback for the slug, which fixes the TEST-23 case where the hostname was a container id. There are no heading anchors, so the line still names the section plainly. `test_messages.py` pins the full line, the version table and the fallbacks. ## 2. Shadow mode `output_clamp()`, `row_power_too_high()` and `peak_saturated()` take `shadow=`. `main.py` passes `self.shadow`. `test_messages.py` covers both modes on the texts, and `test_clamp.py` covers both modes through `check_output_clamp()` and `checks()`. The id, severity and cadence are the same in both modes. ## 3. Term mapping (one term per concept) | Concept | Texts and DOCS say | Setting label (en) | |---|---|---| | who decides / what it does | the add-on / control (rows: `Control: …`) | – (was "controller") | | `peak_soc_floor` | peak reserve | **Peak reserve (%)** (was "Peak guard will not discharge below (%)"; nl "Piekreserve (%)") | | `bms_soc_floor` | battery cut-out level | Battery cut-out level (%) (unchanged) | | `max_w` | “Inverter rating” setting | Inverter rating (W) (unchanged) | | `soc_entity` | battery level / “Battery level entity” | **Battery level entity (%)** (was "Battery charge level …") | | `pv_entity` | “Solar production entity” (was "single sensor setting") | **Solar production entity (W)** (was "PV …") | | `pv_entities` | “Solar production entities, per inverter” | **Solar production entities, per inverter (W)** | | `pv_invert_entities` | “Reversed-sign solar entities” (was "reversed-sign list") | **Reversed-sign solar entities** | | `meter_source` | “Where the meter reading comes from” (was "meter source") | unchanged | | `meter_entity` | “Grid power entity” (was "grid power sensor") | unchanged | | discharge | draw the battery down / give power | – | | failsafe latch | safety stop | – | "Controller" became "the add-on" (who decides) and "control" (what it does). The reader sees "the add-on" in HA, "start control" was already in use, and "controller" was easy to confuse with the control board. The glossary is in STYLE.md §4. Option **descriptions** are phase 2c and were not changed, so some still say "PV". ## 3b. Shouting The shadow banner is now `messages.banner_shadow()`. The inverter link notes are now `row_no_setpoint`, `row_not_controllable` and `row_commands_refused`. All are sampled, so `test_messages.py` checks them. The liveness row colour now comes from `is_live()` instead of matching "NOT COMMANDABLE" in the text. The colours are the same as before. ## Before / after 1. **Power request too high (shadow):** "The controller asked for 3 607 W, above the inverter rating, and sent 2 000 W instead. Control is still running. …" → "The add-on asked for 3 607 W, above the “Inverter rating” setting, and would have sent 2 000 W instead. Shadow mode is on, so the inverter stays at 0 W. This is a software fault, so please report it to your installer." 2. **Battery is not keeping up:** "The battery cannot give what the controller asks, which is normal for a moment but needs a check if it keeps happening." → "The battery does less than asked, which is normal briefly, but if this keeps happening, check that it is on and not empty or full." 3. **Load too high for battery (live):** "… This is not a fault: the load is bigger than the inverter." → "… This is not a fault: the load is more than the battery can supply." (In shadow mode: "The add-on would ask the battery for all it can give, … Shadow mode is on, so the battery gives nothing and this month's peak may rise. …") 4. **Battery stopped limiting the peak:** "Switch off large loads, or lower the peak reserve if the battery allows it. … normal home use can still discharge the battery below it, down to its cut-out level." → "Switch off large loads, or lower the peak reserve. … normal home use can still draw the battery below it, down to the battery cut-out level." 5. **Shadow banner:** "SHADOW MODE - the battery is NOT being controlled. … ⚠️ A FAILSAFE LATCH IS STILL UP (inverter-a sign watchdog) and it will still be up when shadow mode is turned off." → "Shadow mode, battery not controlled. A safety stop stays on, see below." (warn: "Shadow mode: the battery is not controlled. Inverters stay at 0 W.") 6. **Inverter notes:** "inverter-b: NOT COMMANDABLE - every write rejected for 45 s (limit 30 s)" → "inverter-b cannot be controlled. Every command was refused for 45 seconds, limit 30 seconds." and "inverter-b: NO SETPOINT ENTITY - this controller cannot command it, …" → "inverter-b: “Setpoint entity” is not set, so every inverter stays at 0 W" Also: "Controller: last ran 2 seconds ago" → "Control: last ran 2 seconds ago"; "so add it to the reversed-sign list" → "add it to “Reversed-sign solar entities”"; "Peak guard: off, it needs a meter source instead of the grid power sensor" → "Peak guard: off, set “Where the meter reading comes from” to anything but off". ## 4. DOCS "Notifications" and "Status page" are now written for home owners. Each ends in a "For installers" part, which holds the ids, the SAFETY cards, the option keys, integrator and capaciteitstarief. The notification headings are unchanged, so every Details line still finds its section. ## 5. Safety No id, severity, colour, cadence or trigger changed. The latch names left the red shadow banner (12 words cannot carry them). Each latch still has its own red row, which names it and says how it clears, and `test_clamp.py` now pins that. The choices are recorded in `DECISIONS/UX-08-short-notifications.md` under "UX-09". ## 6. Allowlist 168 → 167: the `pv_entities` option name now fits 6 words. The cap equals the length. **Left for later (not in this card):** the "Shadow mode" row of the values table (`shadow.words()`, "ON THIS CYCLE WOULD HAVE COMMANDED") and the SAFETY-37 stale note still shout. They are not on the ratchet. Not deployed. The live link check is for the post-merge test on TEST-23 with a Supervisor-style hostname. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 04:11:14 +02:00
UX-09: Details link for current HA, "would" in shadow mode, plain terms
infra-pin / fresh (pull_request) Successful in 2s
checks / unit (pull_request) Successful in 8m50s
00e3ee9de8
- Details link: /config/app/<slug>/documentation on HA 2026.2+ (the old
  /hassio/addon path is a 404 from 2026.5), /hassio/addon on 2026.1 and
  older. Slug from GET /addons/self/info, version from GET /info, hostname
  as fallback.
- Shadow mode: the over-rating alarm and row say what would have been sent,
  the peak alarm no longer says the battery is giving all it can.
- "controller" -> "the add-on" / "control". Settings named by their labels;
  four labels changed to match (Peak reserve, Battery level entity, Solar
  production ..., Reversed-sign solar entities).
- Shadow banner and inverter link notes moved to messages.py and checked.
- DOCS Notifications and Status page rewritten for home owners, with a
  "For installers" part each. Allowlist 168 -> 167.

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

Review (UX-09), head 00e3ee9

  1. Startup calls: one blocking point. The rest checks out. /addons/self/info and /info are on the Supervisor's api_bypass for the add-on's own token (see selfconfig.py header; config.yaml also sets hassio_api: true). Any failure is caught and logged at INFO, and the path falls back to the hostname. A shim 404 (non-JSON body, or a JSON error body without data) gives None. But the two calls run one after the other with timeout=10 each, awaited before the startup gate. A hung Supervisor delays control start by up to 20 s. Fix: ask both together with a 3 s budget, pinned by a test.
  2. Labels: keys and schema are unchanged, and there is no fr.yaml. en/nl are consistent and test_translations passes. No old label text is left in the code, DOCS or UI. "Peak reserve" is consistent. OK.
  3. Shadow: every "sent" path (output_clamp in all three shapes, row_power_too_high) and peak_saturated have a shadow variant, and tests cover both modes. Gap: in shadow mode only the per-inverter direction latch is pinned to keep its own red row. shadow_latches() also names the circulating and site-direction latches. Fix: add shadow checks for both to test_watchdog.py.
  4. Readability:
    • row_not_controllable says "…refused for 45 s, limit 30 s." "Limit 30 s" is terse jargon for a home owner. Proposed: "It refused every command for 45 s, so control leaves it out."
    • loop_bound says "does less than asked, which is normal briefly". Proposed: "The battery gives less than asked, which is normal for a moment, but if it lasts, check it is on and not empty or full." (≤25 words.)
    • The red shadow banner opens with "Shadow mode," but the amber one opens with "Shadow mode:". Make them the same.
  5. Safety: ids, severities, cadence and triggers are unchanged against release/1.0. The main.py diff touches only payloads and the liveness colour. is_live() is False only in the reject-past-limit branch that printed "NOT COMMANDABLE", so red/amber is unchanged. OK.
  6. DOCS: the home-owner parts are plain and free of card ids. Keys and SAFETY-nn are only under "For installers". OK.
  7. 2026.2 cutoff: spot-checked. core #157554 "Add app panel" and frontend #28245 "Add apps panel as built-in panel" were both merged on 2026-01-19, so they ship in 2026.2. OK.

I'm pushing the fixes for 1, 3 and 4.

**Review (UX-09), head 00e3ee9** 1. **Startup calls: one blocking point.** The rest checks out. `/addons/self/info` and `/info` are on the Supervisor's `api_bypass` for the add-on's own token (see selfconfig.py header; `config.yaml` also sets `hassio_api: true`). Any failure is caught and logged at INFO, and the path falls back to the hostname. A shim 404 (non-JSON body, or a JSON error body without `data`) gives None. But the two calls run **one after the other with `timeout=10` each, awaited before the startup gate**. A hung Supervisor delays control start by up to 20 s. **Fix:** ask both together with a 3 s budget, pinned by a test. 2. **Labels:** keys and schema are unchanged, and there is no fr.yaml. en/nl are consistent and test_translations passes. No old label text is left in the code, DOCS or UI. "Peak reserve" is consistent. OK. 3. **Shadow:** every "sent" path (`output_clamp` in all three shapes, `row_power_too_high`) and `peak_saturated` have a shadow variant, and tests cover both modes. **Gap:** in shadow mode only the per-inverter direction latch is pinned to keep its own red row. `shadow_latches()` also names the circulating and site-direction latches. **Fix:** add shadow checks for both to test_watchdog.py. 4. **Readability:** - `row_not_controllable` says "…refused for 45 s, limit 30 s." "Limit 30 s" is terse jargon for a home owner. Proposed: "It refused every command for 45 s, so control leaves it out." - `loop_bound` says "does less than asked, which is normal briefly". Proposed: "The battery gives less than asked, which is normal for a moment, but if it lasts, check it is on and not empty or full." (≤25 words.) - The red shadow banner opens with "Shadow mode," but the amber one opens with "Shadow mode:". Make them the same. 5. **Safety:** ids, severities, cadence and triggers are unchanged against release/1.0. The main.py diff touches only payloads and the liveness colour. `is_live()` is False only in the reject-past-limit branch that printed "NOT COMMANDABLE", so red/amber is unchanged. OK. 6. **DOCS:** the home-owner parts are plain and free of card ids. Keys and SAFETY-nn are only under "For installers". OK. 7. **2026.2 cutoff:** spot-checked. core #157554 "Add app panel" and frontend #28245 "Add apps panel as built-in panel" were both merged on 2026-01-19, so they ship in 2026.2. OK. I'm pushing the fixes for 1, 3 and 4.
admin added 1 commit 2026-09-27 04:45:08 +02:00
UX-09 review: short start-up timeout, latch rows pinned in shadow, plainer rows
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 1s
63dee6db1e
The Supervisor questions for the Details link are asked together with a
3 s budget instead of one after the other at 10 s each, so a hung
Supervisor cannot hold control start for 20 s. In shadow mode the
circulating and site-direction latches are pinned to keep their own red
row that says how they clear. Plainer inverter and battery wording.

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

Review fixes pushed: 63dee6d (verified with ls-remote)

  1. docs_link_facts() now asks both questions together with asyncio.gather, each with DOCS_LINK_TIMEOUT_S = 3, so the worst case is about 3 s instead of 20 s. It still fails quietly at INFO. test_messages pins the timeout at ≤ 3 s.
  2. test_watchdog.py: in shadow mode the circulating latch and the site-direction latch each turn the banner red with "safety stop". Their own red row stays and says "Only starting control clears this". The per-inverter direction latch was already pinned in test_clamp.py.
  3. row_not_controllable now reads "name cannot be controlled. It refused every command for 45 s, so control leaves it out."
    loop_bound now reads "The battery gives less than asked, which is normal for a moment, but if it lasts, check it is on and not empty or full."
    The red shadow banner now starts with "Shadow mode:", like the amber one.

The full suite passes locally (36 files, without hwsim). Waiting for CI.

**Review fixes pushed: 63dee6d** (verified with ls-remote) 1. `docs_link_facts()` now asks both questions together with `asyncio.gather`, each with `DOCS_LINK_TIMEOUT_S = 3`, so the worst case is about 3 s instead of 20 s. It still fails quietly at INFO. test_messages pins the timeout at ≤ 3 s. 3. test_watchdog.py: in shadow mode the circulating latch and the site-direction latch each turn the banner red with "safety stop". Their own red row stays and says "Only starting control clears this". The per-inverter direction latch was already pinned in test_clamp.py. 4. `row_not_controllable` now reads "*name* cannot be controlled. It refused every command for 45 s, so control leaves it out." `loop_bound` now reads "The battery gives less than asked, which is normal for a moment, but if it lasts, check it is on and not empty or full." The red shadow banner now starts with "Shadow mode:", like the amber one. The full suite passes locally (36 files, without hwsim). Waiting for CI.
admin merged commit e1d0970b23 into release/1.0 2026-09-27 04:54:42 +02:00
Author
Owner

All points fixed. CI was green on 63dee6d (checks/unit and infra-pin). Merged with head_commit_id pinned. release/1.0 is now e1d0970 (verified with ls-remote), and the post-merge infra-pin / fresh is green. Follow-up for the TEST-23 re-test: check the link with a Supervisor-style hostname and with the shim's /addons/self/info.

All points fixed. CI was green on 63dee6d (checks/unit and infra-pin). Merged with head_commit_id pinned. release/1.0 is now e1d0970 (verified with ls-remote), and the post-merge infra-pin / fresh is green. Follow-up for the TEST-23 re-test: check the link with a Supervisor-style hostname and with the shim's /addons/self/info.
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#134