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
+50 -1
View File
@@ -355,10 +355,15 @@ function wireCupCount() {
renderTickRows();
renderDefectRows();
renderTotal();
renderSubtitle();
scheduleSave();
});
}
function renderSubtitle() {
document.getElementById("cupping-subtitle").textContent = `${data.cup_count} cups`;
}
// ── Flavor checklist ──
function renderFlavorChips() {
const chips = document.getElementById("flavor-chips");
@@ -578,7 +583,51 @@ async function initSessionView(user) {
data = body.session.data;
document.getElementById("cupping-title").textContent =
body.session.planTitle || "Cupping session";
document.getElementById("cupping-subtitle").textContent = `${data.cup_count} cups`;
renderSubtitle();
// Sessions created without a plan can attach one later (same PUT the autosave uses) —
// otherwise they're stuck titled "Untitled" forever.
if (!body.session.roastPlanId) {
const subtitle = document.getElementById("cupping-subtitle");
try {
const { plans } = await api("/api/plans");
if (plans.length) {
const wrap = document.createElement("span");
wrap.append(" · Plan: ");
const select = document.createElement("select");
select.className = "field-input sm";
select.style.display = "inline-block";
select.style.width = "auto";
select.append(
Object.assign(document.createElement("option"), {
value: "",
textContent: "— none —",
}),
...plans.map((p) =>
Object.assign(document.createElement("option"), {
value: p.id,
textContent: p.name || p.plan?.fields?.["0.1"] || "Untitled plan",
}),
),
);
select.addEventListener("change", async () => {
try {
await api(`/api/cupping/${sessionId}`, {
method: "PUT",
body: JSON.stringify({ data, roastPlanId: select.value || null }),
});
showToast("Plan attached.");
location.reload();
} catch (error) {
showToast(error.message, "fail");
}
});
wrap.append(select);
subtitle.after(wrap);
}
} catch {
/* attaching later is a convenience — the session still works without it */
}
}
wireCupCount();
renderScoreRows();