Commit Graph

68 Commits

Author SHA1 Message Date
alexandrev-tibco 896c78d260 fix: crash al dictar por closures de callbacks aislados al main actor
Crashlytics: dispatch_assert_queue_fail en com.apple.root.default-qos, dentro de
closure #1 in SpeechDictationService.start, llamado desde __TCCAccessRequest.

El proyecto compila en Swift 6 (activo desde febrero, antes del dictado). Los
callbacks de APIs ObjC pre-concurrency escritos dentro de este tipo @MainActor se
infieren aislados al main actor, y Swift 6 inserta una comprobacion de aislamiento
al entrar. TCC invoca el handler en cola de fondo, la comprobacion falla y mata el
proceso. El Task { @MainActor } de dentro no ayuda: el crash es antes de llegar.

Cuatro sitios con el mismo patron, tres enmascarados detras del primero porque
nadie pasaba de los permisos:

- requestAuthorization y requestRecordPermission -> wrappers nonisolated con
  continuation, el handler deja de estar aislado
- recognitionTask -> closure @Sendable explicito; solo cruzan valores Sendable
  porque SFSpeechRecognitionResult no lo es
- installTap -> captura el request local en vez de ir por self, que el hilo de
  audio en tiempo real no debe tocar estado del main actor

requestAuthorization llama al handler aunque el permiso ya este concedido, asi
que afectaba tambien a usuarios recurrentes: el dictado probablemente no ha
funcionado nunca desde la 2.0.

Los tests que acompanan NO reproducen el crash: el simulador entrega el callback
sincrono en el hilo principal y el codigo previo pasa igual. Comprobado
revirtiendo el fix. Queda pendiente validarlo en dispositivo real.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-19 21:28:29 +02:00
alexandrev-tibco 87e8735621 2.0.2: bump de version y release notes
Version en los tres sitios (Info.plist de app y widget + MARKETING_VERSION) y
widget adelantado a build 67, que es el que generara fastlane.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:03:14 +02:00
alexandrev-tibco cfa9769a06 2.0.2: evento de fallo de compra + analytics off en UI tests
premium_purchase_failed con reason (cancelled/pending/unverified/error) en todas
las salidas no exitosas de StoreManager.purchase. Hasta ahora un usuario que se
echaba atras y uno al que le fallaba la compra eran indistinguibles: los dos
dejaban un premium_upgrade_tapped huerfano. El caso de Aleman (6 taps en 2 dias,
cero compras) no se puede diagnosticar sin esto.

Los UI tests dejan de mandar eventos a GA4 de produccion: launch argument
UITEST_DISABLE_ANALYTICS que hace no-op logEvent y apaga la recoleccion de
Firebase, para cortar tambien los automaticos (first_open, session_start,
screen_view). Ya habian contaminado los datos: los eventos de pasos 5/6 bajo la
version 2.0 eran ejecuciones de test, no usuarios.

Detalle que costo encontrar: setAnalyticsCollectionEnabled persiste entre
lanzamientos, asi que se escribe siempre (!isDisabled) y no solo al apagar. Si
no, un unico test dejaba analytics muerto para siempre en esa instalacion.
Verificado por pares de lanzamientos: con flag acaba en disabled, sin flag
vuelve a enabled.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:02:35 +02:00
alexandrev-tibco dfe97e9553 2.0.1: build 66 subida a TestFlight (fix onboarding)
La primera subida la rechazo App Store Connect con 90062: el IPA salio con
CFBundleShortVersionString 2.0. MARKETING_VERSION del pbxproj no manda; la
version real esta hardcodeada en MealMood/Resources/Info.plist y en el
Info.plist del widget.

Corregidos ambos a 2.0.1 y alineados en build 66. Documentado en CLAUDE.md que
un cambio de version toca tres sitios y que fastlane solo automatiza el build
number de la app.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-06 08:22:56 +02:00
alexandrev-tibco 79423e3511 tests: target MealMoodUITests que recorre el onboarding completo
El bug de los pasos 5 y 6 era invisible a los tests unitarios: el contrato de
datos estaba bien, lo que fallaba era que la vista se desmontaba antes de pintar
el aha moment y el paywall. Hace falta recorrer la UI de verdad.

