Keep source URLs in plans and expose an editable source field
Test and deploy / test-and-deploy (push) Successful in 1m15s
Test and deploy / test-and-deploy (push) Successful in 1m15s
This commit is contained in:
1 parent
a9358048ef
commit
1fd53091ac
5 files changed
+15
-1
No files matched your search
@@ -408,6 +408,10 @@
|
||||
name="0.1"
|
||||
placeholder="e.g. Finca El Ocaso"
|
||||
/></label>
|
||||
<label class="field"><span class="field-label">Source URL</span>
|
||||
<input class="field-input" type="url" name="sourceUrl" placeholder="https://example.com/coffee" />
|
||||
<span class="field-note">Coffee product page or other source for this plan.</span>
|
||||
</label>
|
||||
<label class="field" data-fid="0.2"
|
||||
><span class="field-label">Date</span
|
||||
><input class="field-input" type="date" name="0.2"
|
||||
|
||||
@@ -35,6 +35,7 @@ export function initPrefillPanel({ state, renderFormFromPlan, recompute }) {
|
||||
return;
|
||||
}
|
||||
snapshot = structuredClone(state.plan);
|
||||
state.plan.sourceUrl = url;
|
||||
let applied = 0;
|
||||
for (const [id, value] of Object.entries(body.fields ?? {})) {
|
||||
if (!overwriteBox.checked && (state.plan.fields[id] ?? "") !== "")
|
||||
|
||||
@@ -18,6 +18,7 @@ export function blankPlan() {
|
||||
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sourceUrl: "",
|
||||
fields,
|
||||
blendComponents: [
|
||||
{ cultivar: "", group: "", process: "", sharePct: "", fcAnchor: "" },
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ export function renderReportBody(plan, name = "") {
|
||||
.join("")}</tbody></table>
|
||||
<h2>After the roast</h2><div class="cards">${card("Roasted out", after.out ? after.out + " g" : "________ g")}${card("Weight loss", after.weightLossPct ? after.weightLossPct + "%" : "________ %")}${card("Rest before tasting", after.restedDays ? after.restedDays + " days" : "________ days")}</div>
|
||||
<div class="record-notes"><div><h2>Cup / brew notes</h2><div class="notes">${e([after.cupNotes, after.cupNotesLine2].filter(Boolean).join("\n"))}</div></div><div><h2>One change for the next batch</h2><div class="notes">${e(after.oneChange)}</div></div></div>
|
||||
<h2>Sources & interpretation</h2><p><a href="https://library.sweetmarias.com/first-crack-faq-what-is-first-crack-what-is-second-crack/">Sweet Maria’s: First Crack FAQ</a> — audible event and temperature context.</p><p><a href="https://www.scottrao.com/blog/2020/3/29/coffee-roasting-fundamentals">Scott Rao: Coffee Roasting Fundamentals</a> — probe readings do not transfer directly; time and DTR alone do not establish quality.</p><p>Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.</p><div class="footer">Roast record · Grams & Fahrenheit</div></section></div>`;
|
||||
<h2>Sources & interpretation</h2>${plan.sourceUrl ? `<p style="overflow-wrap:anywhere"><b>Coffee source:</b> ${e(plan.sourceUrl)}</p>` : ""}<p><a href="https://library.sweetmarias.com/first-crack-faq-what-is-first-crack-what-is-second-crack/">Sweet Maria’s: First Crack FAQ</a> — audible event and temperature context.</p><p><a href="https://www.scottrao.com/blog/2020/3/29/coffee-roasting-fundamentals">Scott Rao: Coffee Roasting Fundamentals</a> — probe readings do not transfer directly; time and DTR alone do not establish quality.</p><p>Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.</p><div class="footer">Roast record · Grams & Fahrenheit</div></section></div>`;
|
||||
}
|
||||
export function renderReportHtml(plan, name = "") {
|
||||
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${e(name || plan.fields?.["0.1"] || "Roast plan")}</title><style>body{margin:0}${REPORT_CSS}</style></head><body>${renderReportBody(plan, name)}</body></html>`;
|
||||
|
||||
@@ -292,3 +292,11 @@ test('browser: Clone opens an independent plan whose roast level can change', br
|
||||
const id=new URL(page.url()).searchParams.get('plan');assert.notEqual(id,original.id);await page.locator('#plan-form label').filter({has:page.locator('input[name="4.1"][value="medium"]')}).click();await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');await page.getByRole('link',{name:'Fruit original (copy)',exact:true}).waitFor();
|
||||
const rows=(await db.query('SELECT id,plan FROM roast_plans')).rows;assert.equal(rows.length,2);assert.equal(rows.find(r=>r.id===original.id).plan.fields['4.1'],'light');assert.equal(rows.find(r=>r.id===id).plan.fields['4.1'],'medium');assert.deepEqual(errors,[]);
|
||||
});
|
||||
|
||||
test('browser: source URL persists from manual entry and prefill, with undo and cloning', browserOptions, async t => {
|
||||
const {page,base,create,db,errors}=await session(t);const original=await create(warningPlan(),'Source test');await page.goto(base+'/app?plan='+original.id);
|
||||
await page.locator('#header-coffee-name').filter({hasText:'Source test'}).waitFor();const source=page.locator('#plan-form input[name="sourceUrl"]');await source.fill('https://example.com/manual-coffee');await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');await page.getByRole('link',{name:'Source test',exact:true}).click();await page.locator('#header-coffee-name').filter({hasText:'Source test'}).waitFor();await source.waitFor();assert.equal(await source.inputValue(),'https://example.com/manual-coffee');
|
||||
await page.route('**/api/prefill',route=>route.fulfill({status:200,contentType:'application/json',body:JSON.stringify({ok:true,fields:{},source:{finalUrl:'https://example.com/new-coffee'},warnings:[]})}));
|
||||
await page.locator('#nav-prefill').click();await page.locator('#prefill-url').fill('https://example.com/new-coffee');await page.locator('#prefill-go').click();await page.locator('#prefill-result').filter({hasText:'Applied 0 fields'}).waitFor();assert.equal(await source.inputValue(),'https://example.com/new-coffee');await page.locator('#prefill-undo').click();assert.equal(await source.inputValue(),'https://example.com/manual-coffee');await page.locator('#prefill-go').click();await page.locator('#prefill-result').filter({hasText:'Applied 0 fields'}).waitFor();await page.keyboard.press('Escape');
|
||||
await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');assert.equal((await db.query('SELECT plan FROM roast_plans WHERE id=$1',[original.id])).rows[0].plan.sourceUrl,'https://example.com/new-coffee');await page.getByRole('button',{name:'Clone Source test',exact:true}).click();await page.waitForURL('**/app?plan=*');await page.locator('#header-coffee-name').filter({hasText:'Source test (copy)'}).waitFor();await source.waitFor();assert.equal(await source.inputValue(),'https://example.com/new-coffee');assert.deepEqual(errors,[]);
|
||||
});
|
||||
Reference in new issue
Block a user