diff --git a/public/app.css b/public/app.css index 4a082a1..b0dd01b 100644 --- a/public/app.css +++ b/public/app.css @@ -1706,6 +1706,11 @@ body.show-fids .field[data-fid]::after { display: grid; gap: 8px; } +.plan-list-row { + display: flex; + align-items: stretch; + gap: 6px; +} .plan-list-item { width: 100%; display: grid; @@ -1719,6 +1724,10 @@ body.show-fids .field[data-fid]::after { font: inherit; cursor: pointer; } +.plan-list-delete { + flex-shrink: 0; + align-self: center; +} .plan-list-item:hover, .plan-list-item.active { border-color: var(--ember); @@ -2603,7 +2612,8 @@ body.show-fids .field[data-fid]::after { flex: 1 1 auto; justify-content: center; } - .header-actions #btn-print { + .header-actions #btn-print, + .header-actions #btn-expand-why { flex: 0 0 auto; min-height: 44px; padding: 8px 16px; diff --git a/public/cupping.html b/public/cupping.html index d8c49f3..e6ec5b2 100644 --- a/public/cupping.html +++ b/public/cupping.html @@ -88,6 +88,9 @@ >Not saved yet +
@@ -160,7 +163,7 @@
The 6–10 range is a specialty-grading convention:
@@ -186,7 +189,7 @@
A tag is only useful if you could defend it to
@@ -206,7 +209,7 @@
Numbers compare roasts; sentences explain them. Write
diff --git a/public/index.html b/public/index.html
index 4b05dd1..baa0230 100644
--- a/public/index.html
+++ b/public/index.html
@@ -129,6 +129,9 @@
>Not saved yet
+
@@ -372,7 +375,7 @@
A roast plan is three durations laid end to end: drying +
@@ -543,7 +546,7 @@
The drum has one heat setting and one clock — you cannot
@@ -615,7 +618,7 @@
Processing changed the chemistry that browning starts
@@ -743,7 +746,7 @@
Moisture, density and screen size change how much energy the
@@ -826,7 +829,7 @@
A planned milestone written into the log as though it
@@ -1136,7 +1139,7 @@
after cupping
Move two variables and there are four possible
diff --git a/public/js/main.js b/public/js/main.js
index dd532b9..372c107 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -16,10 +16,11 @@ import { initPrefillPanel } from "./prefill-ui.js?v=__ASSET_VERSION__";
import { initAlogPanel } from "./alog-ui.js?v=__ASSET_VERSION__";
import { initPrint } from "./print.js?v=__ASSET_VERSION__";
import { initLotPicker } from "./lot-picker.js?v=__ASSET_VERSION__";
-import { protectedFetch, csrfToken } from "./api.js?v=__ASSET_VERSION__";
+import { api, protectedFetch, csrfToken } from "./api.js?v=__ASSET_VERSION__";
import { initSideNav } from "./nav.js?v=__ASSET_VERSION__";
import { wireWhyPanels } from "./why-panels.js?v=__ASSET_VERSION__";
import { initFieldHelp } from "./field-help.js?v=__ASSET_VERSION__";
+import { showToast } from "./toast.js?v=__ASSET_VERSION__";
const FIELD_ID_SET = new Set(FIELD_IDS);
const STORAGE_PREFIX = "roastPlannerPlan.v2";
@@ -823,6 +824,7 @@ async function loadPlans() {
list.replaceChildren(
...plans.map((plan) => {
const item = document.createElement("li");
+ item.className = "plan-list-row";
const button = document.createElement("button");
button.type = "button";
button.className = "plan-list-item";
@@ -833,7 +835,20 @@ async function loadPlans() {
updated.textContent = `Updated ${new Date(plan.updated_at).toLocaleDateString()}`;
button.append(title, updated);
button.addEventListener("click", () => selectPlan(plan));
- item.append(button);
+
+ const deleteBtn = document.createElement("button");
+ deleteBtn.type = "button";
+ deleteBtn.className = "icon-btn plan-list-delete";
+ deleteBtn.setAttribute("aria-label", `Delete ${title.textContent}`);
+ deleteBtn.textContent = "✕";
+ // A sibling of the select button, not nested inside it — stopPropagation alone
+ // wouldn't be enough to keep a click here from also selecting the plan otherwise.
+ deleteBtn.addEventListener("click", (event) => {
+ event.stopPropagation();
+ deletePlan(plan);
+ });
+
+ item.append(button, deleteBtn);
return item;
}),
);
@@ -860,10 +875,9 @@ async function selectPlan(plan) {
lotPicker?.refreshRefineSuggestion();
document.querySelector("[data-close-drawer]")?.click();
}
-async function newPlan() {
- if (!confirm("Start a new plan? Your current plan is already saved locally."))
- return;
- if (!(await flushCurrentPlan())) return;
+// Shared by newPlan() and deletePlan() (when the deleted plan was the one on screen) — resets
+// the whole app to a fresh, unsynced plan.
+function resetToBlankPlan() {
remotePlanId = null;
draftSyncedAt = null;
state.plan = blankPlan();
@@ -875,6 +889,28 @@ async function newPlan() {
lotPicker?.renderOptions();
lotPicker?.refreshRefineSuggestion();
}
+async function newPlan() {
+ if (!confirm("Start a new plan? Your current plan is already saved locally."))
+ return;
+ if (!(await flushCurrentPlan())) return;
+ resetToBlankPlan();
+ // Without this, the Plans drawer stays open over the now-blank form — the reset happens but
+ // is invisible, so it doesn't read as "starting something new" until the user closes it themselves.
+ document.querySelector("[data-close-drawer]")?.click();
+}
+async function deletePlan(plan) {
+ const name = plan.plan?.fields?.["0.1"] || "Untitled plan";
+ if (!confirm(`Delete "${name}"? This cannot be undone.`)) return;
+ try {
+ await api(`/api/plans/${plan.id}`, { method: "DELETE" });
+ } catch (error) {
+ showToast(error.message, "fail");
+ return;
+ }
+ showToast("Plan deleted.");
+ if (plan.id === remotePlanId) resetToBlankPlan();
+ await loadPlans();
+}
function wireToolbar() {
document.getElementById("btn-toggle-fids").addEventListener("click", (e) => {
diff --git a/public/js/why-panels.js b/public/js/why-panels.js
index 5838f72..379c80d 100644
--- a/public/js/why-panels.js
+++ b/public/js/why-panels.js
@@ -1,25 +1,42 @@
// Shared teaching-layer collapse memory for every page that uses `.why-panel` (`Flavors
Tag what you can point at
Notes
Notes outlive scores
The Coffee
Why cultivar sets the clock
Why blends combine times, never temperatures
Roast Target
Why processing moves development, not first crack
Bean Condition
Why bean condition is worth seconds, not a new plan
Machine Plan
Why the rate of rise must fall, and never touch zero
@@ -994,7 +997,7 @@
during the roast
Record what you observed, never what you planned
One change per batch — arithmetic, not patience