11 Commits

Author SHA1 Message Date
Millian Lamiaux
04a6512a6b ci: pr-iphone-deploy — filtre paths tabatago-swift/** uniquement
Some checks failed
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Failing after 3m2s
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Has been skipped
CI / Detect Changes (pull_request) Has been cancelled
CI / Admin Web CI (pull_request) Has been cancelled
CI / YouTube Worker (pull_request) Has been cancelled
CI / Deploy (pull_request) Has been cancelled
Ne déclenche le build/deploy iPhone que si des fichiers dans tabatago-swift/
ou le workflow lui-même sont modifiés.
2026-07-12 07:27:55 +02:00
Millian Lamiaux
752e863707 fix(ci): runner nas-runner + youtube-worker lock file
- Tous les runs-on: ubuntu-latest → nas-runner (ci.yml, docker-admin-web.yml, admin-web-tests.yml)
- youtube-worker: régénération package-lock.json (manquait @google/genai et dépendances)
2026-07-12 07:26:09 +02:00
Millian Lamiaux
c938c81df0 ci(admin-web): Docker build & push vers registry Gitea + reusable tests
Some checks failed
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Failing after 6s
CI / Admin Web CI (pull_request) Failing after 7s
CI / Deploy (pull_request) Has been skipped
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Failing after 2m54s
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Nouveau Dockerfile multi-stage Next.js standalone (node:22-bookworm-slim)
- Configuration semantic-release (.releaserc.json, version.json, devDeps)
- docker-compose.portainer.yml pour déploiement Portainer
- Workflow docker-admin-web.yml : test → semantic-release → build push → deploy
- Extraction admin-web-tests.yml (reusable workflow) appelé par ci.yml et docker-admin-web.yml → 0 duplication
- next.config.ts : ajout output: 'standalone'
- .dockerignore : exclusions node_modules, .next, etc.
2026-07-12 07:18:01 +02:00
Millian Lamiaux
560b056dc9 fix: update workflow references to .github/ and gitignore xcuserstate
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
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
Millian Lamiaux
a309b2b593 chore: purge obsolete .archive docs and relocate CI workflows
- Remove 5 outdated TabataFit/Expo concept docs (.archive/)
- Rename .gitea/workflows -> .github/workflows
- Ignore build/ directory
- Add Xcode UserInterfaceState
2026-07-04 16:41:34 +02:00
98cbcd4d52 Merge pull request 'docs: revamp AGENTS.md — canonical AI agent reference' (#7) from docs/revamp-agents-md into main 2026-07-04 16:22:54 +02:00
741376229d docs: revamp AGENTS.md — canonical AI agent reference, purge obsolete TabataFit docs
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
2026-07-03 12:16:53 +00:00
de63ae0546 Merge pull request 'ci: switch from Firebase OTA to Xcode WiFi/USB direct deploy' (#6) from ci/xcode-network-deploy into main 2026-06-28 11:29:26 +02:00
7d9a89e637 fix(ci): correct derived data path in install step
All checks were successful
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Successful in 5m31s
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Successful in 1m3s
2026-06-28 09:22:43 +00:00
97b61d3afb fix(ci): switch from Firebase OTA to Xcode WiFi/USB direct deploy
Some checks failed
PR → Build → WiFi/USB Deploy → LGTM / Build & Deploy to iPhone (WiFi/USB) (pull_request) Failing after 2m59s
PR → Build → WiFi/USB Deploy → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-28 09:14:51 +00:00
a9eda61967 Merge pull request 'ci: add iPhone deploy workflow + CI/CD setup docs' (#4) from ci/iphone-deploy-workflow into main 2026-06-27 22:49:52 +02:00
30 changed files with 1127 additions and 6011 deletions

View File

@@ -1,11 +0,0 @@
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
### Feb 20, 2026
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #5236 | 1:28 PM | 🔵 | Archived TabataGo PRD v1.1 discovered - comprehensive product requirements document | ~462 |
</claude-mem-context>

View File

@@ -1,599 +0,0 @@
# TabataGo — Brand Design Specification Document (BDSD) v1.0
> Ce document définit l'identité visuelle, le positionnement premium, et les principes esthétiques de TabataGo. Il guide toutes les décisions de design UI.
---
## 1. Vision de Marque
### 1.1 Essence
**TabataGo = Énergie pure, designé pour performer.**
Si TabataGo était une marque existante, ce serait **Gymshark** : jeune, audacieux, communauté, et visuellement impactant. Mais avec une touche **premium exclusive** qui élève l'expérience.
### 1.2 Positionnement
| Attribut | Définition |
|-----------|------------|
| **Émotion primaire** | Énergie / Vitalité — l'utilisateur doit se sentir vivant et motivé au premier regard |
| **Positionnement** | Premium exclusif — haut de gamme, design soigné, pas une app "gratuite générique" |
| **Adjectifs clés** | Dynamic • Vibrant • Energetic |
| **Mood global** | Action Sports — comme être dans une salle de sport high-end, la nuit, avec des néons |
### 1.3 Personnalité de Marque
TabataGo est :
- **Audacieux** — pas peur d'être vibrant et lumineux
- **Énergique** — chaque élément respire le mouvement
- **Premium** — jamais cheap, toujours soigné
- **Motivant** — l'UI pousse l'utilisateur à se dépasser
---
## 2. Direction Visuelle
### 2.1 Palette de Couleurs
#### Couleur Primaire : Orange Energy
L'orange est le cœur de l'identité — il représente l'énergie, la chaleur, l'action.
| Token | Hex | Usage |
|-------|-----|-------|
| `ENERGY_500` | `#F97316` | Brand primary, CTA, accents principaux |
| `ENERGY_400` | `#FB923C` | Glow effects, hover states |
| `ENERGY_600` | `#EA580C` | Phase WORK, intensity |
#### Fond : Soft Dark
Un dark mode doux et riche, pas un noir pur agressif.
| Token | Hex | Usage |
|-------|-----|-------|
| `DARK_BASE` | `#1A1A1E` | Background principal |
| `DARK_RAISED` | `#222226` | Cards level 1 |
| `DARK_ELEVATED` | `#2A2A2E` | Cards level 2, modals |
| `DARK_SURFACE` | `#0A0A0E` | Zones de focus, timer |
#### Accents & Phases
| Token | Hex | Usage |
|-------|-----|-------|
| `GLOW_NEON` | `#F97316` + glow | Flamme streak, highlights |
| `PHASE_WORK` | `#F97316` | Gradient work |
| `PHASE_REST` | `#3B82F6` | Gradient rest (bleu calme) |
| `PHASE_READY` | `#EAB308` | Gradient préparation |
| `SUCCESS` | `#22C55E` | Complétion, streak maintenu |
#### Textes
| Token | Valeur | Usage |
|-------|--------|-------|
| `TEXT_PRIMARY` | `#FFFFFF` | Titres, éléments principaux |
| `TEXT_SECONDARY` | `rgba(255,255,255,0.85)` | Body text |
| `TEXT_MUTED` | `rgba(255,255,255,0.60)` | Labels secondaires |
| `TEXT_HINT` | `rgba(255,255,255,0.45)` | Placeholders, hints |
### 2.2 Gradients
**Philosophie : Subtils mais présents.** Les gradients ajoutent de la profondeur sans être agressifs.
#### Gradients de Phase
```css
/* GET_READY - Ambre chaleureux */
--gradient-ready: linear-gradient(135deg, #451A03, #92400E, #D97706);
/* WORK - Orange intense avec touche rouge */
--gradient-work: linear-gradient(135deg, #450A0A, #991B1B, #EA580C);
/* REST - Bleu calme, récupération */
--gradient-rest: linear-gradient(135deg, #0C1929, #1E3A5F, #2563EB);
/* COMPLETE - Vert succès */
--gradient-complete: linear-gradient(135deg, #052E16, #166534, #16A34A);
```
#### Gradients UI
```css
/* Hero backgrounds - subtle */
--gradient-dark: linear-gradient(135deg, #0A0A14, #1A0E28, #2D1810);
/* Card hover - très subtil */
--gradient-card-hover: linear-gradient(135deg, rgba(249,115,22,0.08), rgba(249,115,22,0.02));
/* Button CTA - dimension */
--gradient-cta: linear-gradient(135deg, #F97316, #FB923C);
```
### 2.3 Glow & Neon Effects
L'identité TabataGo repose sur des **effets lumineux** qui donnent vie à l'interface.
#### Glow Colors
```css
/* Glow orange - principal */
--glow-energy: 0 0 40px rgba(249, 115, 22, 0.5);
--glow-energy-intense: 0 0 80px rgba(249, 115, 22, 0.7);
/* Glow blanc - éléments actifs */
--glow-white: 0 0 20px rgba(255, 255, 255, 0.3);
/* Neon flame effect */
--neon-flame:
0 0 10px rgba(249, 115, 22, 0.8),
0 0 20px rgba(249, 115, 22, 0.6),
0 0 40px rgba(249, 115, 22, 0.4);
```
#### Applications du Glow
| Élément | Effet |
|---------|-------|
| Bouton START | Glow breathing animé (pulse) |
| Flamme streak | Neon flame effect |
| Card sélectionnée | Subtle glow border |
| Phase WORK active | Ambient glow en fond |
| Countdown ≤3s | Glow rouge qui pulse |
---
## 3. Typographie
### 3.1 Police : Inter
**Pourquoi Inter ?**
- Excellent rendu sur écran
- Large gamme de weights pour le contrast mix
- Tabular nums pour le timer (pas de jitter)
- Google Fonts — gratuit et performant
### 3.2 Contrast Mix Philosophy
**Heavy pour les titres, light pour le body.** Ce contraste crée de l'impact et de l'élégance.
| Usage | Weight | Size | Exemple |
|-------|--------|------|---------|
| Timer countdown | 900 (Black) | 140px | **20** |
| Brand title | 900 (Black) | 56px | **TABATA** |
| Headlines | 800 (ExtraBold) | 32px | **Terminé !** |
| Body text | 400 (Regular) | 16px | Description du programme |
| Captions | 500 (Medium) | 14px | Labels, hints |
### 3.3 Scale Typographique
```css
/* Display - Impact maximal */
--font-countdown: 900 140px/1 Inter;
--font-display: 900 42px/1.1 Inter;
--font-headline: 800 28px/1.2 Inter;
/* Interactive */
--font-button-hero: 900 30px/1 Inter;
--font-button: 700 18px/1 Inter;
/* Body */
--font-body: 400 16px/1.5 Inter;
--font-caption: 500 14px/1.4 Inter;
--font-overline: 600 11px/1.2 Inter;
```
### 3.4 Règles Typographiques
1. **Toujours tabular-nums** pour les chiffres du timer
2. **Letter-spacing généreux** sur les all-caps (+2px minimum)
3. **Jamais de italic** sauf pour les citations
4. **Line-height serré** sur les grands titres (1.1)
---
## 4. Iconographie
### 4.1 Style : iOS Native
Utiliser des icônes qui s'intègrent naturellement à l'écosystème iOS, avec le style SF Symbols.
**Caractéristiques :**
- Lignes medium (ni trop fines ni trop épaisses)
- Coins arrondis
- Consistance visuelle
- Reconnaissance instantanée
### 4.2 Icônes Système
| Action | Icône | Style |
|--------|-------|-------|
| Play | ▶️ | SF Symbol `play.fill` |
| Pause | ⏸️ | SF Symbol `pause.fill` |
| Stop | ⏹️ | SF Symbol `stop.fill` |
| Skip | ⏭️ | SF Symbol `forward.fill` |
| Settings | ⚙️ | SF Symbol `gearshape.fill` |
| Home | 🏠 | SF Symbol `house.fill` |
| History | 📊 | SF Symbol `clock.fill` |
### 4.3 Tailles d'Icônes
| Contexte | Taille |
|----------|--------|
| Navigation tabs | 24×24px |
| Boutons controls | 28×28px |
| Cards (badges) | 16×16px |
| Headers | 20×20px |
---
## 5. Imagerie & Illustrations
### 5.1 Style : Realistic Photos
**Pas de cartoon, pas d'abstrait.** Des vraies personnes qui font du sport.
**Caractéristiques des photos :**
- Athlètes en action, pas poses statiques
- Éclairage dynamique, contrasté
- Background flou/sombre
- Diversité de corps et d'origines
- Qualité haute résolution
### 5.2 Traitement des Photos
```css
/* Overlay pour intégration dark mode */
.photo-overlay {
background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.6) 100%);
}
/* Warm tint pour cohérence brand */
.photo-tint {
filter: saturate(1.1) sepia(0.1);
}
```
### 5.3 GIFs d'Exercices
- Durée : 1-2 secondes, loop seamless
- Format : 200×200px minimum
- Fond transparent ou sombre
- Modèle en tenue fitness neutre
---
## 6. Composants UI
### 6.1 Boutons
#### Primary CTA (Pill Filled)
```css
.cta-primary {
background: linear-gradient(135deg, #F97316, #FB923C);
border-radius: 9999px; /* Full pill */
padding: 16px 40px;
box-shadow: 0 0 40px rgba(249, 115, 22, 0.4);
}
.cta-primary:active {
transform: scale(0.95);
box-shadow: 0 0 20px rgba(249, 115, 22, 0.6);
}
```
#### Secondary Button
```css
.cta-secondary {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 12px 24px;
}
```
### 6.2 Cards
#### Soft Elevated Cards
```css
.card {
background: rgba(34, 34, 38, 0.8); /* DARK_RAISED */
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
}
.card-selected {
border-color: rgba(249, 115, 22, 0.3);
background: linear-gradient(135deg, rgba(249,115,22,0.08), rgba(249,115,22,0.02));
box-shadow: 0 0 30px rgba(249, 115, 22, 0.2);
}
```
### 6.3 Progress Indicators
#### Progress Bar
```css
.progress-bar {
height: 6px;
background: rgba(255, 255, 255, 0.08);
border-radius: 9999px;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(90deg, #F97316, #FB923C);
border-radius: 9999px;
transition: width 0.3s ease-out;
}
```
#### Creative Loading
Style : barre de progression stylisée avec glow.
```css
.loading-bar {
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 9999px;
overflow: hidden;
}
.loading-fill {
width: 30%;
height: 100%;
background: linear-gradient(90deg, #F97316, #FB923C);
box-shadow: 0 0 10px rgba(249, 115, 22, 0.8);
animation: loading-slide 1.5s ease-in-out infinite;
}
```
---
## 7. Motion & Animation
### 7.1 Philosophie : Bouncy & Playful
**Les animations doivent sentir vivantes, pas robotiques.**
| Type | Durée | Easing |
|------|-------|--------|
| Micro-interactions | 100-200ms | Ease-out |
| Transitions standard | 300-400ms | Ease-in-out |
| Animations bouncy | 400-600ms | Spring (damping: 12, stiffness: 180) |
| Breathing loops | 2000-4000ms | Ease-in-out (loop) |
### 7.2 Animations Clés
#### Glow Breathing (Bouton START)
```css
@keyframes glow-breathe {
0%, 100% {
box-shadow: 0 0 30px rgba(249, 115, 22, 0.3);
transform: scale(1);
}
50% {
box-shadow: 0 0 60px rgba(249, 115, 22, 0.5);
transform: scale(1.02);
}
}
```
#### Pulse (Countdown)
```css
@keyframes countdown-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.03); }
100% { transform: scale(1); }
}
/* 200ms per tick */
```
#### Bouncy Pop (Victory)
```css
@keyframes victory-pop {
0% { transform: scale(0.5); opacity: 0; }
60% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
/* 500ms total */
```
#### Pulsing Circles (Hero)
```css
@keyframes hero-pulse {
0% {
transform: scale(0.8);
opacity: 0.8;
}
100% {
transform: scale(2);
opacity: 0;
}
}
/* 2000ms, staggered */
```
### 7.3 Micro-interactions Premium
| Interaction | Feedback |
|-------------|----------|
| Tap bouton | `scale(0.95)` + haptic light |
| Tap card | `scale(0.98)` + border glow fade |
| Focus input | Border glow animation |
| Swipe | Resistance elastique |
| Phase change | Cross-fade 400ms + haptic medium |
---
## 8. Effets Décoratifs
### 8.1 Flamme Neon (Streak)
La flamme représente le streak — elle doit être **lumineuse, dynamic, motivante**.
**Spécifications :**
- Style : Neon glow flame
- Animation : Scale pulse + opacity flicker
- Couleurs : Orange avec halo lumineux
- Taille : 48-80px selon contexte
```css
.neon-flame {
filter: drop-shadow(0 0 10px rgba(249, 115, 22, 0.8))
drop-shadow(0 0 20px rgba(249, 115, 22, 0.6))
drop-shadow(0 0 40px rgba(249, 115, 22, 0.4));
animation: flame-flicker 800ms ease-in-out infinite;
}
@keyframes flame-flicker {
0%, 100% { opacity: 0.9; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
```
### 8.2 Pulsing Circles (Background Hero)
Utilisés pour les moments de victoire et les écrans hero.
```css
.hero-circles::before,
.hero-circles::after {
content: '';
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(249,115,22,0.3), transparent 70%);
animation: hero-pulse 2000ms ease-out infinite;
}
.hero-circles::after {
animation-delay: 1000ms;
}
```
### 8.3 Floating Particles
Particules subtiles qui flottent en arrière-plan pour ajouter de la vie.
**Spécifications :**
- Nombre : 20-50 particules
- Taille : 2-6px
- Couleur : `rgba(255,255,255,0.1)` à `rgba(249,115,22,0.2)`
- Mouvement : Slow drift, random
- Opacité : 0.1-0.3
---
## 9. Spacing & Layout
### 9.1 Whitespace Généreux
Le premium respire. **Plus d'espace = plus d'impact.**
| Token | Valeur | Usage |
|-------|--------|-------|
| `space-xs` | 4px | Inline gaps |
| `space-sm` | 8px | Between related items |
| `space-md` | 16px | Section padding |
| `space-lg` | 24px | Between sections |
| `space-xl` | 40px | Major sections |
| `space-2xl` | 64px | Hero spacing |
### 9.2 Rounded Corners
| Token | Valeur | Usage |
|-------|--------|-------|
| `radius-sm` | 8px | Tags, small elements |
| `radius-md` | 12px | Buttons medium |
| `radius-lg` | 16px | Cards |
| `radius-xl` | 20px | Large cards, bottom sheets |
| `radius-full` | 9999px | Pills, circles |
---
## 10. Son Design
### 10.1 Philosophie
Les sons doivent être **subtils mais mémorables**. Ils renforcent le feedback sans être intrusifs.
### 10.2 Sons Clés
| Événement | Son | Caractéristiques |
|-----------|-----|------------------|
| Bip phase start | Court, percutant | 200ms, fréquence montante |
| Countdown tick | Click subtil | 100ms, tone medium |
| Phase WORK start | "Go!" ou bip fort | 300ms, énergique |
| Victoire complète | Fanfare courte | 1000ms, célébration |
| Haptic sync | Synchronisé au son | Feedback tactile |
### 10.3 Intégration
- Volume par défaut : 80% pour sons, 60% pour musique
- Ducking : Sons de phase prioritaire sur musique
- Respect silent mode (option override)
---
## 11. Inspirations & Références
### 11.1 Apple Fitness+
**Ce qu'on en reprend :**
- Dark mode élégant avec accents colorés
- Gradients subtils de phase
- Typography bold et impactante
- Animations fluides et premium
- Rings de progression stylisés
### 11.2 Gymshark
**Ce qu'on en reprend :**
- Énergie et dynamisme
- Communauté et motivation
- Photography realistic fitness
- Brand bold et young
### 11.3 Gaming UI (référence secondaire)
**Ce qu'on en reprend :**
- Effets neon/glow
- Animations bouncy
- Feedback immédiat et satisfaisant
- Progress bars créatives
---
## 12. Checklist Brand Consistency
Avant chaque release, vérifier :
- [ ] **Orange utilisé comme accent dominant** (pas dilué)
- [ ] **Glow effects présents** sur les éléments interactifs clés
- [ ] **Animations bouncy** et non robotiques
- [ ] **Whitespace généreux** — pas de surcharge visuelle
- [ ] **Rounded corners** partout (pas de corners carrés)
- [ ] **Photos realistic** et non illustrées
- [ ] **Haptics synchronisés** avec les interactions
- [ ] **Dark mode soft grey** et non true black
- [ ] **Contrast typo** (heavy titles, light body)
- [ ] **Feedback sonore subtil** mais présent
---
## 13. Versions
| Version | Date | Changements |
|---------|------|-------------|
| 1.0 | 18 février 2026 | Création initiale |
---
*Document créé le 18 février 2026 — TabataGo BDSD v1.0*

File diff suppressed because it is too large Load Diff

View File

@@ -1,597 +0,0 @@
| 🔥 TABATAGO Application Mobile de Minuterie Tabata *Product Requirements Document (PRD) v1.1* Framework : Expo (React Native) · Cible : iOS & Android · Février 2026 |
| :---: |
# **1\. Résumé Exécutif**
TabataGo est une application mobile premium dédiée à l'entraînement par intervalles en méthode Tabata (20 secondes d'effort / 10 secondes de repos). Elle cible les personnes qui veulent des séances courtes, intenses et guidées — sans abonnement à une salle. Le marché des applications de fitness dépasse 1,5 milliard de dollars en revenus annuels et le Tabata reste un mot-clé à forte demande (popularité \> 70\) avec une difficulté ASO modérée (\< 50).
| Indicateur | Valeur cible |
| :---- | :---- |
| Marché visé | Fitness enthousiasts 25-45 ans, Home workout, HIIT lovers |
| Plateformes | iOS (priorité) \+ Android |
| Framework | Expo (React Native) — SDK 52+ |
| Modèle économique | Freemium \+ Abonnement mensuel/annuel via RevenueCat |
| Prix cible (US) | $4.99/mois ou $29.99/an |
| Objectif J+30 | 5 000 téléchargements, taux de conversion essai \> 30% |
| Objectif J+90 | MRR $5 000, note App Store ≥ 4.6 |
# **2\. Fonctionnalités Core — Spécifications Détaillées**
TabataGo repose sur trois piliers fonctionnels indissociables : le Timer (moteur de l'expérience), la Musique (moteur émotionnel), et l'Exercice (moteur pédagogique). Ces trois composants doivent fonctionner en parfaite synchronisation.
## **2.1 Le Timer — Moteur Central**
Le timer est la fonctionnalité principale et vitale de l'application. Son implémentation doit être irréprochable : précision, fluidité visuelle, comportement en arrière-plan.
### **2.1.1 Structure d'une Séance Tabata Standard**
| Phase | Durée par défaut | Configurable ? | Couleur écran | Signal |
| :---- | :---- | :---- | :---- | :---- |
| Préparation (Get Ready) | 10 secondes | Oui (530s) | Jaune \#EAB308 | Bip court × 3 \+ vibration légère |
| Travail (Work) | 20 secondes | Oui (560s) | Orange vif \#F97316 | Bip long au démarrage \+ haptique fort |
| Repos (Rest) | 10 secondes | Oui (560s) | Bleu calme \#3B82F6 | Double bip court \+ haptique léger |
| Fin de round | Instantané | — | Flash blanc | Son de cloche \+ vibration |
| Fin de séance | Écran résultat | — | Vert \#22C55E | Fanfare \+ longue vibration |
### **2.1.2 Paramètres Configurables du Timer**
| Paramètre | Valeur par défaut | Min | Max | Accès |
| :---- | :---- | :---- | :---- | :---- |
| Durée Work | 20s | 5s | 60s | Tous |
| Durée Rest | 10s | 5s | 60s | Tous |
| Nombre de rounds | 8 | 1 | 30 | Tous |
| Durée Get Ready | 10s | 0s | 30s | Tous |
| Nombre de cycles | 1 | 1 | 10 | Premium |
| Pause entre cycles | 60s | 10s | 300s | Premium |
| Cycles de récupération | — | — | — | Premium |
### **2.1.3 Affichage Timer — Écran Séance (plein écran)**
| 📱 LAYOUT — Écran Timer Plein Écran |
| :---- |
| ▸ Zone HAUTE (20%) : Nom de l'exercice en cours \+ numéro de round (ex: "Burpees — Round 3/8") |
| ▸ Zone CENTRALE (50%) : Chiffre du compte à rebours — très grand (96-120px), police monospace, couleur de phase |
| ▸ Zone BASSE HAUTE (15%) : Barre de progression de la séance complète (rounds) \+ indicateur phase actuelle |
| ▸ Zone BASSE (15%) : Boutons Pause / Stop / Skip — discrets pour ne pas distraire |
| ▸ FOND : Couleur dynamique selon la phase (orange work, bleu rest, jaune prep) avec transition animée |
| ▸ ANIMATION : Pulsation subtile du chiffre à chaque seconde \+ ring circulaire de progression |
### **2.1.4 Comportement Technique du Timer**
* Précision : Utiliser expo-background-fetch \+ Date.now() delta pour compenser les drifts — tolérance \< 50ms
* Background : Le timer continue en arrière-plan (notification sticky affichant le compte à rebours)
* Verrouillage écran : Écran reste allumé pendant la séance (expo-keep-awake)
* Interruptions : Pause automatique si appel téléphonique entrant (AppState listener)
* Reprise : Si l'app est tuée, afficher une notification "Séance interrompue — Reprendre ?" au retour
* OTA update safe : Le state du timer est isolé du cycle de render React pour éviter les glitches
## **2.2 La Musique — Moteur Émotionnel**
La musique transforme une simple minuterie en expérience motivante. Elle doit s'adapter dynamiquement à chaque phase de la séance (work vs rest) et ne jamais entrer en conflit avec la musique de l'utilisateur ou les signaux sonores.
### **2.2.1 Architecture Sonore**
| Couche audio | Description | Technologie | Contrôle utilisateur |
| :---- | :---- | :---- | :---- |
| Musique d'ambiance | Tracks BPM-synchronisées intégrées à l'app | expo-av (AVAudioSession) | Volume indépendant, on/off |
| Signaux de phase | Bips, voix, cloche — changement work/rest | expo-av (priorité haute) | Volume indépendant, choix du son |
| Voix coach | Annonces vocales (optionnel) : "Go\!", "Rest", "Last round\!" | expo-av (TTS ou fichiers pré-enregistrés) | On/off, langue |
| Haptiques | Vibrations synchronisées aux signaux | expo-haptics | On/off |
### **2.2.2 Catalogue Musical Intégré (Offline)**
| 🎵 TRACKS INTÉGRÉES — 3 ambiances × 3 intensités \= 9 tracks minimum |
| :---- |
| ▸ Ambiance ELECTRO : Low (récup), Medium (standard), High (intense) — BPM 120/140/160 |
| ▸ Ambiance HIP-HOP : Low (récup), Medium (standard), High (intense) — BPM 85/100/115 |
| ▸ Ambiance ROCK/METAL : Low (récup), Medium (standard), High (intense) — BPM 130/150/170 |
| ▸ Mode SILENCE : Aucune musique, uniquement les signaux sonores de phase |
| ▸ Mode SPOTIFY/APPLE MUSIC : L'app n'interfère pas avec la musique de l'utilisateur (coexistence audio) |
| ▸ Toutes les tracks sont royalty-free et embarquées dans le bundle — aucun streaming requis |
### **2.2.3 Synchronisation Musique ↔ Timer**
* Phase WORK : Passer automatiquement à la track haute intensité de l'ambiance sélectionnée
* Phase REST : Transition en fade-out 1s vers la track basse intensité (ambiance calme)
* Phase GET READY : Intro de 10s sur la track principale
* Transition douce : Cross-fade 500ms entre les phases pour éviter les coupures brutales
* BPM adaptatif (Premium) : L'app détecte le rythme de la track et aligne le bip de fin de phase sur le beat
* Pas de conflit : Si l'utilisateur a sa propre musique, les signaux de phase s'y superposent en ducking audio (baisse temporaire du volume)
### **2.2.4 Signaux Sonores de Phase — Options**
| Signal | Options disponibles | Par défaut | Premium uniquement ? |
| :---- | :---- | :---- | :---- |
| Début Work | Bip long, Whistle, Voix "Go\!", Air horn, Clap | Bip long | Non |
| Début Rest | Double bip, Voix "Rest", Bell, Ding | Double bip | Non |
| Décompte 3-2-1 | Bips courts, Voix "3, 2, 1", Silence | Bips courts | Non |
| Fin de round | Cloche, Applaudissements, Voix "Round X done\!" | Cloche | Oui |
| Fin de séance | Fanfare, Applaudissements, Voix "Workout complete\!" | Fanfare | Non |
| Dernier round | Voix "Last round\!", Alarm, Son spécial | Voix | Oui |
### **2.2.5 Gestion Technique Audio (expo-av)**
* Session audio iOS : AVAudioSessionCategoryPlayback avec MixWithOthers — permet de jouer avec la musique utilisateur
* Focus audio Android : AudioManager.AUDIOFOCUS\_GAIN\_TRANSIENT\_MAY\_DUCK pour les signaux
* Préchargement : Tous les sons de phase sont chargés en mémoire au démarrage de la séance (zéro latence)
* Mode silencieux iOS : Les signaux de phase respectent le switch mute SAUF si l'utilisateur a activé "override" dans les settings
* Headphones détection : Si écouteurs branchés, désactiver les haptiques de phase par défaut
## **2.3 L'Exercice — Moteur Pédagogique**
Chaque round du timer doit être associé à un exercice spécifique, affiché clairement pendant la phase de travail. C'est ce qui différencie TabataGo d'une simple minuterie générique.
### **2.3.1 Bibliothèque d'Exercices**
| Catégorie | Exemples d'exercices | Nb exercices V1 | Nb exercices V2 |
| :---- | :---- | :---- | :---- |
| Cardio / Full body | Burpees, Jumping Jacks, Mountain Climbers, High Knees | 8 | 20 |
| Bas du corps | Squats, Fentes, Jump Squats, Glute Bridges, Wall Sit | 8 | 20 |
| Haut du corps | Push-ups, Pike Push-ups, Tricep Dips, Shoulder Taps | 6 | 15 |
| Abdos / Core | Crunches, Planche, Russian Twists, Bicycle Crunches | 6 | 15 |
| Sans saut (low impact) | Slow Squats, Modified Push-ups, Step Touch, March | 6 | 15 |
| Avec matériel | Kettlebell Swings, Dumbbell Thrusters, Jump Rope | 4 | 10 |
### **2.3.2 Fiche Exercice — Données par Exercice**
| 📋 MODÈLE DE DONNÉES — Exercice |
| :---- |
| ▸ id : identifiant unique (ex: "burpee\_classic") |
| ▸ name : Nom localisé (FR: "Burpee", EN: "Burpee", ES: "Burpee") |
| ▸ category : cardio | lower\_body | upper\_body | core | low\_impact | equipment |
| ▸ difficulty : beginner | intermediate | advanced |
| ▸ musclesTargeted : string\[\] (ex: \["quadriceps", "pectoraux", "cardio"\]) |
| ▸ description : Instruction courte (max 80 car.) — affichée pendant la phase Rest |
| ▸ cues : string\[\] — 2-3 points clés de forme (ex: "Dos droit", "Genoux alignés") |
| ▸ gifUrl : Animation GIF courte (1-2s, loop) — 200×200px max — embarquée offline |
| ▸ thumbnailUrl : Image statique pour la bibliothèque |
| ▸ hasModification : bool — si une variante plus facile existe |
| ▸ modificationId : id de l'exercice de remplacement (ex: "burpee\_modified") |
| ▸ equipmentNeeded : string\[\] (ex: \[\] pour aucun, \["tapis"\] pour matériel simple) |
### **2.3.3 Affichage de l'Exercice Pendant la Séance**
| Phase | Affichage exercice | Taille | Information complémentaire |
| :---- | :---- | :---- | :---- |
| GET READY (10s) | Nom \+ GIF animé de démonstration | Grande — focus total | "Voici l'exercice suivant" — prépare mentalement |
| WORK (20s) | Nom en haut \+ compteur central — GIF en petit coin | Nom moyen, timer dominant | 1-2 cues de forme affichés sous le nom |
| REST (10s) | Nom de l'EXERCICE SUIVANT \+ vignette | Taille moyenne | "Prochain : \[Nom\]" — anticipation |
| FIN DE ROUND | Résumé rapide du round (1s) | Plein écran flash | Round X complété \+ prochain exercice |
### **2.3.4 Types de Programmes Tabata**
| 🗂️ PROGRAMMES DISPONIBLES — V1 |
| :---- |
| ▸ MODE 1 — Exercice unique répété : Le même exercice sur les 8 rounds (ex: 8 rounds de Burpees) — Tabata classique |
| ▸ MODE 2 — Circuit 2 exercices : Alternance A/B sur 8 rounds (ex: Squats / Push-ups × 4 répétitions) — Tabata duo |
| ▸ MODE 3 — Circuit 4 exercices : 4 exercices × 2 rounds chacun — Tabata circuit (Premium) |
| ▸ MODE 4 — Programme libre : L'utilisateur assigne manuellement un exercice à chaque round (Premium) |
| ▸ MODE 5 — Programme IA : Sélection automatique selon niveau, objectif et historique (Premium V2) |
### **2.3.5 Créateur de Programme Personnalisé (Premium)**
* Interface drag & drop pour assigner les exercices à chaque round
* Sauvegarde illimitée de programmes personnalisés (nom, description, tags)
* Partage de programme par lien deep link (ex: tabatago://program/abc123)
* Import de programme depuis un lien partagé par un autre utilisateur
* Favoris : marquer des exercices pour les retrouver rapidement
### **2.3.6 Contenu Offline & Performance**
* Tous les GIFs d'exercices V1 (38 exercices × 1 GIF ≈ 150KB chacun ≈ \~6MB total) — embarqués dans le bundle
* Lazy loading pour les exercices V2+ : téléchargement à la demande, mis en cache localement
* Fallback : Si pas de GIF disponible, afficher une icône \+ description textuelle
* Accessibilité : alt text sur chaque GIF pour VoiceOver/TalkBack
## **2.4 Synchronisation Timer × Musique × Exercice**
Les trois composants doivent former une expérience unifiée et cohérente. Ce tableau décrit les événements et leurs effets croisés :
| Événement Timer | Effet sur la Musique | Effet sur l'Exercice | Haptique |
| :---- | :---- | :---- | :---- |
| Début GET READY | Fade-in track principale | Afficher GIF exercice Round 1 | Léger |
| Décompte 3-2-1 | Volume augmente | Animation pulsation sur le GIF | Bip × 3 |
| Début WORK | Switch vers track high BPM | Afficher nom \+ cues en grand | Fort |
| Milieu WORK (10s) | Rien | "Halfway\!" en overlay 1s | Aucun |
| Fin WORK | Switch vers track low BPM | Afficher exercice SUIVANT | Moyen |
| Début REST | Track calme | "Next: \[Exercice\]" \+ vignette | Léger |
| Dernier round warning | Effet sonore spécial | Badge "LAST ROUND" sur nom | Vibration longue |
| Fin de séance | Fade-out \+ fanfare | Écran résultat avec tous les exercices | Célébration |
# **3\. Validation du Marché & Stratégie ASO**
## **3.1 Analyse des Mots-Clés Cibles**
Les mots-clés suivants ont été validés via Astro (difficulté \< 55, popularité \> 20\) :
| Mot-clé | Popularité | Difficulté | Marché |
| :---- | :---- | :---- | :---- |
| tabata timer | 72 | 48 | EN 🇺🇸 |
| hiit timer app | 68 | 52 | EN 🇺🇸 |
| minuterie tabata | 35 | 28 | FR 🇫🇷 |
| temporizador tabata | 41 | 31 | ES 🇪🇸 |
| tabata training timer | 55 | 44 | EN 🇬🇧 |
| intervall timer workout | 38 | 29 | DE 🇩🇪 |
## **3.2 Analyse Concurrentielle**
Principaux concurrents identifiés et opportunités de différenciation :
| Concurrent | Forces | Faiblesses (notre opportunité) |
| :---- | :---- | :---- |
| Tabata Timer (App Store Top 1\) | Notoriété, simplicité | UI datée, pas de suivi streak, pas de localisation |
| Seconds Pro | Très complet, flexible | Trop complexe, prix élevé, courbe d'apprentissage |
| HIIT Interval Training Timer | Gratuit, fonctionnel | Pub intrusive, pas d'onboarding émotionnel |
| Tabata Stopwatch Pro | Simple, rapide | Pas de personnalisation, no streak, no widget |
Notre avantage : onboarding émotionnel fort \+ design moderne \+ widgets iOS/Android \+ streaks \+ localisation dans 5 langues.
# **4\. Onboarding — La Séquence Critique**
L'onboarding est la priorité absolue : 80% des revenus sont générés ici. L'objectif est de créer un investissement émotionnel avant d'afficher le paywall. La séquence suit le schéma : Problème → Empathie → Solution → Moment Wow → Paywall.
## **4.1 Écrans d'Onboarding (séquence de 6 écrans)**
### **Écran 1 — Le Problème (Identifier la douleur)**
| 🎯 OBJECTIF : Identification |
| :---- |
| ▸ Titre : "Tu n'as pas 1 heure pour la salle. Personne n'en a." |
| ▸ Sous-titre : "Et pourtant tu veux progresser. On a la solution." |
| ▸ Visuel : Animation subtile d'une horloge qui se fragmente puis se reconstruit en 20 min |
| ▸ CTA : "Montre-moi comment" (bouton orange pleine largeur) |
### **Écran 2 — L'Empathie (L'utilisateur se sent compris)**
| 💬 OBJECTIF : Connexion émotionnelle |
| :---- |
| ▸ Titre : "Qu'est-ce qui t'empêche de t'entraîner ?" |
| ▸ Choix interactifs (tap) : Manque de temps / Motivation en berne / Je ne sais pas quoi faire / Je n'ai pas accès à une salle |
| ▸ Mécanisme : stocker la réponse → personnaliser le reste de l'onboarding |
| ▸ Transition : "On a conçu TabataGo exactement pour ça." |
### **Écran 3 — La Solution (Présenter la méthode)**
| ⚡ OBJECTIF : Comprendre la valeur |
| :---- |
| ▸ Titre : "4 minutes. Vraiment transformatrices." |
| ▸ Animation interactive : timeline Tabata (20s work / 10s rest × 8 rounds) |
| ▸ Stats affichées : Brûle autant de calories qu'un jogging de 30 min / Prouvé scientifiquement depuis 1996 (Dr. Tabata) |
| ▸ Visuel : compteur animé qui tourne — preview de l'app |
### **Écran 4 — Le Moment "Wow" (Démo interactive)**
| 🔥 OBJECTIF : Engagement actif (ne pas juste regarder) |
| :---- |
| ▸ Titre : "Essaie maintenant. 20 secondes." |
| ▸ Mini-minuterie Tabata LIVE intégrée dans l'écran d'onboarding |
| ▸ L'utilisateur tape sur "Go" et vit 20s de compte à rebours \+ son \+ vibration |
| ▸ Après : "Tu viens de faire ta première série Tabata. 7 de plus et c'est une séance complète." |
| ▸ Note : Ce moment est le plus différenciant — créer une mini-expérience réelle |
### **Écran 5 — Personnalisation (Engagement supplémentaire)**
| ⚙️ OBJECTIF : Investissement personnel |
| :---- |
| ▸ Titre : "Configurons ta première semaine." |
| ▸ Sélection : Niveau (Débutant / Intermédiaire / Avancé) |
| ▸ Sélection : Objectif (Perte de poids / Cardio / Force / Bien-être) |
| ▸ Sélection : Fréquence souhaitée (2x / 3x / 5x par semaine) |
| ▸ Résultat : "Ton programme personnalisé est prêt." (sensation de valeur avant paiement) |
### **Écran 6 — Paywall (Après l'investissement émotionnel)**
| 💳 OBJECTIF : Conversion |
| :---- |
| ▸ Titre : "Continue sur ta lancée. Sans limite." |
| ▸ Présenter l'essai gratuit 7 jours en premier (bouton principal orange) |
| ▸ Options : Mensuel $4.99 / Annuel $29.99 (économie 50% mise en évidence) |
| ▸ Garantie visible : "Annule à tout moment" \+ "Satisfait ou remboursé 30j" |
| ▸ Lien "Continuer sans abonnement" en petit en bas (ne pas le cacher) |
| ▸ Intégration RevenueCat — avec A/B test activé dès le lancement |
# **5\. Design & Identité Visuelle**
## **5.1 Palette de Couleurs**
| Rôle | Couleur | Hex | Usage |
| :---- | :---- | :---- | :---- |
| Primaire / Action | Orange Tabata | \#F97316 | Boutons CTA, accents, timer actif |
| Fond Dark (défaut) | Charcoal Night | \#1E1E2E | Background principal mode sombre |
| Fond Light | Warm White | \#FFF7ED | Background mode clair |
| Texte principal Dark | Stone 900 | \#1C1917 | Titres mode clair |
| Texte secondaire | Stone 600 | \#57534E | Body text, descriptions |
| Succès / Streak | Green 500 | \#22C55E | Streaks, complétion, feedback positif |
| Danger / Alerte | Red 500 | \#EF4444 | Erreurs, derniers secondes |
## **5.2 Typographie**
| Usage | Police | Poids | Taille |
| :---- | :---- | :---- | :---- |
| Timer principal | Inter (monospace fallback) | Black (900) | 96-120px |
| Titres H1 | Inter | Bold (700) | 28-32px |
| Titres H2 | Inter | SemiBold (600) | 22-24px |
| Body / Labels | Inter | Regular (400) | 14-16px |
| Micro-labels | Inter | Medium (500) | 11-12px |
## **5.3 Principes Design**
* Mode sombre par défaut (immersif pendant l'entraînement)
* Timer en plein écran pendant la séance — aucune distraction
* Animations fluides (60fps) pour les transitions et le compteur
* Haptiques natifs : vibration légère à chaque changement de phase
* Support Dynamic Type (iOS) et font scaling (Android)
* Icône app : fond noir, lettre T stylisée en orange avec une flamme
# **6\. Architecture Technique & Développement (Expo)**
## **6.1 Stack Technique**
| Composant | Solution choisie | Justification |
| :---- | :---- | :---- |
| Framework | Expo SDK 52 (React Native) | Cross-platform, OTA updates, accès natif facile |
| Navigation | Expo Router v3 (file-based) | Standard moderne, deep linking natif |
| State management | Zustand \+ AsyncStorage | Léger, performant, persistance simple |
| Timer engine | expo-background-fetch \+ useInterval custom | Précision \+ exécution background |
| Audio | expo-av | Sons de décompte et alertes phase |
| Haptiques | expo-haptics | Retour tactile natif iOS/Android |
| Notifications | expo-notifications | Rappels d'entraînement \+ streaks |
| Widget | react-native-widget-extension | Widget iOS 14+ (Live Activity) |
| Paiements | react-native-purchases (RevenueCat) | Abonnements \+ A/B testing \+ analytics |
| Analytics | PostHog (react-native-posthog) | Funnel tracking, drop-off analysis |
| Storage | expo-secure-store \+ AsyncStorage | Données utilisateur \+ préférences |
| In-App Review | expo-store-review | Prompt après streak 7 jours |
## **6.2 Features — MoSCoW Priorisation**
### **Must Have (V1 — Lancement)**
| ✅ MUST HAVE — Indispensables au lancement |
| :---- |
| ▸ Timer Tabata complet — voir Section 2.1 (toutes phases, sons, haptiques, background) |
| ▸ Musique intégrée — voir Section 2.2 (3 ambiances × 3 intensités, signaux de phase, coexistence audio) |
| ▸ Exercices avec GIFs — voir Section 2.3 (38 exercices, modes 1 et 2, affichage GET READY/WORK/REST) |
| ▸ Synchronisation Timer × Musique × Exercice — voir Section 2.4 |
| ▸ Modes : Tabata classique, HIIT personnalisé, Pause active |
| ▸ Affichage plein écran pendant la séance (mode portrait \+ paysage) |
| ▸ Historique des séances (date, durée, rounds complétés) |
| ▸ Système de Streak (consécutivité quotidienne, animation de feu) |
| ▸ Notifications de rappel configurables |
| ▸ Onboarding 6 écrans avec mini-démo live |
| ▸ Paywall \+ RevenueCat (essai 7j, mensuel, annuel) |
| ▸ Dark mode \+ Light mode |
| ▸ Localisation : EN, FR, ES, DE, PT |
### **Should Have (V1.1 — Semaine 4-8)**
| 🟡 SHOULD HAVE — Valeur ajoutée forte |
| :---- |
| ▸ Widget iOS (Home Screen) — affiche le streak et le dernier entraînement |
| ▸ Widget Android (Glance API) |
| ▸ Bibliothèque de programmes pré-définis (Débutant / Cardio / Force) |
| ▸ Statistiques hebdomadaires et mensuelles avec graphiques |
| ▸ Sons personnalisés (voix, bip, musique d'ambiance) |
| ▸ Integration Apple Health / Google Fit (calories, activité) |
| ▸ Partage social (carte récapitulative de la séance) |
### **Could Have (V2 — Mois 3+)**
| 🔵 COULD HAVE — Différenciation long terme |
| :---- |
| ▸ Mode Coach IA : suggestions de séances basées sur l'historique |
| ▸ Challenges communautaires (leaderboard hebdomadaire) |
| ▸ Apple Watch companion app |
| ▸ Import/Export de programmes (partage entre utilisateurs) |
| ▸ Mode TV / AirPlay pour entraînement sur grand écran |
# **7\. Structure de Navigation (Expo Router)**
Architecture file-based avec Expo Router v3 :
| Route | Écran | Accès |
| :---- | :---- | :---- |
| / | Home — Hub central avec raccourcis et streak | Tous |
| /onboarding | Séquence onboarding 6 étapes | Nouveaux utilisateurs |
| /timer | Minuterie plein écran — séance active | Tous |
| /programs | Bibliothèque de programmes | Premium |
| /history | Historique des séances \+ stats | Tous (limité free) |
| /settings | Préférences, sons, notifications, compte | Tous |
| /paywall | Écran d'abonnement RevenueCat | Free users |
| /(modals)/review | In-App Review prompt | Streak 7j |
# **8\. Paiements, Pricing & RevenueCat**
## **8.1 Structure des Offres**
| Offre | Prix US | Prix FR | Prix BR | Contenu |
| :---- | :---- | :---- | :---- | :---- |
| Gratuit | Free | Free | Free | Timer standard, 7j historique, pas de widget |
| Essai Premium | 7 jours gratuits | 7 jours gratuits | 7 jours gratuits | Accès complet, pas de CB requise si possible |
| Premium Mensuel | $4.99/mois | €4.99/mois | R$9.99/mois | Accès complet illimité |
| Premium Annuel | $29.99/an | €27.99/an | R$59.99/an | Tout Premium — économie 50% mise en avant |
Note : Pricing localisé via RevenueCat Purchasing Power Parity (PPP). Adapter automatiquement les prix pour le Brésil, l'Inde, l'Indonésie et l'Europe de l'Est pour maximiser les conversions mondiales.
## **8.2 Configuration RevenueCat**
* Entitlements : "premium" (accès à toutes les features payantes)
* Products : tabatago\_monthly, tabatago\_annual, tabatago\_trial\_7d
* A/B Tests dès J+7 : tester $3.99 vs $4.99 vs $6.99 mensuel
* A/B Tests paywall : liste vs comparaison de plans vs "best value" badge
* Webhook RevenueCat → PostHog pour corréler revenus et comportement
# **9\. Analytics & Instrumentation (PostHog)**
## **9.1 Événements Critiques à Tracker**
| Événement | Propriétés | Objectif |
| :---- | :---- | :---- |
| onboarding\_step\_viewed | step\_number, step\_name | Identifier le drop-off |
| onboarding\_demo\_started | duration\_seconds | Mesurer l'engagement Wow |
| onboarding\_completed | persona, goal, frequency | Segmentation |
| paywall\_viewed | source, variant | Funnel paiement |
| trial\_started | plan\_selected | Conversion essai |
| subscription\_purchased | plan, price, currency | Revenue |
| session\_started | program\_id, rounds, duration | Engagement produit |
| session\_completed | rounds\_done, streak\_day | Rétention |
| streak\_milestone | days\_count | Gamification KPIs |
| widget\_added | widget\_type | Stickiness |
| notification\_tapped | notification\_type | Rappels efficacité |
## **9.2 Funnels à Monitorer**
* Funnel Acquisition : Impression App Store → Téléchargement → Onboarding Start → Onboarding Complete → Paywall View → Trial Start → Purchase
* Funnel Rétention : J+1 / J+7 / J+30 ouverture de l'app après téléchargement
* Funnel Engagement : Séance démarrée → Séance complétée → Streak maintenu
Règle d'or : Si impressions mais pas de téléchargements → Réparer screenshots/icône. Si téléchargements mais pas d'essais → Réparer onboarding. Si essais mais pas de paiements → Réparer paywall/pricing.
# **10\. Localisation & Growth Hacking**
## **10.1 Stratégie de Localisation (5 Langues)**
| Langue | Marché cible | Mots-clés spécifiques | Priorité |
| :---- | :---- | :---- | :---- |
| Anglais (EN) | US, UK, AU, CA | tabata timer, hiit timer app | 1 — Lancement |
| Français (FR) | France, Belgique, Québec | minuterie tabata, chrono hiit | 1 — Lancement |
| Espagnol (ES) | Espagne, Mexique, Argentine | temporizador tabata, ejercicio hiit | 2 — Semaine 2 |
| Allemand (DE) | Allemagne, Autriche, CH | tabata timer app, intervall training | 2 — Semaine 2 |
| Portugais (PT-BR) | Brésil | timer tabata, treino hiit | 3 — Mois 2 |
## **10.2 Éléments à Localiser**
* App content : tous les textes UI, onboarding, notifications, erreurs
* App Store listing : titre, sous-titre, description courte, description longue
* Screenshots : localisés avec texte natif (utiliser Fastlane Frameit)
* Prix : PPP via RevenueCat (automatique par pays)
* Mots-clés App Store : 100 caractères spécifiques par marché
Outil recommandé : i18next \+ expo-localization pour la gestion des traductions en développement.
# **11\. App Store Optimization (ASO)**
## **11.1 Fiche App Store (iOS — EN)**
| Champ | Valeur optimisée |
| :---- | :---- |
| Nom de l'app (30 car.) | TabataGo — HIIT Timer |
| Sous-titre (30 car.) | Tabata & Interval Workout |
| Mots-clés (100 car.) | tabata,hiit,timer,interval,workout,fitness,training,countdown,sport,exercise |
| Description (1ère ligne) | The most motivating Tabata timer. 20 seconds of effort. Life-changing results. |
| Screenshots (10 max) | Voir section 10.2 |
| Preview video | 15-30s montrant la minuterie en action \+ streak \+ widget |
## **11.2 Plan des Screenshots (6 obligatoires)**
| \# | Contenu | Message clé | Background |
| :---- | :---- | :---- | :---- |
| 1 | Timer plein écran en action (20s) | "Train smarter, not longer" | Dark \+ orange |
| 2 | Écran d'onboarding moment Wow | "Your first Tabata in 20 seconds" | Gradient sombre |
| 3 | Widget iOS home screen | "Train from anywhere — even your lock screen" | iPhone mockup |
| 4 | Historique \+ Streak en feu | "Build the habit. Keep the streak." | Dark \+ vert |
| 5 | Bibliothèque de programmes | "100+ ready-to-go programs" | Gradient |
| 6 | Statistiques mensuelles | "See your progress. Stay motivated." | Fond clair |
# **12\. Plan de Lancement en 72h**
## **12.1 Timeline de Développement (72 heures)**
| Phase | Durée | Livrables | Responsable |
| :---- | :---- | :---- | :---- |
| Setup & Architecture | H0 → H8 | Expo project init, navigation, design system, RevenueCat config | Dev Lead |
| Onboarding | H8 → H20 | 6 écrans onboarding \+ mini-timer démo \+ animation | Dev \+ Design |
| Core Timer | H20 → H36 | Minuterie plein écran, son, haptiques, background timer | Dev |
| Streak \+ Historique | H36 → H48 | Système streak, storage, affichage historique, notifications | Dev |
| Paywall \+ Analytics | H48 → H58 | RevenueCat paywall, PostHog events, A/B test config | Dev |
| Polish & Testing | H58 → H68 | Bug fixes, dark/light mode, performance, edge cases | Dev \+ QA |
| Submission | H68 → H72 | Screenshots, App Store listing (5 langues), soumission Apple | PM \+ Dev |
## **12.2 Stratégie de Validation Post-Lancement**
Profiter du boost de visibilité des 3 premiers jours accordé par l'App Store aux nouvelles applications :
1. Jour 1-3 : Monitoring organique — Analyser impressions, téléchargements, et premiers essais
2. Jour 4-7 : Analyse funnel — Identifier le premier point de friction (onboarding? paywall?)
3. Jour 8-14 : Première itération — Corriger le problème prioritaire et soumettre mise à jour OTA (Expo)
4. Jour 15-30 : Activation publicité — Apple Search Ads seulement si CVR organique \> 15%
5. Jour 30+ : Scale — Augmenter budget pub sur les marchés qui convertissent le mieux
Règle absolue : Ne jamais dépenser en publicité avant d'avoir validé que le funnel organique convertit.
# **13\. KPIs & Métriques de Succès**
| Métrique | Semaine 1 | Mois 1 | Mois 3 | Outil de mesure |
| :---- | :---- | :---- | :---- | :---- |
| Téléchargements | 500+ | 5 000+ | 20 000+ | App Store Connect |
| Taux conversion Impression→DL | \> 5% | \> 8% | \> 10% | App Store Connect |
| Taux essai démarré | \> 25% | \> 30% | \> 35% | RevenueCat \+ PostHog |
| Taux conversion essai→payant | \> 30% | \> 35% | \> 40% | RevenueCat |
| Rétention J+7 | \> 30% | \> 35% | \> 40% | PostHog |
| Rétention J+30 | — | \> 20% | \> 25% | PostHog |
| MRR | — | $2 000+ | $8 000+ | RevenueCat |
| Note App Store | — | ≥ 4.5 | ≥ 4.6 | App Store Connect |
| Streak moyen (actifs) | — | \> 5 jours | \> 10 jours | PostHog custom |
# **14\. Risques & Mitigations**
| Risque | Probabilité | Impact | Mitigation |
| :---- | :---- | :---- | :---- |
| Refus App Store (guidelines) | Moyen | Élevé | Respecter HIG Apple, tester paywall sur TestFlight, pas de dark patterns |
| Mauvais taux de conversion paywall | Moyen | Élevé | A/B test RevenueCat dès J+7, 3 variantes de prix/layout |
| Précision du timer en background | Faible | Élevé | expo-background-fetch \+ notification locale comme fallback |
| Faible rétention sans streaks | Moyen | Moyen | Streak J+1 core feature, reminder push personnalisé par heure d'entraînement |
| Concurrence sur mots-clés EN | Élevé | Moyen | Agressivité sur marchés FR/ES/DE dès J+14 |
| Revue Apple lente (\> 48h) | Faible | Faible | Soumettre J-5 avant objectif lancement, utiliser Expo OTA pour hotfixes |
# **15\. Approbations & Versions**
| Rôle | Nom | Date | Signature |
| :---- | :---- | :---- | :---- |
| Product Owner | | Février 2026 | |
| Tech Lead | | Février 2026 | |
| Design Lead | | Février 2026 | |
| Marketing | | Février 2026 | |
*Document créé le 15 février 2026 — TabataGo PRD v1.1*

File diff suppressed because it is too large Load Diff

41
.github/workflows/admin-web-tests.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Admin Web Tests
on:
workflow_call:
inputs:
node-version:
type: string
default: '22'
jobs:
test:
name: Test
runs-on: nas-runner
timeout-minutes: 10
defaults:
run:
working-directory: admin-web
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
cache-dependency-path: admin-web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Run unit tests
run: npx vitest run
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npx playwright test

View File

@@ -10,7 +10,7 @@ jobs:
# ── Path filter — determines which downstream jobs run ──
changes:
name: Detect Changes
runs-on: ubuntu-latest
runs-on: nas-runner
outputs:
admin-web: ${{ steps.filter.outputs.admin-web }}
youtube-worker: ${{ steps.filter.outputs.youtube-worker }}
@@ -38,41 +38,16 @@ jobs:
name: Admin Web CI
needs: changes
if: needs.changes.outputs.admin-web == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: admin-web
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: admin-web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Run unit tests
run: npx vitest run
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npx playwright test
uses: ./.github/workflows/admin-web-tests.yml
with:
node-version: '22'
# ── YouTube Worker: Node.js microservice ──
youtube-worker-check:
name: YouTube Worker
needs: changes
if: needs.changes.outputs.youtube-worker == 'true'
runs-on: ubuntu-latest
runs-on: nas-runner
defaults:
run:
working-directory: youtube-worker
@@ -103,7 +78,7 @@ jobs:
!contains(needs.*.result, 'failure') &&
(needs.changes.outputs.supabase-functions == 'true' ||
needs.changes.outputs.youtube-worker == 'true')
runs-on: ubuntu-latest
runs-on: nas-runner
steps:
- uses: actions/checkout@v4

154
.github/workflows/docker-admin-web.yml vendored Normal file
View File

@@ -0,0 +1,154 @@
name: Docker Admin Web
on:
push:
branches: [main]
paths:
- 'admin-web/**'
- '.github/workflows/docker-admin-web.yml'
workflow_dispatch:
concurrency:
group: docker-admin-web-${{ github.ref }}
cancel-in-progress: false
jobs:
admin-web-test:
uses: ./.github/workflows/admin-web-tests.yml
semantic-release:
name: Semantic Release
needs: admin-web-test
runs-on: nas-runner
timeout-minutes: 5
defaults:
run:
working-directory: admin-web
outputs:
released: ${{ steps.check-release.outputs.released }}
version: ${{ steps.check-release.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: admin-web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Record version before release
id: before
run: echo "version=$(node -p "require('./version.json').version")" >> $GITHUB_OUTPUT
- name: Run semantic-release
env:
GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
GITEA_URL: https://gitea.1000co.fr
run: npx semantic-release
- name: Check if released
id: check-release
run: |
VERSION=$(node -p "require('./version.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
if [ "${{ steps.before.outputs.version }}" != "$VERSION" ]; then
echo "released=true" >> $GITHUB_OUTPUT
else
echo "released=false" >> $GITHUB_OUTPUT
fi
build-and-push:
name: Build & Push Docker Image
needs: semantic-release
if: needs.semantic-release.outputs.released == 'true' || github.event_name == 'workflow_dispatch'
runs-on: nas-runner
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Docker CLI
run: apt-get update -qq && apt-get install -y -qq docker.io
- name: Login to Gitea registry
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | \
docker login gitea.1000co.fr \
-u "${{ secrets.DOCKER_USERNAME }}" \
--password-stdin
- name: Build and push
run: |
U="${{ secrets.DOCKER_USERNAME }}"
V="${{ needs.semantic-release.outputs.version }}"
docker build \
--file ./admin-web/Dockerfile \
--tag "gitea.1000co.fr/$U/tabatago-admin-web:$V" \
--tag "gitea.1000co.fr/$U/tabatago-admin-web:latest" \
./admin-web
docker push "gitea.1000co.fr/$U/tabatago-admin-web:$V"
docker push "gitea.1000co.fr/$U/tabatago-admin-web:latest"
deploy:
name: Deploy to Portainer
needs: [semantic-release, build-and-push]
if: needs.semantic-release.outputs.released == 'true' || github.event_name == 'workflow_dispatch'
runs-on: nas-runner
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Install deps
run: apt-get update -qq && apt-get install -y -qq gettext-base jq curl
- name: Deploy stack to Portainer
env:
PORTAINER_TOKEN: ${{ secrets.PORTAINER_ACCESS_TOKEN }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
VERSION: ${{ needs.semantic-release.outputs.version }}
run: |
set -e
# Substitute env vars in compose file
export DOCKER_USERNAME VERSION
COMPOSE=$(envsubst '$DOCKER_USERNAME $VERSION' < admin-web/docker-compose.portainer.yml)
# Authenticate and discover Docker endpoint (first endpoint = local Docker)
ENDPOINT_ID=$(curl -sSf -H "X-API-Key: $PORTAINER_TOKEN" \
"https://portainer.1000co.fr/api/endpoints" | jq -r '.[0].Id')
echo "Endpoint ID: $ENDPOINT_ID"
# Check if "tabatago-admin-web" stack already exists
STACK_ID=$(curl -sSf -H "X-API-Key: $PORTAINER_TOKEN" \
"https://portainer.1000co.fr/api/stacks" | jq -r '.[] | select(.Name == "tabatago-admin-web") | .Id')
if [ -n "$STACK_ID" ] && [ "$STACK_ID" != "null" ]; then
echo "Stack tabatago-admin-web exists (ID: $STACK_ID), updating compose content..."
HTTP_RESPONSE=$(curl -s -w '\n%{http_code}' \
-X PUT \
-H "X-API-Key: $PORTAINER_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg content "$COMPOSE" '{stackFileContent: $content, prune: false, pullImage: true}')" \
"https://portainer.1000co.fr/api/stacks/$STACK_ID?endpointId=$ENDPOINT_ID" || true)
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -1)
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" -ge 400 ]; then
echo "Portainer API error (HTTP ${HTTP_CODE:-connection failed}):"
echo "$HTTP_BODY"
exit 1
fi
echo "Stack updated. Portainer will redeploy with new images."
else
echo "⚠️ Stack tabatago-admin-web not found in Portainer."
echo "Create it manually in Portainer first: Stacks → Add stack → paste compose content from admin-web/docker-compose.portainer.yml"
exit 1
fi

View File

@@ -1,13 +1,16 @@
# =============================================================================
# TabataGo — PR → Build → USB Direct Deploy → Wait LGTM → Merge
# TabataGo — PR → Build → WiFi/USB Direct Deploy → Wait LGTM → Merge
# =============================================================================
name: PR → Build → USB Deploy → LGTM
name: PR → Build → WiFi/USB Deploy → LGTM
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
paths:
- 'tabatago-swift/**'
- '.github/workflows/pr-iphone-deploy.yml'
permissions:
contents: write
@@ -16,7 +19,7 @@ permissions:
jobs:
build-deploy:
name: Build & Deploy to iPhone via USB
name: Build & Deploy to iPhone (WiFi/USB)
runs-on: macos
timeout-minutes: 20
@@ -102,7 +105,7 @@ jobs:
- name: Install to iPhone (WiFi → USB fallback)
run: |
UDID="00008120-000925CE3672201E"
APP=$(find ../build/derived -name 'TabataGo.app' -type d | head -1)
APP=$(find build/derived -name 'TabataGo.app' -type d | head -1)
# 1. Try WiFi first (Xcode 15+ devicectl network discovery)
echo "📱 Trying WiFi install via devicectl..."
@@ -133,7 +136,7 @@ jobs:
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB/WiFi).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
wait-approval:

4
.gitignore vendored
View File

@@ -66,3 +66,7 @@ DerivedData/
# XcodeGen generated — NOT in version control
TabataGo.xcodeproj
build/
# Xcode user state
*.xcuserstate

378
AGENTS.md
View File

@@ -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 : `.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 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

142
AGENTS.md.old Normal file
View File

@@ -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

12
admin-web/.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
node_modules
.next
.git
.gitignore
README.md
.npm
.pnp
.DS_Store
*.md
coverage
playwright-report
test-results

14
admin-web/.releaserc.json Normal file
View File

@@ -0,0 +1,14 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/exec", {
"prepareCmd": "echo '{\"version\": \"${nextRelease.version}\"}' > version.json"
}],
["@semantic-release/git", {
"assets": ["version.json"],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}]
]
}

View File

@@ -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

29
admin-web/Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-bookworm-slim AS production
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/public ./public
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
RUN groupadd -g 1001 appgroup && useradd -u 1001 -g appgroup -s /bin/sh -M nodeuser
USER nodeuser
EXPOSE 3000
ENTRYPOINT ["node", "server.js"]

View File

@@ -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!

View File

@@ -0,0 +1,16 @@
services:
admin-web:
image: gitea.1000co.fr/${DOCKER_USERNAME}/tabatago-admin-web:${VERSION:-latest}
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
networks:
- supabase_supabase-network
networks:
supabase_supabase-network:
external: true

View File

@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: 'standalone',
};
export default nextConfig;

View File

@@ -13,7 +13,8 @@
"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"
"test:all": "npm run test && npm run test:e2e",
"semantic-release": "semantic-release"
},
"dependencies": {
"@google/genai": "^1.47.0",
@@ -30,6 +31,10 @@
},
"devDependencies": {
"@playwright/test": "^1.58.2",
"@semantic-release/commit-analyzer": "^13.0.0",
"@semantic-release/exec": "^6.0.0",
"@semantic-release/git": "^10.0.0",
"@semantic-release/release-notes-generator": "^14.0.0",
"@tailwindcss/postcss": "^4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -42,6 +47,7 @@
"eslint": "^9",
"eslint-config-next": "16.1.6",
"jsdom": "^28.1.0",
"semantic-release": "^24.0.0",
"shadcn": "^3.8.5",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",

1
admin-web/version.json Normal file
View File

@@ -0,0 +1 @@
{"version": "0.1.0"}

View File

@@ -66,7 +66,7 @@ The workflow uses **automatic code signing**. Ensure:
## 4. Workflow Overview
The workflow at `.gitea/workflows/pr-iphone-deploy.yml` triggers on PRs to `main`:
The workflow at `.github/workflows/pr-iphone-deploy.yml` triggers on PRs to `main`:
```
PR Opened / Updated

View File

@@ -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
<Pressable testID={`workout-card-${index}`}>
{/* ... */}
</Pressable>
// WorkoutPlayer.tsx
<Button testID="done-button" title="Done" />
// Paywall.tsx
<Pressable testID="yearly-plan">
{/* ... */}
</Pressable>
```
### Required testIDs Checklist
- [ ] `workout-card-{index}` - Workout list items
- [ ] `collection-card-{index}` - Collection items
- [ ] `trainer-card-{index}` - Trainer items
- [ ] `done-button` - Complete workout button
- [ ] `yearly-plan` / `monthly-plan` - Subscription plans
- [ ] `start-workout-button` - Start workout CTA
- [ ] `category-{name}` - Category filter buttons
- [ ] `tab-{name}` - Bottom navigation tabs
---
## Environment Variables
Create `.env.maestro` file:
```bash
# Test Configuration
SKIP_PAYWALL=true
USE_MOCK_PURCHASE=true
TEST_USER_NAME=Test User
TEST_USER_EMAIL=test@example.com
# API Configuration (if needed)
API_BASE_URL=https://api-staging.tabatafit.com
```
---
## Troubleshooting
### Common Issues
1. **Tests fail on first run**
- Clear app state: `maestro test --clear-state`
- Increase timeout in config.yaml
2. **Element not found**
- Verify testID is set correctly
- Add wait times before assertions
- Check for animations completing
3. **Purchase tests fail**
- Ensure `USE_MOCK_PURCHASE=true` in test env
- Use sandbox/test products
4. **Slow tests**
- Use `waitForAnimationToEnd` with shorter timeouts
- Disable animations in test builds
### Debug Commands
```bash
# Interactive mode
maestro studio
# View hierarchy
maestro hierarchy
# Record test execution
maestro record <test-file>
# Verbose logging
maestro test --verbose <test-file>
```
---
## Next Steps (After Features Are Complete)
1. ✅ Create `.maestro/` directory structure
2. ✅ Write `config.yaml`
3. ✅ Implement P0 critical test flows
4. ✅ Add testIDs to React Native components
5. ✅ Run tests locally
6. ✅ Setup CI/CD pipeline
7. ⏳ Implement P1 core test flows
8. ⏳ Add visual regression tests
9. ⏳ Setup nightly regression suite
---
## Resources
- [Maestro Documentation](https://maestro.mobile.dev/)
- [Maestro YAML Reference](https://maestro.mobile.dev/api-reference/commands)
- [React Native Testing with Maestro](https://maestro.mobile.dev/platform-support/react-native)
- [Maestro Best Practices](https://maestro.mobile.dev/advanced/best-practices)
---
**Created:** March 17, 2026
**Status:** Implementation Pending (Waiting for feature completion)

View File

@@ -1,486 +0,0 @@
# TabataFit — UI Feature Brief
> Generated for Google Stitch design handoff.
> Covers all end-user screens, interactions, states, and navigation flows.
---
## App Overview
**TabataFit** is a mobile fitness app ("Apple Fitness+ for Tabata") built with React Native / Expo. It delivers guided Tabata HIIT workouts with video, voice coaching, music, and Apple Watch heart-rate sync.
### Design System
| Token | Value | Usage |
|-------|-------|-------|
| Background | `#000000` | Pure black base |
| Surface | `#1C1C1E` | Cards, sheets |
| Brand | `#FF6B35` | Flame orange — primary accent |
| Rest | `#5AC8FA` | Ice blue — rest phases |
| Success | `#30D158` | Energy green — completion |
| Prep phase | `#FF9500` | Orange-yellow |
| Work phase | `#FF6B35` | Flame orange |
| Rest phase | `#5AC8FA` | Ice blue |
| Complete phase | `#30D158` | Green |
- Supports **dark and light mode**
- Multi-language (i18n)
- Haptic feedback throughout
---
## Navigation Structure
```
Root Stack
├── Onboarding (6-step flow)
├── (tabs)
│ ├── Home — index
│ ├── Explore — browse workouts
│ ├── Activity — stats & history
│ └── Profile — settings & account
├── workout/[id] — Workout detail (push)
├── program/[id] — Program detail (push)
├── collection/[id] — Collection detail (push)
├── player/[id] — Workout player (push, full-screen)
├── complete/[id] — Post-workout celebration (push)
├── paywall — Premium upsell (modal)
├── explore-filters — Filter sheet (form sheet modal)
└── privacy — Privacy policy (push)
```
**Tab bar**: 4 tabs — Home, Explore, Activity, Profile. SF Symbol icons. Badge support.
---
## 1. Onboarding Flow
A 6-screen linear funnel shown on first launch. Progress dots at top. Skip available on some steps.
### 1.1 Problem Screen
- **Purpose**: Motivational hook about time constraints
- **Elements**: Headline text, subtitle, illustration
- **CTA**: Continue
### 1.2 Empathy Screen
- **Purpose**: User selects fitness barriers to build rapport
- **Elements**: Grid of 4 selectable cards — "No time", "Low motivation", "No knowledge", "No gym"
- **Interaction**: Tap to select, max 2 selections, visual highlight on selected
- **CTA**: Continue (enabled after 1+ selection)
### 1.3 Solution Screen
- **Purpose**: Show Tabata's effectiveness
- **Elements**: Animated comparison bar chart (Tabata vs traditional cardio calorie burn)
- **CTA**: Continue
### 1.4 Wow Screen
- **Purpose**: Reveal key app features
- **Elements**: 4 feature cards with staggered entrance animations — Timer, Exercises, Voice Coaching, Progress Tracking
- **CTA**: Continue
### 1.5 Personalization Screen
- **Purpose**: Collect user preferences to personalize experience
- **Inputs**:
- Name (text input)
- Fitness level: Beginner / Intermediate / Advanced (single select chips)
- Goal: Weight Loss / Cardio / Strength / Wellness (single select chips)
- Weekly frequency: 2x / 3x / 5x per week (single select chips)
- **CTA**: Continue (enabled when all fields filled)
### 1.6 Paywall Screen (Onboarding variant)
- **Purpose**: Premium conversion at end of onboarding
- **Elements**: Premium features list, yearly/monthly plan toggle with real prices from RevenueCat
- **CTAs**: Subscribe, Restore Purchases, Skip (close button)
- **States**: Loading prices, purchase in progress, error
---
## 2. Home Tab
Personalized dashboard and primary entry point.
### Elements
- **Greeting header**: Time-based ("Good morning/afternoon/evening") + user's name + animated mascot
- **Streak badge**: Current streak count with flame icon
- **Quick stats row**: 3 stat pills — Current streak, This week (workouts), Total minutes
- **Assessment card**: Feature-flagged (currently OFF) — fitness assessment prompt
- **Program cards**: 3 horizontal cards (Upper Body, Lower Body, Full Body)
- Each shows: icon, title, progress bar (% complete), status badge (Not Started / In Progress / Completed)
- CTA per card: Start / Continue / Restart (depends on state)
- **Switch program button**: Opens program selection
### Navigation
- Tap program card → `program/[id]`
- Tap "Start" on program → `player/[id]` (first workout)
- Tap "Continue" → `player/[id]` (next incomplete workout)
### States
- **New user**: 0 stats, no streak, programs at 0%
- **Returning user**: Populated stats, active streak, program progress
---
## 3. Explore Tab
Browse, search, and filter the full workout catalog.
### Elements
- **Search bar**: Search by workout title, trainer name, exercise name, category. Real-time filtering.
- **Featured collection**: Hero card at top with image, title, workout count. Tap → `collection/[id]`
- **Trainer avatars**: Horizontal scroll of circular trainer photos. Tap to filter workouts by trainer.
- **Collections carousel**: Horizontal scroll of collection cards. Tap → `collection/[id]`
- **Recommended For You**: Horizontal workout card list, personalized based on workout history
- **Featured workouts**: Grid of highlighted workouts
- **All Workouts section**:
- Category filter pills: All, Full Body, Upper Body, Lower Body, Core, Cardio
- Filter button → opens `explore-filters` sheet
- Active filter indicator + clear filters button
- 2-column workout card grid
### Workout Card
- Thumbnail image
- Duration badge overlay
- Title, trainer name, level indicator
- Lock icon if premium-only and user is free tier
### Navigation
- Tap workout card → `workout/[id]`
- Tap collection → `collection/[id]`
- Tap trainer avatar → filters workout list by that trainer
- Tap filter button → `explore-filters` (form sheet modal)
### States
- **Loading**: Skeleton placeholders for cards
- **Error**: Error message with Retry button
- **Empty search**: "No workouts found" message
- **Filtered**: Active filter chips shown, clear all button
---
## 4. Activity Tab
Workout history, statistics, and achievements.
### Elements
- **Streak banner**: Current streak + longest streak (flame icons)
- **Stats grid** (2x2): Each stat in a card with circular progress ring
- Total workouts (ring fills toward goal)
- Total minutes
- Total calories
- Best streak
- **Weekly bar chart**: SunSat, each bar filled if a workout was completed that day, current day highlighted
- **Recent workouts list**: Last 5 workouts
- Each row: workout title, relative time ("2h ago"), duration, calories
- Tap → `workout/[id]`
- **Achievements grid**: 4 achievement badges displayed
- Types: workouts milestone, streak milestone, minutes milestone, calories milestone
- States: locked (greyed out) / unlocked (colored with checkmark)
### States
- **Empty**: No workouts yet — motivational message + "Start Your First Workout" CTA → `explore`
- **Populated**: All sections visible with data
---
## 5. Profile Tab
User settings, account management, and app info.
### Elements
- **User header**: Avatar circle with initial, display name, plan label ("Free" or "TabataFit+")
- **Stats row**: 3 inline stats — workouts count, streak, calories
- **Upgrade CTA** (free users only): Gradient button → `paywall`
#### Workout Settings Section
- Haptic feedback toggle
- Sound effects toggle
- Voice coaching toggle
#### Notifications Section
- Daily reminders toggle
- Reminder time display (when enabled)
#### Personalization Section (premium only)
- Sync status indicator
#### About Section
- Version number
- Rate App → opens App Store rating prompt
- Contact Us → opens email compose
- FAQ → opens external web link
- Privacy Policy → `privacy` screen
#### Account Section (premium only)
- Restore Purchases → triggers RevenueCat restore
#### Danger Zone
- Sign Out button
- Data deletion: triggers confirmation modal
### Data Deletion Modal
- Warning text explaining data loss
- Cancel / Delete buttons
- Delete is destructive (red)
---
## 6. Workout Detail Screen
Pre-workout information screen. Reached by tapping any workout card.
**Route**: `workout/[id]`
### Elements
- **Header**: Thumbnail or video preview, back button, heart/save button (toggle)
- **Title**: Workout name
- **Trainer**: Trainer name (colored text)
- **Metadata row**: Duration (minutes), Calories estimate, Level badge (Beginner/Intermediate/Advanced)
- **Equipment list**: Icons + labels for required equipment (or "No equipment")
- **Timing card**: Prep time, Work time, Rest time, Rounds — displayed in a structured card
- **Exercise list**: Ordered list of exercises with individual durations
- **Repeat rounds indicator**: Shows if rounds repeat the exercise sequence
- **Music vibe label**: Genre/mood of the workout soundtrack
### CTAs
- **Start Workout** → `player/[id]` (if unlocked or user is premium)
- **Unlock with TabataFit+** → `paywall` (if locked and user is free tier)
### Header Actions
- **Back**: Navigate back
- **Save/Unsave**: Heart icon toggle — saves workout to favorites
### States
- **Loading**: Skeleton layout
- **Unlocked**: Full detail visible, "Start Workout" CTA
- **Locked**: Full detail visible but "Unlock with TabataFit+" CTA replaces start button
---
## 7. Program Detail Screen
Multi-week training program overview with per-week workout breakdown.
**Route**: `program/[id]`
### Elements
- **Header**: Program icon, title, subtitle (e.g., "4 weeks · 12 workouts")
- **Description**: Program summary text
- **Stats card**: 3 stats — Weeks, Workouts, Total Minutes
- **Tags**: Equipment required (e.g., Dumbbells, Mat) + Equipment optional + Focus areas (e.g., Arms, Core)
- **Progress bar** (if started): Percentage complete with label
- **Training plan**: Expandable week sections
- Each week shows its workouts in order
- Workout rows show: title, duration, completion checkmark (if done)
- Current week has a "Current" badge
- Future weeks may show lock icons (progressive unlock)
### CTAs
- **Start Program** (not started) → `player/[id]` (first workout)
- **Continue Training** (in progress) → `player/[id]` (next incomplete workout)
- **Restart** (completed) → resets progress, starts from week 1
### States
- **Not Started**: 0% progress, all weeks shown, "Start Program" CTA
- **In Progress**: Progress bar filled, completed workouts checked, "Continue Training" CTA
- **Completed**: 100% progress, all checked, "Restart" CTA
---
## 8. Collection Detail Screen
A curated group of workouts.
**Route**: `collection/[id]`
### Elements
- **Header**: Collection title, description, workout count
- **Workout list**: Vertical list of workout cards in the collection
- Each card: thumbnail, title, trainer, duration, level
- Lock icon for premium-gated workouts
### Navigation
- Tap workout → `workout/[id]`
- Back button → previous screen
---
## 9. Player Screen
Full-screen workout execution with timer, video, audio, and Watch sync.
**Route**: `player/[id]`
### Layout
- **Full-screen dark mode** — no tab bar, no status bar chrome
- **Background**: Workout video (HLS streaming) or gradient fallback
- **Phase-colored tint**: Background overlay changes color per phase (prep=orange, work=flame, rest=blue, complete=green)
### Timer Section
- **Timer ring**: Large circular progress indicator, fills as phase progresses
- **Phase label**: PREP / WORK / REST / COMPLETE (color-coded)
- **Countdown**: Large MM:SS timer (uses tabular-nums for alignment)
- **Round indicator**: "Round 3 of 8" text
### Exercise Info
- **Current exercise name**: Large text
- **Next exercise preview**: Smaller text ("Up next: Burpees")
- **Coach encouragement**: Motivational text overlays (e.g., "Keep going!", "Almost there!")
### Controls
- **Start**: Begins the workout (shown before first start)
- **Pause / Resume**: Toggle button during workout
- **Stop**: Ends workout early (confirmation prompt)
- **Skip**: Skip to next phase
### Stats Overlay
- **Calories**: Running calorie count
- **Heart rate**: BPM from Apple Watch (if connected)
- **Rounds**: Current / total
### Burn Bar
- Horizontal bar comparing user's current calorie burn vs. average for this workout
- Updates in real-time
### Now Playing Pill
- Shows current music track name
- Skip track button
### Audio & Haptics
- **Sound effects**: Phase start chime, 3-2-1 countdown beeps, workout complete fanfare
- **Haptic feedback**: Phase transitions, countdown ticks, button presses
- **Voice coaching**: Audio cues for exercises and encouragement
- **Screen stays awake** (useKeepAwake)
### Apple Watch Integration
- Sends: workout state (phase, timer, exercise)
- Receives: play/pause, skip, stop commands, heart rate data
### Completion State
- Timer ring shows 100%
- Phase label: COMPLETE
- Summary: Rounds completed, calories burned, total minutes
- **Done** CTA → `complete/[id]`
### States
- **Ready**: Before starting — shows workout info, Start CTA
- **Active**: Timer running, video playing, stats updating
- **Paused**: Timer frozen, controls show Resume
- **Complete**: Summary shown, Done CTA
---
## 10. Workout Complete Screen
Post-workout celebration and next steps.
**Route**: `complete/[id]`
### Elements
- **Celebration animation**: Concentric emoji rings spinning (fire, muscle, lightning emojis)
- **Stats grid**: 3 stats — Calories, Minutes, 100% completion
- **Burn bar result**: Percentile comparison ("You burned more than 73% of users")
- **Streak display**: Current streak count + subtitle ("Keep it going!")
- **Share button**: Opens native share sheet with workout summary
- **Recommended next workouts**: 3 horizontal workout cards
- Tap → `workout/[id]`
- **Back to Home** CTA → navigates to Home tab
### Sync Consent Modal
- Appears after first workout for premium users
- Prompts to enable cross-device data sync
- Accept / Decline buttons
---
## 11. Paywall Screen
Premium subscription purchase flow.
**Route**: `paywall` (presented as modal)
### Elements
- **Header**: "TabataFit+" branding
- **Features grid**: 6 premium feature cards with icons
- Music during workouts
- Unlimited workouts
- Detailed stats
- Calorie tracking
- Smart reminders
- No ads
- **Plan selection**: Two radio-style options
- Yearly: Price/year + "Save 50%" badge (highlighted as best value)
- Monthly: Price/month
- Prices fetched live from RevenueCat
- **Subscribe CTA**: Gradient button, shows selected plan price
- **Restore purchases**: Text link below CTA
- **Terms**: Privacy policy + terms of service links
- **Close button**: X in top corner to dismiss
### States
- **Loading**: Skeleton while fetching prices from RevenueCat
- **Ready**: Plans displayed with real prices
- **Purchasing**: Loading spinner on CTA, inputs disabled
- **Error**: Error message with retry
- **Success**: Dismisses modal, unlocks premium features
---
## 12. Explore Filters Sheet
Filter modal for the Explore tab workout grid.
**Route**: `explore-filters` (form sheet modal with grabber)
### Elements
- **Level filter chips**: All / Beginner / Intermediate / Advanced (single select)
- **Equipment filter chips**: All / None / Dumbbells / Band / Mat (dynamic from data, single select)
- **Apply**: Dismiss sheet, filters persist in shared store
- **Clear**: Reset all filters to "All"
---
## 13. Privacy Policy Screen
Static content screen.
**Route**: `privacy`
### Elements
- Privacy policy text content
- Back navigation
---
## Cross-Cutting Features
### Premium Gating
- Free users see all workouts but some are locked (lock icon overlay)
- Tapping a locked workout's "Start" CTA redirects to `paywall`
- Premium users have full access to all workouts, stats sync, and personalization
### Internationalization (i18n)
- All user-facing strings are translated via i18n system
- Multi-language support throughout
### Haptic Feedback
- Configurable via Profile settings toggle
- Triggered on: button presses, phase changes, countdown ticks, achievements
### Analytics (PostHog)
- Events tracked across all screens: screen views, button taps, workout starts/completions, purchases, onboarding steps
### Dark / Light Mode
- Full theme support — colors adapt to system appearance
- Player screen is always dark mode regardless of system setting
### Loading & Error States
- Skeleton placeholders during data fetches
- Error states with descriptive message + Retry button
- Empty states with motivational messaging + CTAs
### Animations
- Onboarding: staggered card reveals, animated charts
- Home: mascot animation
- Player: timer ring fill, phase color transitions
- Complete: spinning emoji celebration rings
- Navigation: standard iOS push/pop + modal presentations

View File

@@ -1,3 +1,6 @@
-- ⚠️ OBSOLÈTE — Ce fichier décrit l'ancien concept "TabataFit" (abandonné).
-- Le schéma actif est dans supabase/migrations/001 → 006.
-- Voir AGENTS.md pour la référence canonique.
-- ============================================================
-- TabataFit Supabase Schema
-- ============================================================

View File

@@ -1,3 +1,5 @@
-- ⚠️ OBSOLÈTE — Ce fichier date du concept "TabataFit" (abandonné).
-- Le vrai seed est dans supabase/migrations/006_seed_workout_programs.sql.
-- ============================================================
-- TabataFit Seed Data
-- ============================================================

View File

@@ -1,3 +1,5 @@
-- ⚠️ PARTIELLEMENT OBSOLÈTE — Référence TabataFit.
-- La fonction is_admin() reste valide pour admin_users.
-- ============================================================
-- Setup: Initial Admin User and Authentication
-- ============================================================

View File

@@ -28,7 +28,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2</string>
<string>1</string>
<key>NSHealthShareUsageDescription</key>
<string>TabataGo reads your health data to show fitness stats and personalize your workouts.</string>
<key>NSHealthUpdateUsageDescription</key>

View File

@@ -8,6 +8,7 @@
"name": "youtube-worker",
"version": "1.0.0",
"dependencies": {
"@google/genai": "^1.46.0",
"@supabase/supabase-js": "^2.49.1",
"youtubei.js": "^17.0.1"
}
@@ -18,6 +19,87 @@
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@google/genai": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz",
"integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.3.0",
"p-retry": "^4.6.2",
"protobufjs": "^7.5.4",
"ws": "^8.18.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.2"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"optional": true
}
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
"license": "BSD-3-Clause"
},
"node_modules/@supabase/auth-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz",
@@ -113,6 +195,12 @@
"undici-types": "~7.18.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -122,6 +210,193 @@
"@types/node": "*"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/gaxios": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz",
"integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/gcp-metadata": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-auth-library": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz",
"integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^7.1.4",
"gcp-metadata": "8.1.2",
"google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-logging-utils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
@@ -131,6 +406,42 @@
"node": ">=20.0.0"
}
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/meriyah": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz",
@@ -140,6 +451,115 @@
"node": ">=18.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/protobufjs": {
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.5",
"@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.1",
"@types/node": ">=13.7.0",
"long": "^5.3.2"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -152,6 +572,15 @@
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",