Anade el target MealMoodUITests (solo simulador, sin firma) y un test que hace
el onboarding entero en instalacion limpia y comprueba que Week Ready y el
paywall aparecen antes de la Home.

Los CTA del onboarding llevan ahora accessibilityIdentifier para que el test no
dependa de los textos, que estan en 6 idiomas.

Validado en los dos sentidos: pasa con el fix, y revirtiendo temporalmente
markFinished: false falla en la asercion de Week Ready. Suite completa: 27 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 23:06:04 +02:00
alexandrev-tibco 3694297d4f 2.0: mostrar los dos ultimos pasos del onboarding (aha moment + paywall)
completeOnboarding escribia settings.onboardingCompleted al terminar el paso
de platos. Ese flag es el que ContentView observa para cambiar de OnboardingView
a HomeView, asi que el save destruia el flujo antes de que nextStep() pudiera
pintar el paso 5. WeekReadyStepView (el aha moment de la 1.2.0) y PaywallStepView
no se han visto nunca. GA4 lo confirma: 3 onboarding_completed en 28 dias y cero
eventos con step 5 o 6.

Se separa persistir los datos de marcar el onboarding como terminado:
completeOnboarding(markFinished:) commitea platos y plan sin tocar el flag, y el
nuevo finishOnboarding() lo escribe al salir del paywall. HomeView ya difiere su
prompt premium a la segunda sesion, asi que no hay paywall duplicado.

De paso, los nombres de paso en AnalyticsService estaban desalineados: el indice
5 decia "5_paywall" cuando el paso 5 es WeekReady, y el 6 no tenia nombre.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 20:53:10 +02:00
alexandrev-tibco 10a3d8bf32 2.0: bump build 63 → 64 after TestFlight upload (slider, banner, share toolbar)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-26 09:21:43 +02:00
alexandrev-tibco a4aedffd5a 2.0: banner export solo en semana completa + compartir desde toolbar
- El banner "ready to export" ya no aparece con la semana incompleta; solo
  cuando no quedan slots vacíos (y sigue siendo descartable con la X).
- Se añade un icono de compartir en la toolbar (cuando hay algo planificado)
  para no perder el acceso a exportar en semanas parciales; misma lógica de
  premium/paywall que el banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-26 08:34:05 +02:00
alexandrev-tibco 543254337f 2.0: banner export descartable + slider calendario/platos en iPad
- El banner "Your week is ready to export" ahora tiene botón de cerrar (X)
  que lo oculta para esa semana (persistido en AppStorage).
- iPad/Mac: handle arrastrable entre calendario y platos para rebalancear
  el alto (fracción persistida, clamp 0.28–0.72); el calendario y la lista
  se recalculan al arrastrar. Sustituye la altura fija anterior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 16:29:22 +02:00
alexandrev-tibco b70140191a 2.0: firma manual (app+widget) para Push + bump build 62 → 63
El archive con firma automática no podía incluir la capability Push de
forma headless. Se pasan los targets MealMood y MealMoodWidget (config
Release) a firma manual con "Apple Distribution" y sus perfiles AppStore
(regenerados con Push). Build 63 sube a TestFlight con aps-environment
production para el sync CloudKit en tiempo real.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:59:42 +02:00
alexandrev-tibco d957921fe3 2.0: sync CloudKit en tiempo real vía push silencioso
NSPersistentCloudKitContainer solo sincronizaba de forma oportunista (sin
push), por lo que un plato nuevo tardaba mucho o no aparecía en el otro
dispositivo. Se activa el push de CloudKit:

- aps-environment en MealMood.entitlements vía variable APS_ENVIRONMENT
  (development en Debug, production en Release) para no romper TestFlight.
