UX-13 P2 review fixes (PR #145, comment 1880)
checks / unit (pull_request) Successful in 10m15s
infra-pin / fresh (pull_request) Successful in 2s

1. Discard reloads /config/model: after a stale save the page kept the
   new fingerprint with the old values, so Discard + Save overwrote the
   other surface's change without a 409.
2. Removing cards back to one restores the stored inverter shape, so a
   flat site stays flat after add + remove.
3. A choice the page can only keep ("Other time windows") stays offered
   while it is the stored value.
4. A load locks every locked value again.
5. at_zero() needs an accepted 0 W write; before the first one Home says
   "Going to 0 W" (the heartbeat writes every heartbeat_s, so it clears).
6. A field with a unit drops the translation's "(W)" from its label: the
   peak limit read "Peak limit (W)" beside a kW value.
7. A mirrored choice lists the mirrored field's keys, so a search for
   static_profile finds the contract on a fixed-price site.
8. test_settings: a save refused by ui_to_options writes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
UX-13 P2 review
2026-09-28 03:43:47 +02:00
parent 1752adf6e6
commit 7be09197d4
6 changed files with 64 additions and 17 deletions
+28 -3
View File
@@ -103,8 +103,12 @@ check(f"every slider and locked value has its range from the schema "
f"({len(_ranged)})", all(lo is not None and hi is not None
for _k, lo, hi in _ranged)
and ("peak_cap_w", 500.0, 15000.0) in _ranged)
check("the peak limit is a kW slider over a value stored in W",
field(FIELD, "peak_cap_w")["scale"] == 1000)
check("the peak limit is a kW slider over a value stored in W, and its label "
"does not say W (the unit is shown beside the control)",
field(FIELD, "peak_cap_w")["scale"] == 1000
and field(FIELD, "peak_cap_w")["label"] == "Peak limit"
and not [f["key"] for _g, f in _flat
if f["unit"] and re.search(r"\([^()\s]{1,6}\)", f["label"])])
_choice = [f for _g, f in _flat
if f["control"] in (L.SEGMENTED, L.SELECT, L.CHOICE)]
check("every choice control has choices with words",
@@ -139,7 +143,8 @@ check("the inverter cards carry their count, and adding one makes a list",
check("a page-only field names the option keys it writes, for the grey key "
"line", _reg["writes"] == ["price_taxes_profile"]
and field(FIELD, "ui.solar")["writes"]
== ["pv_entities", "pv_entity", "pv_invert_entities"])
== ["pv_entities", "pv_entity", "pv_invert_entities"]
and "static_profile" in field(FIELD, "ui.contract")["writes"])
check("without a Supervisor schema the page still draws, with no ranges",
len(uipage.layout([], {})["groups"]) == 6)
@@ -183,6 +188,18 @@ check("a locked value shows no input until 'Change' is pressed",
and "UNLOCKED.has(id)) { open(); return box; }" in JS)
check("nothing is saved while the add-on's own checks refuse the model",
"if (MSG.some((m) => m.tier === \"bad\")) { toast(" in JS)
check("Discard reloads the stored settings (after a stale save the page's "
"copy is no longer what is stored)",
re.search(r'"#sb-discard"\)\.addEventListener\("click", async \(\) => '
r'\{\s*await load\(\);', JS) is not None)
check("a load locks every locked value again, and a choice the page can only "
"keep stays offered while it is the stored value",
"UNLOCKED.clear();" in JS.split("async function load")[1].split("render();")[0]
and "same(c[0], was)" in JS)
check("removing cards back to one restores the stored shape (a flat inverter "
"is not turned into a list of one)",
"if (V[f.key].length === 1) for (const k in f.grow) V[k] = clone(ORIG[k]);"
in JS)
check("the old settings page stays reachable (until P5)",
'href="config" id="set-classic"' in web.page())
@@ -343,6 +360,11 @@ async def _refuse(s, root, sup):
out["floor"] = await validate(peak_soc_floor=5)
out["derive"] = await validate(ui__contract="dynamic", price_provider="")
model = copy.deepcopy(base)
model.update({"ui.contract": "dynamic", "price_provider": ""})
out["derive_save"] = await _post(s, root + "config/save", {
"model": model, "fingerprint": m["fingerprint"]})
out["derive_writes"] = len(sup.writes)
model = copy.deepcopy(base)
model["inverters"][1]["setpoint_entity"] = ""
out["save"] = await _post(s, root + "config/save",
{"model": model, "fingerprint": m["fingerprint"]})
@@ -374,6 +396,9 @@ check("a model no blob can say is refused beside its field, in plain words",
_r["derive"]["messages"] == [{
"field": "price_provider", "card": None, "tier": "bad",
"text": W["derive"]["no_source"]["description"]}])
check("a save no blob can say answers 422 with its sentence and writes nothing",
_r["derive_save"][0] == 422 and _r["derive_writes"] == 0
and _r["derive_save"][1]["messages"] == _r["derive"]["messages"])
check("a refused save answers 422 with the same placement and writes nothing",
_r["save"][0] == 422 and ("inverters.setpoint_entity", 1)
in _at(_r["save"][1]) and _sup.writes == [])