Files
tabatago/AGENTS.md
Millian Lamiaux 560b056dc9
All checks were successful
CI / Detect Changes (push) Successful in 3s
CI / Admin Web CI (push) Has been skipped
CI / YouTube Worker (push) Has been skipped
CI / Deploy (push) Has been skipped
fix: update workflow references to .github/ and gitignore xcuserstate
Update AGENTS.md and docs/ci-cd-setup.md from .gitea/workflows to .github/workflows

Add *.xcuserstate to .gitignore

Remove tracked UserInterfaceState.xcuserstate from git (preserve on disk)
2026-07-04 16:46:09 +02:00

13 KiB
Raw Permalink Blame History

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/001006. 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 XcodeGenproject.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/)

TabataGoWatchAppWatchRootView → é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_workoutsCREATE 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 : .github/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 generatexcodebuild -resolvePackageDependenciesbuild (-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

# 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