diff --git a/src/__tests__/unit/intake-rules.test.ts b/src/__tests__/unit/intake-rules.test.ts index f5eac4f..a51dac7 100644 --- a/src/__tests__/unit/intake-rules.test.ts +++ b/src/__tests__/unit/intake-rules.test.ts @@ -403,3 +403,17 @@ describe("the prompt carries no real merchant to echo", () => { expect(ORDER_ITEM_PROMPT).toMatch(/Do not construct a plausible order/); }); }); + +describe("needsHeaderCard", () => { + it("names the missing field and sends a dateless in-store screenshot to the app's Share button", async () => { + const { needsHeaderCard } = await import("@/lib/intake-blocks"); + const dateless = needsHeaderCard(["date"], "woolworths").text; + expect(dateless).toContain("no date"); + expect(dateless).toContain("Share button"); + expect(dateless).not.toContain("total"); + const delivery = needsHeaderCard(["total"], "uber_eats").text; + expect(delivery).toContain("no total"); + expect(delivery).toContain("part of the screen"); + expect(needsHeaderCard().text).toContain("total or date"); + }); +}); diff --git a/src/lib/intake-blocks.ts b/src/lib/intake-blocks.ts index 7dbb811..8bb937f 100644 --- a/src/lib/intake-blocks.ts +++ b/src/lib/intake-blocks.ts @@ -125,8 +125,22 @@ export function forwardedCard(c: CardInput & { captureId: number; enriching: boo return { text, blocks: [section(`:shopping_trolley: ${text}`)] }; } -export function needsHeaderCard(): { blocks: Block[]; text: string } { - const text = "I can see items but no order total or date — share the top of the screen too so I can match it."; +/** + * Names what is missing, because the advice differs. A delivery-app screenshot + * that lost its header needs the top of the screen; a Woolworths eReceipt + * SCREENSHOT has no date anywhere on it (seen 2026-08-22 — store, total, items, + * payment, and not one date), while the app's own Share button sends the raw + * receipt WITH the date. So for a dateless in-store receipt the right advice is + * the Share button, or pantry's own upload — not "share more of the screen". + */ +export function needsHeaderCard(missing: string[] = ["total", "date"], source?: string | null): { blocks: Block[]; text: string } { + const what = missing.length ? missing.join(" or ") : "total or date"; + const inStore = source === "coles" || source === "woolworths"; + const hint = + missing.length === 1 && missing[0] === "date" && inStore + ? "an app screenshot carries no date — use the app's Share button instead, or upload the picture to pantry directly" + : "share the part of the screen that shows it"; + const text = `I can see items but no ${esc(what)} — ${hint}, so I can match it.`; return { text, blocks: [section(`:warning: ${text}`)] }; } diff --git a/src/lib/intake-router.ts b/src/lib/intake-router.ts index 44f765d..f458442 100644 --- a/src/lib/intake-router.ts +++ b/src/lib/intake-router.ts @@ -656,7 +656,7 @@ export async function runIntake(input: IntakeInput, deps: IntakeDeps): Promise