Commit Graph

7 Commits

Author SHA1 Message Date
Sylvain Bettinelli
98b1cd0c89 Siri connaît enfin l'app, et la montre sait noter un verre
Demandé par Sylvain : des widgets, puis « comment lier Siri à mon appli ».
Constat avant de commencer :

  App Intents déclarés      : 1  (LogDrinkIntent)
  AppShortcutsProvider      : 0  -> Siri n'avait AUCUNE phrase à écouter
  Saisie depuis la montre   : 0  -> complications d'affichage seulement

Et la cause commune : `LogDrinkIntent` vivait dans `CoachQuickWidget.swift`,
qui n'appartient qu'à l'extension widget iPhone. Ni l'app (d'où Siri découvre
les raccourcis), ni la cible Watch ne pouvaient le voir.

Ce qui est ajouté
------------------
- `CoachIntents.swift` — les intents, partagés entre les TROIS cibles.
  `LogDrinkIntent` y est déplacé (pas dupliqué : deux définitions du même
  symbole dans une cible qui compile les deux fichiers casseraient le build),
  plus `HydrationStatusIntent` et `TodaySessionIntent`.
- `CoachAppShortcuts.swift` — 5 raccourcis, 16 phrases françaises. Cible App
  UNIQUEMENT : Apple attend un seul AppShortcutsProvider par application.
- `CoachWatchHydration.swift` — widget Smart Stack de la montre : total du
  jour, jauge, et deux boutons +cl. Le geste se fait au poignet.

Choix qui méritent d'être dits
-------------------------------
⛔ Pas de saisie des repas sur la montre. Pas de caméra, pas de scanner, pas de
saisie de texte confortable : le bouton ne ferait que renvoyer vers l'iPhone.
Tranché avec Sylvain.

⛔ Pas de volume libre dans les phrases Siri. Un intent paramétré oblige Siri à
poser une question — plus long que de sortir le téléphone, et le raccourci perd
sa raison d'être. Volumes fixés : 250 ml, 500 ml, café 60 ml.

⛔ Aucune borne en bpm dans les réponses vocales. `TodaySession` n'en porte pas
(vérifié : sport, title, subtitle, done) et il ne faut pas en ajouter — une
fourchette cardiaque annoncée par haut-parleur, hors de la recommandation du
jour et de ses réserves, reviendrait à prescrire une intensité à un
post-infarctus. Siri annonce QUOI ; l'app dit COMMENT.

Deux boutons et pas trois sur la montre : sur 160 pt, un troisième descend les
cibles sous le seuil tactile, pour un geste qu'on fait en marchant.

⚠️ RIEN DE CE CODE N'EST COMPILÉ
---------------------------------
AppIntents, WidgetKit et SwiftUI sont absents de Swift pour Linux. Ce qui a été
vérifié à la place, par lecture croisée des sources : que chaque symbole appelé
existe, et qu'aucun n'est déclaré deux fois. Deux erreurs ont d'ailleurs été
trouvées ainsi avant commit — `asOf()` ne rend pas un optionnel, et
`TodaySession` n'a pas de `durationMin`. Les erreurs de type, de concurrence et
d'API n'apparaîtront qu'au build.

`docs/siri-et-widget-watch-runbook-mac.md` porte les étapes Xcode, le tableau
de Target Membership, les cinq vérifications à faire sur l'appareil, et les
trois questions que seul le build tranchera — dont celle-ci : une complication
de cadran accepte-t-elle un bouton interactif, ou est-ce réservé à la Smart
Stack ? Par prudence, le cadran n'est ici qu'un affichage.

91 tests Swift Linux au vert (non-régression CoachQuickLog / snapshot).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 06:58:11 +00:00
Sylvain Bettinelli
4cff1d4603 Les widgets reçoivent la bande et les préréglages au lieu de les inventer
La couleur du score de forme était décidée ici, avec un barème périmé
(vert >= 75, jaune >= 50). Le serveur avait recalé le sien sur la
distribution réelle (65/55/40) parce que le score est borné à [25, 75] :
une bande haute à 75 est inatteignable. Mesuré sur 115 jours de
production, le vert n'est jamais sorti et le rouge couvrait 68 jours.
La correction n'avait pas atteint le binaire.

`FormeScore` porte désormais la bande servie par le serveur, et
`resolvedBand` ne sert que de repli — aligné sur les seuils serveur, pas
sur les anciens.

Le widget de saisie rapide enregistrait un café à 100 ml, volume qui ne
correspond à aucun préréglage : le serveur avait séparé l'expresso (60)
du mug (250) parce qu'un volume unique faussait le journal, et ces
préréglages sont personnalisables. Les boutons viennent maintenant du
snapshot ; `fallbackQuickDrinks` reprend les préréglages par défaut du
serveur.

15 tests ajoutés au banc d'essai Linux, qui compile réellement ces deux
fichiers : 25 tests verts. Le reste (SwiftUI, WidgetKit) n'est pas
compilable ici et n'a été que relu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:42:19 +00:00
Sylvain Bettinelli
b8eb1e1b1e Le widget ne passait pas minuit : personne ne lisait updatedAt
Signale par Sylvain le 2026-08-18 : passe minuit, l'hydratation, les calories
et la seance de la veille restaient affichees comme celles du jour. Il fallait
ouvrir l'app pour les voir retomber a zero.

