docs(ideai): mémoire client online, clôture ticket #64, cadrage #65-#70

Ajoute les notes mémoire d'architecture, de philosophie de la couche
online et d'UX pour le client online, clôture le ticket #64, reflète
les tickets #46/#54/#57/#62, ajoute le cadrage des tickets #65 à #70.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 21:25:57 +02:00
parent e60c8bbdb8
commit 53b4957bc6
28 changed files with 910 additions and 37 deletions

View File

@ -16,3 +16,6 @@
- [gametime-server-architecture-sync-sharing](gametime-server-architecture-sync-sharing.md) — memory note gametime-server-architecture-sync-sharing
- [gametime-ux-exercise-steps](gametime-ux-exercise-steps.md) — memory note gametime-ux-exercise-steps
- [gametime-architecture-exercise-steps](gametime-architecture-exercise-steps.md) — memory note gametime-architecture-exercise-steps
- [gametime-online-layer-philosophy](gametime-online-layer-philosophy.md) — memory note gametime-online-layer-philosophy
- [gametime-ux-online-client](gametime-ux-online-client.md) — memory note gametime-ux-online-client
- [gametime-architecture-online-client](gametime-architecture-online-client.md) — memory note gametime-architecture-online-client

View File

@ -0,0 +1,171 @@
---
name: gametime-architecture-online-client
description: memory note gametime-architecture-online-client
metadata:
type: project
---
# GameTime — Architecture client online, auth, sync et partage
Décision d'architecture pour le ticket #63, basée sur les mémoires `gametime-ux-online-client`, `gametime-online-layer-philosophy` et `gametime-server-architecture-sync-sharing`.
## Conclusion serveur : pas d'adaptation requise pour les étapes d'exercice
Le serveur ne valide pas la forme interne des payloads synchronisés. `synced_resources.payload_json` est un JSONB opaque stocké et retourné tel quel.
Preuves dans le code :
- `server/migrations/0001_initial_schema.sql` : `payload_json jsonb NOT NULL` sur `synced_resources`, avec contraintes seulement sur `resource_type`, `client_id`, `schema_version`, pas sur la forme de `payload_json`.
- `server/lib/infrastructure/postgres/synced_resource_repository.dart` : l'upsert écrit `@payload_json::jsonb`, avec `jsonEncode(resource.payloadJson)`, puis retourne `payload_json` via `_payloadValue(...)`. Aucune validation métier de structure exercice/programme/template n'est faite dans ce repository.
- `server/openapi.yaml` : `SyncPushItem.payload` et `SyncedResourceItem.payload` référencent seulement `JsonObject`. `CreateShareRequest.payload` et `ShareInboxItem.payload` idem.
Conclusion : l'ajout des étapes d'exercice côté client ne nécessite pas de modification serveur pour la sync v1. Le client peut envoyer la nouvelle forme JSON dans `payload` tant qu'elle reste un objet JSON valide et porte `schemaVersion` correctement.
## Principe produit non négociable
La couche online est optionnelle et additive :
- pas d'écran de login au démarrage ;
- aucune action locale ne dépend du serveur ;
- aucun échec réseau ne déclenche de popup globale ;
- les données nécessaires à l'UI restent en local ;
- logout ne supprime jamais exercices/programmes/séances/historique locaux.
## Architecture lib/
Conserver l'architecture existante :
- `lib/domain/entities.dart` : entités pures `UserAccountSession`, `SyncStatusSnapshot`, `ShareInboxItem`, `PendingShareAction` si elles sont stables métier.
- `lib/application/ports.dart` : ports `AuthTokenStore`, `OnlineAccountRepository`, `RemoteSyncApi`, `RemoteShareApi`, `OnlineSessionRepository`, `SyncMetadataRepository`, `ShareInboxRepository`.
- `lib/application/use_cases.dart` : `AuthUseCases`, `SyncUseCases`, `ShareUseCases`.
- `lib/infrastructure/local/` : cache profil, curseur sync, inbox partages, queue d'actions pending, mapping Drift.
- `lib/infrastructure/remote/` : adapter HTTP vers `server/openapi.yaml`, DTO wire, sérialisation JSON.
- `lib/presentation/` : Profil, auth, statut sync, partage sortant, inbox.
Le domaine/application ne dépend pas de `http`, `flutter_secure_storage` ni Drift.
## Dépendances client retenues
- Tokens : `flutter_secure_storage`.
- Justification : stockage sécurisé cross-platform via mécanismes natifs ; adapté à access/refresh tokens.
- Ne pas stocker le profil affichable uniquement dedans.
- HTTP : `package:http` avec `Client` injecté.
- Justification : API simple, composable, facile à fake en tests, dépendance plus légère que Dio. Ajouter un wrapper pour baseUrl, auth bearer, JSON, timeouts et mapping d'erreurs.
## Authentification client
Entité locale recommandée : `UserAccountSession`.
Champs :
- `id` local.
- `serverUserId`.
- `email`.
- `displayName?`.
- `avatarLocalUri?` ou `avatarRemoteUri?` si disponible plus tard.
- `isLoggedIn`.
- `createdAt`, `updatedAt`, `lastAuthenticatedAt?`.
Stockage :
- tokens opaques : `flutter_secure_storage` via port `AuthTokenStore`.
- profil/cache visible : Drift, pour affichage instantané hors ligne.
Use cases :
- `register(email, password)` -> API `POST /auth/register`, stocke tokens si réponse connectante, cache profil, déclenche sync en arrière-plan.
- `login(email, password)` -> `POST /auth/login`, stocke tokens, cache profil, déclenche sync en arrière-plan.
- `logout()` -> tente `POST /auth/logout`, mais supprime tokens localement même si réseau KO ; conserve données locales et profil dernier connu si utile à l'historique d'affichage, avec état déconnecté.
- `currentSession()` -> lit cache local + présence token.
Erreurs :
- erreurs credentials : retournées au formulaire auth en erreur inline.
- réseau/timeout : erreur typée non intrusive ; jamais popup globale.
## Synchronisation client
Ressources synchronisées v1 :
- `exercise`
- `program`
- `workoutTemplate`
- `workoutHistory`
- `mediaAsset` metadata uniquement
Mapping push :
```json
{
"resourceType": "exercise",
"clientId": "<local entity id>",
"schemaVersion": <local schema/payload version>,
"clientUpdatedAt": "<entity.metadata.updatedAt UTC>",
"deletedAt": "<entity.metadata.deletedAt|null>",
"payload": { "...": "snapshot complet local" }
}
```
Le payload doit être le snapshot local complet et autonome nécessaire pour reconstruire l'entité. Pour les exercices, il inclut les étapes ajoutées par #54. Le serveur ne l'interprète pas.
Source des changements à pousser : utiliser `change_log`, `syncState`, `updatedAt`, `deletedAt`, `localRevision`. Le gateway doit lire les entités concernées, assembler les payloads, appeler `POST /sync/push` ou `POST /sync/exchange`, puis marquer les items acceptés comme synced et enregistrer les mappings serveur si nécessaires.
Pull :
- stocker `serverCursor` localement.
- appeler `GET /sync/pull?since=<cursor>` ou `POST /sync/exchange`.
- appliquer chaque item selon LWW côté client : si `server.clientUpdatedAt` est plus récent que `local.updatedAt`, appliquer ; sinon ignorer localement.
- soft delete distant : appliquer `deletedAt` local sans hard delete.
- mettre à jour le curseur seulement après transaction locale réussie.
Déclencheurs :
- après login/register : sync en arrière-plan, sans loader bloquant.
- au démarrage si connecté : tentative silencieuse.
- après mutation locale : planifier une sync arrière-plan courte/debounced.
- périodique quand app active : intervalle raisonnable, pas besoin de temps réel.
- manuel depuis Profil : `Synchroniser maintenant`.
Échec sync :
- pas de popup ;
- statut neutre dans Profil ;
- retry différé ;
- l'app reste utilisable localement.
## Drift / migration client
Le schéma local actuel est `schemaVersion = 9`. Si les tables online sont ajoutées, passer à `schemaVersion = 10`.
Tables recommandées :
### `online_account_sessions`
- cache profil et état connecté/déconnecté.
- pas de tokens en clair.
### `sync_metadata`
- singleton ou key/value : `serverCursor`, `lastSuccessfulSyncAt`, `lastAttemptAt`, `lastFailureAt`, `status`, `pendingPushCount?`.
### `remote_resource_mappings`
- `resourceType`, `clientId`, `serverId`, `serverUpdatedAt`, unique `(resourceType, clientId)`.
### `share_inbox_items`
- `shareId`, sender info, `resourceType`, `payloadJson`, `status`, `createdAt`, `updatedAt`, cache offline.
### `pending_share_actions`
- queue locale pour share/send/accept/decline/revoke si réseau indisponible.
- champs : `id`, `actionType`, `shareId?`, `resourceType?`, `payloadJson?`, `recipientEmailsJson?`, `createdAt`, `lastAttemptAt?`, `attemptCount`, `status`.
## Partage client
Ports/use cases :
- `sendShare(resourceType, localResourceId, recipientEmails)` : construit payload snapshot local de Program ou WorkoutTemplate, appelle `POST /shares`; si réseau KO, met en queue.
- `refreshInbox()` : appelle `GET /shares/inbox`, cache les items.
- `acceptShare(shareId)` : appelle `POST /shares/{id}/accept`.
- `declineShare(shareId)`.
- `revokeShare(shareId)`.
Acceptation : OpenAPI indique que `AcceptShareResponse.createdResource` renvoie directement un `SyncedResourceItem`. Le client peut donc importer immédiatement la ressource créée dans le stockage local, puis le prochain pull sert de convergence. Si l'accusé serveur ne peut pas partir mais que le payload inbox est déjà local, l'app peut créer une copie locale indépendante et mettre l'action accept en queue selon le choix d'implémentation, avec message neutre.
Invariant : un partage accepté devient une copie locale normale, non liée dynamiquement à l'expéditeur. Les doublons de noms sont autorisés.
## Tickets créés
- #64 `[DevBackend] Client online : session compte, stockage sécurisé et adapter API`.
- #65 `[DevBackend] Sync client incrémentale LWW vers API serveur`, dépend de #64.
- #66 `[DevBackend] Partage client : use cases, inbox cache et import local`, dépend de #64 et #65.
- #67 `[DevFrontend] Profil et écrans auth optionnels`, dépend de #64.
- #68 `[DevFrontend] Statut de synchronisation discret et action manuelle`, dépend de #65 et #67.
- #69 `[DevFrontend] Partage sortant et boîte de réception`, dépend de #66 et #67.
- #70 `[QA] Validation client online offline-first`, dépend de #68 et #69.
Ordre recommandé : #64 -> #65 -> #66, en parallèle #67 après #64, puis #68 et #69, puis #70.

