Hotfix: planner init crashed for admin accounts, killing autosave and the plans list
Test and deploy / test-and-deploy (push) Successful in 1m34s
Test and deploy / test-and-deploy (push) Successful in 1m34s
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 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c6c392730f
commit
0b4857c225
@@ -1169,6 +1169,19 @@
|
|||||||
Draw from lot
|
Draw from lot
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px"
|
||||||
|
>
|
||||||
|
<button type="button" class="ghost-btn" id="btn-open-cupping">
|
||||||
|
Open cupping session
|
||||||
|
</button>
|
||||||
|
<span class="field-note" id="cupping-link-note" style="margin:0"></span>
|
||||||
|
</div>
|
||||||
|
<p class="field-note">
|
||||||
|
After the roast, upload the .alog on the
|
||||||
|
<a href="/roasts">Roasts page</a> — weights, colour, cup notes,
|
||||||
|
and "one change next batch" now live there with the LLM review.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -1121,6 +1121,9 @@ function wireSectionNav() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
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 {
|
try {
|
||||||
const meResponse = await fetch("/api/auth/me");
|
const meResponse = await fetch("/api/auth/me");
|
||||||
if (!meResponse.ok) {
|
if (!meResponse.ok) {
|
||||||
@@ -1132,7 +1135,7 @@ async function init() {
|
|||||||
document.getElementById("settings-email").textContent = user.email;
|
document.getElementById("settings-email").textContent = user.email;
|
||||||
applyAvatar(document.getElementById("nav-user-avatar"), user);
|
applyAvatar(document.getElementById("nav-user-avatar"), user);
|
||||||
if (user.role === "admin")
|
if (user.role === "admin")
|
||||||
document.getElementById("nav-admin").classList.remove("hidden");
|
document.getElementById("nav-admin")?.classList.remove("hidden");
|
||||||
const localDraft = loadFromStorage(user.id);
|
const localDraft = loadFromStorage(user.id);
|
||||||
state.plan = localDraft ?? blankPlan();
|
state.plan = localDraft ?? blankPlan();
|
||||||
const draftRemoteId = remotePlanId;
|
const draftRemoteId = remotePlanId;
|
||||||
@@ -1187,7 +1190,6 @@ async function init() {
|
|||||||
renderBandRanges();
|
renderBandRanges();
|
||||||
renderFormFromPlan();
|
renderFormFromPlan();
|
||||||
wireForm();
|
wireForm();
|
||||||
initSideNav();
|
|
||||||
wireDrawers();
|
wireDrawers();
|
||||||
wireToolbar();
|
wireToolbar();
|
||||||
wireToolsMenu();
|
wireToolsMenu();
|
||||||
@@ -1225,6 +1227,9 @@ async function init() {
|
|||||||
function wireCuppingLink() {
|
function wireCuppingLink() {
|
||||||
const button = document.getElementById("btn-open-cupping");
|
const button = document.getElementById("btn-open-cupping");
|
||||||
const note = document.getElementById("cupping-link-note");
|
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.addEventListener("click", async () => {
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
try {
|
try {
|
||||||
|
|||||||
+7
-2
@@ -13,6 +13,11 @@ let planList = [];
|
|||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
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) =>
|
const fmtTime = (s) =>
|
||||||
s == null
|
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 }),
|
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 });
|
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);
|
svg.append(label);
|
||||||
}
|
}
|
||||||
const xTitle = svgEl("text", { x: (x0 + x1) / 2, y: y1 + 32, "text-anchor": "middle", fill: "#8a7f74", "font-size": 10.5 });
|
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";
|
xTitle.textContent = "MINUTES FROM CHARGE";
|
||||||
svg.append(xTitle);
|
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" });
|
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);
|
svg.append(yTitle);
|
||||||
|
|
||||||
// Plan curve (dashed) under the actual curve
|
// Plan curve (dashed) under the actual curve
|
||||||
|
|||||||
Reference in New Issue
Block a user