daily-focus-board: reject array progress/doc containers in normalize()

An array is typeof "object", so progress:[] (or a top-level array state file) was kept as the container; JSON.stringify then drops the non-index props (counters, t, day...), the API returns progress:[] and the canvas crashes reading state.counters. Guard both with !Array.isArray, matching looksLikeBoard. Headless regression tests added (48/48).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
This commit is contained in:
Jenny Ferries
2026-07-31 09:48:13 -07:00
parent ee94962c8d
commit 20208a6794
+2 -2
View File
@@ -88,12 +88,12 @@ function normalizeTaskDef(t) {
// Coerce any parsed JSON into a well-formed board doc, and ensure every task has
// a matching progress entry so the UI and mutations can assume presence.
export function normalize(doc) {
doc = doc && typeof doc === "object" ? doc : {};
doc = doc && typeof doc === "object" && !Array.isArray(doc) ? doc : {};
if (typeof doc.name !== "string") doc.name = "";
if (typeof doc.dateKey !== "string") doc.dateKey = todayKey();
doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];
const p = doc.progress && typeof doc.progress === "object" ? doc.progress : {};
const p = doc.progress && typeof doc.progress === "object" && !Array.isArray(doc.progress) ? doc.progress : {};
// Null-prototype maps so a task id can never resolve to an inherited member
// (e.g. reading p.t["toString"] returning Object.prototype.toString).
p.counters = Object.assign(Object.create(null), p.counters && typeof p.counters === "object" ? p.counters : {});