- AppDelegate con registerForRemoteNotifications() + manejo del push
  silencioso; el mirror de SwiftData importa al ser despertado.
- (Capability Push Notifications habilitada en el App ID por API.)

Pendiente: regenerar el provisioning profile AppStore con Push antes de
la build 63, y validar el sync push en iPhone+iPad vía TestFlight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:47:46 +02:00
alexandrev-tibco 5862180426 2.0: bump build 61 → 62 after TestFlight upload (dedup platos)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:01:37 +02:00
alexandrev-tibco 4f3664cdd4 2.0: deduplicar Dishes por id en DeduplicationService (determinista)
DeduplicationService colapsaba settings, tags y week plans pero NO los
platos, dejando duplicados de CloudKit (mismo UUID) que causaban el crash
de arranque y platos repetidos en pantalla. Se añade dedupeDishes:

- Solo agrupa y colapsa copias que comparten el mismo id (nunca fusiona
  platos distintos ni toca platos únicos).
- Keeper elegido de forma DETERMINISTA (copia más rica: foto/desc/
  ingredientes/tags/prioridad; desempate por persistentModelID estable),
  así todos los dispositivos de la cuenta borran los mismos perdedores y
  el grafo converge a una copia, nunca a cero.
- CloudKit private DB por Apple ID (sin CKShare) → jamás cruza cuentas de
  Family Sharing.

Junto con el uniquingKeysWith previo, la app deja de crashear y limpia los
platos duplicados en cada arranque, sin borrar la BD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 20:39:07 +02:00
alexandrev-tibco 0ac81e5fe3 2.0: bump build 60 → 61 after TestFlight upload (fix crash arranque IDs duplicados)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:28:05 +02:00
alexandrev-tibco 5272f6533f 2.0: fix crash de arranque por IDs duplicados (CloudKit)
Fatal error "Duplicate values for key" al construir diccionarios con
Dictionary(uniqueKeysWithValues:) sobre dishes/tags/slots cuyos UUID se
duplican por la sincronización CloudKit de 2.0. Se sustituyen todas las
ocurrencias por Dictionary(_, uniquingKeysWith:) para tolerar duplicados
(se conserva el primero) en lugar de abortar. La app ya no crashea al
abrir con datos duplicados; la deduplicación real queda pendiente aparte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:23:50 +02:00
alexandrev-tibco 817ae549dc 2.0: bump build 59 → 60 after TestFlight upload (dictado + calendario iPad)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:17:07 +02:00
alexandrev-tibco 8e4d0c2d89 2.0: dictado de platos/ingredientes + calendario más alto en iPad/Mac
- Dictado por voz (Speech/SFSpeechRecognizer, on-device) en el nombre de
  plato y en ingredientes, con transcripción en vivo.
- Un solo texto dictado se separa en ingredientes individuales vía Apple
  Foundation Models on-device (IngredientParser), con fallback heurístico.
- Permisos de micrófono y reconocimiento de voz en Info.plist.
- Cadenas de dictado en los 6 idiomas.
- iPad/Mac: el calendario reclama ~50% de la altura disponible en lugar
  de quedar fijo a ~240px; iPhone (compact) sin cambios.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:16:51 +02:00
