Files
GameTime/.ideai/tickets/175/carnet.md

4.7 KiB

issueRef, version, updatedBy, updatedAt
issueRef version updatedBy updatedAt
#175 4
kind
user
1785315994252

#175 — Cadrage Main (2026-07-28)

Symptôme utilisateur

Si l'utilisateur tue l'app téléphone sans quitter d'abord la séance, la montre conserve une séance active apparente et continue d'ouvrir l'ancien écran de séance.

Gravité

Critique côté cohérence :

  • la montre affirme un état actif sans source de vérité confirmée ;
  • l'utilisateur peut croire que la séance continue alors que le téléphone a disparu du cycle ;
  • cela contredit le principe "logique métier côté téléphone".

Décision de cadrage

La montre ne doit pas garder indéfiniment un état "séance active" orphelin.

Comportement cible à trancher techniquement :

  • soit le téléphone envoie explicitement un état terminal/deconnexion avant extinction contrôlée ;
  • soit la montre invalide l'état de séance après une fenêtre d'absence de heartbeat/projection ;
  • soit combinaison des deux, avec dégradation vers un état clair "Téléphone indisponible" ou retour hors séance.

Garde-fous

  • ne pas perdre une vraie séance lors d'une micro-coupure normale ;
  • ne pas laisser une séance fantôme persister après fermeture forcée ;
  • distinguer reconnexion brève et disparition durable du téléphone ;
  • l'écran montre doit refléter un état confirmé ou explicitement dégradé, jamais un faux "tout va bien".

Attendu suite

Ticket d'architecture/implémentation à sortir de celui-ci :

  • stratégie heartbeat / TTL projection ;
  • comportement UI montre si la source téléphone disparaît ;
  • tests kill téléphone / reprise / relance app.

Cadrage Architect (2026-07-28)

Statut : cadrage exploitable, prêt pour découpage dev.

Diagnostic

Le bug vient du mécanisme de reprise montre : au démarrage ou à la reconnexion, la montre relit le dernier DataItem de projection et continue de l'afficher même si le téléphone a disparu. Le WatchSessionViewModel peut marquer la projection stale puis connectionLost, mais n'invalide jamais la projection elle-même.

Décision d'architecture

  • Le téléphone reste l'unique source de vérité.
  • La projection montre doit être traitée comme un cache temporaire, jamais comme une preuve durable qu'une séance est encore active.
  • Une projection phone -> watch devient expirable.
  • Sans rafraîchissement explicite du téléphone dans une fenêtre courte, la montre doit considérer la projection invalide et revenir à un état téléphone indisponible / aucune séance confirmée.

Mécanisme recommandé

  • Étendre WatchSessionProjection avec un TTL explicite, par exemple expiresAtEpochMs.
  • À chaque projection publiée, le téléphone renseigne expiresAtEpochMs = now + ttl.
  • Recommandation : TTL de 12s, cohérent avec le refresh périodique existant toutes les 2s.
  • Si l'app téléphone est killée, le heartbeat s'arrête ; la montre laisse expirer la projection.
  • Si le téléphone sait explicitement qu'il n'y a plus de séance, il publie phase = noActiveSession immédiatement comme aujourd'hui ; le TTL couvre le cas brutal.

Comportement montre attendu

  • Avant expiration : garder l'écran courant, avec l'état stale existant si utile.
  • Après expiration durable :
    • remplacer localement la projection active par une projection synthétique noActiveSession ;
    • vider sensorSample, commandes pending, score optimiste ;
    • arrêter ongoing / foreground liés à la séance ;
    • afficher un état type Téléphone indisponible / rouvre GameTime sur le téléphone, pas l'ancienne séance.
  • Si le téléphone republie ensuite une projection valide, la montre se recale normalement.

Garde-fous micro-coupures

  • Ne pas invalider sur simple connectionLost capability.
  • Invalider seulement quand aucune projection fraîche n'est reçue au-delà du TTL et qu'aucun ack/commande retour ne prouve une communication en cours.
  • Fenêtres recommandées :
    • refresh téléphone : 2s inchangé ;
    • seuil stale : 6s actuel conservé ;
    • expiration dure : 12s à 15s.

Impacts

  • Contrat : WatchSessionProjection gagne expiresAtEpochMs, schemaVersion incrémentée.
  • Téléphone : renseigne ce champ à chaque publish.
  • Bridge natif watch : pas de refonte structurelle ; la lecture du dernier DataItem peut rester, mais ce snapshot sera auto-invalidé par TTL.
  • UI montre : ajouter l'invalidation locale dans _syncFreshnessState() ; injecter une projection synthétique noActiveSession après expiration.

Lots recommandés

  1. B1 [DevBackend] Projection expirable côté téléphone
  2. F1 [DevFrontend] Invalidation locale montre sur TTL expiré
  3. F2 [DevFrontend] Ajustements natifs reprise/ongoing
  4. QA1 [QA] Validation disparition source téléphone