Files
tabatago/AGENTS.md
millianlmx 741376229d
All checks were successful
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Successful in 6m52s
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Successful in 1m3s
docs: revamp AGENTS.md — canonical AI agent reference, purge obsolete TabataFit docs
2026-07-03 12:16:53 +00:00

273 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TabataGo — Référence canonique pour agents IA
> **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.
---
## 0. ⚠️ Fichiers obsolètes — NE PAS LIRE
| 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) |
**Source de vérité du schéma : `supabase/migrations/001` → `006`.** Jamais `schema.sql`.
---
## 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
```
TabataGoApp (@main)
└─ RootView()
├─ .environment(AppState.shared) // @Observable singleton
├─ .modelContainer(TabataGoSchema.container) // SwiftData
└─ .task { await AppState.shared.bootstrap() }
```
`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.
### Navigation (PAS de NavigationStack)
`MainTabView` = **`TabView` + `Tab(value:)`** (Liquid Glass, iOS 26), 4 onglets dans cet ordre exact du code :
```
home → programs → activity → profile
```
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
# Générer le projet Xcode
cd tabatago-swift && xcodegen generate
# Ouvrir
open tabatago-swift/TabataGo.xcodeproj
# Déployer les Edge Functions
bash scripts/deploy-functions.sh
# Statut CI
python3 scripts/ci-status.py
```