alexandrev-tibco 148a301d6c 2.0: analytics — log onboarding step as String + step_name for GA4 drop-off
GA4 text custom dimensions do not capture numeric params (step showed as
"(not set)"). Log step as String and add a readable step_name so the
onboarding drop-off is queryable. Registered source/environment/step_name
custom dimensions in the GA4 property to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Df9kZEUMXPLn4Kb9Zx1f5o
2026-07-21 09:19:50 +02:00
alexandrev-tibco 029e8d7e1a 2.0: bump build 57 → 58 after TestFlight upload (beta feedback round 1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-14 22:28:03 +02:00
alexandrev-tibco 29cba08678 2.0: beta feedback — edit dish from shopping list + dish photos in planner
- Shopping list: dishes under "No ingredients yet" now open the dish editor on
  tap (pencil affordance); list reconciles on dismiss. This was a dead end on
  devices without Apple Intelligence (no Generate button, e.g. iPhone 14).
- Planner: dish photo as a scrimmed card background in the week calendar,
  behind a new Settings toggle (settings_show_dish_photos, default on). New
  optional AppSettings.showDishPhotosInPlanner (CloudKit/migration safe).
- FilledSlotView: inlines the meal-card chrome to support the photo layer.
- Toggle localized in all 6 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-14 22:06:11 +02:00
alexandrev-tibco 9118cc6e86 2.0: bump build 56 → 57 after TestFlight upload (2.0 beta)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-13 21:54:33 +02:00
alexandrev-tibco 4ffa15b06a 2.0: CloudKit private-database sync replaces KV snapshot sync
Foundation for 2.1 family sharing (CKShare needs these models + container).
SwiftData cannot share across Apple IDs today, so 2.0 ships true multi-device
sync for the same account instead:

- Models made CloudKit-compatible: dropped @Attribute(.unique) on all 5,
  inline defaults on every attribute, WeekPlan.slots stored as optional
  relationship (name preserved → lightweight migration) with non-optional
  slotList facade; ~73 call sites renamed.
- Container: cloudKitDatabase .automatic, falling back to the local-only
  store when CloudKit is unavailable; failures recorded to Crashlytics.
- Entitlements: iCloud CloudKit service (container already existed);
  remote-notification background mode for push-driven sync.
- Legacy KV snapshot sync (ICloudSyncService) stays inert when CloudKit is
  active — kept only for 1.x devices.
- DeduplicationService collapses cross-device duplicates deterministically on
  launch (settings singleton, default tags with tagId remapping, same-week
  plans).
- Note: AppSettings.isPremium now syncs across same-Apple-ID devices; StoreKit
  (PremiumSyncService + Transaction.updates) remains the source of truth and
  reconciles on every launch/foreground.

All 21 unit tests pass, including ICloudSyncPremiumIsolationTests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 18:13:45 +02:00
alexandrev-tibco e15ff93465 2.0: breakfast and snack meal types across the app
- MealType gains breakfast/snack (chronological order drives slot/row order).
- AppSettings.activeMealTypes: single source of truth for enabled meals, backed
  by new optional enabledMealTypesRaw with legacy mealWindows fallback —
  additive migration, keeps pre-2.0 installs and iCloud snapshots working.
  Legacy field kept coherent by the setter. time(for:) resolves per-meal event
  times (breakfast 8:00 / snack 17:00 defaults on old stores).
- Replaced the 5 duplicated mealWindows→[MealType] derivations (slot sync,
  default plan creation, week calendar, export view) with activeMealTypes.
- CalendarService: per-type event names and times.
- Export styles: per-type accent colors and gradients.
- Widget: generic meals list (N rows) instead of hardcoded lunch/dinner;
  compact families fall back to main meals when >2 are active.
- Settings: 4 meal toggles (min 1) replace the 3-option picker.
- Onboarding: meal step is now multi-select over the 4 types.
- iCloud sync: enabledMealTypesRaw synced (optional, pre-2.0 compatible).
- Localized breakfast/snack in all 6 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 18:00:50 +02:00
alexandrev-tibco 98230c732d 2.0: dish editor gains photo picker and ingredients section
- PhotosPicker with downscaling (1024pt max, JPEG 0.8) before storing in
  photoData; change/remove actions.
- Ingredients: editable line list (add/edit/remove) + on-device "Generate"
  from the dish name (hidden when Foundation Models unavailable; paywall
  after the free generation quota, source=ingredient_generation).
- DishViewModel: drafts ingredients/photo, cleans empty lines on save.
- Localized in all 6 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 17:51:33 +02:00
alexandrev-tibco ce88d7b265 2.0: weekly shopping list from planned dishes
- ShoppingItem model (per-week lines; dish-derived or free-text) + schema.
- ShoppingListService.reconcile mirrors planned dishes' ingredients into the
  list, preserving check-off state and user items; plain-text export helper.
- ShoppingListView: grouped by dish, check-off, swipe-delete, free-text adds,
  clear-checked, ShareLink export, and inline on-device "Generate" for planned
  dishes without ingredients (paywall after 3 free generations,
  source=ingredient_generation).
- Home toolbar: cart entry point (all users) + sheet.
- Analytics: shopping_list_opened, shopping_item_added, ingredients_generated.
- Localized in all 6 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 17:47:41 +02:00
alexandrev-tibco a5c304e209 2.0: Dish gains optional ingredients + photo; on-device ingredient generation
- Dish: ingredients [String] (default [], never required) and photoData
  (externalStorage). Additive SwiftData migration — existing stores unaffected.
- ICloudSyncService: sync ingredients (optional in payload so pre-2.0 snapshots
  still decode); photos deliberately excluded from KV sync (1MB limit).
- IngredientGenerator: Foundation Models (iOS 26+) generates localized
  ingredient lines from the dish name, fully on-device. isAvailable gates the
  UI affordance on older OS/devices; failures record to Crashlytics and
  return [].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 17:41:46 +02:00
alexandrev-tibco c6c33d8143 2.0: bump MARKETING_VERSION 1.2.1 → 2.0 (major feature release: ingredients + shopping list, photos, breakfast/snacks, CloudKit family sharing)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 17:37:21 +02:00
alexandrev-tibco 8ba8e4e933 1.2.1: bump MARKETING_VERSION 1.2.0 → 1.2.1 (1.2.0 shipped to App Store)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-11 10:59:43 +02:00
alexandrev-tibco 6ba71e6341 1.2.1: surface "Copy previous week" on the empty-week banner
The exact-copy-from-previous-week action already existed but was buried in the
magic-wand long-press context menu. Expose it as a visible secondary button on
the auto-assign banner (shown only when the previous week actually has a menu),
so users landing on a fresh week can one-tap copy last week's plan and then tweak
it. Confirms before overwriting a week that already has dishes.

- Extract requestCopyPreviousWeek() shared by the menu and the banner.
- Add week_copied_previous analytics (copied_slots + source: empty_banner|menu).
- Reuses the existing localized home_copy_previous_week string (all 6 languages).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-11 10:58:53 +02:00
alexandrev-tibco c5fd1088cd 1.2.0: bump build 55 → 56 after TestFlight upload (Crashlytics non-fatals)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-10 14:37:32 +02:00
alexandrev-tibco 56abe28a82 1.2.0: report non-fatal errors to Crashlytics across key error paths
CrashlyticsService was wired only for the is_premium custom key. Route the
swallowed errors in the main catch blocks through CrashlyticsService.record
with a context tag, so non-fatals show up in Firebase (StoreKit load/purchase/
restore, calendar access/create/update/delete, reset-all-data, onboarding save).
Debug prints kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-10 14:27:28 +02:00
alexandrev-tibco 313403ecd5 1.2.0: bump version 1.1.5 → 1.2.0, build 54 → 55 after TestFlight upload
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-03 10:19:43 +02:00
alexandrev-tibco cbe0ca1326 1.2.0: offer code redemption + share link alongside export image
- "Redeem promo code" button in PremiumView via offerCodeRedemption
  (hidden on Mac where the sheet is unavailable); entitlement re-checked
  on success — unblocks influencer/blog promo campaigns
- System share sheet now includes a localized text with the mealmood.app
  link next to the exported image (viral loop no longer QR-only)
- 2 new strings × 6 languages

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-03 10:14:58 +02:00
alexandrev-tibco f5cb67adc0 1.2.0: widget deep-link, Lock Screen widgets, in-app widget promo
- Tapping the widget now deep-links via mealmood://today back to the current
  week (URL scheme registered, onOpenURL handler in Home)
- New Lock Screen widget families: accessoryRectangular (weekday + both
  meals) and accessoryInline (next relevant meal)
- One-time dismissable widget promo card on Home from the 2nd session,
  with widget_promo_shown/dismissed analytics
- 2 new strings × 6 languages

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-03 10:11:40 +02:00
alexandrev-tibco 73c2039338 1.2.0: contextual notifications + in-onboarding auto-fill (aha moment)
- New "Week Ready" onboarding step: auto-fills the first week inline right
  after dish creation, then pitches the Sunday reminder with context before
  requesting OS notification permission (no more cold prompt on Home)
- Notifications section in Settings: weekly reminder toggle, deep-link to
  system settings when permission is denied
- NotificationService: remindersEnabled preference, requestPermission(),
  authorizationStatus(), cancelAllReminders()
- Post-onboarding premium prompt deferred to 2nd session (paywall was just
  shown as the final onboarding step — 2s-later alert was prompt fatigue)
- Back navigation blocked after onboarding commit to avoid duplicate dishes
- 10 new strings × 6 languages

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-03 10:08:04 +02:00
alexandrev-tibco 13ece5f2e7 1.1.5: bump build 53 → 54 after TestFlight upload
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-02 13:31:39 +02:00
alexandrev-tibco 9cadc1ad18 1.1.5: fix activation analytics blind spots + purchase environment tagging
- dish_added now carries a `source` param; onboarding-created dishes fire
  it with source="onboarding" (previously untracked, undercounting activation)
- onboarding_completed now reports dish_count
- premium_purchased tagged with StoreKit environment (production/sandbox/xcode)
  so test purchases can be excluded from conversion metrics
- add .gitignore to keep signing secrets (.p12/.cer/.mobileprovision) and
  build artifacts out of the repo
- document branch↔MARKETING_VERSION naming convention

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
2026-07-02 13:26:45 +02:00
alexandrev-tibco caf25bc33f 1.1.5: auto-fill first week after onboarding
Land new users on a ready, auto-planned week (with confetti) instead of an
empty one, to improve activation. Only fires when the user added dishes;
otherwise keeps the current empty-week + auto-assign prompt behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 08:02:15 +02:00
alexandrev-tibco 82d623aa05 1.1.5: bump build 51 → 52 after TestFlight upload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 22:27:04 +02:00
alexandrev-tibco b45cb2a530 1.1.5: conversion funnel overhaul
Paywall:
- Redesigned PremiumView with yearly/monthly/lifetime side by side
- 7-day free trial badge with savings % vs monthly
- Social proof line, BEST VALUE highlight on yearly
- StoreManager: new yearlyProduct, lifetimeProduct, yearlySavingsPercent, hasTrialAvailable

Funnel:
- Paywall step added to onboarding (PaywallStepView)
- Contextual modal at dish limit (replaces silent block)
- Visible "X/15 dishes" counter in DishListView
- BottomPromoBanner rotates AdMob with internal "Remove ads" CTA (1 in 4)
- Free dish limit lowered 20 → 15
- Review prompt earlier: 2 weeks → 1 week

Analytics:
- paywall_viewed/dismissed with source + seconds_on_screen
- dish_limit_hit, week_limit_hit, free_trial_started events
- Every PremiumView call site now passes its source

StoreKit:
- MealMood.storekit: added yearly $19.99 + 7d trial, monthly trial, lifetime $39.99
- AppStoreConnect-1.1.5-setup.md with full manual setup instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 22:21:36 +02:00
alexandrev-tibco 72770801d3 1.1.5: add analytics for stats_viewed and week_rated events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:49:33 +02:00
alexandrev-tibco f4e1aef8d5 1.1.4 (50): TestFlight upload — switch to ASC API key auth
Build 50 uploaded to TestFlight. Also migrates Fastfile from
Apple ID + app-specific-password to App Store Connect API key
(via pass) to avoid session expiry on future uploads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:36:49 +02:00
alexandrev-tibco 304774991d 1.1.4 (49): bump version for TestFlight
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:26:13 +02:00
alexandrev-tibco 84b74f9ea4 Autocomplete feedback loop: implicit rejection + weekly rating
A) Implicit: HomeViewModel tracks auto-assigned slot IDs after each
   autocomplete run. When the user manually replaces one of those dishes,
   the rejected dish ID is recorded in FeedbackStore (UserDefaults).
   AutocompleteEngine applies a 25% penalty per rejection (floor 0.30).

