Six self-contained HTML documents, one per user journey, each combining mobile/desktop previews with machine-readable implementation guides: - j1-add-recipe.html (B1, B3) - j2-plan-the-week.html (C1, C2, C3) - j3-cook-tonight.html (B2, B4) - j4-adapt-on-the-fly.html (swap trigger, C2 swap) - j5-shopping-list.html (D1) - j6-household-setup.html (A1, A2, A3/D3, A4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
389 lines
32 KiB
HTML
389 lines
32 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
<title>Recipe App — J5 Shopping List Journey Spec</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|
<style>
|
|
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--green-tint:#E8F5EA;--green-light:#AEDCB0;--green:#3D8C4A;--green-dark:#2E6E39;--green-deeper:#1E4A26;--yellow-tint:#FDF6D8;--yellow-light:#F9E08A;--yellow:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--color-error:#DC4C3E;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
|
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
|
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
|
|
|
/* Header */
|
|
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
|
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
|
.doc-header p{font-size:13px;color:var(--color-text-muted);}
|
|
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
|
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
|
|
|
/* Sections */
|
|
.section{margin-bottom:64px;}
|
|
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
|
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
|
|
|
/* Journey headers */
|
|
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
|
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
|
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
|
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
|
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
|
.jh-g{background:var(--green-tint);border:1px solid var(--green-light);}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
|
.jh-y{background:var(--yellow-tint);border:1px solid var(--yellow-light);}.jh-y .jn{color:var(--yellow-dark);}.jh-y p,.jh-y .fl{color:var(--yellow-text);}
|
|
.jh-o{background:var(--orange-tint);border:1px solid #FBCDA4;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
|
.jh-b{background:var(--blue-tint);border:1px solid var(--blue-light);}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
|
|
|
/* Screen block */
|
|
.scr{margin-bottom:56px;}
|
|
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
|
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
|
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
|
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
|
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
|
|
|
/* Preview container */
|
|
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
|
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
|
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
|
|
|
/* Phone frame - 320px */
|
|
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
|
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
|
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
|
|
|
/* Desktop frame - 1040px */
|
|
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
|
|
|
/* Shared nav components */
|
|
.mtb{padding:10px 16px;background:var(--color-page);border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;}
|
|
.mtb-t{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
|
.mi{width:32px;height:32px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--color-text-muted);flex-shrink:0;}.mi.gn{background:var(--green);border-color:var(--green);color:#fff;}
|
|
.mbt{border-top:1px solid var(--color-border);background:var(--color-surface);padding:8px 16px 28px;display:flex;justify-content:space-around;}
|
|
.mt-i{display:flex;flex-direction:column;align-items:center;gap:2px;}.mt-ic{width:20px;height:20px;border-radius:4px;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;}.mt-i.a .mt-ic{background:var(--green-tint);}.mt-l{font-size:9px;font-weight:500;color:var(--color-text-muted);}.mt-i.a .mt-l{color:var(--green-dark);}
|
|
|
|
/* Desktop sidebar - 224px per nav-spec */
|
|
.dsb{width:224px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;}
|
|
.dsb-logo{padding:20px 16px 16px;border-bottom:1px solid var(--color-border);}
|
|
.dsb-lm{display:flex;align-items:center;gap:8px;margin-bottom:2px;}.dsb-ic{width:24px;height:24px;border-radius:5px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:12px;}.dsb-nm{font-family:var(--font-display);font-size:16px;font-weight:500;letter-spacing:-.02em;}.dsb-sub{font-size:10px;color:var(--color-text-muted);padding-left:32px;}
|
|
.dsb-nav{padding:12px 10px;flex:1;}.dsb-nl{font-size:8px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);padding:0 8px;margin-bottom:4px;}.dsb-ni{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--radius-md);font-size:13px;color:var(--color-text-muted);margin-bottom:2px;cursor:default;}.dsb-ni.a{background:var(--green-tint);color:var(--green-dark);font-weight:500;}.dsb-nc{font-size:13px;width:18px;text-align:center;}
|
|
.dm{flex:1;display:flex;flex-direction:column;min-width:0;}
|
|
.dtb{padding:12px 24px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;}
|
|
.dtb-t{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;}
|
|
.dtb-r{display:flex;align-items:center;gap:8px;}
|
|
.dab{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;}
|
|
.dab-b{background:var(--blue);}
|
|
|
|
/* Shared form */
|
|
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
|
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
|
.fg{margin-bottom:16px;}
|
|
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
|
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
|
|
|
/* Tags */
|
|
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
|
.badge{font-size:9px;font-weight:500;padding:2px 6px;border-radius:3px;display:inline-block;}.badge-g{background:var(--green-tint);color:var(--green-dark);}.badge-y{background:var(--yellow-tint);color:var(--yellow-text);}.badge-m{background:var(--color-subtle);color:var(--color-text-muted);}
|
|
|
|
/* Ingredient rows */
|
|
.ir{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--color-subtle);}.ir:last-child{border-bottom:none;}.ir-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);width:55px;flex-shrink:0;text-align:right;}.ir-n{font-size:13px;color:var(--color-text);flex:1;}.ir-x{font-size:14px;color:var(--color-border);cursor:pointer;width:20px;text-align:center;}
|
|
|
|
/* Checklist */
|
|
.ck{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--color-subtle);cursor:pointer;}.ck:last-child{border-bottom:none;}.ck-b{width:22px;height:22px;border-radius:4px;border:2px solid var(--color-border);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}.ck.d .ck-b{background:var(--green);border-color:var(--green);color:#fff;}.ck-c{flex:1;}.ck-n{font-size:14px;color:var(--color-text);}.ck.d .ck-n{text-decoration:line-through;color:var(--color-text-muted);}.ck-s{font-size:10px;color:var(--color-text-muted);}.ck-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);flex-shrink:0;}
|
|
|
|
/* Suggestion cards */
|
|
.sg{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:10px;}.sg-r{font-family:var(--font-display);font-size:16px;font-weight:300;color:var(--color-text-muted);width:20px;text-align:center;flex-shrink:0;}.sg-b{flex:1;}.sg-n{font-family:var(--font-display);font-size:13px;font-weight:400;color:var(--color-text);margin-bottom:2px;}.sg-i{font-size:10px;color:var(--color-text-muted);}.sg-w{font-size:9px;color:var(--green-dark);background:var(--green-tint);padding:2px 6px;border-radius:3px;display:inline-block;margin-top:3px;}.sg-p{font-size:11px;font-weight:500;color:var(--green);flex-shrink:0;}
|
|
|
|
/* Eyebrow labels */
|
|
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
|
|
|
/* Agent table (inline) */
|
|
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
|
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
|
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
|
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
|
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
|
|
|
/* LLM instruction section */
|
|
.llm{background:var(--color-text);color:#E8E8E2;border-radius:var(--radius-lg);padding:36px 44px;margin-top:80px;}
|
|
.llm h2{font-size:10px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#6B6A63;margin-bottom:6px;}
|
|
.llm > p{font-size:13px;color:#9A9990;margin-bottom:28px;line-height:1.6;max-width:640px;}
|
|
.llm h3{font-size:9px;font-weight:500;letter-spacing:.09em;text-transform:uppercase;color:#6B6A63;margin-top:32px;margin-bottom:10px;padding-top:20px;border-top:1px solid #2A2A26;}
|
|
.llm h3:first-of-type{border-top:none;padding-top:0;margin-top:0;}
|
|
.llm pre{font-family:var(--font-mono);font-size:11px;color:#444440;margin-bottom:20px;line-height:1.8;white-space:pre-wrap;}
|
|
.llm ul{list-style:none;padding:0;margin:0 0 16px;}
|
|
.llm li{font-family:var(--font-mono);font-size:11px;color:#9A9990;line-height:1.8;padding-left:14px;position:relative;}
|
|
.llm li::before{content:"-";position:absolute;left:0;color:#5A5A55;}
|
|
.llm code{font-family:var(--font-mono);font-size:11px;color:#E8E8E2;background:#2A2A26;padding:1px 5px;border-radius:3px;}
|
|
|
|
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!--
|
|
spec:agent
|
|
id: J5-shopping-list
|
|
type: journey-spec
|
|
version: 1.0
|
|
-->
|
|
<div class="doc">
|
|
|
|
<!-- ── Header ── -->
|
|
<div class="doc-header">
|
|
<div>
|
|
<h1>J5 — Shopping list</h1>
|
|
<p>Journey spec — Generate shopping list, real-time shared checklist</p>
|
|
</div>
|
|
<div class="doc-meta">
|
|
v1.0<br/>
|
|
<span class="pill">Journey</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ J5 SHOPPING ═══ -->
|
|
<div class="jh jh-b">
|
|
<div class="jn">J5</div>
|
|
<div><h2>Generate shopping list</h2><p>Merge ingredients, filter staples. Always live and shared with household.</p><div class="fl">C1 → D1 (always live) · Planner generates · All members add/remove/check off</div></div>
|
|
</div>
|
|
|
|
<!-- ═══ D1 SHOPPING LIST ═══ -->
|
|
<div class="scr" id="d1">
|
|
<div class="scr-head"><h3>Shopping list (live)</h3><span class="scr-id">D1</span></div>
|
|
<div class="scr-desc">V1 Checklist with sources. Desktop: sidebar + topbar + two-column content — checklist on the left, a "This week's recipes" reference panel on the right that shows which recipes contributed which items. The panel is a page section (surface bg), not a card.</div>
|
|
<div class="scr-var"><strong>V1 · Checklist with sources</strong> — desktop: list left, recipe reference right</div>
|
|
|
|
<div class="previews">
|
|
<div class="prev-col">
|
|
<div class="bp-lbl">Mobile · 320px</div>
|
|
<div class="phone">
|
|
<div class="pst"><b>10:24</b><span>●●● WiFi 🔋</span></div>
|
|
<div class="pb">
|
|
<div class="mtb"><div class="mtb-t">Shopping list</div><div class="mi">⚙️</div></div>
|
|
<div style="padding:8px 12px;">
|
|
<div style="background:var(--blue-tint);border:1px solid var(--blue-light);border-radius:var(--radius-lg);padding:8px 10px;display:flex;align-items:center;gap:8px;margin-bottom:10px;">
|
|
<div style="width:8px;height:8px;border-radius:50%;background:var(--blue);"></div>
|
|
<div style="font-size:11px;color:var(--blue-dark);">Shared with household · 2 members online</div>
|
|
</div>
|
|
<div class="eye" style="margin-bottom:4px;">5 items remaining</div>
|
|
<div style="padding:0 4px;">
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Cherry tomatoes</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">500g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Penne pasta</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">300g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Salmon fillet</div><div class="ck-s">For: Salmon teriyaki</div></div><div class="ck-q">400g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Jasmine rice</div><div class="ck-s">For: Teriyaki, Curry</div></div><div class="ck-q">500g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Soy sauce</div><div class="ck-s">For: Teriyaki, Stir-fry</div></div><div class="ck-q">4 tbsp</div></div>
|
|
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Coconut milk</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">400ml</div></div>
|
|
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Curry paste</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">2 tbsp</div></div>
|
|
</div>
|
|
<div style="margin-top:12px;text-align:center;font-size:11px;color:var(--blue-dark);font-weight:500;">+ Add custom item</div>
|
|
</div>
|
|
<div class="mbt"><div class="mt-i"><div class="mt-ic">📅</div><div class="mt-l">Planner</div></div><div class="mt-i"><div class="mt-ic">📖</div><div class="mt-l">Recipes</div></div><div class="mt-i a"><div class="mt-ic">🛒</div><div class="mt-l">Shopping</div></div><div class="mt-i"><div class="mt-ic">⚙️</div><div class="mt-l">Settings</div></div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="prev-col">
|
|
<div class="bp-lbl">Desktop · 1040px</div>
|
|
<div class="desk">
|
|
<div class="dsb">
|
|
<div class="dsb-logo"><div class="dsb-lm"><div class="dsb-ic">🥗</div><div class="dsb-nm">Mealplan</div></div><div class="dsb-sub">Smith household</div></div>
|
|
<div class="dsb-nav"><div><div class="dsb-nl">Plan</div><div class="dsb-ni"><span class="dsb-nc">📅</span>Planner</div><div class="dsb-ni"><span class="dsb-nc">📖</span>Recipes</div><div class="dsb-ni a"><span class="dsb-nc">🛒</span>Shopping</div></div></div>
|
|
</div>
|
|
<div class="dm">
|
|
<div class="dtb"><div class="dtb-t">Shopping list</div><div class="dtb-r"><div style="background:var(--blue-tint);border:1px solid var(--blue-light);border-radius:var(--radius-md);padding:5px 12px;font-size:11px;color:var(--blue-dark);display:flex;align-items:center;gap:6px;"><div style="width:6px;height:6px;border-radius:50%;background:var(--blue);"></div>2 members online</div></div></div>
|
|
<div style="flex:1;display:flex;overflow:hidden;">
|
|
<!-- Left: checklist -->
|
|
<div style="flex:1;padding:20px 24px;overflow-y:auto;">
|
|
<div class="eye" style="margin-bottom:10px;">5 items remaining · 2 checked off</div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Cherry tomatoes</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">500g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Penne pasta</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">300g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Salmon fillet</div><div class="ck-s">For: Salmon teriyaki</div></div><div class="ck-q">400g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Jasmine rice</div><div class="ck-s">For: Salmon teriyaki, Thai curry</div></div><div class="ck-q">500g</div></div>
|
|
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Soy sauce</div><div class="ck-s">For: Salmon teriyaki, Stir-fry</div></div><div class="ck-q">4 tbsp</div></div>
|
|
<div style="border-top:1px solid var(--color-border);margin-top:12px;padding-top:10px;">
|
|
<div class="eye" style="margin-bottom:6px;opacity:.6;">Checked off</div>
|
|
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Coconut milk</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">400ml</div></div>
|
|
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Curry paste</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">2 tbsp</div></div>
|
|
</div>
|
|
<div style="margin-top:16px;"><span style="font-size:12px;color:var(--blue-dark);font-weight:500;cursor:pointer;">+ Add custom item</span></div>
|
|
</div>
|
|
<!-- Right: recipe reference -->
|
|
<div style="width:280px;flex-shrink:0;border-left:1px solid var(--color-border);background:var(--color-surface);padding:20px;overflow-y:auto;">
|
|
<div class="eye" style="margin-bottom:12px;">This week's recipes</div>
|
|
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
|
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Tomato pasta</div>
|
|
<div style="font-size:10px;color:var(--color-text-muted);">Tue · 2 ingredients on list</div>
|
|
</div>
|
|
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
|
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Salmon teriyaki</div>
|
|
<div style="font-size:10px;color:var(--color-text-muted);">Wed · 3 ingredients on list</div>
|
|
</div>
|
|
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
|
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Thai green curry</div>
|
|
<div style="font-size:10px;color:var(--color-text-muted);">Thu · 2 ingredients on list</div>
|
|
</div>
|
|
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
|
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Chicken stir-fry</div>
|
|
<div style="font-size:10px;color:var(--color-text-muted);">Mon · shared soy sauce</div>
|
|
</div>
|
|
<div style="border-top:1px solid var(--color-border);margin-top:12px;padding-top:12px;">
|
|
<div class="eye" style="margin-bottom:6px;">Filtered staples</div>
|
|
<div style="font-size:11px;color:var(--color-text-muted);line-height:1.5;">Olive oil · Garlic · Salt · Pepper · Rice · Pasta</div>
|
|
<div style="margin-top:8px;font-size:11px;color:var(--blue);font-weight:500;cursor:pointer;">Edit staples →</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="agent">
|
|
<h4>D1 · Shopping list</h4>
|
|
<pre>/* Desktop: 224px sidebar + topbar (title + shared status badge) + 2-col content:
|
|
* Left (flex:1, page bg): remaining count + checklist rows + "checked off" section + add custom
|
|
* Right (280px, surface bg): recipe reference cards + filtered staples list + edit staples link
|
|
* Recipe reference panel: page-section with surface bg, not a floating card.
|
|
* Mobile: full-width checklist + shared banner + bottom tabs.
|
|
* Real-time sync: is_checked updates broadcast to all connected clients.
|
|
* Both roles: planner + member can view and check off. Only planner can regenerate. */</pre>
|
|
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
|
<tr class="grp"><td colspan="3">Desktop</td></tr>
|
|
<tr><td>Checklist area</td><td>flex:1, page bg, 20px 24px padding</td><td>Remaining items + divider + checked items</td></tr>
|
|
<tr><td>Recipe reference</td><td>280px, surface bg, border-left</td><td>Recipe name + day + ingredient count. Filtered staples below.</td></tr>
|
|
<tr class="grp"><td colspan="3">Shared state</td></tr>
|
|
<tr><td>Banner (mobile)</td><td>blue-tint, blue dot, radius-lg</td><td>"Shared · N online"</td></tr>
|
|
<tr><td>Badge (desktop)</td><td>blue-tint pill in topbar</td><td>Compact: dot + "N members online"</td></tr>
|
|
</tbody></table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════════
|
|
LLM INSTRUCTION SECTION
|
|
════════════════════════════════════════════════════════ -->
|
|
<!--
|
|
spec:agent:start
|
|
document: J5 Shopping List Journey
|
|
version: 1.0
|
|
-->
|
|
<div class="llm">
|
|
<h2>LLM instructions — J5 Shopping list journey</h2>
|
|
<p>Authoritative implementation reference for the shopping list journey. Covers screen D1 and references A3/D3 (staples). Use this when building or modifying the shopping list feature.</p>
|
|
|
|
<h3>1. Journey flow</h3>
|
|
<pre>/* J5 flow
|
|
* C1 (week confirmed) → D1 (shopping list, always live).
|
|
* Actor: Planner generates the list. All household members shop (view, check off, add items).
|
|
* Preconditions: J1 (recipes exist) + J2 (week is planned) for generating a list.
|
|
* J6 (household setup) for shared access.
|
|
* There is NO draft/publish workflow — the list is always live. */</pre>
|
|
|
|
<h3>2. Screen D1 — Shopping list (live shared)</h3>
|
|
<pre>/* Mobile layout:
|
|
* topbar (title + settings icon)
|
|
* + blue-tint shared banner ("Shared with household · N members online", blue dot)
|
|
* + checklist (unchecked items, then checked items below divider)
|
|
* + "+ Add custom item" link (blue-dark, centred)
|
|
* + bottom tabs (Planner | Recipes | Shopping [active] | Settings)
|
|
*
|
|
* Desktop layout:
|
|
* sidebar (224px, dsb) + topbar (dtb: title + blue-tint "N members online" badge)
|
|
* + split content area:
|
|
* Left: checklist (flex:1, page bg, 20px 24px padding)
|
|
* - eyebrow "N items remaining · N checked off"
|
|
* - unchecked rows
|
|
* - border-top divider → "Checked off" eyebrow (opacity .6) → checked rows
|
|
* - "+ Add custom item" link (12px, blue-dark, font-weight 500)
|
|
* Right: recipe reference panel (280px, surface bg, border-left, 20px padding)
|
|
* - eyebrow "This week's recipes"
|
|
* - recipe cards: page bg, border, radius-md, 10px padding
|
|
* - recipe name (13px/500) + day + ingredient count (10px muted)
|
|
* - border-top divider → "Filtered staples" eyebrow
|
|
* - staple names inline (11px muted, dot-separated)
|
|
* - "Edit staples →" link (11px, blue, font-weight 500) — links to D3
|
|
*
|
|
* Checklist row (.ck):
|
|
* checkbox (.ck-b, 22px, radius 4px, 2px border)
|
|
* + content (.ck-c): name (.ck-n, 14px) + source (.ck-s, 10px muted, "For: [recipe names]")
|
|
* + quantity (.ck-q, mono 12px muted, flex-shrink 0)
|
|
*
|
|
* Checked state (.ck.d):
|
|
* checkbox fills green with white checkmark
|
|
* name gets line-through + muted colour
|
|
* row moves below divider into "Checked off" section */</pre>
|
|
|
|
<h3>3. Shopping list generation</h3>
|
|
<ul>
|
|
<li>Only the planner role can generate or regenerate the shopping list</li>
|
|
<li>Ingredients from ALL planned meals for the week are collected</li>
|
|
<li>Shared ingredients are merged and quantities summed (e.g. "Jasmine rice: 500g" from "Salmon teriyaki" + "Thai curry")</li>
|
|
<li>Pantry staples (defined in A3/D3) are automatically filtered out</li>
|
|
<li>The "For:" source line shows ALL recipes that use that ingredient</li>
|
|
<li>Filtered staples are listed in the recipe reference panel (desktop) for transparency</li>
|
|
</ul>
|
|
|
|
<h3>4. Real-time sync</h3>
|
|
<pre>/* Real-time rules:
|
|
* - is_checked updates broadcast to ALL connected clients instantly
|
|
* - "N members online" indicator shows who is currently viewing the shopping list
|
|
* - Prevents double-buying when multiple family members shop simultaneously or at different times
|
|
* - Blue accent colour for all shared-state UI:
|
|
* Mobile: blue-tint banner with blue dot
|
|
* Desktop: blue-tint badge in topbar with blue dot
|
|
* - WebSocket or SSE for real-time — implementation choice, but must be instant */</pre>
|
|
|
|
<h3>5. Custom items</h3>
|
|
<ul>
|
|
<li>Any household member (planner or member) can add items not on the generated list</li>
|
|
<li>Use case: household supplies, snacks, items forgotten by the planner</li>
|
|
<li>Custom items appear at the bottom of the unchecked list</li>
|
|
<li>"+ Add custom item" link — blue-dark colour, centred (mobile) or left-aligned (desktop)</li>
|
|
<li>Custom items can be checked off and removed just like generated items</li>
|
|
</ul>
|
|
|
|
<h3>6. Data operations</h3>
|
|
<pre>/* Generate list:
|
|
* SELECT ri.ingredient_id, i.name, SUM(ri.quantity), ri.unit
|
|
* FROM recipe_ingredient ri
|
|
* JOIN ingredient i ON ri.ingredient_id = i.id
|
|
* JOIN week_plan_slot wps ON wps.recipe_id = ri.recipe_id
|
|
* WHERE wps.week = :current_week
|
|
* AND i.is_staple = false
|
|
* GROUP BY ri.ingredient_id, i.name, ri.unit
|
|
*
|
|
* Check off:
|
|
* UPDATE shopping_list_item
|
|
* SET is_checked = true/false
|
|
* WHERE id = :item_id
|
|
* → broadcast change to all connected clients via real-time channel
|
|
*
|
|
* Add custom:
|
|
* INSERT INTO shopping_list_item (name, quantity, is_custom, is_checked, shopping_list_id)
|
|
* VALUES (:name, :quantity, true, false, :list_id)
|
|
* → broadcast new item to all connected clients */</pre>
|
|
|
|
<h3>7. Design constraints</h3>
|
|
<ul>
|
|
<li>List is ALWAYS live — no draft/publish workflow, no approval step</li>
|
|
<li>Both planner and member can: view, check off, add custom items, remove items</li>
|
|
<li>Only planner can: generate list, regenerate list</li>
|
|
<li>"Edit staples" link navigates to D3 (same component as A3 — build once, reference from two entry points)</li>
|
|
<li>CalDAV export is future scope (E3) — do not build in v1</li>
|
|
<li>Recipe reference panel is a page section with surface bg — NOT a floating card</li>
|
|
<li>Blue accent colour is reserved for shared/collaborative state indicators</li>
|
|
<li>Checked items must visually separate from unchecked via a divider and "Checked off" label</li>
|
|
</ul>
|
|
|
|
<h3>8. Preconditions</h3>
|
|
<pre>/* Precondition chain:
|
|
* J1 (recipes exist) — cannot generate a shopping list without recipes
|
|
* J2 (week is planned) — cannot generate a shopping list without planned meals
|
|
* J6 (household setup) — required for shared access (multiple members online)
|
|
*
|
|
* If no meals are planned: show empty state on D1 with prompt to plan the week first
|
|
* If no household members: list works for solo planner, shared banner is hidden */</pre>
|
|
</div>
|
|
<!--
|
|
spec:agent:end
|
|
-->
|
|
|
|
</div>
|
|
</body>
|
|
</html> |