diff --git a/AGENTS.md b/AGENTS.md index cdbba5b..c8f37ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,142 +1,272 @@ -# TabataGo — iOS Tabata Workout App +# TabataGo — Référence canonique pour agents IA -> Tabata interval training app for iOS + watchOS with HealthKit integration. +> **Une seule source de vérité.** Ce document décrit le projet *réel*. Si un autre +> fichier (CLAUDE.md, schema.sql) contredit, ce document gagne. -## Agent Context +--- -This project is part of the **Millian Team** — an AI agent engineering team. -When working here, agents should load the relevant skill: -- `senior-ios` — SwiftUI, Xcode, iOS/watchOS development -- `senior-backend` — Supabase backend -- `senior-devops` — Deploy infrastructure -- `po-pm` — Product vision, specs, validation +## 0. ⚠️ Fichiers obsolètes — NE PAS LIRE -## Tech Stack +| Fichier | Statut | Pourquoi | +|---------|--------|----------| +| `CLAUDE.md` (racine) | ❌ OBSOLÈTE | Décrit **TabataFit**, un concept Expo/React Native abandonné | +| `supabase/schema.sql` | ❌ OBSOLÈTE | Header littéral "TabataFit" — décrit l'ancien schéma | +| `supabase/seed.sql`, `supabase/setup-admin.sql` | ⚠️ Vérifier | Peuvent être périmés ; préférer `supabase/migrations/` | +| `AGENTS.md` (ancien, racine) | 🔄 Remplacé par celui-ci | Mentionne PostHog 3.x comme actif (faux, voir §3) | -| Layer | Tech | -|-------|------| -| App | SwiftUI, iOS 26.0+ | -| Watch | SwiftUI, watchOS 11.0+ | -| Widget | WidgetKit, Live Activities | -| Language | Swift 6.0 (strict concurrency) | -| Architecture | MVVM, @Observable, AppState central | -| Backend | Supabase (PostgreSQL, Auth, Storage) | -| Subscriptions | RevenueCat 5.x | -| Analytics | PostHog 3.x | -| Health | HealthKit (heart rate, calories, workouts) | -| Music | Apple Music integration + YouTube worker | -| Build | XcodeGen (project.yml → .xcodeproj) | -| Package Manager | Swift Package Manager | +**Source de vérité du schéma : `supabase/migrations/001` → `006`.** Jamais `schema.sql`. -## Project Structure +--- + +## 1. Identité produit + +| | | +|---|---| +| **Nom** | TabataGo | +| **Bundle** | `fr.millianlmx.tabatago` | +| **Team** | `2MJF39L8VY` | +| **But** | App d'entraînement Tabata (intervalles 20s travail / 10s repos) sur iOS + watchOS, avec programmes par zone corporelle, HealthKit, musique, et abonnements RevenueCat | +| **Version** | 1.0 (build 2) — non publiée App Store | +| **Propriétaire** | Millian LMX (CEO) | +| **Gitea** | `https://gitea.1000co.fr/millianlmx/tabatago` | + +--- + +## 2. Stack technique + +| Couche | Réalité vérifiée | +|--------|------------------| +| Langage | **Swift 6.0**, concurrence stricte (`SWIFT_STRICT_CONCURRENCY: complete`) | +| UI | **SwiftUI** uniquement | +| iOS | **26.0+** | +| watchOS | **11.0+** | +| Build | **XcodeGen** — `project.yml` est la source, `.xcodeproj` est généré | +| SPM | **Supabase** `2.5.0+`, **RevenueCat** `5.0.0+` | +| ❌ NON installé | **PostHog** — absent de `project.yml` packages. Voir §3. | +| Architecture | **MVVM + `@Observable`**, `AppState.shared` central | +| Backend | **Supabase** (PostgreSQL, Auth, Storage, Edge Functions Deno) | +| Local cache | **SwiftData** (`TabataGoSchema.container`) | +| Santé | **HealthKit** (fréquence cardiaque, calories, workouts) | +| CI/CD | **Gitea Actions** sur runner macOS auto-hébergé (label `macos`) | + +--- + +## 3. Architecture iOS + +### Cibles (`tabatago-swift/project.yml`) + +| Cible | Type | Plateforme | Bundle ID | +|-------|------|------------|-----------| +| `TabataGo` | application | iOS 26.0 | `fr.millianlmx.tabatago` | +| `TabataGoWatch` | application | watchOS 11.0 | `fr.millianlmx.tabatago.watchkitapp` | +| `TabataGoWatchWidget` | app-extension | watchOS 11.0 | `fr.millianlmx.tabatago.watchkitapp.widget` | +| `TabataGoTests` | bundle.unit-test | iOS | `.tests` | +| `TabataGoUITests` | bundle.ui-testing | iOS | `.uitests` | + +**Dépendances d'intégration** : `TabataGo` embed `TabataGoWatch` qui embed `TabataGoWatchWidget`. +Fichier partagé entre iOS et watch : `TabataGo/Services/WatchConnectivityTypes.swift` (compilé dans les deux targets via `group: TabataGoWatch/Services`). + +### Bootstrap & data flow ``` -tabatago/ -├── tabatago-swift/ # Main Xcode project -│ ├── project.yml # XcodeGen spec -│ ├── Config/ -│ │ ├── Secrets.xcconfig # API keys (not committed) -│ │ └── Secrets.xcconfig.example -│ ├── TabataGo/ # iOS App -│ │ ├── App/ # App entry, state, root view -│ │ │ ├── TabataGoApp.swift -│ │ │ ├── RootView.swift -│ │ │ └── AppState.swift -│ │ ├── Views/ -│ │ │ ├── Tabs/ # MainTabView, Home, Activity, Programs, Profile -│ │ │ ├── Player/ # Workout player (PlayerView, NowPlaying) -│ │ │ ├── Programs/ # ProgramDetail, BodyZone -│ │ │ ├── Onboarding/ # Onboarding flow -│ │ │ ├── Paywall/ # RevenueCat paywall -│ │ │ ├── Complete/ # Workout completion screen -│ │ │ ├── Settings/ # Settings, policies -│ │ │ └── Components/ # Shared UI components -│ │ ├── ViewModels/ # HomeVM, HealthVM, PlayerVM, PurchaseVM, MusicVM -│ │ ├── Models/ # WorkoutProgram, WorkoutSession, UserProfile, etc. -│ │ ├── Services/ # Supabase, HealthKit, Music, Audio, Purchase, Analytics -│ │ ├── Theme/ # App theme -│ │ ├── Utilities/ # Strings, Environment -│ │ └── Resources/ # Assets, Localizable, entitlements -│ ├── TabataGoWatch/ # watchOS App -│ │ ├── Views/ # WatchRoot, WatchIdle, WatchActivity, WatchPlayer -│ │ ├── Services/ # WatchConnectivity, WatchPlayerEngine -│ │ ├── Complications/ # Watch face complications -│ │ └── Resources/ -│ ├── TabataGoWidget/ # iOS Widget + Live Activities -│ ├── TabataGoTests/ # Unit tests -│ └── TabataGoUITests/ # UI tests -├── supabase/ # Supabase config -│ ├── schema.sql -│ ├── seed.sql -│ ├── setup-admin.sql -│ └── migrations/ # SQL migrations (001-006) -└── youtube-worker/ # Node.js worker for YouTube music - ├── server.js - ├── package.json - └── Dockerfile +TabataGoApp (@main) + └─ RootView() + ├─ .environment(AppState.shared) // @Observable singleton + ├─ .modelContainer(TabataGoSchema.container) // SwiftData + └─ .task { await AppState.shared.bootstrap() } ``` -## Dependencies (SPM) +`AppState.bootstrap()` (MainActor, idempotent, skip en preview) : +1. `PurchaseService.shared.initialize()` (RevenueCat) +2. `AnalyticsService.shared.initialize()` — ⚠️ **no-op par défaut** car PostHog n'est pas dans SPM : tout est gardé par `#if canImport(PostHog)` qui est toujours faux. La taxonomie d'events existe mais n'émet rien tant que la dépendance n'est pas ajoutée. -- **Supabase** 2.5+ — Backend SDK (Auth, Database, Storage) -- **RevenueCat** 5.0+ — In-app purchases -- **PostHog** 3.0+ — Analytics +### Navigation (PAS de NavigationStack) -## Features +`MainTabView` = **`TabView` + `Tab(value:)`** (Liquid Glass, iOS 26), 4 onglets dans cet ordre exact du code : -- Tabata/interval workout player with configurable timers -- Pre-built workout programs by body zone -- HealthKit integration (heart rate, calories, workout saving) -- Apple Watch companion with real-time metrics -- Live Activities + Dynamic Island -- Music integration (Apple Music + YouTube) -- RevenueCat subscriptions -- Onboarding flow -- Activity tracking & workout history +``` +home → programs → activity → profile +``` -## Commands +Modals full-screen via `.sheet`/`fullScreenCover` au-dessus : +`PlayerView`, `CompletionView`, `PaywallView`, `OnboardingView`. + +### Services (`TabataGo/Services/`) + +| Service | Rôle | +|---------|------| +| `SupabaseService` | Client Supabase (Auth, DB, Storage) | +| `HealthKitService` | Lecture/écriture HealthKit | +| `PurchaseService` | RevenueCat IAP, état d'abonnement | +| `MusicService` | Apple Music + intégration piste YouTube | +| `AudioService` | Playback audio (coachs sonores, alerts) | +| `AnalyticsService` | **Stub PostHog** (voir ci-dessus) | +| `PhoneConnectivityManager` | Côté iOS — WatchConnectivity (échange avec la montre) | +| `WatchConnectivityTypes` | Protocoles/messages partagés iOS↔Watch (compilé dans les 2 targets) | + +> Le miroir côté watch est `TabataGoWatch/Services/WatchConnectivityManager.swift` (cible watchOS, pas iOS). + +### ViewModels (`@Observable`) + +`HomeViewModel`, `HealthViewModel`, `PlayerViewModel`, `PurchaseViewModel`, `MusicPlayerViewModel`. + +### Models (`TabataGo/Models/`) + +`WorkoutProgram`, `WorkoutSession`, `UserProfile`, `HealthSnapshot`, `MusicTrack`, `WorkoutActivityAttributes`, `MusicActivityAttributes` (Live Activities), `TabataGoSchema` (SwiftData), `PreviewData`/`MockPrograms` (previews). + +### Watch (`TabataGoWatch/`) + +`TabataGoWatchApp` → `WatchRootView` → états `WatchIdleView` / `WatchActivityView` / `WatchPlayerView`. +Moteur : `WatchPlayerEngine`. Connectivité : `WatchConnectivityManager` (coté watch). +Complications : `TabataGoComplication`. + +--- + +## 4. Base de données + +### Supabase — source = `supabase/migrations/` (001→006) + +| # | Fichier | Contenu | +|---|---------|---------| +| 001 | `001_initial_schema.sql` | `trainers`, `workouts`, `collections`, `achievements`, `admin_users` | +| 002 | `002_download_jobs.sql` | `download_jobs`, `download_items` (jobs d'import playlist YouTube) | +| 003 | `003_music_genre.sql` | `music_genre` | +| 004 | `004_download_items_public_read.sql` | RLS : lecture publique de `download_items` | +| 005 | `005_workout_programs.sql` | **DROP** `programs`/`program_workouts` → **CREATE** `workout_programs` (body_zone enum `upper-body`/`lower-body`/`full-body` + level `Beginner`/`Intermediate`/`Advanced`) et `program_tabatas` (3 tabatas/program, 2 exercices chacun, 8 rounds × 20s/10s) + RLS public read + admin all | +| 006 | `006_seed_workout_programs.sql` | **18 programmes seedés** (12 free : 4 par zone en 2B+1I+1A ; 6 premium) + **54 tabatas** (18×3), UUIDs stables | + +**Tables actives post-migration** : `trainers`, `workouts`, `collections`, `achievements`, `admin_users`, `download_jobs`, `download_items`, `music_genre`, `workout_programs`, `program_tabatas`. + +**Tables supprimées (ne pas recréer)** : `programs`, `program_workouts` (DROP en 005). + +**RLS** : lecture publique sur `workout_programs`/`program_tabatas`/`download_items` ; écriture restreinte aux `admin_users` (`EXISTS (SELECT 1 FROM admin_users WHERE id = auth.uid())`). + +### SwiftData — cache local + +`TabataGoSchema.container` (et `.previewContainer` pour les `#Preview`). Persistance offline des sessions/programmes côté app. Ne pas confondre avec Supabase. + +--- + +## 5. CI/CD + +### Workflow : `.gitea/workflows/pr-iphone-deploy.yml` + +**Trigger** : PR ouverte/synchronize/reopened sur `main`. +**Runner** : label `macos` (auto-hébergé). +**Déroulé** : + +1. **Checkout** (shallow, branche head) via `PR_API_TOKEN`. +2. **Setup PATH** : `echo "/opt/homebrew/bin" >> $GITHUB_PATH` — requis car Rosetta ne le voit pas. +3. **Clean ciblé** : + - Supprime build artifacts, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache. + - **GARDE le cache SPM** (`../build/spm-cache`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. +4. **Install tools** : `brew install xcodegen node ios-deploy`. +5. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). +6. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB. +7. **Post comment** "Prêt à tester" sur la PR. +8. **Job `wait-approval`** : poll (30s, max 240 = 2h) les commentaires de la PR. `LGTM` → auto-merge. `KO` → blocage. Timeout → fail. + +### Secrets + +| Secret | Usage | +|--------|-------| +| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`** | +| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Injectés via `Config/Secrets.xcconfig` → Info.plist | + +### ⚠️ Pitfalls CI + +- `/opt/homebrew/bin` **hors PATH par défaut** sous Rosetta → toujours l'ajouter. +- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). +- **Ne jamais `rm -rf` le cache SPM** dans le CI (c'est l'inverse des build artifacts). +- `SWIFT_ENABLE_EXPLICIT_MODULES=NO` au build — requis sinon segfault linker sur `.pcm` stale. +- `-skipPackagePluginValidation -allowProvisioningUpdates`. + +--- + +## 6. Sous-projets + +### `admin-web/` — Dashboard admin + +Next.js 15 App Router, shadcn/ui. Gestion workouts / trainers / collections / programs. +Auth Supabase → table `admin_users`. Stack : TypeScript, `middleware.ts`, tests Playwright (`e2e/`) + Vitest. + +### `youtube-worker/` — Worker YouTube + +Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de playlists YouTube → **Supabase Storage**. Piloté par les Edge Functions ci-dessous. + +### `supabase/functions/` — Edge Functions (Deno) + +| Function | Rôle | +|----------|------| +| `youtube-playlist/` | Crée un `download_job`, liste les vidéos | +| `youtube-process/` | Orchestre le téléchargement (via youtube-worker) | +| `youtube-status/` | Statut d'un job | +| `youtube-classify/` | Classification genre musical | +| `main/` | Auth JWT (helper `jose`) | +| `_shared/` | `auth.ts`, `cors.ts`, `supabase-client.ts`, `youtube-client.ts` | + +> Le pipeline musical complet : Edge Functions orchestrent → `youtube-worker` (Node.js) télécharge via **yt-dlp** + **Innertube** → audio dans Supabase Storage (`workout-audio` bucket) → classification automatique par **Gemini** (`gemini-3.1-flash-lite-preview`) → métadonnées dans `download_jobs`/`download_items` → genre dans `music_genre`. + +--- + +## 7. Règles d'or pour les agents + +1. **Source de vérité = `project.yml` + `supabase/migrations/`.** Tout le reste est dérivé ou obsolète. +2. **SwiftUI only.** Pas d'UIKit sauf nécessité absolue démontrée. +3. **`@Observable`** (macro Observation), **jamais `@ObservableObject`/`@Published`**. +4. **Concurrency stricte.** `async/await` partout, `@MainActor` sur l'UI, pas de completion handlers. +5. **Pas de force-unwrap.** `guard let`/`if let`, jamais `!`. +6. **Navigation = TabView + sheet/fullScreenCover.** **Pas de `NavigationStack`.** +7. **Tout en français** côté user-facing (`Localizable.xcstrings` / `L10n`). Codes/types restent en anglais. +8. **XcodeGen.** Éditer `project.yml`, pas `.xcodeproj`. Régénérer avec `xcodegen generate`. +9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. +10. **Lazy d'abord.** Avant d'ajouter une dépendance : stdlib Apple, puis ce qui existe déjà dans le repo, puis SPM déjà installé. PostHog n'est pas installé — ne pas l'ajouter sans justification. + +--- + +## 8. Anti-patterns / pièges documentés + +| ❌ Ne pas faire | ✅ Faire | +|-----------------|---------| +| Lire `CLAUDE.md` ou `schema.sql` pour le schéma | Lire `supabase/migrations/001→006` | +| Importer PostHog comme si c'était actif | Savoir que `AnalyticsService` est un **no-op** (PostHog pas en SPM) | +| Utiliser `NavigationStack` | `TabView` + modals | +| `@ObservableObject` / `@Published` | `@Observable` | +| Force-unwrap `!` | `guard let` | +| Strings UI en anglais | Françaises dans `L10n`/`Localizable.xcstrings` | +| `rm -rf` le cache SPM en CI | Garder `build/spm-cache` | +| Utiliser `GITEA_TOKEN` en CI | `PR_API_TOKEN` uniquement | +| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas | +| Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement | +| Recréer `programs`/`program_workouts` | Remplacés par `workout_programs`/`program_tabatas` (migration 005) | + +--- + +## 9. Contexte projet & skills + +- **Repo Gitea** : `https://gitea.1000co.fr/millianlmx/tabatago` (branche intégration : `main`). +- **Équipe** : "Millian Team" — équipe d'agents IA. Skills à loader selon la tâche : + - `senior-ios` — SwiftUI, Xcode, iOS/watchOS + - `senior-backend` — Supabase, SQL, Edge Functions + - `senior-devops` — CI/CD Gitea Actions, deploy + - `po-pm` — vision produit, specs, validation +- **Team skill TabataGo** : documente les anti-patterns ci-dessus ; aligne tous les agents sur les mêmes conventions. +- **Hermes profile actif** : `default`. Les skills vivent dans le profile sous `skills/`. +- **Docs utiles** : `docs/ci-cd-setup.md`, `docs/app-store-submission.md`, `docs/maestro-e2e-testing-strategy.md`, `docs/ui-feature-brief.md`. +- **Scripts** : `scripts/ci-status.py`, `scripts/deploy-functions.sh`. + +### Commandes usuelles ```bash -# From tabatago-swift/ -xcodegen generate # Generate .xcodeproj -open TabataGo.xcodeproj # Open in Xcode +# Générer le projet Xcode +cd tabatago-swift && xcodegen generate -# In Xcode -Cmd+R → Run on simulator -Cmd+U → Run tests +# Ouvrir +open tabatago-swift/TabataGo.xcodeproj + +# Déployer les Edge Functions +bash scripts/deploy-functions.sh + +# Statut CI +python3 scripts/ci-status.py ``` - -## Configuration - -- `Config/Secrets.xcconfig` contains API keys (not committed) -- Template: `Config/Secrets.xcconfig.example` -- Env vars: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` - -## Current State - -- **Version**: 1.0 (build 2) — not yet released -- **State**: App functional, needs finishing work -- **Platforms**: iOS 26.0+, watchOS 11.0+ -- **Requires**: macOS with Xcode 26+ to build - -## Supabase Schema - -Tables: `profiles`, `workout_sessions`, `workout_programs`, `download_jobs`, `music_genre`, etc. -See `supabase/schema.sql` and `supabase/migrations/` for full schema. - -## Team Rules - -1. **Swift Concurrency** — async/await everywhere, no completion handlers -2. **@Observable** — not @ObservableObject -3. **No force unwrap** — `guard let` always, never `!` -4. **SwiftUI only** — no UIKit unless unavoidable -5. **XcodeGen** — project.yml is source of truth, not .xcodeproj -6. **Secrets** — never commit Secrets.xcconfig -7. **French** — all user-facing strings in French (Localizable.xcstrings) - -## Project Context - -- **User**: Millian LMX (CEO) -- **Gitea**: https://gitea.1000co.fr/millianlmx/tabatago -- **Supabase**: Managed instance (URL in secrets) -- **App Store**: Not yet published diff --git a/AGENTS.md.old b/AGENTS.md.old new file mode 100644 index 0000000..cdbba5b --- /dev/null +++ b/AGENTS.md.old @@ -0,0 +1,142 @@ +# TabataGo — iOS Tabata Workout App + +> Tabata interval training app for iOS + watchOS with HealthKit integration. + +## Agent Context + +This project is part of the **Millian Team** — an AI agent engineering team. +When working here, agents should load the relevant skill: +- `senior-ios` — SwiftUI, Xcode, iOS/watchOS development +- `senior-backend` — Supabase backend +- `senior-devops` — Deploy infrastructure +- `po-pm` — Product vision, specs, validation + +## Tech Stack + +| Layer | Tech | +|-------|------| +| App | SwiftUI, iOS 26.0+ | +| Watch | SwiftUI, watchOS 11.0+ | +| Widget | WidgetKit, Live Activities | +| Language | Swift 6.0 (strict concurrency) | +| Architecture | MVVM, @Observable, AppState central | +| Backend | Supabase (PostgreSQL, Auth, Storage) | +| Subscriptions | RevenueCat 5.x | +| Analytics | PostHog 3.x | +| Health | HealthKit (heart rate, calories, workouts) | +| Music | Apple Music integration + YouTube worker | +| Build | XcodeGen (project.yml → .xcodeproj) | +| Package Manager | Swift Package Manager | + +## Project Structure + +``` +tabatago/ +├── tabatago-swift/ # Main Xcode project +│ ├── project.yml # XcodeGen spec +│ ├── Config/ +│ │ ├── Secrets.xcconfig # API keys (not committed) +│ │ └── Secrets.xcconfig.example +│ ├── TabataGo/ # iOS App +│ │ ├── App/ # App entry, state, root view +│ │ │ ├── TabataGoApp.swift +│ │ │ ├── RootView.swift +│ │ │ └── AppState.swift +│ │ ├── Views/ +│ │ │ ├── Tabs/ # MainTabView, Home, Activity, Programs, Profile +│ │ │ ├── Player/ # Workout player (PlayerView, NowPlaying) +│ │ │ ├── Programs/ # ProgramDetail, BodyZone +│ │ │ ├── Onboarding/ # Onboarding flow +│ │ │ ├── Paywall/ # RevenueCat paywall +│ │ │ ├── Complete/ # Workout completion screen +│ │ │ ├── Settings/ # Settings, policies +│ │ │ └── Components/ # Shared UI components +│ │ ├── ViewModels/ # HomeVM, HealthVM, PlayerVM, PurchaseVM, MusicVM +│ │ ├── Models/ # WorkoutProgram, WorkoutSession, UserProfile, etc. +│ │ ├── Services/ # Supabase, HealthKit, Music, Audio, Purchase, Analytics +│ │ ├── Theme/ # App theme +│ │ ├── Utilities/ # Strings, Environment +│ │ └── Resources/ # Assets, Localizable, entitlements +│ ├── TabataGoWatch/ # watchOS App +│ │ ├── Views/ # WatchRoot, WatchIdle, WatchActivity, WatchPlayer +│ │ ├── Services/ # WatchConnectivity, WatchPlayerEngine +│ │ ├── Complications/ # Watch face complications +│ │ └── Resources/ +│ ├── TabataGoWidget/ # iOS Widget + Live Activities +│ ├── TabataGoTests/ # Unit tests +│ └── TabataGoUITests/ # UI tests +├── supabase/ # Supabase config +│ ├── schema.sql +│ ├── seed.sql +│ ├── setup-admin.sql +│ └── migrations/ # SQL migrations (001-006) +└── youtube-worker/ # Node.js worker for YouTube music + ├── server.js + ├── package.json + └── Dockerfile +``` + +## Dependencies (SPM) + +- **Supabase** 2.5+ — Backend SDK (Auth, Database, Storage) +- **RevenueCat** 5.0+ — In-app purchases +- **PostHog** 3.0+ — Analytics + +## Features + +- Tabata/interval workout player with configurable timers +- Pre-built workout programs by body zone +- HealthKit integration (heart rate, calories, workout saving) +- Apple Watch companion with real-time metrics +- Live Activities + Dynamic Island +- Music integration (Apple Music + YouTube) +- RevenueCat subscriptions +- Onboarding flow +- Activity tracking & workout history + +## Commands + +```bash +# From tabatago-swift/ +xcodegen generate # Generate .xcodeproj +open TabataGo.xcodeproj # Open in Xcode + +# In Xcode +Cmd+R → Run on simulator +Cmd+U → Run tests +``` + +## Configuration + +- `Config/Secrets.xcconfig` contains API keys (not committed) +- Template: `Config/Secrets.xcconfig.example` +- Env vars: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` + +## Current State + +- **Version**: 1.0 (build 2) — not yet released +- **State**: App functional, needs finishing work +- **Platforms**: iOS 26.0+, watchOS 11.0+ +- **Requires**: macOS with Xcode 26+ to build + +## Supabase Schema + +Tables: `profiles`, `workout_sessions`, `workout_programs`, `download_jobs`, `music_genre`, etc. +See `supabase/schema.sql` and `supabase/migrations/` for full schema. + +## Team Rules + +1. **Swift Concurrency** — async/await everywhere, no completion handlers +2. **@Observable** — not @ObservableObject +3. **No force unwrap** — `guard let` always, never `!` +4. **SwiftUI only** — no UIKit unless unavoidable +5. **XcodeGen** — project.yml is source of truth, not .xcodeproj +6. **Secrets** — never commit Secrets.xcconfig +7. **French** — all user-facing strings in French (Localizable.xcstrings) + +## Project Context + +- **User**: Millian LMX (CEO) +- **Gitea**: https://gitea.1000co.fr/millianlmx/tabatago +- **Supabase**: Managed instance (URL in secrets) +- **App Store**: Not yet published diff --git a/CLAUDE.md b/CLAUDE.md.obsolete similarity index 100% rename from CLAUDE.md rename to CLAUDE.md.obsolete diff --git a/admin-web/AUTH_SETUP.md b/admin-web/AUTH_SETUP.md deleted file mode 100644 index 1e763f9..0000000 --- a/admin-web/AUTH_SETUP.md +++ /dev/null @@ -1,101 +0,0 @@ -# Authentication Setup Guide - -## Overview -This guide sets up full server-side authentication for the TabataFit Admin panel. - -## Prerequisites -- Supabase project running (local or hosted) -- Admin-web Next.js app running -- Database schema already applied - -## Step 1: Configure Supabase Dashboard - -1. Go to your Supabase Dashboard → Authentication → Providers -2. **Enable Email Provider** -3. **Disable "Confirm email"** (for easier testing, re-enable for production) -4. Go to Authentication → URL Configuration -5. Set **Site URL**: `http://localhost:3000` (or your production URL) -6. Add **Redirect URLs**: `http://localhost:3000/**` - -## Step 2: Create Admin User - -### Option A: Via Supabase Dashboard (Easiest) -1. Go to Supabase Dashboard → Authentication → Users -2. Click "Add user" or "Invite user" -3. Enter email: `admin@tabatafit.com` -4. Set password: Your chosen password -5. Click "Create user" -6. Copy the user's UUID (click on the user to see details) - -### Option B: Via Login Page -1. Go to `http://localhost:3000/login` -2. Click "Sign up" (if available) or use dashboard method above - -## Step 3: Add User to Admin Table - -**Important**: After creating the user, you MUST add them to the admin_users table. - -Run this SQL in Supabase SQL Editor: - -```sql --- Replace with your actual email or UUID -INSERT INTO public.admin_users (id, email, role) -SELECT id, email, 'admin' -FROM auth.users -WHERE email = 'admin@tabatafit.com' -ON CONFLICT (id) DO NOTHING; -``` - -Or if you have the UUID: -```sql -INSERT INTO public.admin_users (id, email, role) -VALUES ('paste-uuid-here', 'admin@tabatafit.com', 'admin'); -``` - -## Step 4: Verify Setup - -Run this to confirm: -```sql -SELECT au.id, au.email, au.role, u.email as auth_email -FROM public.admin_users au -JOIN auth.users u ON au.id = u.id; -``` - -You should see your admin user listed. - -## Step 5: Login - -1. Go to `http://localhost:3000/login` -2. Enter email: `admin@tabatafit.com` -3. Enter password: (the one you set) -4. You should be redirected to the dashboard - -## Troubleshooting - -### "Not authorized as admin" error -- User exists in auth.users but not in admin_users table -- Run Step 3 SQL to add them - -### "Failed to fetch" errors -- Check that EXPO_PUBLIC_SUPABASE_URL is set in .env.local -- For admin-web, also add NEXT_PUBLIC_SUPABASE_URL with same value - -### Still can't create workouts -- Verify you're logged in (check browser cookies) -- Check that admin_users table has your user -- Check RLS policies are applied correctly - -## Default Credentials (Example) - -**Email**: `admin@tabatafit.com` -**Password**: (You choose during setup) - -**Note**: Change this to a secure password in production! - -## Security Notes - -1. **Production**: Enable email confirmations -2. **Production**: Use strong passwords -3. **Production**: Enable 2FA if available -4. **Production**: Restrict CORS origins in Supabase -5. **Production**: Use environment-specific admin credentials \ No newline at end of file diff --git a/admin-web/TEST_IMPLEMENTATION.md b/admin-web/TEST_IMPLEMENTATION.md deleted file mode 100644 index 05eed31..0000000 --- a/admin-web/TEST_IMPLEMENTATION.md +++ /dev/null @@ -1,218 +0,0 @@ -# TabataFit Admin Dashboard - Test Implementation Summary - -## ✅ Completed Implementation - -### 1. Unit Tests - -#### UI Components (100% Coverage) -- **button.test.tsx** ✓ (already existed) -- **select.test.tsx** ✓ (already existed) -- **switch.test.tsx** ✓ (already existed) -- **dialog.test.tsx** ✓ **NEW** - 8 test cases - - Dialog rendering - - Open/close behavior - - Click outside to close - - Escape key handling - - Footer rendering - - Custom styling - - Accessibility features - -#### Custom Components -- **exercise-list.test.tsx** ✓ (already existed) -- **media-upload.test.tsx** ✓ (42 tests, 90%+ coverage) -- **tag-input.test.tsx** ✓ (already existed) -- **sidebar.test.tsx** ✓ **NEW** - 11 test cases - - Logo and navigation rendering - - Active state highlighting - - Navigation links - - Logout functionality - - Error handling - - Icon rendering - - Styling verification - -### 2. Integration Tests - -#### Page Tests -- **login/page.test.tsx** ✓ **NEW** - 11 test cases - - Form rendering - - Input validation - - Successful login - - Invalid credentials - - Non-admin user handling - - Loading states - - Network error handling - -- **page.test.tsx** (Dashboard) ✓ **NEW** - 12 test cases - - Header rendering - - Stats cards display - - Loading states - - Quick actions section - - Getting started section - - Route links - - Error handling - - Icon verification - - Color classes - -### 3. E2E Tests - -#### Playwright Configuration ✓ **NEW** -- Installed @playwright/test -- Configured for Chromium and Firefox -- Automatic dev server startup -- Screenshot on failure -- HTML reporting - -#### E2E Test Suite -- **e2e/auth.spec.ts** ✓ **NEW** - - Login form display - - Validation errors - - Route protection - - Navigation between pages - -### 4. Test Scripts Added - -```json -{ - "test:e2e": "playwright test", - "test:e2e:ui": "playwright test --ui", - "test:e2e:headed": "playwright test --headed", - "test:all": "npm run test && npm run test:e2e" -} -``` - -## 📊 Current Test Coverage - -| Metric | Before | After | Target | -|--------|--------|-------|--------| -| Lines | 88% | ~92% | 95% | -| Functions | 92% | ~94% | 95% | -| Branches | 78% | ~82% | 85% | -| Statements | 85% | ~90% | 90% | - -## 🧪 Test Files Created - -### New Test Files (7 files) -1. `components/ui/dialog.test.tsx` -2. `components/sidebar.test.tsx` -3. `app/login/page.test.tsx` -4. `app/page.test.tsx` -5. `e2e/auth.spec.ts` -6. `playwright.config.ts` - -### Modified Files -- `package.json` - Added E2E test scripts - -## 🎯 Test Categories - -### Unit Tests -- ✅ Component rendering -- ✅ User interactions (clicks, input) -- ✅ State changes -- ✅ Props validation -- ✅ Styling verification -- ✅ Accessibility checks - -### Integration Tests -- ✅ Page routing -- ✅ Data fetching -- ✅ Form submissions -- ✅ Error handling -- ✅ Loading states - -### E2E Tests -- ✅ Authentication flows -- ✅ Navigation -- ✅ Route protection -- ✅ Cross-browser testing - -## 🚀 How to Run Tests - -### Unit Tests -```bash -# Run all unit tests -npm run test - -# Run with coverage -npm run test:coverage - -# Watch mode -npm run test:watch -``` - -### E2E Tests -```bash -# Run E2E tests -npm run test:e2e - -# Run with UI -npm run test:e2e:ui - -# Run in headed mode (see browser) -npm run test:e2e:headed -``` - -### All Tests -```bash -# Run both unit and E2E tests -npm run test:all -``` - -## 📋 Remaining Work (Optional) - -### High Priority -- [ ] workouts/page.test.tsx - Full CRUD tests -- [ ] trainers/page.test.tsx - Delete dialog tests -- [ ] workout-form.tsx coverage improvement (currently 71%) - -### Medium Priority -- [ ] collections/page.test.tsx -- [ ] media/page.test.tsx -- [ ] Additional E2E tests for CRUD operations - -### Low Priority -- [ ] Visual regression tests with Storybook -- [ ] Performance benchmarks -- [ ] Accessibility audit with axe - -## 🎉 Achievements - -✅ **All Critical Paths Tested** -- Authentication flow -- Dashboard stats -- Navigation -- Sidebar functionality -- UI component library - -✅ **Test Infrastructure Complete** -- Vitest configured -- Playwright configured -- Mock utilities ready -- CI/CD scripts prepared - -✅ **Coverage Improved** -- Added 42+ new test cases -- Improved dialog component to 100% -- Added sidebar component tests -- Added page integration tests - -## 🔧 Testing Best Practices Implemented - -1. **Mock External Dependencies** - Supabase, Next.js router -2. **Test User Interactions** - Clicking, typing, navigation -3. **Verify Error States** - Network errors, auth failures -4. **Check Accessibility** - Roles, labels, ARIA attributes -5. **Test Responsive Behavior** - Different viewports (E2E) -6. **Isolate Tests** - Clean mocks between tests - -## 📚 Documentation - -- Each test file is self-documenting -- Descriptive test names explain the behavior -- Comments for complex mock setups -- Type safety with TypeScript - ---- - -**Status**: ✅ **Phase 1 Complete** - Core testing infrastructure implemented - -**Next Steps**: Run `npm run test:all` to execute full test suite! \ No newline at end of file diff --git a/docs/maestro-e2e-testing-strategy.md b/docs/maestro-e2e-testing-strategy.md deleted file mode 100644 index f54cb9e..0000000 --- a/docs/maestro-e2e-testing-strategy.md +++ /dev/null @@ -1,643 +0,0 @@ -# Maestro E2E Testing Strategy for TabataFit - -## Executive Summary - -**Maestro** is a mobile UI testing framework that uses YAML-based test flows. It's ideal for TabataFit because: -- ✅ Declarative YAML syntax (no code required) -- ✅ Built-in support for React Native -- ✅ Handles animations and async operations gracefully -- ✅ Excellent for regression testing critical user flows -- ✅ Can run on physical devices and simulators - ---- - -## Prerequisites - -Before implementing these tests, ensure the following features are complete: - -### Required Features (Implement First) -- [ ] Onboarding flow (5 screens + paywall) -- [ ] Workout player with timer controls -- [ ] Browse/Workouts tab with workout cards -- [ ] Category filtering (Full Body, Core, Cardio, etc.) -- [ ] Collections feature -- [ ] Trainer profiles -- [ ] Subscription/paywall integration -- [ ] Workout completion screen -- [ ] Profile/settings screen - -### Nice to Have (Can Add Later) -- [ ] Activity history tracking -- [ ] Offline mode support -- [ ] Deep linking -- [ ] Push notifications - ---- - -## Priority Test Flows - -### **P0 - Critical Flows (Must Test Every Release)** -1. **Onboarding → First Workout** -2. **Browse → Select Workout → Play → Complete** -3. **Subscription Purchase Flow** -4. **Background/Foreground During Workout** - -### **P1 - High Priority** -5. **Category Filtering** -6. **Collection Navigation** -7. **Trainer Workout Discovery** -8. **Profile Settings & Data Persistence** - -### **P2 - Medium Priority** -9. **Activity History Tracking** -10. **Offline Mode Behavior** -11. **Deep Linking** -12. **Push Notifications** - ---- - -## Test Suite Structure - -``` -.maestro/ -├── config.yaml # Global test configuration -├── flows/ -│ ├── critical/ # P0 flows - Run on every PR -│ │ ├── onboarding.yaml -│ │ ├── workoutComplete.yaml -│ │ └── subscription.yaml -│ ├── core/ # P1 flows - Run before release -│ │ ├── browseAndPlay.yaml -│ │ ├── categoryFilter.yaml -│ │ ├── collections.yaml -│ │ └── trainers.yaml -│ └── regression/ # P2 flows - Run nightly -│ ├── activityHistory.yaml -│ ├── offlineMode.yaml -│ └── settings.yaml -├── helpers/ -│ ├── common.yaml # Shared test steps -│ ├── assertions.yaml # Custom assertions -│ └── mock-data.yaml # Test data -└── environments/ - ├── staging.yaml - └── production.yaml -``` - ---- - -## Installation & Setup - -### 1. Install Maestro CLI - -```bash -# macOS/Linux -curl -Ls "https://get.maestro.mobile.dev" | bash - -# Verify installation -maestro --version -``` - -### 2. Setup Test Directory Structure - -```bash -mkdir -p .maestro/flows/{critical,core,regression} -mkdir -p .maestro/helpers -mkdir -p .maestro/environments -``` - -### 3. Maestro Configuration (`config.yaml`) - -```yaml -# .maestro/config.yaml -appId: com.tabatafit.app -name: TabataFit E2E Tests - -# Timeouts -timeout: 30000 # 30 seconds default -retries: 2 - -# Environment variables -env: - TEST_USER_NAME: "Test User" - TEST_USER_EMAIL: "test@example.com" - -# Include flows -include: - - flows/critical/*.yaml - - flows/core/*.yaml - -# Exclude on CI -exclude: - - flows/regression/offlineMode.yaml # Requires airplane mode -``` - ---- - -## P0 Critical Test Flows - -### Test 1: Complete Onboarding Flow - -**File:** `.maestro/flows/critical/onboarding.yaml` - -```yaml -appId: com.tabatafit.app -name: Complete Onboarding & First Workout - -onFlowStart: - - clearState - -steps: - # Splash/Loading - - waitForAnimationToEnd: - timeout: 5000 - - # Screen 1: Problem - "Not Enough Time" - - assertVisible: "Not enough time" - - tapOn: "Continue" - - # Screen 2: Empathy - - assertVisible: "We get it" - - tapOn: "Continue" - - # Screen 3: Solution - - assertVisible: "4-minute workouts" - - tapOn: "Continue" - - # Screen 4: Wow Moment - - assertVisible: "Transform your body" - - tapOn: "Get Started" - - # Screen 5: Personalization - - tapOn: "Name input" - - inputText: "Test User" - - tapOn: "Beginner" - - tapOn: "Lose Weight" - - tapOn: "3 times per week" - - tapOn: "Start My Journey" - - # Screen 6: Paywall (or skip in test env) - - runScript: | - if (maestro.env.SKIP_PAYWALL === 'true') { - maestro.tapOn('Maybe Later'); - } - - # Should land on Home - - assertVisible: "Good morning|Good afternoon|Good evening" - - assertVisible: "Test User" - -onFlowComplete: - - takeScreenshot: "onboarding-complete" -``` - -### Test 2: Browse, Select, and Complete Workout - -**File:** `.maestro/flows/critical/workoutComplete.yaml` - -```yaml -appId: com.tabatafit.app -name: Browse, Play & Complete Workout - -steps: - # Navigate to Workouts tab - - tapOn: "Workouts" - - waitForAnimationToEnd - - # Wait for data to load - - assertVisible: "All|Full Body|Core|Upper Body" - - # Select first workout - - tapOn: - id: "workout-card-0" - optional: false - - # Workout Detail Screen - - assertVisible: "Start Workout" - - tapOn: "Start Workout" - - # Player Screen - - waitForAnimationToEnd: - timeout: 3000 - - # Verify timer is running - - assertVisible: "Get Ready|WORK|REST" - - # Fast-forward through workout (simulation) - - repeat: - times: 3 - commands: - - waitForAnimationToEnd: - timeout: 5000 - - assertVisible: "WORK|REST" - - # Complete workout - - tapOn: - id: "done-button" - optional: true - - # Complete Screen - - assertVisible: "Workout Complete|Great Job" - - assertVisible: "Calories" - - assertVisible: "Duration" - - # Return to home - - tapOn: "Done|Continue" - - assertVisible: "Home|Workouts" - -onFlowComplete: - - takeScreenshot: "workout-completed" -``` - -### Test 3: Subscription Purchase Flow - -**File:** `.maestro/flows/critical/subscription.yaml` - -```yaml -appId: com.tabatafit.app -name: Subscription Purchase Flow - -steps: - # Trigger paywall (via profile or workout limit) - - tapOn: "Profile" - - tapOn: "Upgrade to Premium" - - # Paywall Screen - - assertVisible: "Unlock Everything|Premium" - - assertVisible: "yearly|monthly" - - # Select plan - - tapOn: - id: "yearly-plan" - - # Verify Apple Pay/Google Pay sheet appears - - assertVisible: "Subscribe|Confirm" - - # Note: Actual purchase is mocked in test env - - runScript: | - if (maestro.env.USE_MOCK_PURCHASE === 'true') { - maestro.tapOn('Mock Purchase Success'); - } - - # Verify premium activated - - assertVisible: "Premium Active|You're all set" - -tags: - - purchase - - revenue-critical -``` - ---- - -## P1 Core Test Flows - -### Test 4: Category Filtering - -**File:** `.maestro/flows/core/categoryFilter.yaml` - -```yaml -appId: com.tabatafit.app -name: Category Filtering - -steps: - - tapOn: "Workouts" - - waitForAnimationToEnd - - # Test each category - - tapOn: "Full Body" - - assertVisible: "Full Body" - - - tapOn: "Core" - - assertVisible: "Core" - - - tapOn: "Cardio" - - assertVisible: "Cardio" - - - tapOn: "All" - - assertVisible: "All Workouts" - - # Verify filter changes content - - runScript: | - const before = maestro.getElementText('workout-count'); - maestro.tapOn('Core'); - const after = maestro.getElementText('workout-count'); - assert(before !== after, 'Filter should change workout count'); -``` - -### Test 5: Collection Navigation - -**File:** `.maestro/flows/core/collections.yaml` - -```yaml -appId: com.tabatafit.app -name: Collection Navigation - -steps: - - tapOn: "Browse" - - waitForAnimationToEnd - - # Scroll to collections - - swipe: - direction: UP - duration: 1000 - - # Tap first collection - - tapOn: - id: "collection-card-0" - - # Collection Detail Screen - - assertVisible: "Collection" - - assertVisible: "workouts" - - # Start collection workout - - tapOn: "Start" - - assertVisible: "Player|Timer" - -onFlowComplete: - - takeScreenshot: "collection-navigation" -``` - -### Test 6: Trainer Discovery - -**File:** `.maestro/flows/core/trainers.yaml` - -```yaml -appId: com.tabatafit.app -name: Trainer Discovery - -steps: - - tapOn: "Browse" - - # Navigate to trainers section - - swipe: - direction: UP - - # Select trainer - - tapOn: - id: "trainer-card-0" - - # Verify trainer profile - - assertVisible: "workouts" - - # Select trainer's workout - - tapOn: - id: "workout-card-0" - - # Should show workout detail - - assertVisible: "Start Workout" -``` - ---- - -## Reusable Test Helpers - -### Common Actions (`helpers/common.yaml`) - -```yaml -# .maestro/helpers/common.yaml -appId: com.tabatafit.app - -# Launch app fresh -- launchApp: - clearState: true - -# Wait for data loading -- waitForDataLoad: - commands: - - waitForAnimationToEnd: - timeout: 3000 - - assertVisible: ".*" # Any content loaded - -# Handle permission dialogs -- handlePermissions: - commands: - - tapOn: - text: "Allow" - optional: true - - tapOn: - text: "OK" - optional: true - -# Navigate to tab -- navigateToTab: - params: - tabName: ${tab} - commands: - - tapOn: ${tab} - -# Start workout from detail -- startWorkout: - commands: - - tapOn: "Start Workout" - - waitForAnimationToEnd: - timeout: 5000 - - assertVisible: "Get Ready|WORK" -``` - ---- - -## Running Tests - -### Local Development - -```bash -# Install Maestro -curl -Ls "https://get.maestro.mobile.dev" | bash - -# Run single test -maestro test .maestro/flows/critical/onboarding.yaml - -# Run all critical tests -maestro test .maestro/flows/critical/ - -# Run with specific environment -maestro test --env SKIP_PAYWALL=true .maestro/flows/ - -# Record video of test -maestro record .maestro/flows/critical/workoutComplete.yaml - -# Run with tags -maestro test --include-tags=critical .maestro/flows/ -``` - -### CI/CD Integration (GitHub Actions) - -```yaml -# .github/workflows/maestro.yml -name: Maestro E2E Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - e2e-tests: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup Node - uses: actions/setup-node@v3 - - - name: Install Maestro - run: curl -Ls "https://get.maestro.mobile.dev" | bash - - - name: Start iOS Simulator - run: | - xcrun simctl boot "iPhone 15" - sleep 10 - - - name: Install App - run: | - npm install - npx expo prebuild - npx pod install - npx react-native run-ios --simulator="iPhone 15" - - - name: Run Critical Tests - run: | - export MAESTRO_DRIVER_STARTUP_TIMEOUT=120000 - maestro test .maestro/flows/critical/ - - - name: Upload Test Results - if: always() - uses: actions/upload-artifact@v3 - with: - name: maestro-results - path: | - ~/.maestro/tests/ - *.png -``` - ---- - -## Test Coverage Matrix - -| Feature | Test File | Priority | Frequency | Status | -|---------|-----------|----------|-----------|--------| -| Onboarding | `onboarding.yaml` | P0 | Every PR | ⏳ Pending | -| Workout Play | `workoutComplete.yaml` | P0 | Every PR | ⏳ Pending | -| Purchase | `subscription.yaml` | P0 | Every PR | ⏳ Pending | -| Category Filter | `categoryFilter.yaml` | P1 | Pre-release | ⏳ Pending | -| Collections | `collections.yaml` | P1 | Pre-release | ⏳ Pending | -| Trainers | `trainers.yaml` | P1 | Pre-release | ⏳ Pending | -| Activity | `activityHistory.yaml` | P2 | Nightly | ⏳ Pending | -| Offline | `offlineMode.yaml` | P2 | Weekly | ⏳ Pending | - ---- - -## React Native Prerequisites - -Before running tests, add `testID` props to components for reliable selectors: - -```tsx -// WorkoutCard.tsx - - {/* ... */} - - -// WorkoutPlayer.tsx -