B) Explicit: WeekPlan gains userRating (0/1/-1). Past weeks with dishes
   show a compact 👍/👎 row (Premium only). Liked weeks boost their
   dishes (+0.20 each, max +0.50); disliked weeks penalise them
   (-0.25 each, max -0.50). Signal is intentionally modest so the
   period cycle score remains dominant.

Also adds Stats + week-rating localization strings to all 6 languages
(were missing from the previous commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:09:15 +02:00
alexandrev-tibco e13771fa06 Autocomplete: period-based dish scoring
Replace flat recency penalty with cycle detection. For each dish with
≥2 appearances in history, compute the median interval between uses and
score by how well weeksSinceLast aligns with that period:
  ratio < 0.5  → 0.10 (too soon)
  ratio ~1.0   → 3.00 (right on schedule, boost)
  ratio > 2.5  → 1.00 (pattern may have changed, neutral)

Expand history window from 4 to 12 weeks so monthly cycles (period=4)
have enough data points to be detected reliably.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 16:30:22 +02:00
alexandrev-tibco 330f21a019 1.1.0: Smarter autocomplete + Statistics view
- AutocompleteEngine: MRV heuristic picks the most-constrained slot first
  at each step; historical scoring deprioritises dishes used in recent
  weeks (decay: 0.20 last week → 0.90 four+ weeks ago)
- HomeViewModel: passes last 4 weeks as recentPlans to autocomplete
- StatsView (Premium): streak, weeks planned, completion rate, top 8
  dishes with bar charts, top 6 tags with colour bars — 6 languages
- StoreManager: update product ID to approved bundle-ID convention
- Version bump to 1.1.3 / build 48

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:05:39 +02:00
alexandrev-tibco d09e8a7bee 1.1.2 (47): fix subscription purchase button fallback
Remove legacy CONSUMABLE fallback from monthlyProduct — when the
subscription fails to load, the UI now shows the retry state instead
of silently offering the legacy one-time product for purchase.
Legacy purchasers retain access via hasActiveSubscription() as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:58:57 +02:00
alexandrev-tibco 89b35dc4ae 1.1.1 (46): subscription IAP, export compliance, metadata
- Switch premium to AutoRenewable subscription (com.mealmood.premium.monthly.sub);
  legacy one-time purchasers retain lifetime access via StoreManager fallback
- Add ITSAppUsesNonExemptEncryption=false to Info.plist (no custom encryption)
- Update storekit config with both legacy NonConsumable and new subscription
- Bump version 1.1.1 build 46; update fastlane metadata and release notes (6 langs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:28:14 +02:00
alexandrev-tibco 7da7995538 Fix SwiftData migration crash on schema upgrades
Add = false/true defaults to all non-optional Bool properties so
SwiftData's lightweight migration knows what value to assign existing
rows. Replace try! ModelContainer with crash-safe do-catch that resets
the store on migration failure instead of crashing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 11:24:56 +02:00
alexandrev-tibco 4ac81d6223 1.0.6: Fix widget version, bump to build 41, fix Fastfile signing
- MealMoodWidget CFBundleShortVersionString: 1.0.3 → 1.0.6 (fixes ITMS-90473)
- App + Widget build number: 40 → 41 (App Store release build)
- Fastfile: add manageAppVersionAndBuildNumber: false to EXPORT_OPTIONS
  to prevent xcodebuild from requiring App Store Connect during export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 08:36:31 +02:00