UX-10: the last plain-language leftovers: log lines, two table rows, 'Why: Other', red at 0 s
checks / unit (pull_request) Successful in 9m3s
infra-pin / fresh (pull_request) Successful in 2s

- test_messages.py part 6: every dynamic part of a WARNING+ line is classified
  as data or as a built text, and every built text is produced by the real
  code and checked. This is the gap the 2d scan had: it counted `%s` as one
  word, so reasons built by loop_clamp_note() and friends were never seen.
- Quoted reasons rewritten to STYLE.md (red-alarm shape, codes kept):
  OUTPUT_CLAMP, METER_FROZEN, CIRCULATING_POWER, STALE_READING, the peak
  guard (codes now PEAK_GUARD*), the migration report, price feed, price
  history and gate reasons. Plurals via messages.times()/count().
- Top table: Price history "96 prices over 24 hours for 26 Sep", Tariff
  "one every 15 minutes or every hour", "not final yet" dropped.
- Levels and noise: per-cycle shadow line DEBUG, shadow on/off INFO,
  aiohttp access log only at log level debug, settings warnings once per
  start, one plain line per Home Assistant outage (hass.Outage).
- INFO jargon and error texts: no "§8.3", ISO times, "banked", "keys ARE",
  "NOT_OBSERVED", "did NOT land", "AND".
- Display bugs: WHY_WINNER covers every winner and safety reason
  (main.why_key(), part 7 finds them in the code); the meter row is amber
  "waiting for the first reading" inside its limit after a start; the
  missing-reading age is counted when the page asks.
- STYLE.md and DECISIONS/UX-08-short-notifications.md (UX-10 section).

No change to safety behaviour, alarm levels, notification ids, triggers,
cadence or the banner cascade. One row colour changes (meter at start),
justified in DECISIONS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-09-27 10:58:43 +02:00
co-authored by Claude Opus 5.5
parent 125b0702a2
commit 23a3a3f5b5
33 changed files with 1268 additions and 347 deletions
+5 -6
View File
@@ -1644,9 +1644,8 @@ check("...and a re-trip's reason names the TWO durations separately, each "
[f.submit(A_FROZEN_W, 1300.0, float(t)) for t in range(80, 130)],
f.stale and f.releases == 1
and "unchanged for 92 s" in f.reason
and "the 35 s window this verdict rests on opened when the previous "
"one was withdrawn" in f.reason
and "graded 2 times" in f.reason)[-1])(
and "checked 2 times over a 35 s window since the last stop was "
"withdrawn" in f.reason)[-1])(
Freshness(BOUNDS["batt"])))
@@ -1799,8 +1798,8 @@ check("...and the two runs were driven at 50 % and 11 % SoC and the sentence "
# bytes, and "the register may be unable to comply / the source may have
# stopped reporting reality" is a different and wrong claim.
_S56_EXPLANATIONS = [
("DEAD", "the register may have stopped reporting reality"),
("REFUSED", "the source may be unable to comply with the command"),
("DEAD", "reading may have stopped"),
("REFUSED", "battery may be unable to follow the command"),
]
# ⚠️ WHAT THE THREE SENTENCES USED TO ASSERT. Each is a claim about the
# REGISTER or about the ENTITY, and 11a measured the register to be alive on
@@ -1868,7 +1867,7 @@ check(f"11c. BOTH EXPLANATIONS ARE NAMED, AND IN THAT ORDER: "
check("...and the sentence says in its own words that it CANNOT choose, rather "
"than leaving two explanations side by side for a reader to rank - an "
"operator who reads a list picks the first item",
"CANNOT TELL THEM APART" in _r56_ref)
"this check cannot tell which" in _r56_ref)
# ⚠️ THE POSITIVE CONTROL FOR THE `not any(...)` BELOW, AND IT IS NOT OPTIONAL:
# a quantifier over an empty list is True, and so is one over three literals
# with a typo in each. These are the three sentences as 0.10.1 SHIPPED them,