diff --git a/public/index.html b/public/index.html index c8a4fcf..814d26a 100644 --- a/public/index.html +++ b/public/index.html @@ -408,6 +408,10 @@ name="0.1" placeholder="e.g. Finca El Ocaso" /> + Source URL + + Coffee product page or other source for this plan. + Date After the roast${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")} Cup / brew notes${e([after.cupNotes, after.cupNotesLine2].filter(Boolean).join("\n"))}One change for the next batch${e(after.oneChange)} - Sources & interpretationSweet Maria’s: First Crack FAQ — audible event and temperature context.Scott Rao: Coffee Roasting Fundamentals — probe readings do not transfer directly; time and DTR alone do not establish quality.Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.`; + Sources & interpretation${plan.sourceUrl ? `Coffee source: ${e(plan.sourceUrl)}` : ""}Sweet Maria’s: First Crack FAQ — audible event and temperature context.Scott Rao: Coffee Roasting Fundamentals — probe readings do not transfer directly; time and DTR alone do not establish quality.Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.`; } export function renderReportHtml(plan, name = "") { return `${e(name || plan.fields?.["0.1"] || "Roast plan")}${renderReportBody(plan, name)}`; diff --git a/test/report-browser.test.js b/test/report-browser.test.js index 2736592..b889538 100644 --- a/test/report-browser.test.js +++ b/test/report-browser.test.js @@ -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,[]); +});
Sweet Maria’s: First Crack FAQ — audible event and temperature context.
Scott Rao: Coffee Roasting Fundamentals — probe readings do not transfer directly; time and DTR alone do not establish quality.
Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.
Coffee source: ${e(plan.sourceUrl)}