Add finished-roast .alog uploads with Pi agent deep review and /roasts history
Test and deploy / test-and-deploy (push) Successful in 1m39s

- POST /api/roasts stores the original .alog verbatim (multiple per plan),
  parses it server-side, and kicks off an async zero-tool Pi agent review
  (grade, highlights, concerns, next-batch suggestions, plan-vs-actual)
- /roasts page: table of historical actual roasts with review summaries;
  row detail renders the BT curve as SVG with the plan curve overlaid,
  shows the full review, and offers original-.alog backup download,
  re-evaluate, and delete
- Planner's Reference curve drawer gains a multi-file finished-roast
  uploader that attaches to the open (synced) plan
- Failed reviews record the error on the row and are retryable via
  POST /api/roasts/:id/evaluate (e.g. once a model is configured)

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-08 20:18:22 -04:00
co-authored by Claude Fable 5
parent 84ce75dc14
commit eb82263ead
15 changed files with 1425 additions and 8 deletions
+58 -2
View File
@@ -9,11 +9,18 @@ const setText = (el, text, error = false) => {
if (error) node.style.color = "#a8371a";
el.replaceChildren(node);
};
export function initAlogPanel({ state, recompute }) {
export function initAlogPanel({
state,
recompute,
getRemotePlanId,
flushCurrentPlan,
}) {
const fileInput = document.getElementById("alog-file"),
libraryBtn = document.getElementById("alog-library-refresh"),
libraryList = document.getElementById("alog-library-list"),
resultEl = document.getElementById("alog-result");
resultEl = document.getElementById("alog-result"),
actualInput = document.getElementById("actual-alog-file"),
actualResultEl = document.getElementById("actual-alog-result");
fileInput.addEventListener("change", async (e) => {
const file = e.target.files?.[0];
if (!file) return;
@@ -72,6 +79,55 @@ export function initAlogPanel({ state, recompute }) {
libraryList.replaceChildren(li);
}
});
// Finished-roast uploads: each file becomes an actual_roasts row attached to the open plan
// (which must exist server-side first — hence the flush), and the Pi agent reviews it
// asynchronously; the roast history page is where results land.
actualInput?.addEventListener("change", async (e) => {
const files = [...(e.target.files ?? [])];
e.target.value = "";
if (!files.length) return;
setText(actualResultEl, "Syncing plan…");
await flushCurrentPlan();
const planId = getRemotePlanId();
if (!planId) {
setText(
actualResultEl,
"Could not sync this plan to the server — connect and try again, or upload from the Roasts page without a plan.",
true,
);
return;
}
let uploaded = 0;
for (const file of files) {
setText(actualResultEl, `Uploading ${file.name}`);
try {
const res = await fetch("/api/roasts", {
method: "POST",
headers: { "content-type": "application/json", "x-csrf-token": csrf() },
body: JSON.stringify({
roastPlanId: planId,
filename: file.name,
content: await file.text(),
}),
});
const body = await res.json();
if (!body.ok) throw new Error(body.error ?? body.code);
uploaded++;
} catch (err) {
setText(actualResultEl, `${file.name}: ${err.message}`, true);
return;
}
}
const node = document.createElement("p");
node.append(
`Uploaded ${uploaded} roast${uploaded === 1 ? "" : "s"} to this plan. `,
);
const link = document.createElement("a");
link.href = "/roasts";
link.textContent = "See the review →";
node.append(link);
actualResultEl.replaceChildren(node);
});
function applyResult(body) {
if (!body.ok) {
setText(resultEl, body.error ?? body.code, true);