UX-13 P4 review fixes (PR #146, comment 1887)
checks / unit (pull_request) Successful in 10m24s
infra-pin / fresh (pull_request) Successful in 1s

R1: first_start also needs a Store created at this boot (Store.fresh), so
    old never-started sites (both TEST-23 add-ons, one in shadow) stay on Home.
R2: a kettle skip after a fall says on the last step that the reading went down.
R3: the shadow switch goes only on the model the wizard posts, never into V.
R4: Home Assistant not answering is red on the last step.
R5: meter + solar + battery below -300 W flags a reversed battery sensor.
R6: the live checks say what to change and where.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
review-agent
2026-09-28 06:12:09 +02:00
parent be57b87c34
commit 3198f346fd
10 changed files with 195 additions and 75 deletions
+16 -11
View File
@@ -636,31 +636,37 @@
if (k.sig !== sig) { k.before = null; k.ok = false; k.sig = sig; }
const w = await readMeter(src);
if (w == null) WZ.kettle = Object.assign({}, k, { sig, say: "unread" });
else if (k.before == null || k.ok) WZ.kettle = { sig, before: w };
else if (k.before == null || k.ok) WZ.kettle = { sig, before: w, wrong: !k.ok && !!k.wrong };
else {
const o = kettleOutcome(src, k.before, w);
WZ.kettle = { sig, before: o.say === "none" ? k.before : null, say: o.say, d: o.d, ok: o.sets != null };
// `wrong`: the reading went the way no option can fix. A skip after
// that keeps saying so on the last step (review R2).
const wrong = o.say === "none" ? !!k.wrong : o.sets == null;
WZ.kettle = { sig, before: o.say === "none" ? k.before : null, say: o.say, d: o.d, ok: o.sets != null, wrong };
if (o.sets) { Object.assign(V, o.sets); queueValidate(); }
}
drawStep();
});
const row = h("div", { class: "row" }, read);
if (!k.ok) row.append(h("button", { type: "button", class: "btn link", onclick: () => {
WZ.kettle = { sig: kettleSig(src), say: "skipped", ok: true, skipped: true }; drawStep();
WZ.kettle = { sig: kettleSig(src), say: "skipped", ok: true, skipped: true, wrong: !!k.wrong }; drawStep();
} }, WS.kettle.skip));
box.append(row);
return box;
}
// Step 6: the shadow switch, the checks, and Save and restart.
function applyShadow() {
// The shadow switch goes only on the model the wizard posts, never into V,
// which the Installer view saves too (review R3).
function shadowValue() {
const s = M.setup.shadow;
V[s.field] = WZ.shadowOn ? s.on : (ORIG[s.field] !== s.on ? ORIG[s.field] : s.off);
return WZ.shadowOn ? s.on : (ORIG[s.field] !== s.on ? ORIG[s.field] : s.off);
}
function posted() { return Object.assign({}, V, { [M.setup.shadow.field]: shadowValue() }); }
function shadowRow() {
const lid = "wzs";
const b = h("button", { type: "button", class: "switch", role: "switch", "aria-checked": String(WZ.shadowOn), "aria-labelledby": lid });
b.addEventListener("click", () => { WZ.shadowOn = !WZ.shadowOn; applyShadow(); queueValidate(); drawStep(); });
b.addEventListener("click", () => { WZ.shadowOn = !WZ.shadowOn; drawStep(); });
return h("div", { class: "f inline" },
h("div", {}, h("div", { class: "lbl", id: lid }, WS.shadow.name), h("div", { class: "help" }, WS.shadow.description)),
h("div", { class: "ctl" }, b));
@@ -669,7 +675,7 @@
const list = $("#wz-checks");
if (list) list.replaceChildren(line("info", WS.check.running.description));
clearTimeout(vtimer);
const b = await validate({ setup: true });
const b = await validate({ setup: true, model: posted() });
WZ.checked = b;
if (!list || !list.isConnected) return b;
if (!b) { list.replaceChildren(line("bad", WS.check.failed.description)); return b; }
@@ -682,7 +688,7 @@
if (b.device_note) add("info", b.device_note);
const src = kettleSource();
if (!src) add("info", WS.kettle.self.description);
else if (WZ.kettle && WZ.kettle.skipped) add("warn", WS.kettle.skipped.description);
else if (WZ.kettle && WZ.kettle.skipped) add("warn", (WZ.kettle.wrong ? WS.kettle.skipped_wrong : WS.kettle.skipped).description);
else if (kettleOk()) add("ok", WS.kettle.tested.description);
const rank = { bad: 0, warn: 1, ok: 2, info: 3 };
items.sort((a, z) => rank[a[0]] - rank[z[0]]);
@@ -708,11 +714,11 @@
const b = await runChecks();
if (!b) { toast(WS.check.failed.description); return; }
if (WZ.blocked) { toast(WS.check.blocked.description); return; }
const shadow = V[M.setup.shadow.field] === M.setup.shadow.on;
const shadow = shadowValue() === M.setup.shadow.on;
ask(shadow ? WS.dialog : WS.dialog_live, async () => {
wzBanner();
let res;
try { res = await postSave({ setup: "done" }); } catch (e) { toast(W.toast.failed); return; }
try { res = await postSave({ setup: "done", model: posted() }); } catch (e) { toast(W.toast.failed); return; }
const { r, b: body } = res;
if (r.status === 409 && body.kind === "stale") { wzBanner("bad", W.stale.name, staleText(body)); return; }
if (r.status === 422) {
@@ -758,7 +764,6 @@
ROWS.length = 0;
const body = [];
if (n === 1 && !M.setup_needed) body.push(h("div", { class: "msg info" }, WS.done_before.description));
if (n === S.final) applyShadow();
for (const f of allFields()) if (f.wizard === n && f.key !== S.shadow.field) body.push(fieldRow(f, null, "wz"));
if (n === 1) body.push(kettleBox());
if (n === 5) body.push(h("p", { class: "small muted" }, WS.modes_later.description));