docs: revamp AGENTS.md — canonical AI agent reference, purge obsolete TabataFit docs
This commit is contained in:
378
AGENTS.md
378
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
|
||||
|
||||
Reference in New Issue
Block a user