Move After-the-Roast onto actual roasts; add Artisan-importable updated .alog download
Test and deploy / test-and-deploy (push) Successful in 56s
Test and deploy / test-and-deploy (push) Successful in 56s
One system: post-roast observations (weights, colour, DTR, cup notes, 'one change next batch', disproof) now live on the uploaded roast (actual_roasts.after, migration 011), edited on the roast detail view with weights/DTR prefilled from the .alog itself. The planner's screen section is removed (the print worksheet keeps its hand-fill copy), and the lot 'last refine' suggestion reads the note from both the new home and legacy plans, newest wins. Every roast now downloads two ways: the untouched original .alog, and an updated supplemental copy with the app's data written back as valid Artisan fields (weight, beans, roastingnotes incl. the LLM review, cuppingnotes incl. linked cupping score/flavors) — serialized as a Python literal so Artisan's ast.literal_eval re-imports it. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9b6ea9880e
commit
4a3d192c2c
+32
-1
@@ -77,7 +77,7 @@ export function pyLiteralToJson(text) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseAlogRaw(content) {
|
||||
export function parseAlogRaw(content) {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
@@ -85,6 +85,37 @@ function parseAlogRaw(content) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Serializes a JS value as a Python dict/list literal — the format Artisan itself writes
|
||||
* and re-reads with ast.literal_eval. JSON is NOT safe for re-import (true/false/null are
|
||||
* not Python literals), so the supplemental "updated" .alog must go out in this form. */
|
||||
export function jsToPyLiteral(value) {
|
||||
if (value === null || value === undefined) return "None";
|
||||
if (value === true) return "True";
|
||||
if (value === false) return "False";
|
||||
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "None";
|
||||
if (typeof value === "string") {
|
||||
// Python string literal with double quotes; escape backslashes, quotes, newlines.
|
||||
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
||||
}
|
||||
if (Array.isArray(value)) return `[${value.map(jsToPyLiteral).join(", ")}]`;
|
||||
if (typeof value === "object")
|
||||
return `{${Object.entries(value)
|
||||
.map(([k, v]) => `${jsToPyLiteral(String(k))}: ${jsToPyLiteral(v)}`)
|
||||
.join(", ")}}`;
|
||||
return "None";
|
||||
}
|
||||
|
||||
/** The supplemental .alog: the original file re-serialized with `updates` merged in (only
|
||||
* keys whose value is not undefined are touched — everything else is preserved verbatim
|
||||
* from the parse). Returns a Python-literal string Artisan can import. */
|
||||
export function buildUpdatedAlog(originalContent, updates) {
|
||||
const data = parseAlogRaw(originalContent);
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value !== undefined) data[key] = value;
|
||||
}
|
||||
return jsToPyLiteral(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} content raw file text
|
||||
* @param {string} filename for the title fallback
|
||||
|
||||
Reference in New Issue
Block a user