Files
mealprep/specs/userjourneys.html
Marcel Raddatz fa4a4c9ef7 docs(specs): add J9 variety score config user journey and variety page rework spec
- Adds J9 (Configure variety score) to userjourneys.html — new journey for
  tuning the algorithm per household dietary context (e.g. disabling protein
  penalties for vegetarian households); introduces screen E4 (Variety settings)
- Adds specs/frontend/variety-page-rework.html with 3 design variations for
  the /planner/variety page rework: recipe-name pills, action rows (recommended),
  and week-grid with side panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 15:51:26 +02:00

1606 lines
86 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Recipe App — User Journeys v1.0</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"/>
<!--
spec:agent
document: User Journeys
version: 1.0
project: recipe-app
status: locked
journeys: J1 Add a recipe | J2 Plan the week | J3 Cook tonight | J4 Adapt on the fly | J5 Generate shopping list | J6 Household setup | J7 Manage members | J8 Edit staples | J9 Configure variety score
last-updated: 2026-04
-->
<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;
--color-error: #DC4C3E;
--purple-tint: #EEEDFE;
--purple: #534AB7;
--purple-dark: #3C3489;
--blue-tint: #E6F1FB;
--blue: #185FA5;
--blue-dark: #0C447C;
--font-display: 'Fraunces', Georgia, serif;
--font-sans: 'DM Sans', system-ui, sans-serif;
--font-mono: 'DM Mono', monospace;
--space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px;
--space-5:20px; --space-6:24px; --space-8:32px;
--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);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-sans);
background: var(--color-page);
color: var(--color-text);
font-size: 14px;
line-height: 1.6;
}
/* ── Doc layout ── */
.doc { max-width: 960px; margin: 0 auto; padding: 48px 40px 96px; }
.doc-header {
display: flex; justify-content: space-between; align-items: flex-end;
padding-bottom: 28px; border-bottom: 1px solid var(--color-border);
margin-bottom: 52px;
}
.doc-header h1 {
font-family: var(--font-display); font-size: 26px; font-weight: 500;
letter-spacing: -0.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: 0.05em;
}
.pill-locked { background: var(--green-tint); color: var(--green-dark); }
/* ── Overview ── */
.overview-section { margin-bottom: 56px; }
.overview-grid {
display: grid; grid-template-columns: repeat(3, 1fr);
gap: var(--space-3); margin-top: 24px;
}
.overview-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-4) var(--space-4) var(--space-5);
display: flex; flex-direction: column; gap: var(--space-2);
cursor: default;
}
.overview-card-top {
display: flex; align-items: center; gap: var(--space-2);
}
.overview-num {
font-family: var(--font-display); font-size: 24px; font-weight: 300;
letter-spacing: -0.02em; line-height: 1; flex-shrink: 0;
}
.overview-card-title { font-size: 14px; font-weight: 500; color: var(--color-text); }
.overview-card-desc { font-size: 12px; color: var(--color-text-muted); line-height: 1.55; }
.overview-card-actor {
font-size: 10px; font-weight: 500; letter-spacing: 0.06em;
text-transform: uppercase; margin-top: var(--space-1);
}
/* Journey colour theming */
.j1 { --j-tint: var(--green-tint); --j-border: var(--green-light); --j-accent: var(--green); --j-dark: var(--green-dark); --j-num: #3D8C4A; }
.j2 { --j-tint: var(--yellow-tint); --j-border: var(--yellow-light); --j-accent: var(--yellow); --j-dark: var(--yellow-text); --j-num: #C49610; }
.j3 { --j-tint: var(--green-tint); --j-border: var(--green-light); --j-accent: var(--green-dark); --j-dark: var(--green-deeper);--j-num: #2E6E39; }
.j4 { --j-tint: var(--purple-tint); --j-border: #CECBF6; --j-accent: var(--purple); --j-dark: var(--purple-dark); --j-num: #534AB7; }
.j5 { --j-tint: var(--blue-tint); --j-border: #B5D4F4; --j-accent: var(--blue); --j-dark: var(--blue-dark); --j-num: #185FA5; }
.j6 { --j-tint: var(--purple-tint); --j-border: #CECBF6; --j-accent: var(--purple); --j-dark: var(--purple-dark); --j-num: #534AB7; }
.j7 { --j-tint: var(--blue-tint); --j-border: #B5D4F4; --j-accent: var(--blue); --j-dark: var(--blue-dark); --j-num: #185FA5; }
.j8 { --j-tint: var(--yellow-tint); --j-border: var(--yellow-light); --j-accent: var(--yellow); --j-dark: var(--yellow-text); --j-num: #C49610; }
.j9 { --j-tint: var(--purple-tint); --j-border: #CECBF6; --j-accent: var(--purple); --j-dark: var(--purple-dark); --j-num: #534AB7; }
.overview-card { border-left: 3px solid var(--j-border, var(--color-border)); }
.overview-num { color: var(--j-num, var(--color-text-muted)); }
.overview-card-actor { color: var(--j-dark, var(--color-text-muted)); }
/* ── Journey sections ── */
.journey { margin-bottom: 72px; }
.journey-header {
display: flex; align-items: flex-start; gap: var(--space-5);
padding: var(--space-5) var(--space-6);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
background: var(--j-tint);
border: 1px solid var(--j-border);
border-bottom: none;
}
.journey-num-big {
font-family: var(--font-display); font-size: 48px; font-weight: 300;
letter-spacing: -0.03em; line-height: 1; color: var(--j-accent);
flex-shrink: 0; opacity: 0.6;
}
.journey-header-body { flex: 1; }
.journey-id {
font-size: 10px; font-weight: 500; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--j-dark);
margin-bottom: var(--space-1);
}
.journey-title {
font-family: var(--font-display); font-size: 22px; font-weight: 500;
letter-spacing: -0.02em; color: var(--color-text); margin-bottom: var(--space-2);
}
.journey-desc { font-size: 13px; color: var(--color-text-muted); line-height: 1.6; max-width: 560px; }
.journey-meta-row {
display: flex; gap: var(--space-5); margin-top: var(--space-3); flex-wrap: wrap;
}
.journey-meta-item { display: flex; flex-direction: column; gap: 2px; }
.journey-meta-label {
font-size: 9px; font-weight: 500; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--j-dark); opacity: 0.7;
}
.journey-meta-value { font-size: 12px; color: var(--color-text); font-weight: 500; }
/* Journey body */
.journey-body {
border: 1px solid var(--j-border);
border-top: none;
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
overflow: hidden;
}
/* Steps flow */
.steps-area { padding: var(--space-6); background: var(--color-page); }
.steps-flow {
display: flex; align-items: flex-start;
gap: 0; flex-wrap: wrap;
}
.step {
display: flex; flex-direction: column; align-items: center;
gap: var(--space-2); flex: 1; min-width: 100px;
}
.step-connector {
display: flex; align-items: center; padding-top: 22px; flex-shrink: 0;
}
.step-connector-line {
width: 24px; height: 2px;
background: var(--j-border); flex-shrink: 0;
}
.step-connector-arrow {
width: 0; height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 7px solid var(--j-border);
flex-shrink: 0; margin-left: -1px;
}
.step-node {
width: 44px; height: 44px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
font-size: 11px; font-weight: 500;
}
.step-node-start { background: var(--j-accent); color: #fff; }
.step-node-action { background: var(--color-surface); border: 2px solid var(--j-border); color: var(--j-dark); }
.step-node-end { background: var(--green); color: #fff; }
.step-node-branch { background: var(--yellow-tint); border: 2px solid var(--yellow-light); color: var(--yellow-text); }
.step-label {
font-size: 11px; font-weight: 500; color: var(--color-text);
text-align: center; line-height: 1.35;
}
.step-sublabel {
font-size: 10px; color: var(--color-text-muted);
text-align: center; line-height: 1.3;
}
/* Step detail table */
.step-table-wrap {
background: var(--color-surface);
border-top: 1px solid var(--j-border);
}
.step-table {
width: 100%; border-collapse: collapse; font-size: 13px;
}
.step-table thead tr {
background: var(--j-tint);
border-bottom: 1px solid var(--j-border);
}
.step-table th {
text-align: left; padding: 10px 16px;
font-size: 10px; font-weight: 500; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--j-dark);
font-family: var(--font-sans);
}
.step-table td {
padding: 12px 16px; border-bottom: 1px solid var(--color-subtle);
vertical-align: top;
}
.step-table tr:last-child td { border-bottom: none; }
.step-table td:first-child {
font-weight: 500; white-space: nowrap;
color: var(--color-text); min-width: 160px;
}
.step-table td:nth-child(2) { color: var(--color-text-muted); font-size: 12px; }
.step-table td:nth-child(3) { font-family: var(--font-mono); font-size: 11px; color: var(--color-text-muted); }
.step-table td:nth-child(4) { font-size: 12px; color: var(--color-text-muted); }
.page-ref {
display: inline-block; font-family: var(--font-mono); font-size: 10px;
font-weight: 500; padding: 2px 6px; border-radius: 3px;
background: var(--j-tint); color: var(--j-dark);
border: 1px solid var(--j-border); margin: 1px;
}
.role-planner { background: var(--green-tint); color: var(--green-dark); font-size:10px; font-weight:500; padding:2px 7px; border-radius:3px; white-space:nowrap; }
.role-member { background: var(--blue-tint); color: var(--blue-dark); font-size:10px; font-weight:500; padding:2px 7px; border-radius:3px; white-space:nowrap; }
.role-both { background: var(--yellow-tint); color: var(--yellow-text); font-size:10px; font-weight:500; padding:2px 7px; border-radius:3px; white-space:nowrap; }
/* Notes / callout strip */
.journey-notes {
padding: var(--space-4) var(--space-6);
background: var(--j-tint);
border-top: 1px solid var(--j-border);
display: flex; flex-direction: column; gap: var(--space-2);
}
.journey-notes-label {
font-size: 10px; font-weight: 500; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--j-dark);
}
.journey-notes ul {
list-style: none; display: flex; flex-direction: column; gap: 4px;
}
.journey-notes li {
font-size: 12px; color: var(--color-text-muted); line-height: 1.5;
display: flex; align-items: flex-start; gap: 8px;
}
.journey-notes li::before {
content: '→'; color: var(--j-accent); font-weight: 500; flex-shrink: 0; margin-top: 0px;
}
/* Section title */
.section-title {
font-size: 10px; font-weight: 500; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--color-text-muted);
padding-bottom: 10px; border-bottom: 1px solid var(--color-border);
margin-bottom: 28px;
}
/* Prose */
.prose { font-size: 14px; line-height: 1.75; color: var(--color-text); max-width: 680px; }
/* ── Agent table ── */
.agent-section {
background: var(--color-text); color: #E8E8E2;
padding: 40px 48px; margin-top: 64px;
}
.agent-section h2 {
font-size: 10px; font-weight: 500; letter-spacing: 0.1em;
text-transform: uppercase; color: #6B6A63; margin-bottom: 4px;
}
.agent-section > p {
font-size: 13px; color: #9A9990; margin-bottom: 28px; line-height: 1.6; max-width: 640px;
}
.spec-comment {
font-family: var(--font-mono); font-size: 11px; color: #3A3A36;
margin-bottom: 32px; line-height: 1.9; white-space: pre-wrap;
}
.agent-table {
width: 100%; border-collapse: collapse; font-family: var(--font-mono);
font-size: 11px; margin-bottom: 40px;
}
.agent-table thead tr { border-bottom: 1px solid #2A2A26; }
.agent-table th {
text-align: left; padding: 8px 14px; font-size: 9px; font-weight: 500;
letter-spacing: 0.09em; text-transform: uppercase; color: #5A5A55;
font-family: var(--font-sans);
}
.agent-table td {
padding: 9px 14px; border-bottom: 1px solid #1E1E1A;
vertical-align: top; line-height: 1.5;
}
.agent-table tr:last-child td { border-bottom: none; }
.agent-table td:first-child { color: #7A7A72; white-space: nowrap; }
.agent-table td:nth-child(2) { color: #E8E8E2; font-weight: 500; }
.agent-table td:nth-child(3) { color: #5A5A55; }
.agent-table td:nth-child(4) { color: #5A5A55; }
.agent-table td:nth-child(5) { color: #3A3A36; }
.group-row td {
padding-top: 20px; font-family: var(--font-sans); font-size: 9px;
font-weight: 500; letter-spacing: 0.09em; text-transform: uppercase; color: #3A3A36;
}
</style>
</head>
<body>
<div class="doc">
<!-- ── Header ── -->
<div class="doc-header">
<div>
<h1>User journeys</h1>
<p>Recipe app · Eight core journeys · Planner &amp; household member roles</p>
</div>
<div class="doc-meta">
<span class="pill pill-locked">locked</span><br>
Version: 1.2<br>
Last updated: 2026-04<br>
Journeys: 9<br>
Roles: planner · household member
</div>
</div>
<!-- ── Overview ── -->
<div class="overview-section">
<div class="section-title">All journeys at a glance</div>
<p class="prose">The app serves two user roles. The <strong style="font-weight:500">planner</strong> (you) has full access — adding recipes, building the weekly plan, generating the shopping list, and managing the household. The <strong style="font-weight:500">household member</strong> (your partner, other household members) has read-only access to the plan and collaborative access to the shopping list. Journeys J7 and J8 cover post-onboarding household management: adding or removing members and keeping the pantry staples configuration current. J9 covers tuning the variety score algorithm to match the household's dietary context (e.g. disabling meat-centric protein penalties for a vegetarian household).</p>
<div class="overview-grid">
<div class="overview-card j1">
<div class="overview-card-top">
<div class="overview-num">J1</div>
<div class="overview-card-title">Add a recipe</div>
</div>
<div class="overview-card-desc">Save a recipe from memory, a cookbook, or improvisation into the app library with ingredients and tags.</div>
<div class="overview-card-actor">Planner only</div>
</div>
<div class="overview-card j2">
<div class="overview-card-top">
<div class="overview-num">J2</div>
<div class="overview-card-title">Plan the week</div>
</div>
<div class="overview-card-desc">Assign meals to days, get suggestions that avoid ingredient repetition, review the variety score, and confirm the plan.</div>
<div class="overview-card-actor">Planner only</div>
</div>
<div class="overview-card j3">
<div class="overview-card-top">
<div class="overview-num">J3</div>
<div class="overview-card-title">Cook tonight</div>
</div>
<div class="overview-card-desc">Check what's for dinner, open the recipe, cook step-by-step, and mark the meal as cooked to update variety tracking.</div>
<div class="overview-card-actor">Planner only</div>
</div>
<div class="overview-card j4">
<div class="overview-card-top">
<div class="overview-num">J4</div>
<div class="overview-card-title">Adapt on the fly</div>
</div>
<div class="overview-card-desc">When plans change mid-week, quickly swap a meal for a low-effort alternative and keep the week on track.</div>
<div class="overview-card-actor">Planner only</div>
</div>
<div class="overview-card j5">
<div class="overview-card-top">
<div class="overview-num">J5</div>
<div class="overview-card-title">Generate shopping list</div>
</div>
<div class="overview-card-desc">Turn the confirmed week plan into a live shared shopping list — filtered for pantry staples. Anyone can add or remove items anytime.</div>
<div class="overview-card-actor">Planner generates · Household shops</div>
</div>
<div class="overview-card j6">
<div class="overview-card-top">
<div class="overview-num">J6</div>
<div class="overview-card-title">Household setup</div>
</div>
<div class="overview-card-desc">One-time setup: create the planner account, configure pantry staples, invite household members, and confirm access levels.</div>
<div class="overview-card-actor">Planner creates · Members join</div>
</div>
<div class="overview-card j7">
<div class="overview-card-top">
<div class="overview-num">J7</div>
<div class="overview-card-title">Manage members</div>
</div>
<div class="overview-card-desc">Invite new household members post-setup or revoke access. Keep the household roster current as people join or leave.</div>
<div class="overview-card-actor">Planner manages · Members join</div>
</div>
<div class="overview-card j8">
<div class="overview-card-top">
<div class="overview-num">J8</div>
<div class="overview-card-title">Edit staples</div>
</div>
<div class="overview-card-desc">Update which pantry ingredients are always on hand. Changes take effect on the next generated shopping list.</div>
<div class="overview-card-actor">Planner only</div>
</div>
<div class="overview-card j9">
<div class="overview-card-top">
<div class="overview-num">J9</div>
<div class="overview-card-title">Configure variety score</div>
</div>
<div class="overview-card-desc">Tune the variety algorithm to the household's dietary context — disable protein-type penalties for vegetarian households or adjust how heavily ingredient overlaps are weighted.</div>
<div class="overview-card-actor">Planner only</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J1 — ADD A RECIPE
════════════════════════════════════════ -->
<div class="journey j1">
<div class="journey-header">
<div class="journey-num-big">J1</div>
<div class="journey-header-body">
<div class="journey-id">Journey 1</div>
<div class="journey-title">Add a recipe</div>
<div class="journey-desc">The planner saves a recipe into the app — from memory, a physical cookbook, or improvisation. Tags are applied so the planner and suggestion engine can use the recipe intelligently in future planning.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Occasional — as new recipes are discovered</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Recipe library (B1) or + button in nav</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">B1</div>
<div class="step-label">Open library</div>
<div class="step-sublabel">Recipe list view</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">B3</div>
<div class="step-label">Add recipe</div>
<div class="step-sublabel">Name + ingredients</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">B3</div>
<div class="step-label">Add steps</div>
<div class="step-sublabel">Cooking instructions</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">B3</div>
<div class="step-label">Tag it</div>
<div class="step-sublabel">Effort, protein, child-friendly</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Saved</div>
<div class="step-sublabel">In library</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr>
<th>Step</th>
<th>What happens</th>
<th>Screen</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Open library</td>
<td>Planner taps Recipes in nav or the + button anywhere in the app.</td>
<td><span class="page-ref">B1</span></td>
<td>Entry from nav or from the planner (C1) when a day has no meal assigned.</td>
</tr>
<tr>
<td>Add recipe form</td>
<td>Planner enters recipe name, serves count, and adds ingredients one by one with name and quantity.</td>
<td><span class="page-ref">B3</span></td>
<td>B3 is a single form used for both add and edit states. Form is prefilled when editing.</td>
</tr>
<tr>
<td>Add cooking steps</td>
<td>Planner adds numbered steps in free text. Steps are used in cook mode (B4).</td>
<td><span class="page-ref">B3</span></td>
<td>Steps are optional at save time — a recipe without steps can still be planned and cooked from ingredient list only.</td>
</tr>
<tr>
<td>Tag the recipe</td>
<td>Planner selects tags: effort level (easy / medium / hard), child-friendly (yes/no), primary protein or category (chicken, fish, vegetarian, pasta, etc.).</td>
<td><span class="page-ref">B3</span></td>
<td>Tags are used by the suggestion engine in J2 to avoid repetition. Minimum: effort + one category tag required to save.</td>
</tr>
<tr>
<td>Save</td>
<td>Recipe is saved and appears in the library (B1). Planner is returned to B1 or to wherever they came from.</td>
<td><span class="page-ref">B1</span></td>
<td>If entered from a day slot in the planner, the recipe is optionally offered for that day immediately after saving.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>B3 and B4 (add and edit recipe) share one form component — the only difference is the initial state (empty vs prefilled).</li>
<li>Tags drive intelligent suggestions. The more recipes are tagged, the better the variety algorithm performs.</li>
<li>Recipes from memory or physical cookbooks are the primary source — the app makes no assumptions about digital import.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J2 — PLAN THE WEEK
════════════════════════════════════════ -->
<div class="journey j2">
<div class="journey-header">
<div class="journey-num-big">J2</div>
<div class="journey-header-body">
<div class="journey-id">Journey 2</div>
<div class="journey-title">Plan the week</div>
<div class="journey-desc">The planner builds the dinner plan for the coming week. The app suggests recipes that avoid repeating ingredients from recent meals, balances effort across days, and scores variety so the planner can confirm or adjust before the week begins.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Weekly — typically at the weekend</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Planner (C1) — default home screen</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">C1</div>
<div class="step-label">Open planner</div>
<div class="step-sublabel">7-day grid view</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C2</div>
<div class="step-label">Get suggestions</div>
<div class="step-sublabel">Filtered by variety</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C1</div>
<div class="step-label">Fill day slots</div>
<div class="step-sublabel">Pick or drag meals</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C3</div>
<div class="step-label">Review variety</div>
<div class="step-sublabel">Ingredient overlap score</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-branch">?</div>
<div class="step-label">Swap needed?</div>
<div class="step-sublabel">Optional adjustment</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Week confirmed</div>
<div class="step-sublabel">Plan locked</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Open planner</td>
<td>Planner opens the app. The weekly planner is the default home screen showing the current week with any already-planned meals.</td>
<td><span class="page-ref">C1</span></td>
<td>Today's slot is always highlighted in yellow. Empty slots show a dashed + prompt.</td>
</tr>
<tr>
<td>Get suggestions</td>
<td>Planner taps an empty day slot or the suggestions button. The app shows recipes filtered to avoid ingredients used in the past 3 days and the same protein as adjacent days.</td>
<td><span class="page-ref">C2</span></td>
<td>Suggestions also balance effort — if the previous two days were hard meals, easy meals are surfaced first.</td>
</tr>
<tr>
<td>Fill day slots</td>
<td>Planner picks a suggestion or manually selects a recipe from the library for each day.</td>
<td><span class="page-ref">C1</span></td>
<td>Saturday is optional — some weeks have no planned meal on Saturday. Empty slots are valid.</td>
</tr>
<tr>
<td>Review variety score</td>
<td>The variety score (010) updates live as meals are added. It reflects ingredient overlap across the week. Warnings surface for specific repeated ingredients.</td>
<td><span class="page-ref">C3</span> <span class="page-ref">C1</span></td>
<td>Score is visible at all times on C1 (variety banner on mobile/tablet, sidebar widget on desktop). C3 shows the full breakdown.</td>
</tr>
<tr>
<td>Swap a meal (optional)</td>
<td>If the variety score is low or a specific warning appears, the planner swaps one meal for an alternative suggestion.</td>
<td><span class="page-ref">C2</span></td>
<td>Swap uses the same suggestion engine as the initial fill. Swapping updates the score immediately.</td>
</tr>
<tr>
<td>Confirm the week</td>
<td>Planner confirms the plan. The week is locked and visible (read-only) to household members. This also triggers the option to generate a shopping list (J5).</td>
<td><span class="page-ref">C1</span></td>
<td>Confirming does not prevent future edits — the planner can still swap meals mid-week via J4.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>The variety score is the central metric of the app — it must be visible throughout this entire journey without extra navigation.</li>
<li>The suggestion filter considers: ingredients used in the last 3 days, same-protein consecutive days, and effort balance. Recipe tags (from J1) power all three filters.</li>
<li>Household members can see the confirmed plan as read-only immediately after confirmation.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J3 — COOK TONIGHT
════════════════════════════════════════ -->
<div class="journey j3">
<div class="journey-header">
<div class="journey-num-big">J3</div>
<div class="journey-header-body">
<div class="journey-id">Journey 3</div>
<div class="journey-title">Cook tonight</div>
<div class="journey-desc">The planner checks what's for dinner, opens the recipe, and cooks using the step-by-step cook mode. Marking the meal as cooked logs it to the variety history, which feeds back into next week's suggestions.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Daily — used at the stove</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Planner (C1) — today highlight</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Context</div>
<div class="journey-meta-value">Mobile, hands busy, kitchen environment</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">C1</div>
<div class="step-label">Check today</div>
<div class="step-sublabel">Tonight's meal highlighted</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">B2</div>
<div class="step-label">Open recipe</div>
<div class="step-sublabel">Ingredients + steps</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">B4</div>
<div class="step-label">Cook mode</div>
<div class="step-sublabel">Step-by-step, big text</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Mark cooked</div>
<div class="step-sublabel">Logged to history</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Check today</td>
<td>Planner opens the app. Today's slot is highlighted in yellow on the planner. The meal name is visible without any tap.</td>
<td><span class="page-ref">C1</span></td>
<td>The today highlight is the most important element on C1. It must be visible on first render with zero interaction.</td>
</tr>
<tr>
<td>Open recipe</td>
<td>Planner taps the today slot. The recipe detail screen shows the full ingredient list and step count.</td>
<td><span class="page-ref">B2</span></td>
<td>B2 shows ingredients scaled to the saved serving count. No serving adjustment is required in this journey.</td>
</tr>
<tr>
<td>Enter cook mode</td>
<td>Planner taps "Cook now". The screen switches to full-screen cook mode with one step visible at a time in large, readable text. Screen stays awake.</td>
<td><span class="page-ref">B4</span></td>
<td>B4 is designed for kitchen use: 16px body text, 1.75 line height, single step per screen, tap-anywhere to advance. Screen wake lock is requested.</td>
</tr>
<tr>
<td>Mark as cooked</td>
<td>On the final step, the planner taps "Done". The meal is logged to cooking history with today's date.</td>
<td><span class="page-ref">B4</span><span class="page-ref">C1</span></td>
<td>The cooking log is the data source for the variety algorithm. Meals cooked more recently are weighted more heavily in the repetition filter.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>Cook mode (B4) is a high-stakes screen — the user is standing at a stove with wet hands. It must be operable with one tap and no fine motor precision required.</li>
<li>Screen wake lock prevents the phone from sleeping during cooking. This should be requested on entering B4 and released on exit.</li>
<li>The "mark as cooked" action is the feedback loop that makes J2 smarter over time. It should feel lightweight, not like admin work.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J4 — ADAPT ON THE FLY
════════════════════════════════════════ -->
<div class="journey j4">
<div class="journey-header">
<div class="journey-num-big">J4</div>
<div class="journey-header-body">
<div class="journey-id">Journey 4</div>
<div class="journey-title">Adapt on the fly</div>
<div class="journey-desc">Plans change. A busy evening, a missing ingredient, or a child who won't eat what was planned triggers a mid-week swap. The app suggests low-effort alternatives and updates the plan instantly.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">12× per week typically</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Planner (C1) — any day slot</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Urgency</div>
<div class="journey-meta-value">High — decision needed quickly</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">C1</div>
<div class="step-label">Plan changes</div>
<div class="step-sublabel">Tap day slot</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C2</div>
<div class="step-label">Quick swap</div>
<div class="step-sublabel">Low-effort alternatives</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C1</div>
<div class="step-label">Pick alternative</div>
<div class="step-sublabel">Confirm new meal</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Plan updated</div>
<div class="step-sublabel">History logged</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Trigger swap</td>
<td>Planner taps a meal slot and selects "Swap meal". The original meal is not yet removed — the swap is a preview.</td>
<td><span class="page-ref">C1</span></td>
<td>The "Swap" button is visible inline in the upcoming list on mobile and in the detail panel on desktop — one tap away at all times.</td>
</tr>
<tr>
<td>View quick alternatives</td>
<td>The suggestion screen shows 35 low-effort alternatives that avoid the ingredients already used that week. Sorted: easiest first.</td>
<td><span class="page-ref">C2</span></td>
<td>When the swap is triggered mid-week (today or a past day), effort filter defaults to Easy. The variety filter still applies.</td>
</tr>
<tr>
<td>Confirm new meal</td>
<td>Planner picks an alternative. The day slot updates immediately. Variety score recalculates.</td>
<td><span class="page-ref">C1</span></td>
<td>If no suitable suggestion exists, the planner can manually select any recipe from the library.</td>
</tr>
<tr>
<td>History logged</td>
<td>The swap is logged — both the original meal (not cooked) and the replacement. This is used to ensure the original recipe isn't over-suppressed in future suggestions.</td>
<td></td>
<td>The original uncooked meal remains in the library and can be planned for a future week.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>Speed is critical in this journey — the user is making a decision under time pressure. The swap flow should never exceed 3 taps from the moment "Swap" is tapped to the plan being updated.</li>
<li>Alternatives are sorted by effort (easiest first) because mid-week swaps typically happen because the original plan was too ambitious for that day.</li>
<li>The variety score updates immediately after confirmation, giving the planner instant feedback on whether the swap improved or worsened the week's balance.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J5 — GENERATE SHOPPING LIST
════════════════════════════════════════ -->
<div class="journey j5">
<div class="journey-header">
<div class="journey-num-big">J5</div>
<div class="journey-header-body">
<div class="journey-id">Journey 5</div>
<div class="journey-title">Generate shopping list</div>
<div class="journey-desc">Once the week is confirmed, a shopping list is generated from all meal ingredients. Pantry staples are automatically filtered out. The list is immediately live and shared with all household members. Anyone can add or remove items at any time.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actors</div>
<div class="journey-meta-value">Planner generates · Household shops</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Weekly — after plan confirmation</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Shopping tab (D1) or prompt after J2</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">C1</div>
<div class="step-label">Week confirmed</div>
<div class="step-sublabel">Plan locked in</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">D1</div>
<div class="step-label">Collect ingredients</div>
<div class="step-sublabel">All 7 meals merged</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">D1</div>
<div class="step-label">Smart filter</div>
<div class="step-sublabel">Remove staples</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">List is live</div>
<div class="step-sublabel">Shared with household immediately</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Role</th></tr>
</thead>
<tbody>
<tr>
<td>Collect ingredients</td>
<td>All ingredients from all planned meals are gathered. Shared ingredients across meals are merged and quantities summed (e.g. 3 + 2 carrots = 5 carrots).</td>
<td><span class="page-ref">D1</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Smart filter</td>
<td>Pantry staples (olive oil, salt, pasta, rice, etc.) defined by the planner in settings are automatically removed from the list.</td>
<td><span class="page-ref">D3</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>List goes live</td>
<td>The generated list is immediately live and visible to all household members. No approval step needed.</td>
<td><span class="page-ref">D1</span></td>
<td><span class="role-both">Both roles</span></td>
</tr>
<tr>
<td>Shop collaboratively</td>
<td>Any household member can check off items while shopping. Checked items stay checked for all members — no double buying.</td>
<td><span class="page-ref">D1</span></td>
<td><span class="role-member">Household member</span></td>
</tr>
<tr>
<td>Add items</td>
<td>Household members can add items not on the generated list (e.g. household supplies, snacks). These appear at the bottom of the list.</td>
<td><span class="page-ref">D1</span></td>
<td><span class="role-both">Both roles</span></td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>Only the planner can generate the list. All household members can view, check off, add, and remove items. The list is always live — no approval or publishing step.</li>
<li>Checked items stay checked for everyone — this prevents double-buying when multiple family members are shopping simultaneously or at different times.</li>
<li>The smart filter (D3) is configured once during onboarding (J6, step A3) and can be updated at any time from settings. The planner's staples list grows over time.</li>
<li>CalDAV export is planned as a future enhancement (E3 Integrations) — not part of v1. The in-app shared list is the primary collaboration mechanism.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J6 — HOUSEHOLD SETUP
════════════════════════════════════════ -->
<div class="journey j6">
<div class="journey-header">
<div class="journey-num-big">J6</div>
<div class="journey-header-body">
<div class="journey-id">Journey 6</div>
<div class="journey-title">Household setup</div>
<div class="journey-desc">A one-time journey completed when the app is first used. The planner creates an account, names the household, defines pantry staples, and invites household members. Members accept the invite and gain access to the meal plan and shopping list.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actors</div>
<div class="journey-meta-value">Planner creates · Members join</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Once — on first use</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">A1 Welcome screen (new user)</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">A1</div>
<div class="step-label">Welcome</div>
<div class="step-sublabel">Sign up</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">A2</div>
<div class="step-label">Household setup</div>
<div class="step-sublabel">Name the household</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">A3</div>
<div class="step-label">Staples setup</div>
<div class="step-sublabel">Define pantry defaults</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">A2</div>
<div class="step-label">Invite members</div>
<div class="step-sublabel">Send link or code</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">A4</div>
<div class="step-label">Member joins</div>
<div class="step-sublabel">Accepts invite</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Access granted</div>
<div class="step-sublabel">Household ready</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Role</th></tr>
</thead>
<tbody>
<tr>
<td>Create account</td>
<td>Planner signs up with email and password. The account is created with the Planner role automatically assigned.</td>
<td><span class="page-ref">A1</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Name the household</td>
<td>Planner gives the household a name (e.g. "Smith family"). This name appears in the sidebar on desktop and in the app header.</td>
<td><span class="page-ref">A2</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Define pantry staples</td>
<td>Planner selects which ingredients their household always has on hand. These are excluded from generated shopping lists. A default list of common staples is pre-selected and can be adjusted.</td>
<td><span class="page-ref">A3</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Invite household members</td>
<td>Planner sends an invite link or code to household members (e.g. spouse). One invite per member. The invite grants Household Member role on acceptance.</td>
<td><span class="page-ref">A2</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Member accepts invite</td>
<td>The invited person opens the link, creates an account, and is automatically joined to the household with the Member role.</td>
<td><span class="page-ref">A4</span></td>
<td><span class="role-member">Household member</span></td>
</tr>
<tr>
<td>Access granted</td>
<td>The member can now see the meal plan (read-only) and the shopping list (view, check off, add items). The planner retains full access.</td>
<td><span class="page-ref">C1</span> <span class="page-ref">D1</span></td>
<td><span class="role-both">Both roles</span></td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>A3 (staples setup) and D3 (staples manager in settings) are the same component — set up once in onboarding and editable at any time from settings.</li>
<li>The invite mechanism uses a link or short code — no email-based invite system is required in v1. The planner shares the link via any messaging app.</li>
<li>Role summary: Planner has full access to all 18 screens. Household member has read-only access to C1 (weekly planner) and collaborative access to D1 (shopping list). No other screens are accessible to household members in v1.</li>
<li>Future: child accounts with view-only access to the meal plan are planned but not scoped for v1.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J7 — MANAGE HOUSEHOLD MEMBERS
════════════════════════════════════════ -->
<div class="journey j7">
<div class="journey-header">
<div class="journey-num-big">J7</div>
<div class="journey-header-body">
<div class="journey-id">Journey 7</div>
<div class="journey-title">Manage household members</div>
<div class="journey-desc">After the initial setup, the planner may need to invite additional members or revoke access for someone who has left the household. The members page provides a live overview of who is in the household, their role, and invite status.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actors</div>
<div class="journey-meta-value">Planner (invites · removes) · New member (accepts)</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Rare — when household composition changes</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Members page (E2) via settings nav</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">E2</div>
<div class="step-label">Open members</div>
<div class="step-sublabel">See household roster</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">E2</div>
<div class="step-label">Invite member</div>
<div class="step-sublabel">Generate link or code</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-branch">A4</div>
<div class="step-label">Member accepts</div>
<div class="step-sublabel">Creates account</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Access granted</div>
<div class="step-sublabel">Visible in roster</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Role</th></tr>
</thead>
<tbody>
<tr>
<td>Open members page</td>
<td>Planner navigates to the Members page from the settings nav. The page lists all current household members with their name, role (Planner / Member), and join date. Pending invites are shown separately with their expiry status.</td>
<td><span class="page-ref">E2</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Invite a new member</td>
<td>Planner taps "Mitglied einladen". An invite link or short code is generated and displayed. The planner copies it and sends it via any messaging app (WhatsApp, SMS, email). No email delivery system is required — the link is the mechanism.</td>
<td><span class="page-ref">E2</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
<tr>
<td>Member accepts invite</td>
<td>The invited person opens the link on their device, creates an account if they don't have one, and is automatically added to the household with the Household Member role. The planner sees the new member appear in the roster without refreshing.</td>
<td><span class="page-ref">A4</span></td>
<td><span class="role-member">Household member</span></td>
</tr>
<tr>
<td>Access granted</td>
<td>The new member can now see the weekly meal plan (read-only) and the shopping list (view, check off, add items). Their name appears in the member list on E2.</td>
<td><span class="page-ref">C1</span> <span class="page-ref">D1</span></td>
<td><span class="role-both">Both roles</span></td>
</tr>
<tr>
<td>Remove a member</td>
<td>Planner taps a member's row and selects "Zugang entziehen". After an explicit confirmation prompt showing the member's name, the member is removed. They lose all access immediately. Their account is not deleted — they simply leave the household.</td>
<td><span class="page-ref">E2</span></td>
<td><span class="role-planner">Planner</span></td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>J7 is the post-onboarding continuation of the J6 invite step — same A4 acceptance mechanism, different entry point (E2 not A2). The invite component is re-used.</li>
<li>Pending invites must show a clear expiry state. Expired invites should be re-generatable with one tap — the planner should not need to go through the full invite flow again.</li>
<li>Removing a member is a destructive, irreversible action. Require an explicit confirmation ("Zugang für [Name] wirklich entziehen?") to prevent accidental removal.</li>
<li>Household members can view E2 in read-only mode — they see who is in the household but cannot invite or remove anyone.</li>
<li>The planner cannot remove themselves — the Planner role must always have at least one person. Household role transfer is out of scope for v1.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J8 — EDIT PANTRY STAPLES
════════════════════════════════════════ -->
<div class="journey j8">
<div class="journey-header">
<div class="journey-num-big">J8</div>
<div class="journey-header-body">
<div class="journey-id">Journey 8</div>
<div class="journey-title">Edit pantry staples</div>
<div class="journey-desc">The planner's pantry changes over time — a new dietary preference, a bulk buy, or a noticed pattern on the shopping list triggers a staple update. This journey lets the planner keep their "always on hand" list accurate so generated shopping lists stay useful.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner only</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Occasional — after household dietary or pantry changes</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Settings (E1) → Staples section (D3)</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">E1</div>
<div class="step-label">Open settings</div>
<div class="step-sublabel">Settings hub</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">D3</div>
<div class="step-label">Open staples</div>
<div class="step-sublabel">Browse categories</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">D3</div>
<div class="step-label">Toggle items</div>
<div class="step-sublabel">Add or remove staples</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Staples updated</div>
<div class="step-sublabel">Next list reflects changes</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Open settings</td>
<td>Planner navigates to the Settings page (E1) from the settings nav. The settings hub provides access to profile information and pantry staples configuration.</td>
<td><span class="page-ref">E1</span></td>
<td>E1 is the entry hub for settings-area screens. The Staples section is the most common destination from here and should be prominently placed.</td>
</tr>
<tr>
<td>Open staples manager</td>
<td>Planner taps the Staples section. The same StaplesManager component from onboarding (A3) renders in settings context — no sidebar, no "Weiter" navigation, just the ingredient category list.</td>
<td><span class="page-ref">D3</span></td>
<td>D3 = A3. One component, two render contexts (onboarding and settings). Context is passed as a prop — no duplicate component needed.</td>
</tr>
<tr>
<td>Browse and toggle</td>
<td>Planner browses ingredient categories. Checked items are staples and will be excluded from shopping lists. Unchecked items will appear on the next generated list. Changes are saved automatically on each toggle — no save button required.</td>
<td><span class="page-ref">D3</span></td>
<td>Auto-save on toggle is required. The planner must never lose a change because they forgot to tap a save button mid-browsing.</td>
</tr>
<tr>
<td>Changes applied</td>
<td>The updated staple configuration is persisted immediately. The next time a shopping list is generated (J5), the new staple set is used to filter out always-on-hand ingredients.</td>
<td></td>
<td>Changes do not retroactively update an already-generated shopping list. If the current list should reflect the change, the planner must regenerate it via J5.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>D3 and A3 are the same component with two render contexts. The onboarding context includes the progress sidebar and navigation footer. The settings context renders the staples list standalone — no onboarding chrome.</li>
<li>Trigger for this journey: the planner notices an item on their shopping list they always have at home, or a missing item they now always keep stocked. This is a fast corrective action — optimise for speed, not discoverability.</li>
<li>No confirmation required for staple toggles — they are low-stakes and instantly reversible. Auto-save on change is the correct pattern.</li>
<li>Consider a direct "Vorräte bearbeiten" shortcut link on the shopping list page (D1) — the most common trigger for this journey is noticing a wrong staple while looking at the list.</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
J9 — CONFIGURE VARIETY SCORE
════════════════════════════════════════ -->
<div class="journey j9">
<div class="journey-header">
<div class="journey-num-big">J9</div>
<div class="journey-header-body">
<div class="journey-id">Journey 9</div>
<div class="journey-title">Configure variety score</div>
<div class="journey-desc">The variety algorithm ships with defaults designed for omnivore households — protein-type tags penalise consecutive repeats and ingredient overlaps reduce the score. A vegetarian or vegan household will find these defaults unfair: tofu, eggs, and legumes repeat because there are simply fewer protein sources available. This journey lets the planner tune the algorithm to their household's dietary reality without any score gaming.</div>
<div class="journey-meta-row">
<div class="journey-meta-item">
<div class="journey-meta-label">Actor</div>
<div class="journey-meta-value">Planner only</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Frequency</div>
<div class="journey-meta-value">Rare — once when dietary context changes</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Entry point</div>
<div class="journey-meta-value">Settings (E1) → Variety settings card (E4)</div>
</div>
<div class="journey-meta-item">
<div class="journey-meta-label">Trigger</div>
<div class="journey-meta-value">Score feels persistently unfair or misleading</div>
</div>
</div>
</div>
</div>
<div class="journey-body">
<div class="steps-area">
<div class="steps-flow">
<div class="step">
<div class="step-node step-node-start">E1</div>
<div class="step-label">Open settings</div>
<div class="step-sublabel">Settings hub</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">E4</div>
<div class="step-label">Open variety settings</div>
<div class="step-sublabel">Current config visible</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">E4</div>
<div class="step-label">Toggle tag types</div>
<div class="step-sublabel">Protein on / off</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-branch">?</div>
<div class="step-label">Adjust weights?</div>
<div class="step-sublabel">Optional fine-tuning</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-action">C3</div>
<div class="step-label">Review impact</div>
<div class="step-sublabel">Updated score</div>
</div>
<div class="step-connector"><div class="step-connector-line"></div><div class="step-connector-arrow"></div></div>
<div class="step">
<div class="step-node step-node-end"></div>
<div class="step-label">Config saved</div>
<div class="step-sublabel">Persisted per household</div>
</div>
</div>
</div>
<div class="step-table-wrap">
<table class="step-table">
<thead>
<tr><th>Step</th><th>What happens</th><th>Screen</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Open settings</td>
<td>Planner navigates to Settings (E1). The settings hub now shows a third card: "Vielfalt-Einstellungen". Current score is shown as a stat number on the card so the planner knows what they are about to tune.</td>
<td><span class="page-ref">E1</span></td>
<td>Planner role only. Household members cannot see the Vielfalt-Einstellungen card. The card is added to the E1 settings hub grid alongside Vorräte and Haushalt cards.</td>
</tr>
<tr>
<td>Open variety settings</td>
<td>Planner taps the card. Screen E4 shows the full algorithm config: which tag types trigger repeat warnings (default: Protein, Küche) and the penalty weights for each violation type.</td>
<td><span class="page-ref">E4</span></td>
<td>E4 shows the current household config. If no custom config has been saved, defaults are displayed: Protein ✓, Küche ✓; all weights at "Mittel". A "Zurücksetzen auf Standard" link is always visible.</td>
</tr>
<tr>
<td>Toggle tag type checks</td>
<td>Planner toggles "Protein" off. For a vegetarian household, removing the protein check means tofu, eggs, and legumes appearing on consecutive days no longer reduces the variety score. The change auto-saves immediately.</td>
<td><span class="page-ref">E4</span></td>
<td>Each tag type in the household's recipe library can be checked or unchecked. Only types that actually appear in tagged recipes are listed — empty types are hidden. At least one type must remain checked; the toggle is disabled if it would leave zero checked types.</td>
</tr>
<tr>
<td>Adjust penalty weights (optional)</td>
<td>Planner uses segmented controls (Niedrig / Mittel / Hoch) to tune how severely each violation type reduces the score. For example, reducing ingredient overlap weight to "Niedrig" for households that cook mostly single-ingredient dishes.</td>
<td><span class="page-ref">E4</span></td>
<td>Four weights are tunable: Tag-Wiederholung (default Mittel = 1.5 pts), Zutaten-Überschneidung (default Niedrig = 0.3 pts), Letzte Wochen (default Mittel = 1.0 pts), Doppelte Rezepte (default Hoch = 2.0 pts). Changes auto-save on interaction — no save button.</td>
</tr>
<tr>
<td>Review updated score</td>
<td>Planner navigates back to the variety review page (C3). The score and all warning cards now reflect the updated config. Warnings for the disabled tag type no longer appear.</td>
<td><span class="page-ref">C3</span></td>
<td>The variety score shown on C1 (the planner home screen) also updates the next time the plan is loaded or a recipe is swapped. No full page reload is required on C3 — the server recalculates using the persisted config on each request.</td>
</tr>
<tr>
<td>Reset to defaults (optional exit)</td>
<td>If the planner wants to undo all changes, they tap "Zurücksetzen auf Standard" at the bottom of E4. A confirmation prompt names the specific values that will be reset. On confirm, the household config is deleted and the system defaults are restored.</td>
<td><span class="page-ref">E4</span></td>
<td>Reset is a destructive action — it deletes the household's custom VarietyScoreConfig row. Confirmation dialog is required. Unlike most auto-save interactions, this one needs an explicit confirm because it discards all customisations at once.</td>
</tr>
</tbody>
</table>
</div>
<div class="journey-notes">
<div class="journey-notes-label">Design notes</div>
<ul>
<li>The protein check is the primary motivation for this journey. Default it to OFF for households whose recipe library contains only vegetarian/vegan protein tags — consider detecting this automatically on first variety score load and surfacing a one-tap "Protein-Prüfung deaktivieren?" nudge on C3.</li>
<li>Penalty weights use three presets (Niedrig / Mittel / Hoch) rather than numeric sliders. Planners should not need to understand the backend scoring formula. Presets translate to fixed multipliers: Niedrig = ×0.5 of default, Mittel = ×1.0, Hoch = ×1.5.</li>
<li>Auto-save on every toggle/segmented-control change follows the same pattern as D3 staples. No save button. The "Zurücksetzen auf Standard" action is the only one that requires a confirmation step.</li>
<li>E4 is a settings screen, not a planning tool — it lives under E1, not under C3. The planner should access it deliberately, not accidentally while checking their variety score. However, C3 should surface a "Einstellungen anpassen →" link when the score has been consistently low for 3+ consecutive weeks.</li>
<li>Backend: config is stored per household in VarietyScoreConfig. PATCH /v1/households/mine/variety-config updates the config; DELETE resets to defaults. The variety score endpoint already reads from VarietyScoreConfig per request — no further backend work needed to make tuned configs take effect.</li>
</ul>
</div>
</div>
</div>
</div><!-- /doc -->
<!-- ═══════════════════════════════════════════════════════
MACHINE-READABLE SPEC (LLM AGENT REGION)
════════════════════════════════════════════════════════ -->
<!--
spec:agent:start
document: User Journeys
version: 1.1
-->
<div class="agent-section">
<h2>Machine-readable spec — User journeys</h2>
<p>This section is the authoritative journey reference for all agentic LLM tasks. Use it to understand which pages are involved in each journey, who performs each step, and what the key constraints are before building any screen or component.</p>
<pre class="spec-comment">/* Journey rules for agents
* 1. Two roles exist: PLANNER (full access) and HOUSEHOLD_MEMBER (limited access).
* 2. PLANNER can access: all screens (A1E4).
* 3. HOUSEHOLD_MEMBER can access: C1 (read-only) and D1 (view + check off + add items).
* 4. The variety score must be visible on C1 at all times — it is not a secondary feature.
* 5. J1 and J2 are preconditions for J5 — a shopping list requires planned meals with tagged recipes.
* 6. J6 is a precondition for J5 shared list — household members must be invited before the list is shared.
* 7. J3 "mark as cooked" feeds variety history which filters J2 suggestions. This feedback loop is critical.
* 8. J4 swap must complete in ≤ 3 taps from "Swap" to updated plan.
* 9. A3 and D3 are the same component (staples) — design and build once, reference from two entry points.
* 10. B3 and B4 (add + edit recipe) are the same form — build once with two initial states (empty vs prefilled).
* 11. The shopping list (D1) is real-time shared — checked items update for ALL household members instantly.
* 12. CalDAV export is future scope (E3) — do not build in v1.
* 13. Child accounts are future scope — do not design for in v1.
* 14. J7 is the post-setup continuation of J6 invite step — same A4 acceptance mechanism, different entry point (E2 not A2). The invite component is shared.
* 15. J8 entry is E1 (settings hub) → D3 (staples). D3 = A3: same component, settings context prop removes onboarding chrome (sidebar + nav footer).
* 16. Staple changes (J8) do not retroactively update an already-generated shopping list — planner must regenerate via J5 if the current list should reflect the change.
* 17. PLANNER cannot remove themselves from the household. Household role transfer is future scope, not v1.
* 18. J9 variety config is per-household and persisted in VarietyScoreConfig. Auto-save on toggle/weight change; reset-to-defaults requires a confirmation dialog. HOUSEHOLD_MEMBER cannot access E4.
* 19. J9 "Protein" tag-type toggle is the primary use case. Vegetarian households should disable it so tofu/eggs/legumes are not penalised for consecutive-day repetition.
* 20. E4 weight presets map to: Niedrig = ×0.5, Mittel = ×1.0 (default), Hoch = ×1.5 of the backend default weight.
*/</pre>
<table class="agent-table">
<thead>
<tr>
<th>Journey</th>
<th>Title</th>
<th>Actor(s)</th>
<th>Screens touched (in order)</th>
<th>Key constraint</th>
</tr>
</thead>
<tbody>
<tr>
<td>J1</td>
<td>Add a recipe</td>
<td>PLANNER</td>
<td>B1 → B3 → B1</td>
<td>Minimum tags required: effort + 1 category. Tags power J2 suggestions.</td>
</tr>
<tr>
<td>J2</td>
<td>Plan the week</td>
<td>PLANNER</td>
<td>C1 → C2 → C1 → C3 → C1 (→ C2 if swap needed)</td>
<td>Variety score must be visible throughout. Suggestions filter on: last 3 days ingredients, same-protein consecutive days, effort balance.</td>
</tr>
<tr>
<td>J3</td>
<td>Cook tonight</td>
<td>PLANNER</td>
<td>C1 → B2 → B4 → C1</td>
<td>Cook mode (B4): 16px body text, 1.75 line-height, one step per screen, screen wake lock, tap-anywhere to advance.</td>
</tr>
<tr>
<td>J4</td>
<td>Adapt on the fly</td>
<td>PLANNER</td>
<td>C1 → C2 → C1</td>
<td>Max 3 taps from Swap button to plan updated. Suggestions sorted by effort (easiest first) when triggered mid-week.</td>
</tr>
<tr>
<td>J5</td>
<td>Generate shopping list</td>
<td>PLANNER (generate) + HOUSEHOLD_MEMBER (shop)</td>
<td>C1 → D1 (always live)</td>
<td>Ingredients merged + summed across meals. Staples filtered (D3 config). Planner generates. List is always live — all members can view, check off, add, and remove items. Checked state persists for all users.</td>
</tr>
<tr>
<td>J6</td>
<td>Household setup</td>
<td>PLANNER (creates) + HOUSEHOLD_MEMBER (joins)</td>
<td>A1 → A2 → A3 → A2 → [invite] → A4</td>
<td>One-time journey. A3 = D3 (same component). Invite via link/code (no email system). Member role grants: C1 read-only + D1 collaborative.</td>
</tr>
<tr>
<td>J7</td>
<td>Manage household members</td>
<td>PLANNER (manages) + HOUSEHOLD_MEMBER (accepts)</td>
<td>E2 → [invite link] → A4 → E2</td>
<td>Post-onboarding companion to J6 invite step. One-tap re-generation for expired invite links. Member removal requires explicit confirmation with member name. PLANNER cannot remove themselves.</td>
</tr>
<tr>
<td>J8</td>
<td>Edit pantry staples</td>
<td>PLANNER</td>
<td>E1 → D3</td>
<td>Auto-save on toggle — no save button. Changes apply to next J5 shopping list generation only, not retroactively. D3 = A3 (same component, settings context prop).</td>
</tr>
<tr>
<td>J9</td>
<td>Configure variety score</td>
<td>PLANNER</td>
<td>E1 → E4 → C3</td>
<td>Auto-save on every toggle/weight change. Reset-to-defaults requires explicit confirmation. Protein tag-type toggle is the primary use case for vegetarian households. Config is stored per household — changes take effect on next variety score request, no plan changes required.</td>
</tr>
</tbody>
</table>
<table class="agent-table">
<thead>
<tr>
<th>Screen</th>
<th>Name</th>
<th>Journeys</th>
<th>Planner access</th>
<th>Member access</th>
</tr>
</thead>
<tbody>
<tr class="group-row"><td colspan="5">A — Onboarding &amp; auth</td></tr>
<tr><td>A1</td><td>Welcome / sign up</td><td>J6</td><td>Full</td><td>Full (joining only)</td></tr>
<tr><td>A2</td><td>Household setup + invite</td><td>J6</td><td>Full</td><td>No access</td></tr>
<tr><td>A3</td><td>Staples setup (= D3)</td><td>J6, J5</td><td>Full</td><td>No access</td></tr>
<tr><td>A4</td><td>Join household (accept invite)</td><td>J6</td><td>N/A</td><td>Full</td></tr>
<tr class="group-row"><td colspan="5">B — Recipe library</td></tr>
<tr><td>B1</td><td>Recipe library</td><td>J1, J2</td><td>Full</td><td>No access</td></tr>
<tr><td>B2</td><td>Recipe detail</td><td>J3</td><td>Full</td><td>No access</td></tr>
<tr><td>B3</td><td>Add / edit recipe (one form, two states)</td><td>J1</td><td>Full</td><td>No access</td></tr>
<tr><td>B4</td><td>Cook mode</td><td>J3</td><td>Full</td><td>No access</td></tr>
<tr class="group-row"><td colspan="5">C — Meal planning</td></tr>
<tr><td>C1</td><td>Weekly planner</td><td>J2, J3, J4, J5</td><td>Full</td><td>Read-only</td></tr>
<tr><td>C2</td><td>Meal suggestions</td><td>J2, J4</td><td>Full</td><td>No access</td></tr>
<tr><td>C3</td><td>Variety review</td><td>J2</td><td>Full</td><td>No access</td></tr>
<tr class="group-row"><td colspan="5">D — Shopping list</td></tr>
<tr><td>D1</td><td>Shopping list (live shared)</td><td>J5</td><td>Full</td><td>View + check off + add items</td></tr>
<tr><td>D3</td><td>Staples manager (= A3)</td><td>J5, J6, J8</td><td>Full</td><td>No access</td></tr>
<tr class="group-row"><td colspan="5">E — Settings</td></tr>
<tr><td>E1</td><td>Settings hub</td><td>J8, J9</td><td>Full</td><td>No access</td></tr>
<tr><td>E2</td><td>Household (members + roles)</td><td>J6, J7</td><td>Full</td><td>View-only</td></tr>
<tr><td>E3</td><td>Integrations (CalDAV — future)</td><td></td><td>Full</td><td>No access</td></tr>
<tr><td>E4</td><td>Variety settings (tag types + weights)</td><td>J9</td><td>Full</td><td>No access</td></tr>
</tbody>
</table>
</div>
<!--
spec:agent:end
-->
</body>
</html>