View File

@ -0,0 +1,31 @@
---
name: gametime-online-layer-philosophy
description: memory note gametime-online-layer-philosophy
metadata:
type: project
---
# GameTime — Philosophie de la couche online (compte, sync, partage)
Décision produit posée par l'utilisateur pour le ticket #57, à respecter par tous les tickets futurs touchant au serveur/à la synchronisation/au compte utilisateur (notamment #63 et la suite).
## Principe directeur
L'application reste **offline-first en priorité**, la couche online est un **complément transparent**, jamais une dépendance bloquante. Référence produit explicite : Hevy.
- La connexion à un compte est **toujours optionnelle**. L'app doit être pleinement utilisable sans jamais se connecter.
- Si le serveur est indisponible (pas de réseau, serveur down, timeout...), **aucune popup d'erreur, aucun blocage, aucun message intrusif** ne doit apparaître à l'utilisateur pendant son usage normal. L'absence de connectivité doit être silencieuse pour les fonctionnalités de base.
- Le serveur **sert l'application, jamais l'inverse** : en cas de divergence de structure de données entre le client (source de vérité fonctionnelle) et le serveur, c'est le serveur qui doit être adapté pour accepter la donnée du client, pas le client qui doit se plier au serveur. (Exemple concret déjà identifié : le schéma serveur des exercices doit être mis à jour pour accepter les étapes ajoutées côté client par le chantier #54, la forme serveur ayant été figée avant ce chantier.)
## Ce qui doit toujours rester stocké en local (jamais uniquement distant)
Tout ce qui est nécessaire au bon affichage/fonctionnement normal de l'app doit avoir une copie locale à jour, sans dépendre d'un appel réseau pour s'afficher correctement :
- Exercices, programmes, séances-modèles, historique de séances (déjà le cas, c'est la base offline-first existante).
- Statistiques calculées, si/quand elles existeront.
- Données de profil utilisateur une fois connecté : pseudo, photo de profil, et toute autre donnée de compte affichée dans l'UI — gardées en cache local pour un affichage instantané et cohérent même hors ligne, sans jamais afficher une valeur fausse ou périmée qui induirait l'utilisateur en erreur (mieux vaut réafficher la dernière valeur connue que d'afficher une erreur ou un vide trompeur).
## Comment appliquer ce principe
- Toute UI liée au compte/sync (menu "Profil", statut de connexion, etc.) doit être conçue comme une couche additive et discrète, jamais comme un gate ou une interruption du parcours principal.
- Les échecs réseau/sync doivent être gérés silencieusement en arrière-plan (retry différé, file d'attente, etc.) — voir [[gametime-server-architecture-sync-sharing]] pour le protocole de sync LWW déjà défini côté serveur, qui doit être consommé côté client dans cet esprit.
- Ne jamais bloquer une action locale (créer un exercice, terminer une série, etc.) en attendant une confirmation serveur.

View File

@ -0,0 +1,429 @@
---
name: gametime-ux-online-client
description: memory note gametime-ux-online-client
metadata:
type: project
---
# GameTime — UX client couche online, compte, sync, partage (ticket #63, UX 2026-07-19)
Conception client pour consommer les fonctionnalités serveur : comptes utilisateur, synchronisation incrémentale LWW, partage ciblé de programmes/séances. Cette conception respecte la mémoire `gametime-online-layer-philosophy` : l'app reste offline-first, la connexion est optionnelle, aucune action locale n'attend le serveur, aucun échec réseau ne doit interrompre le parcours principal.
## Principe directeur UI
La couche online est une **option de profil**, pas une porte d'entrée obligatoire.
- Ne jamais afficher d'écran de connexion au démarrage.
- Ne jamais bloquer `Exercices`, `Programmes`, `Séances`, `Historique` parce que l'utilisateur est déconnecté.
- Ne jamais afficher de popup globale en cas d'échec de sync.
- Les statuts online apparaissent uniquement dans des zones volontaires : `Profil`, formulaires de connexion, partage, boîte de réception.
- Toute donnée locale reste immédiatement consultable/modifiable, connecté ou non.
## Navigation
Contexte actuel : `HomeScreen` est une liste d'entrées (`Exercices`, `Programmes`, `Séances`, `Historique`) avec AppBar logo + menu thème, sans bottom nav.
Décision UX : ne pas introduire de bottom nav ni refondre la navigation.
Ajouter une entrée `Profil` dans la liste d'accueil, après `Historique` :
```text
Profil
Compte, synchronisation et partages
```
Icône : `Icons.account_circle_outlined` si déconnecté, avatar/photo locale si connecté.
L'AppBar peut rester dédiée au logo et au thème. Si une indication rapide est souhaitée plus tard, préférer un petit avatar en AppBar seulement sur l'accueil, mais ce n'est pas nécessaire au MVP.
## 1. Écran Profil
### État déconnecté
Titre : `Profil`
Bloc principal Court Blazer :
```text
Compte optionnel
GameTime fonctionne entièrement sans compte. Connecte-toi seulement si tu veux sauvegarder tes données en ligne ou partager des programmes et séances.
[Créer un compte]
[Se connecter]
```
Style : ton neutre, aucune culpabilisation, aucun warning.
Section informative :
```text
Données locales
Tes exercices, programmes, séances et historiques sont enregistrés sur cet appareil.
```
Section partages, non interactive ou secondaire :
```text
Partages
Connecte-toi pour envoyer et recevoir des programmes ou des séances.
```
Ne pas afficher de bouton `Continuer sans compte` : l'utilisateur est déjà dans l'app, donc ce bouton serait redondant.
### État connecté
En haut : carte profil.
```text
[photo/avatar]
Pseudo
email@example.com
```
Si pseudo absent : afficher l'email comme identifiant principal. Si photo absente : avatar initiales ou icône compte. Pseudo/photo doivent venir du cache local et s'afficher instantanément même hors ligne.
Actions :
```text
[Modifier le profil] // seulement si API/client le supporte dans ce lot
[Se déconnecter]
```
Si `Modifier le profil` n'est pas supporté par le serveur, ne pas afficher l'action au MVP.
Section synchronisation :
```text
Synchronisation
À jour à 14:32
```
États possibles, toujours neutres :
- Sync en cours : `Synchronisation en cours...`
- Sync réussie : `À jour à 14:32`
- Sync jamais faite : `Synchronisation en attente`
- Sync échouée : `Dernière synchronisation : hier à 18:20. Nouvelle tentative automatique.`
- Hors ligne détecté : `Hors ligne. Les données restent disponibles.`
Ne pas utiliser de rouge pour la sync échouée. Utiliser icônes neutres : `cloud_outlined`, `sync`, `cloud_done_outlined`, `schedule`.
Section partages :
```text
Partages reçus [badge si éléments en attente]
Programmes et séances reçus d'autres comptes
```
Tap → `Partages reçus`.
Déconnexion : confirmation nécessaire.
```text
Se déconnecter ?
Les données restent sur cet appareil. La synchronisation et les partages seront suspendus jusqu'à une prochaine connexion.
[Annuler]
[Se déconnecter]
```
Après logout : retour à l'état déconnecté, aucune suppression locale.
## 2. Création de compte / Connexion
Écrans accessibles depuis `Profil`, jamais imposés ailleurs.
### Se connecter
AppBar : `Se connecter`
Champs :
```text
Email
Mot de passe
```
Actions :
```text
[Se connecter]
[Créer un compte]
```
Texte secondaire bas d'écran :
```text
Tu peux continuer à utiliser GameTime sans compte.
```
Validation locale :
- email vide/invalide : `Saisis une adresse email valide.`
- mot de passe vide : `Saisis ton mot de passe.`
Erreurs serveur affichées inline dans le formulaire, jamais en popup :
- identifiants invalides : `Email ou mot de passe incorrect.`
- serveur/réseau indisponible : `Connexion impossible pour le moment. Réessaie plus tard.`
Après succès : revenir à `Profil` avec message discret :
```text
Compte connecté. Synchronisation en arrière-plan.
```
Ne pas afficher de loader bloquant de synchronisation initiale.
### Créer un compte
AppBar : `Créer un compte`
Champs :
```text
Email
Mot de passe
Confirmer le mot de passe
```
Actions :
```text
[Créer le compte]
[Déjà un compte ? Se connecter]
```
Texte secondaire :
```text
Le compte sert à synchroniser tes données et partager tes contenus. L'app reste utilisable sans compte.
```
Validation locale :
- email vide/invalide : `Saisis une adresse email valide.`
- mot de passe vide : `Saisis un mot de passe.`
- confirmation différente : `Les mots de passe ne correspondent pas.`
Erreurs serveur inline :
- email déjà utilisé : `Un compte existe déjà avec cet email.`
- réseau/serveur : `Création impossible pour le moment. Réessaie plus tard.`
Après succès : connecter l'utilisateur si le serveur renvoie un token, retour Profil, sync arrière-plan.
## 3. Indication de synchronisation
La synchronisation doit être transparente et non intrusive.
### Où afficher
- Principalement dans `Profil`, section `Synchronisation`.
- Optionnel : sous-titre de l'entrée `Profil` sur l'accueil :
- déconnecté : `Compte optionnel`
- connecté à jour : `Synchronisé récemment`
- connecté sync en attente : `Synchronisation en attente`
Ne pas ajouter de bannière globale ou snackbar automatique sur échec réseau.
### États visuels
- `Synchronisation en cours...` : petite icône `sync`, éventuellement rotation subtile.
- `À jour à 14:32` : icône neutre ou `cloud_done_outlined` en couleur texte secondaire, pas nécessairement vert.
- `Dernière synchronisation : hier à 18:20. Nouvelle tentative automatique.` : icône `schedule`, texte secondaire.
- `Hors ligne. Les données restent disponibles.` : neutre/informatif.
Actions possibles dans Profil :
```text
[Synchroniser maintenant]
```
Si échec : rester sur le même écran avec texte neutre, pas de dialog.
## 4. Partage sortant
Le partage concerne les `Programmes` et les `Séances`.
### Point d'entrée
Ne pas surcharger les listes avec une nouvelle icône visible sur chaque ligne.
Point d'entrée recommandé : dans l'écran de modification/détail d'un programme ou d'une séance déjà enregistrée, ajouter une action AppBar :
```text
Partager
```
Icône : `Icons.ios_share` ou `Icons.share_outlined`.
Dans les listes, si un menu `...` existe plus tard, `Partager` peut y être ajouté, mais ce n'est pas obligatoire au MVP.
### Si utilisateur déconnecté
Quand il tape `Partager` : écran ou bottom sheet explicative, non bloquante pour le reste de l'app.
```text
Compte requis pour partager
Connecte-toi pour envoyer ce programme à un autre compte GameTime.
[Se connecter]
[Créer un compte]
[Annuler]
```
Aucune obligation de connexion pour continuer à modifier localement.
### Formulaire de partage
Titre :
```text
Partager le programme
```
ou :
```text
Partager la séance
```
Contenu :
```text
Programme à partager
Programme tirs extérieur
6 exercices · 18 séries
Email du destinataire
[ joueur@example.com ]
[Envoyer le partage]
```
Validation locale :
- email vide/invalide : `Saisis l'email du destinataire.`
Après action :
- si envoyé immédiatement : `Partage envoyé.`
- si réseau/serveur indisponible : `Partage enregistré. Envoi dès que possible.`
Pas de popup d'erreur réseau. Le partage peut être mis en file d'attente si l'architecture le permet ; sinon rester inline avec `Envoi impossible pour le moment. Réessaie plus tard.` dans le formulaire, mais ne jamais perturber les autres écrans.
## 5. Partages reçus
Accessible depuis `Profil` > `Partages reçus`.
### Liste
AppBar : `Partages reçus`
États :
- chargement local : indicateur discret si nécessaire ;
- vide :
```text
Aucun partage reçu
Les programmes et séances qu'on t'envoie apparaîtront ici.
```
Chaque item :
```text
Programme
Programme tirs extérieur
Envoyé par Alex
6 exercices · 18 séries
[Accepter] [Refuser]
```
ou :
```text
Séance
Prépa match
Envoyée par Alex
2 programmes · 11 exercices
[Accepter] [Refuser]
```
### Acceptation
`Accepter` importe une copie locale immédiatement si les données du partage sont disponibles localement.
Après acceptation :
- Programme : `Programme ajouté.`
- Séance : `Séance ajoutée.`
La copie devient un objet local normal, disponible offline. Elle n'est pas liée dynamiquement à l'expéditeur.
Si l'accusé serveur ne peut pas partir :
```text
Accepté sur cet appareil. Mise à jour du partage dès que possible.
```
### Refus
Confirmation légère non obligatoire. Recommandation : action directe avec undo possible si facile ; sinon confirmation courte.
Message :
```text
Partage refusé.
```
Si réseau indisponible :
```text
Refus enregistré. Mise à jour dès que possible.
```
### Conflits / doublons
Ne pas bloquer l'acceptation si un programme ou une séance porte déjà le même nom. Autoriser les doublons comme le reste de l'app. Optionnellement ajouter suffixe local si nécessaire : `Prépa match (partagé)`.
## 6. Données locales et cache profil
Exigence UX : après connexion, l'écran Profil doit afficher instantanément la dernière identité connue.
À mettre en cache local :
- email ;
- pseudo si disponible ;
- photo/avatar si disponible ;
- dernier état de sync affichable ;
- partages reçus déjà récupérés si possible.
Ne jamais vider les listes locales lors du logout. Le logout suspend seulement token/sync/partage.
## 7. Libellés à éviter
Éviter :
- `Erreur de synchronisation`
- `Non synchronisé` en rouge
- `Connexion requise`
- `Impossible d'utiliser l'app`
Préférer :
- `Synchronisation en attente`
- `Nouvelle tentative automatique`
- `Compte optionnel`
- `Les données restent disponibles`
- `Partage enregistré. Envoi dès que possible.`
## 8. Découpage tickets conseillé
1. `Profil · entrée navigation + états connecté/déconnecté`.
2. `Auth · écrans créer un compte / se connecter / se déconnecter`.
3. `Sync · statut discret + action Synchroniser maintenant`.
4. `Partage sortant · action Partager sur programme/séance + formulaire email`.
5. `Partages reçus · boîte de réception + accepter/refuser + import local`.

View File

@ -1,6 +1,6 @@
---
issueRef: "#46"
version: 6
updatedBy: {"kind":"user"}
updatedAt: 1784482343900
version: 7
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784488112374
---

View File

@ -2,15 +2,15 @@
id: "dda499ac-7197-4321-bd2b-a303510b1bbe"
number: 46
title: "Créer un serveur headless pour l'application"
status: "open"
status: "qa"
priority: "low"
sprint: "4237adfd-91eb-45ff-9f32-6ce1daa39822"
links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784379695135
updatedAt: 1784482343900
version: 6
updatedAt: 1784488112374
version: 7
---
J'aimerais que l'application puisse proposer des services online aussi. Un peu a la manière de Heavy avec la possibilité de partager ses séances etc. A la manière de Heavy, j'aimerais que l'on puisse se créer un compte et s'y connecter, synchroniser son historique de seances, ses séances, ses programmes ainsi que ses exercices. Ce ticket est la partie serveur/web API, il faudra que ça soit un serveur headless linux qui tourne dans un container docker. Il faudra que je puisse y acceder via un reverse proxy qui tourne sur une machine différente de celle du serveur headless mais dans le même réseau local. J'y accederais via une adresse https. le container se lancera ensuite à partir d'un docker-compose.yaml dans lequel tu laissera les champs libre de tout ce qui sera necessaire de configurer (adresse ip du reverse proxy, de la machine sur laquelle tourne le docker si necessaire etc.). Pour la création du serveur, créé un subdirectory spécialement pour cette partie serveur. Dans le dossier serveur à lma racine, je veux un script pour pouvoir push le container sur le gitea qui est déjà lié au projet, je veux aussi le docker-compose.yaml demandé.

View File

@ -1,6 +1,6 @@
---
issueRef: "#54"
version: 4
updatedBy: {"kind":"user"}
updatedAt: 1784451193831
version: 5
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784487430850
---

View File

@ -2,15 +2,15 @@
id: "7e6894d5-36ad-413b-9fda-be3ba26db6cb"
number: 54
title: "Exercice à plusieurs étapes"
status: "open"
status: "qa"
priority: "medium"
sprint: "1bb8bdf2-9c35-4f53-9a31-1390a47bec63"
links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784449895047
updatedAt: 1784451193831
version: 4
updatedAt: 1784487430850
version: 5
---
Dans certains exercices, il peut être interessant qu'il y ai plusieurs étapes. Par exemple, pour un exercice de dribble, il peut être interessant de dire "dribbler main droite pendant 10 sec, puis dribller main gauche pendant 10 secondes, effectuer un double pas et réaliser 10 pompes" par exemple. Le tout représente alors une répétition mais se décompose en 4 étapes avec deux étapes qui se base sur un temps, et deux étapes sur un nombre de répétitions. J'aimerais donc pouvoir définir des étapes pour chaque exercice. L'ensemble de toutes les étape de l'exercice forme une répetition d'une série. Comme pour une série, chacune des étape doit pouvoir avoir un temps à définir (avec une valeur par défaut obligatoire > 0) ou un nombre de répétitions. Dans le cas d'une étape avec un temps, un chronometre est lancé. Dans les 3 dernieres secondes, un bip retenti pour chaque seconde et quand le temps arrive à 0 le bip est plus long que les autres. dans le cas ou deux étapes avec un temps s'enchainent, le second chronometre part directement à la fin de l'etape précédent. Le but est d'enchainer les étapes, il n'y a pas de pauses entre les différentes étapes. On eput aussi proposer de renseigner un score pour chaque étape de la même façon que pour les série.

View File

@ -1,6 +1,7 @@
---
issueRef: "#57"
version: 4
updatedBy: {"kind":"user"}
updatedAt: 1784482343900
version: 6
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784488107329
---
Mémoire IdeA écrite par Main : `gametime-online-layer-philosophy`. Couvre : connexion toujours optionnelle, aucune interruption/popup si le serveur est indisponible, principe "le serveur sert l'app, jamais l'inverse" (adapter le schéma serveur aux changements client, ex. étapes d'exercice du chantier #54), et la liste de ce qui doit rester en cache local (profil, pseudo, photo, stats futures) pour un affichage toujours cohérent hors ligne. Cette mémoire est référencée pour les tickets futurs (#63 et suite).

View File

@ -2,15 +2,15 @@
id: "d5b0bd7e-a021-4824-a278-b2e9c14a1cf6"
number: 57
title: "Allimenter la mémoire projet pour l'UI online de l'app"
status: "open"
status: "closed"
priority: "medium"
sprint: "4237adfd-91eb-45ff-9f32-6ce1daa39822"
links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784481519943
updatedAt: 1784482343900
version: 4
updatedAt: 1784488107329
version: 6
---
L'application aura donc une couche online. L'application se veut cependant rester un maximum offline sur les ofnctionnalités de base. Je veux que cette couche online soit le plus transparente possible pour l'utilisateur. En clair, si le serveur n'est pas disponible, je ne veux pas de popup qui lui dise qu'il n'est pas connecté. Le but, c'est que les informations soient autant que possibles stockées en local pour tout ce qui est necessaire au bon affichage de l'application sans pour autant donner de mauvaises ou de fausses informations (par exemple, garder les exercices créés, les programmes, les séances etc en local, si plus tard on calcule des statistiques pareilles, les garder aussi en local, si il y a un systeme de compte, garder la photo de profile ainsi que le pseudo etc en local. En bref la référence reste encore une fois Heavy qui fait très bien ça). Ajoute ceci à la mémoire du projet pour les futurs features qui traiteront de ça.

View File

@ -1,6 +1,6 @@
---
issueRef: "#62"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784481549150
version: 2
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784487426036
---

View File

@ -2,15 +2,15 @@
id: "a53d84b5-06d9-4683-ac64-9100cb60669f"
number: 62
title: "[QA] Validation exercices à étapes, persistance et historique"
status: "open"
status: "qa"
priority: "medium"
sprint: null
links: [{"target":"#54","kind":"relatesTo"},{"target":"#60","kind":"dependsOn"},{"target":"#61","kind":"dependsOn"}]
agentRefs: [{"agentId":"7efa512f-3b3a-47b5-ade0-a2dd13073055","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784481549150
updatedAt: 1784481549150
version: 1
updatedAt: 1784487426036
version: 2
---
Préparer et exécuter le plan QA pour les exercices à étapes : création/édition et limites max 8, snapshots programme/séance, exécution étapes temps/répétitions, bips 3/2/1/0, enchaînement de chronos, skips étape/passage/série, pause/reprise/kill app, projection historique, édition ponctuelle sans déplacer la progression ni mélanger résultats d'étape et résultats de série.

View File

@ -0,0 +1,6 @@
---
issueRef: "#64"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488337677
---

View File

@ -0,0 +1,16 @@
---
id: "4a18ba38-9b18-4b39-99a4-ad5a8779e47f"
number: 64
title: "[DevBackend] Client online : session compte, stockage sécurisé et adapter API"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"}]
agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488337677
updatedAt: 1784488337677
version: 1
---
Ajouter le socle client online côté domain/application/infrastructure : entité locale `UserAccountSession` (profil cache non sensible), ports `AuthTokenStore`, `OnlineAccountRepository`/`RemoteGameTimeApi`, adapter HTTP sous `lib/infrastructure/remote/` basé sur `package:http` avec timeouts courts et erreurs typées silencieuses. Stocker access/refresh token via `flutter_secure_storage`; stocker email/pseudo/avatar/statut sync en Drift. Ne jamais bloquer l'usage offline si l'API échoue.

View File

@ -0,0 +1,6 @@
---
issueRef: "#65"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488343589
---

View File

@ -0,0 +1,16 @@
---
id: "f0c554b7-e388-49b3-aa74-0a47886961ad"
number: 65
title: "[DevBackend] Sync client incrémentale LWW vers API serveur"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#64","kind":"dependsOn"}]
agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488343589
updatedAt: 1784488343589
version: 1
---
Remplacer le `NoOpSyncGateway` par une orchestration réelle offline-first. Mapper Exercise, Program, WorkoutTemplate, WorkoutHistory et MediaAsset metadata vers `resourceType/clientId/schemaVersion/clientUpdatedAt/deletedAt/payload`. Utiliser `change_log`, `syncState`, `updatedAt` et `localRevision` pour pousser les mutations, stocker localement `serverCursor`, appliquer `/sync/pull` LWW sans résolution interactive, puis marquer les ressources acceptées comme synced. Les erreurs réseau restent silencieuses et déclenchent retry différé.

View File

@ -0,0 +1,6 @@
---
issueRef: "#66"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488349264
---

View File

@ -0,0 +1,16 @@
---
id: "bdcf80ef-1360-4baa-9c49-86b091412ba1"
number: 66
title: "[DevBackend] Partage client : use cases, inbox cache et import local"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#64","kind":"dependsOn"},{"target":"#65","kind":"dependsOn"}]
agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488349264
updatedAt: 1784488349264
version: 1
---
Ajouter les ports/use cases client pour `POST /shares`, `GET /shares/inbox`, accept/decline/revoke. Stocker une inbox locale lisible offline et une file d'actions de partage en attente si réseau indisponible. À l'acceptation, importer une copie locale indépendante du Programme ou WorkoutTemplate depuis le payload reçu si disponible; l'accusé serveur peut partir ensuite, et la ressource créée côté serveur retombe aussi via le prochain `/sync/pull` sans doublon grâce aux IDs/mapping.

View File

@ -0,0 +1,6 @@
---
issueRef: "#67"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488354627
---

View File

@ -0,0 +1,16 @@
---
id: "961d38c4-a124-47da-8118-be8b559fc617"
number: 67
title: "[DevFrontend] Profil et écrans auth optionnels"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#64","kind":"dependsOn"}]
agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488354627
updatedAt: 1784488354627
version: 1
---
Ajouter l'entrée `Profil` sur l'accueil et les surfaces UX connecté/déconnecté. Créer les écrans `Créer un compte`, `Se connecter`, confirmation logout. La connexion reste optionnelle, jamais affichée au démarrage. Erreurs serveur/réseau uniquement inline dans les formulaires, jamais popup globale. Profil affiché depuis cache local même hors ligne.

View File

@ -0,0 +1,6 @@
---
issueRef: "#68"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488359218
---

View File

@ -0,0 +1,16 @@
---
id: "ac634023-e7ba-4588-9c7c-9f93c12d9aad"
number: 68
title: "[DevFrontend] Statut de synchronisation discret et action manuelle"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#65","kind":"dependsOn"},{"target":"#67","kind":"dependsOn"}]
agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488359218
updatedAt: 1784488359218
version: 1
---
Afficher la section Synchronisation dans Profil : en cours, à jour, jamais faite, retry automatique, hors ligne. Ajouter `Synchroniser maintenant`. Ne jamais afficher de snackbar/dialog d'échec réseau hors contexte Profil. Optionnel : sous-titre discret de l'entrée Profil sur l'accueil.

View File

@ -0,0 +1,6 @@
---
issueRef: "#69"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488364646
---

View File

@ -0,0 +1,16 @@
---
id: "05482a20-83a6-44da-b0c6-d065a5a1ffba"
number: 69
title: "[DevFrontend] Partage sortant et boîte de réception"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#66","kind":"dependsOn"},{"target":"#67","kind":"dependsOn"}]
agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488364646
updatedAt: 1784488364646
version: 1
---
Ajouter l'action `Partager` dans les écrans programme/séance enregistrés, formulaire email destinataire, états déconnecté et messages discrets. Ajouter `Profil > Partages reçus`, liste inbox, accepter/refuser, import local des programmes/séances reçus. Les erreurs réseau restent inline/neutres; accepter un partage rend la copie disponible offline.

View File

@ -0,0 +1,6 @@
---
issueRef: "#70"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784488373778
---

View File

@ -0,0 +1,16 @@
---
id: "cb93ee24-0041-4926-b0c6-3ab26aab46ec"
number: 70
title: "[QA] Validation client online offline-first"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#63","kind":"relatesTo"},{"target":"#68","kind":"dependsOn"},{"target":"#69","kind":"dependsOn"}]
agentRefs: [{"agentId":"7efa512f-3b3a-47b5-ade0-a2dd13073055","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784488373778
updatedAt: 1784488373778
version: 1
---
Valider le chantier client online : app utilisable sans compte, login/register/logout optionnels, tokens persistés, profil cache hors ligne, sync push/pull LWW, erreurs réseau silencieuses, retry différé, partage sortant ciblé, inbox, accept/decline/revoke, import local offline, absence de régression sur les parcours locaux exercices/programmes/séances/historique.

View File

@ -1,3 +1,3 @@
{
"nextNumber": 64
"nextNumber": 71
}

View File

@ -525,13 +525,13 @@
"issueRef": "#46",
"path": "46",
"title": "Créer un serveur headless pour l'application",
"status": "open",
"status": "qa",
"priority": "low",
"sprint": "4237adfd-91eb-45ff-9f32-6ce1daa39822",
"assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae"
],
"updatedAt": 1784482343900
"updatedAt": 1784488112374
},
{
"issueRef": "#47",
@ -607,13 +607,13 @@
"issueRef": "#54",
"path": "54",
"title": "Exercice à plusieurs étapes",
"status": "open",
"status": "qa",
"priority": "medium",
"sprint": "1bb8bdf2-9c35-4f53-9a31-1390a47bec63",
"assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae"
],
"updatedAt": 1784451193831
"updatedAt": 1784487430850
},
{
"issueRef": "#56",
@ -631,13 +631,13 @@
"issueRef": "#57",
"path": "57",
"title": "Allimenter la mémoire projet pour l'UI online de l'app",
"status": "open",
"status": "closed",
"priority": "medium",
"sprint": "4237adfd-91eb-45ff-9f32-6ce1daa39822",
"assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae"
],
"updatedAt": 1784482343900
"updatedAt": 1784488107329
},
{
"issueRef": "#58",
@ -691,13 +691,13 @@
"issueRef": "#62",
"path": "62",
"title": "[QA] Validation exercices à étapes, persistance et historique",
"status": "open",
"status": "qa",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"7efa512f-3b3a-47b5-ade0-a2dd13073055"
],
"updatedAt": 1784481549150
"updatedAt": 1784487426036
},
{
"issueRef": "#63",
@ -710,6 +710,90 @@
"57695b92-24d0-4876-837c-76116e70a6ae"
],
"updatedAt": 1784482673684
},
{
"issueRef": "#64",
"path": "64",
"title": "[DevBackend] Client online : session compte, stockage sécurisé et adapter API",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"10ee045b-1c41-479e-ba03-dceed9edd495"
],
"updatedAt": 1784488337677
},
{
"issueRef": "#65",
"path": "65",
"title": "[DevBackend] Sync client incrémentale LWW vers API serveur",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"10ee045b-1c41-479e-ba03-dceed9edd495"
],
"updatedAt": 1784488343589
},
{
"issueRef": "#66",
"path": "66",
"title": "[DevBackend] Partage client : use cases, inbox cache et import local",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"10ee045b-1c41-479e-ba03-dceed9edd495"
],
"updatedAt": 1784488349264
},
{
"issueRef": "#67",
"path": "67",
"title": "[DevFrontend] Profil et écrans auth optionnels",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
],
"updatedAt": 1784488354627
},
{
"issueRef": "#68",
"path": "68",
"title": "[DevFrontend] Statut de synchronisation discret et action manuelle",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
],
"updatedAt": 1784488359218
},
{
"issueRef": "#69",
"path": "69",
"title": "[DevFrontend] Partage sortant et boîte de réception",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
],
"updatedAt": 1784488364646
},
{
"issueRef": "#70",
"path": "70",
"title": "[QA] Validation client online offline-first",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"7efa512f-3b3a-47b5-ade0-a2dd13073055"
],
"updatedAt": 1784488373778
}
]
}