Le widget n'a AUCUN acces reseau : il rend ce que l'app lui a pousse la
derniere fois qu'elle a tourne. Le snapshot portait deja `updatedAt` — rien
ne le lisait. CoachQuickWidget programmait meme un reveil a minuit, avec le
bon commentaire, mais relisait ensuite les memes chiffres sans regarder leur
date : le rechargement avait lieu, la remise a zero non.

- `CoachWidgetSnapshot.asOf(_:)` : vide ce qui decrit UNE journee (eau, cafes,
  kcal, routine, activite faite, score de forme) quand le snapshot date d'un
  autre jour. Conserve ce qui traverse les jours — objectifs et zones
  cardiaques. Les compteurs repartent a 0 et non a nil : au matin, « rien bu »
  est vrai, « je ne sais pas » ne l'est pas.
- Les trois providers emettent une 2e entree datee de minuit. WidgetKit
  bascule seul, sans reveiller l'app.
- `tomorrow` : la seance du lendemain, poussee par le pont web. Sans elle,
  vider la seance ferait annoncer JOUR OFF chaque nuit — l'agacement d'un
  incident precedent, repete tous les jours.

⚠️ Deuxieme occurrence trouvee en verifiant, et pire : CoachQuickSync.credit()
ajoutait au total EXISTANT puis rehorodatait a maintenant. Sur un snapshot de
la veille, 200 ml bus le matin donnaient « 2 200 ml » estampilles du jour — un
total faux, que la peremption ne pouvait plus rattraper.

⚠️ NON COMPILE : pas de toolchain Swift ici. Build Xcode requis sur le Mac
mini pour que le correctif arrive sur l'iPhone et la Watch.
2026-08-18 07:11:15 +00:00
Sylvain Bettinelli
3202e03a63 Trois widgets d'ecran verrouille et une complication Routine
Les familles accessory* n'etaient declarees que cote Watch, alors que ce sont
exactement celles de l'ecran verrouille iOS 16+. Les vues existaient donc
deja : il ne manquait que la declaration cote iPhone.

Ajoutes sur l'ecran verrouille : Forme (circulaire/rectangulaire/inline),
Seance (rectangulaire/inline, avec la bascule doneToday du commit precedent)
et Routine du jour. Sur la Watch : complication Routine, et la seance gagne la
famille circulaire (icone du sport, verte quand elle est faite).

Le snapshot gagne RoutineProgress {done, total} au grain EXERCICE — celui de
la page web et de l'app Watch depuis le 03/08. `fraction` borne le ratio et
protege de la division par zero ; un total nul ne doit pas remplir la jauge.
Meme garde du jour que doneToday : la routine repart a zero au reveil, un
report aveugle afficherait 24/24 des le matin.

Vues dupliquees plutot que partagees entre les deux extensions : elles ne
partagent aucun fichier et l'App Group n'est pas commun entre appareils. Trois
petites vues coutent moins qu'un fichier de plus a cabler dans deux cibles —
et pour la meme raison tout est ajoute a CoachWidgets.swift, deja membre de sa
cible : AUCUN fichier nouveau, donc rien a cabler dans le pbxproj.

⚠️ NON COMPILE — a builder au prochain passage sur le Mac, avec le correctif
« JOUR OFF » (12b56a4).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 18:08:13 +00:00
Sylvain Bettinelli
12b56a4d39 Le widget disait « JOUR OFF » les jours de sortie non planifiee
Pendant du commit web 25f53b5. Quand la seance prevue est remplacee par une
autre activite, le plan du jour se vide : le widget iPhone affichait « JOUR
OFF / Pas de seance prevue » et la complication Watch « Jour OFF » — le jour
meme d'une sortie de 20 km. Le snapshot ne portait que le PLAN.

CoachWidgetSnapshot gagne doneToday (sport, titre, sous-titre), optionnel :
un snapshot ecrit par une version anterieure reste decodable. today decrit le
plan, doneToday decrit la journee ; les deux peuvent coexister.

CoachWidgetBridge le conserve quand l'appel l'omet, comme la nutrition — mais
seulement si le snapshot precedent date d'aujourd'hui, sinon la sortie de la
veille resterait affichee indefiniment.

Les deux vues basculent sur l'activite realisee, avec la pastille verte
« Realisee » deja utilisee pour une seance planifiee faite. Le sous-titre
(« 20,3 km · 56 min ») passe devant le titre : le titre d'une seance importee
est souvent vide.

⚠️ NON COMPILE — a builder au prochain passage sur le Mac. Aucun fichier
nouveau, donc rien a cabler dans le pbxproj : les quatre fichiers touches sont
deja membres de leurs cibles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:56:53 +00:00
Sylvain
5984685916 chore(xcode): target CoachWatchWidgets, fichiers du widget Saisie rapide, build 22 aligne 2026-08-11 16:45:51 +02:00
Sylvain Bettinelli
cc88431f6b feat(watch): complications Apple Watch (Forme + Séance du jour)
L'App Group n'étant pas partagé entre appareils, le snapshot transite par
WatchConnectivity : iPhone CoachWidgetBridge.pushToWatch (updateApplicationContext)
-> CoachWatch/ConnectivityManager reçoit -> App Group de la montre + reload des
complications. Nouvelles complications (CoachWatchWidgets/CoachWatchComplications.swift) :
Forme (accessoryCircular/corner, Gauge) + Séance du jour (accessoryRectangular/inline).

⚠️ Reste Mac : créer la target widget watchOS + App Group/capability + Target
Membership. Runbook complet dans docs/widgets-runbook-mac.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 05:50:29 +00:00