§5.2.5 Aderenza al piano alimentare
- MealSlot.consumed Boolean + consumedAt DateTime? (migration)
- POST /me/meal-plans/slots/:id/consumed (toggle, idempotente con body opzionale)
- @ketopath/shared/planner/adherence: computeAdherence() su slot dei giorni
passati, free-meal contati come "rispettati"
- /plan: bottone ✓ in ogni SlotCard (oliva quando attivo) + 5° card nel
summary settimanale "Aderenza: X% (n/m)"
§5.2.6 Export PDF per medico/nutrizionista
- pdfkit (Node, no headless Chrome) + @types/pdfkit
- GET /me/tracking/export.pdf: profilo, storico peso (30 entries),
piano corrente con aderenza, footer con disclaimer medico
- Proxy Next.js /api/tracking-export per same-origin + cookie sessione
- /tracking: link "Esporta PDF per medico/nutrizionista" sotto subtitle
Schema migration: add_meal_slot_consumed.
81/81 unit test verdi. Lint, typecheck verdi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quattro completamenti del PRD §5.1: catalogo, varianti/sostituti, fase 2,
free meal.
Catalogo ricette 16 → 46
- seed-recipes.ts: +30 ricette italiane keto bilanciate
(8 colazione · 8 pranzo · 6 spuntino · 8 cena)
- seed-ingredients.ts: 33 → 55 ingredienti
(verdura/carne/pesce/latticini/condimenti + categorie nuove `legumi` e
`cereali` per Fase 2)
- seed.ts: ora fa upsert anche degli ingredienti pre-esistenti per
propagare campi nuovi (es. phase2Week)
Varianti + sostituti
- Recipe.variants Json (array { name, description, kcalDelta? })
- RecipeIngredient.substitutes String[]
- /recipes/[id]: nuova sezione "Varianti suggerite" + riga "Sostituzioni:"
sotto ogni ingrediente quando presenti
Reintroduzione progressiva Fase 2
- Ingredient.phase2Week (settimana minima di reintroduzione, NULL=sempre)
- User.phase2StartedAt: settato automaticamente quando si passa a TRANSITION
- @ketopath/shared/planner/phase-progression: currentPhase2Week,
isRecipeAllowedForPhaseWeek, weeksSince
- plan.routes filtra fuori le ricette con ingredienti non ancora reintrodotti
- POST /me/profile/phase per avanzare di fase (idempotente sul timestamp)
- 12 unit test su phase-progression
- Esempi seed: lenticchie phase2Week=3, ceci=4, mela=5, pera=6, quinoa=7
Free Meal pianificato Fase 3
- MealSlot.isFreeMeal Boolean (default false)
- POST /me/meal-plans/slots/:id/free-meal toggle, valido solo in MAINTENANCE
- @ketopath/shared/planner/phase-progression: freeMealKcalAdjustment
(compensazione clamp -200/+50 kcal per pasto sui rimanenti)
- /plan: bottone ☆ in ogni SlotCard quando phase=MAINTENANCE; lo slot
marcato come pasto libero rende "Pasto libero pianificato" + ~750 kcal
stimate; macro tracker ne tiene conto
Indicatori di fase nel piano
- /plan ora include currentPhase + phase2Week dalla API
- Header del piano: "Settimana N di Fase 2" o "Mantenimento — Fase 3"
i18n: namespaces Plan + Recipe arricchiti
Schema migration 20260429XXXXXX_add_meal_plan_extras.
66/66 unit test verdi (12 nuovi). Smoke verificato live: API ritorna
correctly i campi nuovi, UI rende badge fase, bottoni ☆/↻, free-meal slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tre completamenti del PRD §5.1 lasciati indietro fino a oggi.
1. Macro tracker giornaliero
- GET /me/meal-plans/current ora include proteinG/fatG/netCarbG/prepMinutes
per ogni recipe selected e alternative
- /plan: nuova summary card settimanale (medie kcal/proteine/grassi/carb
sui giorni attivi)
- Header per ogni giorno: "1390 KCAL · P 100 · G 92 · C 17"
2. cookingTime nel matchmaking
- Nuovo prepMinutes su RecipeCandidate
- MatchOptions accetta maxPrepMinutes (soft cap, non hard filter)
- Score: penalty 0.01 per minuto di sforamento — preserva la diversità
ma orienta le scelte
- maxPrepMinutesFor(level): LOW=15, MEDIUM=30, HIGH=60
- plan.routes deriva la soglia da Preferences.cookingTime
3. Rigenera singolo pasto
- POST /me/meal-plans/slots/:slotId/regenerate
- Riusa l'intero pipeline di calcolo (BMR, deficit, condizioni, training,
protocollo, mealsPerDay, cookingTime) e ricalcola top5 per quello slot
- Passa consumedSoFar (kcal/macros già scelti negli altri pasti del
giorno) al matchmaking → coerenza dei macros giornalieri
- Aggiunge la ricetta corrente al recentlyConsumedIds per forzare il
cambio
- UI: pulsante ↻ in ogni SlotCard, accessibile via aria-label
Smoke verificato live: 21 bottoni rigenera (3 pasti × 7 giorni), summary
"Media kcal/giorno 1397", header giornaliero "1390 KCAL · P 100 · G 92 · C 17".
54/54 unit test verdi. Lint, typecheck, build verdi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finora il fastingProtocol era una preferenza salvata ma ignorata dal
planner — ogni giorno generava 4 pasti. Ora cambia struttura per protocollo
e per giorno della settimana.
@ketopath/shared/planner
- protocolPlanForDay(protocol, dayOfWeek) → { share, kcalMultiplier }
- 14:10 → tutti e 4 i pasti, share default
- 16:8 → niente colazione: pranzo 50%, spuntino 10%, cena 40%
- 18:6 → niente colazione: pranzo 55%, spuntino 5%, cena 40%
- 20:4 → solo spuntino+cena (10/90)
- ESE 24h → mercoledì kcalMultiplier=0 (giorno saltato), altri default
- 5:2 → lunedì + giovedì kcalMultiplier=0.25, share 100% cena
- 7 unit test in protocol.test.ts
apps/api/plan
- POST /me/meal-plans: legge preferences.fastingProtocol, deriva il piano
per ogni giorno, salta MealSlot con share=0 e giorni a kcalMultiplier=0
- GET /me/meal-plans/current: include fastingProtocol nella risposta
- baseDailyTarget restituito invariato per il client
apps/web
- /plan mostra "Finestra alimentare 16:8" sotto il titolo della settimana
- plan-week distingue i giorni "fasting" (digiuno completo) e "leggeri"
(5:2 fasting day) con copy dedicato; rimuove totali calorici inappropriati
- i18n: Plan.protocolLabel + Plan.fastingDay/lightDay
Smoke tested: settato 16:8 da /profile, rigenerato il piano, verificato
che l'etichetta appaia e che ogni giorno abbia esattamente 3 pasti
(Pranzo / Spuntino / Cena, niente Colazione).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recipe detail
- Seed catalog of 33 italian ingredients with macros, allergens, avg price.
- Each seed recipe now declares its RecipeIngredient links (qty + unit).
- /recipes/[id] page: editorial layout with prep meta, ingredients list,
macros block, preparation, chef's note. Plan-week meal names linkable.
Shopping list
- GET /me/shopping-list aggregates RecipeIngredient quantities of the
ACTIVE plan, grouped by ingredient category, sorted alphabetically.
- /shopping page: total items, estimated cost, per-category checklist
with check-off persisted in localStorage per plan id.
- Home gets a "Spesa" nav item; /plan links the list when a plan exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A complete visual + tonal pass on every page. Replaces the previous shadcn
slate+emerald defaults with an Italian editorial aesthetic — refined slow-food
magazine crossed with a luxury cookbook. Mobile-first, premium, distinctly
Italian, no generic AI-wellness vibes.
Tokens:
- New palette: carta (cream parchment), ink (warm black), oliva (deep olive),
pomodoro (terracotta), oro (aged gold), rule (hairline). All exposed as
HSL CSS variables and as Tailwind colors. Dark mode = oxblood/cream.
- Type stack: Fraunces (variable, optical-sizing auto, italic + roman) for
display / Inter Tight for body / JetBrains Mono with tnum + zero for
numerals. Replaces Inter-only setup.
- Font feature settings on body and .font-display for ligatures, opsz
and stylistic alternates.
- Subtle SVG paper grain on body::before (multiply blend, 4.5% opacity).
- 2px radius default (editorial print, not soft web).
Component primitives:
- Button — mono uppercase 11px tracking-widest, sharp corners, hover
inverts ink/cream. Variants tuned for the new palette.
- Input — bottom-only hairline, Fraunces 18px text, italic placeholders.
- Label — tiny mono caps, ink-soft.
- Card — top + bottom hairlines only, no shadow; Fraunces title and italic
description; CardFooter has its own top-rule.
- Select — same hairline-bottom trigger as Input; SelectContent panel uses
a hard 6px offset shadow for an editorial print stamp feel.
New component:
- Masthead — top-of-page newspaper header with thick top-rule, KP wordmark
with terracotta dot, "ANNO I · N. 0X" issue label, today's date in Italian.
Pages:
- / — Massive Fraunces "Una keto italiana, sostenibile." with terracotta
dot, italic Fraunces tagline, right-column nav with category eyebrows
(CUCINA / PROFILO) and arrow rule. Bottom: "Tre principi" with roman
numerals I/II/III. Disclaimer in italic.
- /sign-in — "CAPITOLO II / Bentornato in cucina." split layout.
- /sign-up — "CAPITOLO III / Inizia il tuo percorso." Italian copy refresh.
- /welcome — Editor's-letter layout with the four PRD §14.3 points as a
Roman-numeral list; custom hand-drawn check icon for the consent boxes.
- /profile — 7/5 split: form on the left (anagrafica + peso group), summary
on the right with massive mono numerals (BMR, TDEE, multiplier) and italic
"kcal" units; empty-state copy until the profile is saved.
- /plan — "CAPITOLO V / Il tuo piano." Italian-formatted week range. Each
day has a Roman-numeral mark (I-VII), Fraunces day name, mono kcal total.
Each meal slot uses Roman numerals (I-IV) for the meal index.
- Cookie banner — refreshed to match (italic Fraunces, "PRIVACY" eyebrow,
outline button).
Italian copy refresh:
- Headline: "Una keto italiana, sostenibile" (was "KetoPath")
- Tagline: narrative arc instead of marketing line
- CTAs: "Iscriviti" / "Accedi" (was "Inizia gratis" / "Scopri come funziona")
- Greeting: "Buongiorno, {name}." (was "Accesso effettuato come ...")
- Profile: weight columns become Iniziale / Attuale / Obiettivo under a
"Peso (chilogrammi)" group eyebrow.
E2E updated to match: home looks for the new headline and CTA labels;
profile uses the new "Come ti chiami" / "Iniziale / Attuale / Obiettivo"
labels and asserts numeric values separately from the "kcal" unit span.
All 4 specs pass. Verified visually at 1280×900 and 375×812.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
apps/api:
- New PATCH /me/meal-plans/slots/:slotId — accepts { recipeId } and validates
ownership + that the new recipe is one of the slot's existing alternatives
(or already selected). Returns the updated slot with the new selected recipe.
apps/web — /plan route:
- Server component runs the same auth+disclaimer+profile guard chain as
/profile, then redirects to /profile if no profile exists yet (you can't
generate a plan without macros)
- actions.ts exposes three server actions: fetchCurrentPlan, regeneratePlan
and swapSlotRecipe; all proxy to api with the user's session cookie and
revalidatePath('/plan') after writes
- plan-week.tsx renders day-by-day sections (Lun→Dom) with a 4-card meal
row (1/2/4 columns at sm/lg breakpoints), recipe name + kcal, daily kcal
totals, expandable list of alternatives with one-click "Scegli"
- "Genera piano" CTA when there's no plan yet, "Rigenera" when there is one
- Italian copy under Plan.* with day, meal and kcal-total interpolations
- Home gains a "Vedi il piano settimanale" CTA next to "Completa profilo"
Verified end-to-end in Chrome:
- generate plan → 7 sections (Lun-Dom), each with 4 meal cards and per-day
kcal sum (e.g. Lunedì 1510 kcal = 360+540+150+460)
- recently-consumed penalty visible: Mon/Tue/Wed have different breakfasts
- swap: clicked "Vedi 3 alternative" on Mon Colazione → "Scegli" on
"Uova strapazzate" → card refreshed to that recipe and 410 kcal
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>