Fix desktop header overflow; add plan attachment on roast detail
Test and deploy / test-and-deploy (push) Successful in 55s
Test and deploy / test-and-deploy (push) Successful in 55s
- The app header now wraps: brand + actions on the first row (actions shrink and wrap instead of pushing Print off-screen), section pills always on their own full-width row; the planner's sticky offsets use a two-row --header-h via body:has(#plan-form) - Roast detail gains a Plan selector (attach/detach any of your plans) alongside the Machine selector; PUT /api/roasts/:id accepts roastPlanId with ownership checks Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4a3d192c2c
commit
c6c392730f
+46
-1
@@ -9,6 +9,7 @@ let openId = null;
|
||||
let pollTimer = null;
|
||||
let machineProfile = null;
|
||||
let roasterList = [];
|
||||
let planList = [];
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
@@ -316,11 +317,50 @@ async function openDetail(id, { keepScroll = false } = {}) {
|
||||
meta.textContent = [
|
||||
detail.roast?.roastDate || fmtDate(detail.createdAt),
|
||||
detail.roast?.roasterType,
|
||||
detail.planTitle ? `Plan: ${detail.planTitle}` : "No plan attached",
|
||||
detail.filename,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
// Plan attachment: link this roast to one of your plans (enables plan-vs-actual in the
|
||||
// review, the refine handoff, and the plan overlay on the graph).
|
||||
if (planList.length) {
|
||||
const planWrap = document.createElement("span");
|
||||
planWrap.append(" · Plan: ");
|
||||
const planSelect = document.createElement("select");
|
||||
planSelect.className = "field-input sm";
|
||||
planSelect.style.display = "inline-block";
|
||||
planSelect.style.width = "auto";
|
||||
planSelect.append(
|
||||
Object.assign(document.createElement("option"), {
|
||||
value: "",
|
||||
textContent: "— none —",
|
||||
}),
|
||||
...planList.map((plan) =>
|
||||
Object.assign(document.createElement("option"), {
|
||||
value: plan.id,
|
||||
textContent: plan.plan?.fields?.["0.1"] || "Untitled plan",
|
||||
}),
|
||||
),
|
||||
);
|
||||
planSelect.value = detail.roastPlanId ?? "";
|
||||
planSelect.addEventListener("change", async () => {
|
||||
try {
|
||||
await api(`/api/roasts/${detail.id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ roastPlanId: planSelect.value || null }),
|
||||
});
|
||||
showToast("Plan attached — re-evaluate to review against its targets.");
|
||||
await loadRoasts();
|
||||
await openDetail(detail.id, { keepScroll: true });
|
||||
} catch (error) {
|
||||
showToast(error.message, "fail");
|
||||
}
|
||||
});
|
||||
planWrap.append(planSelect);
|
||||
meta.append(planWrap);
|
||||
} else if (detail.planTitle) {
|
||||
meta.append(` · Plan: ${detail.planTitle}`);
|
||||
}
|
||||
// Machine assignment: which of the user's roasters this roast trains.
|
||||
if (roasterList.length) {
|
||||
const wrap = document.createElement("span");
|
||||
@@ -501,6 +541,11 @@ async function init() {
|
||||
} catch {
|
||||
roasterList = [];
|
||||
}
|
||||
try {
|
||||
planList = (await api("/api/plans")).plans;
|
||||
} catch {
|
||||
planList = [];
|
||||
}
|
||||
await loadRoasts();
|
||||
const requested = new URLSearchParams(location.search).get("roast");
|
||||
if (requested && roasts.some((roast) => roast.id === requested))
|
||||
|
||||
Reference in New Issue
Block a user