From f9f8f95642605a57b432b2a496828517c9956c7c Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Mon, 3 Aug 2026 15:53:08 +0000 Subject: [PATCH] =?UTF-8?q?fix(watch):=20transmet=20les=20blocs=20de=20rou?= =?UTF-8?q?tine,=20pas=20seulement=20l'=C3=A9tat=20coch=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pushToWatch ne lisait que `date` et `done` dans l'appel JS et ignorait `blocks`. La montre recevait donc un snapshot sans aucun bloc et restait sur son écran vide « Ouvre l'app Coach sur l'iPhone » — alors que le log confirmait que l'envoi partait bien (constaté sur device le 2026-08-03). sanitizeBlocks ne garde que les clés attendues en types property-list stricts : updateApplicationContext lève une exception ObjC sur tout ce qui ne l'est pas, et les entiers venus du JS arrivent en NSNumber — un `as? Int` direct échoue quand JavaScriptCore les passe en double (même piège que le score perdu dans CoachWidgetBridge). Le log distingue désormais blocs transmis et blocs faits : « 0 blocs » était ambigu et désignait le compte des cochés, pas celui des blocs envoyés. Co-Authored-By: Claude Opus 5 --- ios/App/App/CoachRoutineBridge.swift | 29 ++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/ios/App/App/CoachRoutineBridge.swift b/ios/App/App/CoachRoutineBridge.swift index 5f0e586..de139f6 100644 --- a/ios/App/App/CoachRoutineBridge.swift +++ b/ios/App/App/CoachRoutineBridge.swift @@ -54,10 +54,14 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { // MARK: - JS → natif - /// `pushToWatch({ date: "2026-08-03", done: ["hanches", ...] })` + /// `pushToWatch({ date, done: [...], blocks: [{id, num, title, duration_min, moment}] })` + /// + /// Les blocs voyagent avec l'état : la montre n'a aucune liste codée en dur, + /// modifier la routine côté web ne demande donc pas de rebuild watchOS. @objc func pushToWatch(_ call: CAPPluginCall) { let date = call.getString("date") ?? Self.todayISO() let done = call.getArray("done", String.self) ?? [] + let blocks = Self.sanitizeBlocks(call.getArray("blocks") as? [[String: Any]] ?? []) guard WCSession.isSupported() else { call.resolve(["pushed": false, "reason": "WCSession non supporté"]) @@ -75,10 +79,10 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { do { try session.updateApplicationContext([ - "routineSnapshot": ["date": date, "done": done] + "routineSnapshot": ["date": date, "done": done, "blocks": blocks] ]) - routineLog.info("routine poussée vers la Watch (\(done.count, privacy: .public) blocs)") - call.resolve(["pushed": true, "count": done.count]) + routineLog.info("routine poussée vers la Watch : \(blocks.count, privacy: .public) blocs, \(done.count, privacy: .public) faits") + call.resolve(["pushed": true, "blocks": blocks.count, "done": done.count]) } catch { routineLog.error("updateApplicationContext a échoué : \(error.localizedDescription, privacy: .public)") call.reject("Envoi vers la Watch impossible : \(error.localizedDescription)") @@ -151,6 +155,23 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { }.resume() } + /// Ne garde que les clés attendues, en types property-list stricts. + /// + /// Deux raisons : `updateApplicationContext` rejette (exception ObjC) tout + /// ce qui n'est pas plist-compatible, et les entiers venus du JS arrivent en + /// `NSNumber` — un `as? Int` direct échoue quand JavaScriptCore les a passés + /// en double. Même piège que le score perdu dans `CoachWidgetBridge`. + private static func sanitizeBlocks(_ raw: [[String: Any]]) -> [[String: Any]] { + raw.compactMap { b in + guard let id = b["id"] as? String, let title = b["title"] as? String else { return nil } + var out: [String: Any] = ["id": id, "title": title] + out["num"] = (b["num"] as? NSNumber)?.intValue ?? 0 + out["duration_min"] = (b["duration_min"] as? NSNumber)?.intValue ?? 0 + out["moment"] = (b["moment"] as? String) ?? "matin" + return out + } + } + private static func todayISO() -> String { let fmt = DateFormatter() fmt.dateFormat = "yyyy-MM-dd"