Removable reference curves, audit fixes across pages, shaded Academy art
Test and deploy / test-and-deploy (push) Successful in 1m8s

The planner's Artisan .alog drawer now shows what's attached with a
"Remove reference curve" control (re-rendered per drawer open), so adding
a reference curve is no longer a one-way door; /api/alog shares the 8 MB
body cap so real-sized logs parse instead of failing with "bad_request".

Deep-evaluation fixes: editing a brew of an archived bean no longer
silently detaches the bean; the roasts pending-review poll no longer
wipes in-progress after-roast edits; roasters gain an Edit (rename/model)
action; the gear page refuses to autosave over a failed load; duplicating
a plan carries its custom name; cupping sessions can attach a plan after
creation (ownership-checked PUT + selector); admin user deletion also
refreshes plans/audit; cupping cup-count subtitle stays live; roasts
error-row colspan corrected. Regression tests cover the new cupping PUT
and the /api/alog body cap.

Academy scenes drop the flat paper-cutout look: shared defs provide
radial-gradient shading on every bean/half-bean/particle, flame gradients
with radiant halos, soft ground shadows, and a warm-lit stage background;
fill-shift animations now ride a partial-opacity tint overlay so shading
survives the color change.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-08-11 16:59:28 -04:00
co-authored by Claude Fable 5
parent 8b80c3fa6f
commit 385474ecef
14 changed files with 368 additions and 34 deletions
+14 -2
View File
@@ -86,7 +86,7 @@ async function loadRoasts() {
schedulePoll();
} catch {
document.getElementById("roasts-body").innerHTML =
`<tr><td colspan="7" class="empty-state">Could not load roasts.</td></tr>`;
`<tr><td colspan="6" class="empty-state">Could not load roasts.</td></tr>`;
}
}
@@ -406,7 +406,12 @@ async function openDetail(id, { keepScroll = false } = {}) {
`/api/roasts/${encodeURIComponent(id)}/download`;
document.getElementById("detail-download-updated").href =
`/api/roasts/${encodeURIComponent(id)}/download?variant=updated`;
renderAfterForm(detail);
// The 4s pending-evaluation poll re-runs openDetail; never let that background refresh
// overwrite after-roast fields the user is currently editing.
const afterForm = document.getElementById("after-form");
if (!keepScroll || !(afterFormDirty || afterForm.contains(document.activeElement)))
renderAfterForm(detail);
if (!keepScroll) afterFormDirty = false;
renderGraph(detail);
renderStats(detail);
renderEvaluation(detail);
@@ -421,6 +426,12 @@ const AFTER_FIELDS = [
"restedDays", "brewRatio", "method", "cupNotes", "oneChange", "disproof",
];
// True while the after-roast form holds unsaved user edits (cleared on save/open).
let afterFormDirty = false;
document.getElementById("after-form").addEventListener("input", () => {
afterFormDirty = true;
});
function renderAfterForm(detail) {
const form = document.getElementById("after-form");
const after = detail.after ?? {};
@@ -449,6 +460,7 @@ document.getElementById("after-form").addEventListener("submit", async (event) =
method: "PUT",
body: JSON.stringify({ after }),
});
afterFormDirty = false;
showToast("After-roast saved — included in the updated .alog.");
} catch (error) {
showToast(error.message, "fail");