From 0b4857c22543643d9ae34f7a05a37af01d7a9b03 Mon Sep 17 00:00:00 2001 From: Shane Maynard Date: Sun, 9 Aug 2026 08:53:56 -0400 Subject: [PATCH] Hotfix: planner init crashed for admin accounts, killing autosave and the plans list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin nav-link reveal ran before the generated nav existed (getElementById('nav-admin') was null only for admins — which is why tests and non-admin checks missed it), aborting init before form wiring, autosave, and loadPlans. initSideNav now runs first, the reveal is null-safe, and wireCuppingLink degrades instead of crashing (its button lost its home when After-the-Roast moved — it now lives at the end of the Roast Log section with a pointer to the Roasts page). Also: the Roasts page graph now honors the planner's °C/°F preference. Co-Authored-By: Claude Fable 5 --- public/index.html | 13 +++++++++++++ public/js/main.js | 9 +++++++-- public/js/roasts.js | 9 +++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/public/index.html b/public/index.html index 4922575..12cb90c 100644 --- a/public/index.html +++ b/public/index.html @@ -1169,6 +1169,19 @@ Draw from lot +
+ + +
+

+ After the roast, upload the .alog on the + Roasts page — weights, colour, cup notes, + and "one change next batch" now live there with the LLM review. +

diff --git a/public/js/main.js b/public/js/main.js index b07f37d..53788f3 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1121,6 +1121,9 @@ function wireSectionNav() { } async function init() { + // The side nav is generated — render it before anything touches its elements (the admin + // link reveal below crashed init for admin accounts when this ran later). + initSideNav(); try { const meResponse = await fetch("/api/auth/me"); if (!meResponse.ok) { @@ -1132,7 +1135,7 @@ async function init() { document.getElementById("settings-email").textContent = user.email; applyAvatar(document.getElementById("nav-user-avatar"), user); if (user.role === "admin") - document.getElementById("nav-admin").classList.remove("hidden"); + document.getElementById("nav-admin")?.classList.remove("hidden"); const localDraft = loadFromStorage(user.id); state.plan = localDraft ?? blankPlan(); const draftRemoteId = remotePlanId; @@ -1187,7 +1190,6 @@ async function init() { renderBandRanges(); renderFormFromPlan(); wireForm(); - initSideNav(); wireDrawers(); wireToolbar(); wireToolsMenu(); @@ -1225,6 +1227,9 @@ async function init() { function wireCuppingLink() { const button = document.getElementById("btn-open-cupping"); const note = document.getElementById("cupping-link-note"); + // Defensive: this UI lives in the Roast Log section; a missing element must degrade to + // "no cupping shortcut", never crash init (that failure mode silently killed autosave). + if (!button || !note) return; button.addEventListener("click", async () => { button.disabled = true; try { diff --git a/public/js/roasts.js b/public/js/roasts.js index 72a7f29..ffead66 100644 --- a/public/js/roasts.js +++ b/public/js/roasts.js @@ -13,6 +13,11 @@ let planList = []; const SVG_NS = "http://www.w3.org/2000/svg"; +// Same display preference the planner's °C/°F toggle stores — data stays canonical °C, +// only labels convert. +const tempUnit = localStorage.getItem("roastPlannerTempUnit.v1") === "F" ? "F" : "C"; +const displayTemp = (c) => (tempUnit === "F" ? Math.round((c * 9) / 5 + 32) : Math.round(c)); + const fmtTime = (s) => s == null ? "—" @@ -167,14 +172,14 @@ function renderGraph(detail) { svgEl("line", { x1: x0, y1: yFor(c), x2: x1, y2: yFor(c), stroke: "#e5dcd2", "stroke-width": c === tempLo ? 1.2 : 0.7 }), ); const label = svgEl("text", { x: x0 - 6, y: yFor(c) + 3, "text-anchor": "end", fill: "#8a7f74", "font-size": 10 }); - label.textContent = String(c); + label.textContent = String(displayTemp(c)); svg.append(label); } const xTitle = svgEl("text", { x: (x0 + x1) / 2, y: y1 + 32, "text-anchor": "middle", fill: "#8a7f74", "font-size": 10.5 }); xTitle.textContent = "MINUTES FROM CHARGE"; svg.append(xTitle); const yTitle = svgEl("text", { x: 14, y: (y0 + y1) / 2, fill: "#8a7f74", "font-size": 10.5, transform: `rotate(-90 14 ${(y0 + y1) / 2})`, "text-anchor": "middle" }); - yTitle.textContent = "BEAN TEMP °C"; + yTitle.textContent = `BEAN TEMP °${tempUnit}`; svg.append(yTitle); // Plan curve (dashed) under the actual curve