Commit Graph
3 Commits
Author SHA1 Message Date
lucianoandClaude Opus 4.7 4633f42bc5 feat: §6 quick wins (Gruppo A) — 5 quality-of-life che chiudono gap user-flow
A1. Notifiche per-pasto (PRD §6.3)
- NotificationSettings.mealReminders (default off)
- 3 ScheduledTask aggiuntivi: 13:00 pranzo, 16:00 spuntino, 19:00 cena
  Europe/Rome
- runMealReminderJob: skip se utente in pausa, altrimenti push "Ora di
  pranzo: vedi cosa c'è nel piano di oggi" → /plan
- Toggle in /profile sezione notifiche

A2. Auto-start digiuno a fine cena (PRD §6.3)
- POST /me/meal-plans/slots/:id/consumed: se è il "pasto finale" del
  giorno (CENA o ultimo dello schedule IF) e l'utente ha preferences.
  fastingProtocol senza FastEvent IN_PROGRESS → crea automaticamente
  un nuovo digiuno col protocollo preferito
- isMealLastOfDay helper (rank-based: COL=0, PRA=1, SPU=2, CENA=3)
- Risposta include autoStartedFast: { id } | null

A3. Riassunto piano nel done step onboarding (PRD §6.1.9)
- StepDone fetcha il profilo dopo regenerate
- Card 4-colonne con Fase corrente + durata, BMR, TDEE, aderenza target
- i18n: phaseLabel + phaseDuration

A4. "% del percorso" in /tracking (PRD §6.4.5)
- WeightHistory accetta startKg, calcola (start-current)/(start-goal)
  clampato 0-100
- 4° card nel summary: "Del percorso · 65%"

A5. Lista spesa: spuntati in fondo + totale rimanente (PRD §6.5.4-5)
- ShoppingChecklist sort items con consumed=true in fondo,
  alfabetico tra non-spuntati
- "Restano €X" mostrato accanto al "n di total" — solo non-spuntati

i18n: namespace Notifications, Onboarding, Tracking, Shopping estesi.

81/81 unit test verdi. Lint, typecheck verdi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:35:35 +02:00
lucianoandClaude Opus 4.7 ea88ed8775 feat: §5.3 completion — stats, symptoms, milestones, pause, break-fast
Cinque feature che chiudono il PRD §5.3 (gestione digiuno).

§5.3.1 Streak + statistiche settimanali/mensili
- @ketopath/shared/tracking/fasting-stats: computeFastingStats(events) ritorna
  weeklyHours, monthlyHours, currentStreak, longestStreak, completionRate,
  totalSessions, completedSessions
- 6 unit test (currentStreak consecutivo, longestStreak storico, completionRate
  ignora IN_PROGRESS, range temporali)
- /fasting: la striscia di stat passa da 3 a 5 (settimanali, mensili, streak
  attuale, streak record, % completamento)

§5.3.2 Diario sintomi durante il digiuno
- updateFastSymptoms server action → PATCH /me/fast-events/:id { symptoms }
- SymptomsDiary component nel timer attivo: checkbox "Mal di testa" + 3 slider
  1-10 (Energia, Fame, Lucidità) + textarea note libere; "Salva" non
  interrompe il timer

§5.3.3 Notifiche per-fast
- FastEvent.notifiedMilestones String[] (idempotenza)
- Cron */5 * * * * `runFastingMilestonesJob` scansiona le sessioni IN_PROGRESS
  e invia push secondo le milestone:
  · hydration_2h / 4h / 6h — promemoria idratazione
  · electrolyte_18h — alert elettroliti per digiuni lunghi
  · ending_soon — 30 min prima della fine
- Skip automatico se l'utente ha fastingPausedUntil futuro
- Skip se l'utente non ha attivato `fastingMilestones` nelle preferenze
- Server.ts gestisce ora 2 ScheduledTask (weekly + fasting)

§5.3.4 Modalità giorno libero
- User.fastingPausedUntil DateTime?
- POST /me/fasting/pause con body { until?: ISO|null } — default: domani 06:00
- GET /me/fasting/pause ritorna { fastingPausedUntil, paused }
- /fasting: FastingPauseToggle bottone "Pausa per oggi" → banner con countdown +
  "Riprendi adesso" per terminare prima

§5.3.5 Pasto di rottura intelligente
- GET /me/fast-events/:id/break-fast-suggestions: solo per digiuni >= 24h,
  ritorna 3 ricette con prepMinutes ≤ 20, kcal ≤ 350, categoria
  COLAZIONE/SPUNTINO. Heuristica per "facile da digerire".
- /fasting history: per le sessioni completate >= 24h aggiunge un disclosure
  "Suggerimenti per riprendere a mangiare" che fetcha on-demand e linka alle
  ricette

Schema migrations: add_fasting_paused_until, add_fast_event_milestones.

72/72 unit test verdi (6 nuovi). Lint, typecheck, build verdi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:41:20 +02:00
lucianoandClaude Opus 4.7 bb48357179 feat: web push notifications, mobile-ready (PRD §5.6)
ADR 0003: VAPID self-hosted today, DeviceToken model agnostic to
platform so iOS/Android (Expo/APNs/FCM) plug in as new senders later.

Backend (apps/api/src/modules/notifications)
- sender.ts: NotificationSender interface, WebPushSender via VAPID
- notifications.routes.ts: GET /me/notifications/config, POST/DELETE
  /me/device-tokens, PATCH /me/notifications/settings, POST /me/notifications/test
- scheduler.ts: node-cron Mon 09:00 Europe/Rome for weekly weigh-in
  reminder; auto-cleanup of expired tokens on 404/410
- env: VAPID_PUBLIC_KEY/PRIVATE_KEY/SUBJECT (all optional → push gracefully off)

Frontend
- public/sw.js minimal (push + notificationclick)
- lib/notifications/push-client.ts: subscribe / unsubscribe / getCurrentSubscription
- profile/notifications-{actions,panel}.tsx: editorial panel with toggles,
  device list, "send test", per-device removal
- pushReady requires both permission AND active subscription (covers the
  case where the user revoked the SW but kept the browser permission)

Schema
- DeviceToken { userId, platform, endpoint, p256dh, auth, token, userAgent,
  createdAt, lastSeenAt } with unique(userId, endpoint)
- ExtendedPrismaClient type exported from @ketopath/db
- NotificationSettings zod schema in @ketopath/shared

Tooling
- lint-staged: split .js out of eslint glob so service worker is only
  formatted (it lives outside the TS project)

i18n
- Notifications namespace (it) with typed error keys

Smoke tested: POST /me/device-tokens 201, POST /me/notifications/test 200,
real push delivered to a macOS Chrome device.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:58:35 +02:00