UX-09: Details link for current HA, "would" in shadow mode, plain terms
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 2s

- 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
This commit is contained in:
2026-09-27 04:10:39 +02:00
co-authored by Claude Opus 5.5
parent d57767fe38
commit 00e3ee9de8
16 changed files with 666 additions and 264 deletions
+12 -12
View File
@@ -535,7 +535,7 @@ print(f" {[r['text'] for r in _bad_row]}")
check("a refused read says WHICH refusal - 'no reading' on its own sends an "
"operator to look at a working sensor",
len(_bad_row) == 1
and _bad_row[0]["text"] == ("Solar production: no reading, the sensor's "
and _bad_row[0]["text"] == ("Solar production: no reading, the entity's "
"unit is not a power unit"))
check("...and every refusal pv.watts() can give has its own plain words",
len({messages.pv_problem(pv.watts(st)[1]) for st in (
@@ -996,7 +996,7 @@ check("⭐ BOTH KEYS SET: the page SAYS `pv_entity` is ignored and how many "
"key still names - but a replacement nobody is told about is the 'one of "
"which silently wins' this config refuses elsewhere",
len(_ign) == 1 and _ign[0]["ok"] is False and _ign[0]["warn"] is True
and "clear the single sensor setting" in _ign[0]["text"]
and "clear “Solar production entity”" in _ign[0]["text"]
and _c_both.pv_w == 3750.0)
check("...and that caution appears ONLY when both are set - not on the 0.11.0 "
"configuration, not on a list-only one, and not on an install with "
@@ -1126,7 +1126,7 @@ check("⭐ THE REPORTED CASE: 13 W of night-time standby on a reversed meter is
_c_own.pv_w == -13.0
and [r["text"] for r in pv_rows(_c_own)]
== ["Solar production: -13 W, sign reversed"]
and _row(_c_own, "reversed-sign list") == [])
and _row(_c_own, "Reversed-sign solar entities") == [])
check("...and WITHOUT the option it is the plain row",
[r["text"] for r in pv_rows(_c_own0)] == ["Solar production: 13 W"])
@@ -1140,7 +1140,7 @@ check("⭐ PADDING: ` sensor.x` in the roster and `sensor.x ` in "
"`pv_invert_entities` are the same entity - flipped on the read path, "
"marked on the page, and no stray caution",
_c_pad.pv_w == MIX_TOTAL
and _row(_c_pad, "reversed-sign list") == [])
and _row(_c_pad, "Reversed-sign solar entities") == [])
# --- the negative caution -------------------------------------------------------
def _neg(watts_by_entity, flip=()):
@@ -1156,12 +1156,12 @@ _n_rm = _neg({E1: 1200, E2: 1500, E3: 450}, flip=[E2])
print(f" {[r['text'] for r in _n_add + _n_rm]}")
check("⭐ AN UNFLIPPED ENTITY FAR BELOW ZERO gets a caution that names it by "
"its place in the list, its watts, and says to ADD it to the "
"reversed-sign list (`pv_invert_entities`)",
"setting 'Reversed-sign solar entities' (`pv_invert_entities`)",
[r["text"] for r in _n_add]
== ["Solar production: source 2 reads -1 500 W, so add it to the "
"reversed-sign list"])
== ["Solar production: source 2 reads -1 500 W, add it to "
"“Reversed-sign solar entities”"])
check("...and a FLIPPED one far below zero is told to REMOVE it instead",
len(_n_rm) == 1 and "remove it from the reversed-sign list"
len(_n_rm) == 1 and "remove it from “Reversed-sign solar entities”"
in (_at(_n_rm, 0) or {}).get("text", ""))
check("⭐ ...and it is a CAUTION, never a `bad` row - a telemetry sign cannot "
"put a battery site into NOT READY",
@@ -1184,10 +1184,10 @@ _c_typo, _, _ = run(cycles=1, by_entity=FULL,
check("a `pv_invert_entities` entry naming no PV entity in use is a named "
"caution (a typo would otherwise leave the sign silently unflipped)",
[(r["text"], r.get("ok"), r.get("warn"))
for r in _row(_c_typo, "not a solar sensor in use")]
== [("Solar production: entry 1 of the reversed-sign list is not a "
"solar sensor in use", False, True)]
and _row(_c_mix, "not a solar sensor in use") == [])
for r in _row(_c_typo, "not a solar entity in use")]
== [("Solar production: entry 1 of “Reversed-sign solar entities” is "
"not a solar entity in use", False, True)]
and _row(_c_mix, "not a solar entity in use") == [])
# --- default: byte-identical to 0.12.0 ------------------------------------------
_c_d0, _, _p_d0 = run(cycles=1, by_entity=MIX, **PV_MANY)