ReviewOS

craft-native/craft

watchOS: a companion target, and what deep integration should mean

#344
Closed glennmichael123 opened this 9 days ago ·
9 days ago

From Chris Breuer, testing hq.training:

heart rate stuff could work seamlessly apparently, when you start testing using the Apple Watch

but thinking about it, sounds like we should have an Apple Watch app, or how would it nicely/deeply integrate best?

That second sentence is the real question, and it is worth answering before anyone builds anything: a training app can meet a watch at several depths, and they cost very different amounts.

The depths, cheapest first:

  1. Nothing on the watch. The phone reads what the watch already wrote to HealthKit. This is where hq.training is now, and it is why heart rate "could work seamlessly" — the data arrives without a watch app existing.
  2. A workout sent to the watch. The planned session reaches the watch as a structured workout through the platform's own mechanism, so the member starts it there. hq.training has an open issue for the Garmin equivalent of this.
  3. A complication and notifications. Today's session on the face; the reminders land on the wrist.
  4. A real watchOS app. Start, follow and finish a session on the watch, live heart rate and zones on the wrist, works away from the phone, syncs when it is back.

Only (4) needs a watchOS target, and it is the one that would need Craft to grow one: a second native target, its own bundle and runtime, a way to share state with the phone app, and a way to write a watch screen from TypeScript rather than SwiftUI — which is #343's goal applied to a new platform.

Worth deciding here: whether Craft takes on a watchOS target at all, or whether the right answer for a training app is (2) and (3) done well, with the watch staying a sensor and a notification surface. hq.training is the app that would use it first either way, and its members are already wearing the watches.

9 days ago

yes, Craft should take it on as a watchOS target

9 days ago

yes, Craft should take it on as a watchOS target

It already does — the target is there and switched off, and the reason no Stacks app can switch it on is a missing wire rather than missing work.

What Craft already ships (0.0.160):

CraftWatchApp.swift.template163 lines: CraftWatchSession over WCSession, a CraftWatchRecordingView, phone reachability, recording/paused state, distance and duration
WatchApp.Info.plist.template, WatchExtension.Info.plist.templateboth present
CraftHeartRate.swiftthe strap/watch heart-rate service
the generatorknows enableWatchApp, watchosVersion, renderWatchEntitlements, watchInfo, watchExtensionInfo, WatchApp, WatchExtension

And hq.training's generated craft.config.json already carries enableWatchApp: false and watchosVersion: "9.0".

Where it breaks. enableWatchApp is a Craft-side option. The Stacks capability type (@stacksjs/mobile CraftMobileCapabilities) has no key that reaches it — the nearest, watchConnectivity, does not. Measured: adding watchConnectivity: true to config/mobile.ts and running ./buddy build:ios leaves enableWatchApp: false and emits no watch target into project.yml.

So the first task is small and unblocks the rest: a config key that reaches enableWatchApp, and a watch target in the generated project when it is on.

Then the actual question this issue asks — what deep integration means — becomes answerable with something running. The stock template records a workout and mirrors distance/duration over WCSession. Deep would be more than that:

  • the planned session for today on the wrist, so the watch starts the right workout rather than a generic one
  • heart rate from the watch's own sensor rather than a strap, which is the thing that cannot be tested today (hq#218 and hq#232 are both blocked on exactly this)
  • the workout written back to HealthKit from the watch, so it counts towards the rings without the phone
  • what happens when the phone is not reachable at all — a watch-only session that reconciles later is a different design from a mirror of the phone

Worth noting for whoever picks this up: CraftHealth.swift completes off the main queue (craft-native/craft#352), and a watch app leans on HealthKit harder than the phone does.

9 days ago

Current Stacks main already carries the first config wire described here: capabilities.watchApp is typed in storage/framework/core/types/src/mobile.ts, mapped to Craft enableWatchApp in ios-config.ts, and watchDeploymentTarget maps to watchosVersion with a watchOS 9.0 validation gate. The change landed in Stacks 3b3dbdb61 and remains on current main. The local targeted tests are currently blocked by this checkout's stale installed craft-native package (dist/mobile.js is missing the storage export), not by the watch config path. I am leaving #344 open for the deeper watchOS integration Chris requested: watch-owned heart rate/workout persistence, planned-session handoff, and offline reconciliation.

9 days ago

Deep-integration progress is now on main:

  • b7b5744e adds the versioned watch protocol (protocolVersion, requestId), planned-session handoff through application context, persisted watch state, and state snapshots with monotonic stateVersion reconciliation across offline/reconnect.
  • 4fe04067 makes the watch own the HealthKit workout lifecycle when enableHealthKit is enabled: authorization, HKWorkoutSession/HKLiveWorkoutBuilder, live heart rate and five-zone metrics, pause/resume/end, and a saved HealthKit completion summary synced through the watch state context.
  • Generated watch entitlements and HealthKit usage descriptions are now emitted for both Watch targets.
  • packages/ios/src/index.test.ts: 121 passed; generated WatchKit extension compiled with Xcode 26.6/watchOS 26.5 for arm64 and x86_64 simulator architectures.

The physical watch sensor path still needs device validation; the generator, protocol, and target wiring are covered in source tests and the generated extension build.

9 days ago

The requested watchOS depth is now implemented on main:

  • the generated watch app and extension target are wired from the mobile config;
  • the versioned watch protocol carries planned-session handoff, persisted state, and monotonic state snapshots across reconnects;
  • the watch owns the HealthKit workout lifecycle when enabled, including live heart rate and zone metrics, pause/resume/end, and completion sync;
  • generated WatchKit tests compile the extension for simulator architectures and cover the target/protocol output.

Physical watch-sensor validation still requires hardware, but the repository implementation and generated-host tests are complete. Closing #344 as completed.

Sign in to comment on this issue.