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>
This commit is contained in:
Sylvain Bettinelli
2026-09-25 06:58:11 +00:00
parent 3b5b61268c
commit 98b1cd0c89
6 changed files with 564 additions and 35 deletions

View File

@@ -0,0 +1,101 @@
# Runbook Mac — Siri + widget Hydratation de la montre
**Écrit le 25/09/2026, depuis Linux.** Le code Swift est committé ; il reste les
étapes Xcode, qui ne peuvent pas se faire d'ici.
> ⛔ **Rien de ce code n'a été compilé.** `AppIntents`, `WidgetKit` et `SwiftUI`
> sont des frameworks Apple, absents de Swift pour Linux — le CLAUDE.md de ce
> dépôt le dit. Ce qui a été vérifié à la place, par lecture croisée des
> sources : que chaque symbole appelé existe (`CoachQuickLog.append`,
> `todayTotal`, `pending`, `CoachWidgetStore.load`, `nextMidnight`,
> `fallbackQuickDrinks`, `resolvedQuickDrinks`, `waterMlToday`, `waterGoalMl`,
> `asOf`), et qu'aucun symbole n'est déclaré deux fois. **Les erreurs de type,
> de concurrence et d'API n'apparaîtront qu'au build.**
## Ce que ça apporte
| | Avant | Après |
|---|---|---|
| Siri | rien — aucun `AppShortcutsProvider` | 5 raccourcis, 16 phrases |
| Montre | complications d'affichage | widget Smart Stack qui **note un verre** |
| Intents | 1, enfermé dans l'extension iPhone | 3, partagés entre 3 cibles |
## Fichiers ajoutés / modifiés
- **`ios/App/App/CoachIntents.swift`** *(nouveau)* — `LogDrinkIntent` (déplacé),
`HydrationStatusIntent`, `TodaySessionIntent`, `CoachQuickError`.
- **`ios/App/App/CoachAppShortcuts.swift`** *(nouveau)* — les phrases Siri.
- **`ios/App/CoachWatchWidgets/CoachWatchHydration.swift`** *(nouveau)* — widget
Smart Stack de la montre.
- `ios/App/CoachLiveActivity/CoachQuickWidget.swift` — l'intent en a été
**retiré** (il vivait là, invisible des deux autres cibles).
- `ios/App/CoachWatchWidgets/CoachWatchComplications.swift` — le widget rejoint
le `WidgetBundle`.
## Étapes Xcode
⚠️ **Quitter Xcode puis `git pull` d'abord** (règle COWORK : jamais de pull
Xcode ouvert).
### 1. Target Membership — c'est l'étape qui décide de tout
| Fichier | App | CoachLiveActivity | CoachWatchWidgets |
|---|:--:|:--:|:--:|
| `CoachIntents.swift` | ✅ | ✅ | ✅ |
| `CoachAppShortcuts.swift` | ✅ | ❌ | ❌ |
| `CoachWatchHydration.swift` | ❌ | ❌ | ✅ |
| `CoachQuickLog.swift` | ✅ | ✅ | ✅ *(à vérifier)* |
| `CoachWidgetSnapshot.swift` | ✅ | ✅ | ✅ *(déjà le cas)* |
⚠️ **`CoachAppShortcuts.swift` dans la cible App UNIQUEMENT.** Apple attend un
seul `AppShortcutsProvider` par application ; un second dans une extension
produirait au mieux un doublon.
⚠️ **`CoachQuickLog.swift` doit être dans `CoachWatchWidgets`**, sinon le widget
de la montre compile mais n'écrit nulle part. Son en-tête le réclamait déjà.
### 2. Vérifier l'App Group sur la cible Watch
`group.ch.hypnotruck.coach` sur **CoachWatch** ET **CoachWatchWidgets**.
Vérifié depuis Linux le 25/09 : les quatre `.entitlements` le portent et les
`CODE_SIGN_ENTITLEMENTS` sont câblés. À reconfirmer dans Signing & Capabilities.
### 3. Build, puis les vérifications qui comptent
1. **Siri** — dire « Note un verre d'eau dans Coach ». Attendu : « Noté.
X,XX L d'eau aujourd'hui », **sans que l'app s'ouvre**.
2. **Raccourcis** — l'app *Raccourcis* doit lister les 5 actions sous « Coach ».
3. **Smart Stack** — ajouter le widget Hydratation, toucher « +25 cl », vérifier
que le total monte **et** que le badge des saisies en attente s'incrémente.
4. **Synchro** — ouvrir l'app iPhone, vérifier que la saisie faite au poignet
remonte sur `coach.hypnotruck.ch`.
5. **Minuit** — le total doit repartir de zéro (c'est `asOf(_:)` qui l'assure,
pas le réveil de la timeline : le piège a déjà été payé côté iPhone).
## Les trois questions que seul le build tranchera
1. **Une complication de CADRAN accepte-t-elle un bouton d'App Intent, ou est-ce
réservé à la Smart Stack ?** La doc Apple n'était pas récupérable depuis
Linux. Par prudence, `.accessoryCircular` n'est ici qu'un **affichage** ; si
le cadran accepte l'interactivité, on pourra l'enrichir.
2. **Les phrases Siri passent-elles la validation ?** Chaque phrase contient
`\(.applicationName)`, comme Apple l'exige, et le nom supposé est « Coach »
(`CFBundleDisplayName`). Si le nom affiché diffère, les phrases ne se
déclencheront pas — c'est silencieux.
3. **`ProvidesDialog` sur watchOS.** Les intents rendent
`some IntentResult & ProvidesDialog` pour que Siri réponde à voix haute. À
confirmer que la cible watchOS l'accepte sans ajustement.
## Ce qui a été volontairement écarté
- ⛔ **Saisie des repas depuis la montre.** Pas de caméra, pas de scanner de
code-barres, pas de saisie de texte confortable : un bouton « repas » au
poignet ne ferait que renvoyer vers l'iPhone. Décidé avec Sylvain le 25/09.
- ⛔ **Un paramètre libre de volume dans les phrases Siri.** Un intent
paramétré oblige Siri à poser une question — ce qui coûte plus de temps que
de sortir le téléphone, et vide le raccourci de son intérêt. Les volumes sont
donc fixés (250 ml, 500 ml, café 60 ml).
- ⛔ **Toute borne en bpm dans les réponses de Siri.** `TodaySession` n'en porte
pas, 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.