13 KiB
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) :
PurchaseService.shared.initialize()(RevenueCat)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é :
- Checkout (shallow, branche head) via
PR_API_TOKEN. - Setup PATH :
echo "/opt/homebrew/bin" >> $GITHUB_PATH— requis car Rosetta ne le voit pas. - 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.
- Supprime build artifacts,
- Install tools :
brew install xcodegen node ios-deploy. - xcodegen generate →
xcodebuild -resolvePackageDependencies→ build (-scheme TabataGo, Debug, auto-provisioning, team2MJF39L8VY). - Deploy iPhone UDID
00008120-000925CE3672201E:devicectlWiFi d'abord, fallbackios-deployUSB. - Post comment "Prêt à tester" sur la PR.
- 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/binhors PATH par défaut sous Rosetta → toujours l'ajouter.- xcodegen 2.45.4 ne génère pas de schemes auto → scheme
TabataGodéfini explicitement dansproject.yml(ne pas le supprimer). - Ne jamais
rm -rfle cache SPM dans le CI (c'est l'inverse des build artifacts). SWIFT_ENABLE_EXPLICIT_MODULES=NOau build — requis sinon segfault linker sur.pcmstale.-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-audiobucket) → classification automatique par Gemini (gemini-3.1-flash-lite-preview) → métadonnées dansdownload_jobs/download_items→ genre dansmusic_genre.
7. Règles d'or pour les agents
- Source de vérité =
project.yml+supabase/migrations/. Tout le reste est dérivé ou obsolète. - SwiftUI only. Pas d'UIKit sauf nécessité absolue démontrée.
@Observable(macro Observation), jamais@ObservableObject/@Published.- Concurrency stricte.
async/awaitpartout,@MainActorsur l'UI, pas de completion handlers. - Pas de force-unwrap.
guard let/if let, jamais!. - Navigation = TabView + sheet/fullScreenCover. Pas de
NavigationStack. - Tout en français côté user-facing (
Localizable.xcstrings/L10n). Codes/types restent en anglais. - XcodeGen. Éditer
project.yml, pas.xcodeproj. Régénérer avecxcodegen generate. - Secrets. Jamais committer
Config/Secrets.xcconfig. Template =.example. - 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/watchOSsenior-backend— Supabase, SQL, Edge Functionssenior-devops— CI/CD Gitea Actions, deploypo-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 sousskills/. - 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
# 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