@glassly/engine 0.3.7-dev.871 → 0.3.7-dev.874

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/build/engine.d.ts +2 -2
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +4 -4
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/glasses.d.ts +4 -4
  6. package/build/facades/glasses.d.ts.map +1 -1
  7. package/build/facades/glasses.js +4 -4
  8. package/build/facades/glasses.js.map +1 -1
  9. package/build/generated/releaseMetadata.js +3 -3
  10. package/build/generated/releaseMetadata.js.map +1 -1
  11. package/build/services/controllers.d.ts.map +1 -1
  12. package/build/services/controllers.js +4 -3
  13. package/build/services/controllers.js.map +1 -1
  14. package/build/services/{appleWatch/AppleWatchLink.d.ts → watch/WatchLink.d.ts} +8 -7
  15. package/build/services/watch/WatchLink.d.ts.map +1 -0
  16. package/build/services/{appleWatch/AppleWatchLink.js → watch/WatchLink.js} +14 -15
  17. package/build/services/watch/WatchLink.js.map +1 -0
  18. package/build/stores/settings.d.ts.map +1 -1
  19. package/build/stores/settings.js +28 -3
  20. package/build/stores/settings.js.map +1 -1
  21. package/build/types/enums.d.ts +2 -1
  22. package/build/types/enums.d.ts.map +1 -1
  23. package/build/types/enums.js +1 -0
  24. package/build/types/enums.js.map +1 -1
  25. package/package.json +7 -7
  26. package/src/engine.ts +4 -4
  27. package/src/facades/glasses.ts +5 -5
  28. package/src/generated/releaseMetadata.ts +3 -3
  29. package/src/services/controllers.ts +4 -3
  30. package/src/services/{appleWatch/AppleWatchLink.ts → watch/WatchLink.ts} +20 -20
  31. package/src/stores/settings.ts +35 -7
  32. package/src/types/enums.ts +1 -0
  33. package/build/services/appleWatch/AppleWatchLink.d.ts.map +0 -1
  34. package/build/services/appleWatch/AppleWatchLink.js.map +0 -1
package/src/engine.ts CHANGED
@@ -25,7 +25,7 @@ import {startPhoneActivityTracking, stopPhoneActivityTracking} from "./services/
25
25
  import {startPhoneNotificationsSync, stopPhoneNotificationsSync} from "./services/PhoneNotificationsSync"
26
26
  import {startSpeakerIdSync, stopSpeakerIdSync} from "./services/SpeakerIdSync"
27
27
  import {startNeuralBandSettingsSync, stopNeuralBandSettingsSync} from "./services/neuralBand/NeuralBandSettingsSync"
28
- import {startAppleWatchLink, stopAppleWatchLink} from "./services/appleWatch/AppleWatchLink"
28
+ import {startWatchLink, stopWatchLink} from "./services/watch/WatchLink"
29
29
  import {startCaptionsTesterReportService, stopCaptionsTesterReportService} from "./services/CaptionsTesterReportService"
30
30
  import {startJSCrashloopReportService, stopJSCrashloopReportService} from "./services/JSCrashloopReportService"
31
31
  import {ensureMiniappEngine, stopMiniappEngine} from "./services/MiniappEngine"
@@ -146,8 +146,8 @@ export const engine = {
146
146
  startSpeakerIdSync()
147
147
  // Meta Neural Band: push the air mouse and wearing-hand settings to the band on connect.
148
148
  startNeuralBandSettingsSync()
149
- // Apple Watch remote (iOS): status and mirror frames out, wrist commands in.
150
- startAppleWatchLink()
149
+ // Watch remote (Apple Watch on iOS, Wear OS on Android): status and mirror frames out, wrist commands in.
150
+ startWatchLink()
151
151
  // Android internal/e2e: laptop captions tester can broadcast a failure intent;
152
152
  // engine owns turning that into a Cloud report.
153
153
  startCaptionsTesterReportService()
@@ -192,7 +192,7 @@ export const engine = {
192
192
  await safely("phone activity tracking", stopPhoneActivityTracking)
193
193
  await safely("speaker id sync", stopSpeakerIdSync)
194
194
  await safely("neural band settings sync", stopNeuralBandSettingsSync)
195
- await safely("apple watch link", stopAppleWatchLink)
195
+ await safely("watch link", stopWatchLink)
196
196
  await safely("captions tester report service", stopCaptionsTesterReportService)
197
197
  await safely("js crashloop report service", stopJSCrashloopReportService)
198
198
  await safely("miniapp engine", stopMiniappEngine)
@@ -12,7 +12,7 @@
12
12
  // standalone build); jest moduleNameMapper + tsconfig both resolve it.
13
13
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
14
14
  import type {
15
- AppleWatchAvailability,
15
+ WatchAvailability,
16
16
  ButtonPressEvent,
17
17
  ConnectOptions,
18
18
  ControllerStatus,
@@ -346,10 +346,10 @@ export const glasses = {
346
346
  setHand: (id: string, hand: "left" | "right"): Promise<void> => BluetoothSdk.neuralBandSetHand(id, hand),
347
347
  },
348
348
 
349
- // --- Apple Watch (iOS): pairs through the Glassly watch app over WatchConnectivity ---
350
- appleWatch: {
351
- /** Whether this iPhone has a paired watch and the Glassly watch app is on it. */
352
- availability: (): Promise<AppleWatchAvailability> => BluetoothSdk.appleWatchAvailability(),
349
+ // --- watch: an Apple Watch over WatchConnectivity (iOS), a Wear OS watch over the Data Layer (Android) ---
350
+ watch: {
351
+ /** Whether this phone has a paired watch and the Glassly watch app is on it. */
352
+ availability: (): Promise<WatchAvailability> => BluetoothSdk.watchAvailability(),
353
353
  },
354
354
 
355
355
  // --- audio (glasses media-volume, own-app playback hint, live mic level) ---
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.3.7",
15
- "releaseIdentity": "0.3.7-dev.871",
16
- "releaseSetId": "glassly-0.3.7-dev.871",
17
- "sourceCommit": "4d290c520f9e7a2842da37f7fbc0f61c513eed9e",
15
+ "releaseIdentity": "0.3.7-dev.874",
16
+ "releaseSetId": "glassly-0.3.7-dev.874",
17
+ "sourceCommit": "1d02e138714d57d40eeafb5eca71dbb30684e69a",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -26,8 +26,8 @@ const NEURAL_BAND_SURFACE: InputSurface = {
26
26
  ],
27
27
  }
28
28
 
29
- /** The Apple Watch trackpad's taps and four-way swipes, and the Digital Crown as the dial. */
30
- const APPLE_WATCH_SURFACE: InputSurface = {
29
+ /** A watch trackpad's taps and four-way swipes, and the crown (Apple) or rotary bezel (Wear OS) as the dial. */
30
+ const WATCH_SURFACE: InputSurface = {
31
31
  type: "swipe2d",
32
32
  events: [
33
33
  "TAP",
@@ -45,7 +45,8 @@ const APPLE_WATCH_SURFACE: InputSurface = {
45
45
  // The R1 swipes up/down like the temples it stands in for, so it adds nothing.
46
46
  const INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {
47
47
  [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
48
- [ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],
48
+ [ControllerTypes.APPLE_WATCH]: [WATCH_SURFACE],
49
+ [ControllerTypes.WEAR_OS]: [WATCH_SURFACE],
49
50
  [ControllerTypes.R1]: [],
50
51
  }
51
52
 
@@ -1,15 +1,15 @@
1
1
  /**
2
- * The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in
2
+ * The watch remote's phone half, for the Apple Watch (iOS, WatchConnectivity) and a Wear OS
3
+ * watch (Android, Wearable Data Layer) alike: the watch app's status (glasses, the miniapp in
3
4
  * front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames
4
5
  * while the setting is on, and the commands the watch sends back. Native adds whether the active
5
6
  * watch is paired and keeps the newest frame for when the watch app opens. Gestures ride
6
7
  * `touch_event` like any controller's.
7
8
  */
8
- import type {AppleWatchCommandEvent} from "@glassly/bluetooth-sdk"
9
+ import type {WatchCommandEvent} from "@glassly/bluetooth-sdk"
9
10
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
10
11
  import type {MirrorSnapshot} from "@glassly/miniapp"
11
12
  import {MiniappStreamType} from "@glassly/miniapp"
12
- import {Platform} from "react-native"
13
13
 
14
14
  import {useAppStatusStore} from "../../stores/apps"
15
15
  import {useGlassesStore} from "../../stores/glasses"
@@ -23,7 +23,7 @@ import {keyEventsForTextChange} from "../neuralBand/NeuralBandInput"
23
23
 
24
24
  export const ASSIST_PACKAGE = "com.glassly.assistant"
25
25
  export const ASSIST_ACTION = "ask"
26
- /** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */
26
+ /** WatchConnectivity messages top out near 64 KB (Data Layer messages at 100 KB); frames past this are skipped, not truncated. */
27
27
  const MAX_FRAME_BYTES = 48_000
28
28
  const MAX_IMAGE_CHARS = 16_000
29
29
  const MAX_APPS = 24
@@ -142,7 +142,7 @@ export function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> |
142
142
  return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame
143
143
  }
144
144
 
145
- export interface AppleWatchCommandDeps {
145
+ export interface WatchCommandDeps {
146
146
  apps: () => readonly ClientApp[]
147
147
  start: (app: ClientApp) => Promise<unknown>
148
148
  stop: (packageName: string) => Promise<unknown>
@@ -151,7 +151,7 @@ export interface AppleWatchCommandDeps {
151
151
  now: () => number
152
152
  }
153
153
 
154
- const defaultDeps: AppleWatchCommandDeps = {
154
+ const defaultDeps: WatchCommandDeps = {
155
155
  apps: () => useAppStatusStore.getState().apps,
156
156
  start: (app) => useAppStatusStore.getState().start(app),
157
157
  stop: (packageName) => useAppStatusStore.getState().stop(packageName),
@@ -160,9 +160,9 @@ const defaultDeps: AppleWatchCommandDeps = {
160
160
  now: () => Date.now(),
161
161
  }
162
162
 
163
- export async function handleAppleWatchCommand(
164
- event: AppleWatchCommandEvent,
165
- deps: AppleWatchCommandDeps = defaultDeps,
163
+ export async function handleWatchCommand(
164
+ event: WatchCommandEvent,
165
+ deps: WatchCommandDeps = defaultDeps,
166
166
  ): Promise<void> {
167
167
  switch (event.command) {
168
168
  case "launch": {
@@ -170,7 +170,7 @@ export async function handleAppleWatchCommand(
170
170
  ? deps.apps().find((entry) => entry.packageName === event.packageName)
171
171
  : undefined
172
172
  if (!app) {
173
- console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`)
173
+ console.warn(`Watch: launch of unknown miniapp ${event.packageName}`)
174
174
  return
175
175
  }
176
176
  await deps.start(app)
@@ -206,7 +206,7 @@ function currentState(): Record<string, unknown> {
206
206
  },
207
207
  apps: useAppStatusStore.getState().apps,
208
208
  assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),
209
- mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,
209
+ mirror: useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true,
210
210
  })
211
211
  }
212
212
 
@@ -226,7 +226,7 @@ function syncMirror(enabled: boolean): void {
226
226
  const send = (snapshot: MirrorSnapshot) => {
227
227
  lastSentAt = Date.now()
228
228
  void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) =>
229
- console.warn("AppleWatch: mirror push failed:", err),
229
+ console.warn("Watch: mirror push failed:", err),
230
230
  )
231
231
  }
232
232
  const unsubscribe = mirrorService.subscribe((snapshot) => {
@@ -252,8 +252,8 @@ function syncMirror(enabled: boolean): void {
252
252
  }
253
253
  }
254
254
 
255
- export function startAppleWatchLink(): void {
256
- if (Platform.OS !== "ios" || stops.length > 0) return
255
+ export function startWatchLink(): void {
256
+ if (stops.length > 0) return
257
257
  let lastKey = ""
258
258
  let timer: ReturnType<typeof BgTimer.setTimeout> | null = null
259
259
  const push = () => {
@@ -262,21 +262,21 @@ export function startAppleWatchLink(): void {
262
262
  const key = JSON.stringify(state)
263
263
  if (key === lastKey) return
264
264
  lastKey = key
265
- void BluetoothSdk.setWatchState(state).catch((err) => console.warn("AppleWatch: state push failed:", err))
265
+ void BluetoothSdk.setWatchState(state).catch((err) => console.warn("Watch: state push failed:", err))
266
266
  }
267
267
  const schedule = () => {
268
268
  timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS)
269
269
  }
270
- const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true
271
- const commands = BluetoothSdk.addListener("apple_watch_command", (event) => {
272
- handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err))
270
+ const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true
271
+ const commands = BluetoothSdk.addListener("watch_command", (event) => {
272
+ handleWatchCommand(event).catch((err) => console.warn(`Watch: ${event.command} failed:`, err))
273
273
  })
274
274
  stops = [
275
275
  () => commands.remove(),
276
276
  useGlassesStore.subscribe(schedule),
277
277
  useAppStatusStore.subscribe(schedule),
278
278
  useSettingsStore.subscribe(
279
- (s) => s.getSetting(SETTINGS.apple_watch_mirror.key),
279
+ (s) => s.getSetting(SETTINGS.watch_mirror.key),
280
280
  () => {
281
281
  syncMirror(mirrorEnabled())
282
282
  schedule()
@@ -291,7 +291,7 @@ export function startAppleWatchLink(): void {
291
291
  schedule()
292
292
  }
293
293
 
294
- export function stopAppleWatchLink(): void {
294
+ export function stopWatchLink(): void {
295
295
  stops.splice(0).forEach((stop) => stop())
296
296
  syncMirror(false)
297
297
  }
@@ -841,12 +841,21 @@ export const SETTINGS: Record<string, Setting> = {
841
841
  saveOnServer: false,
842
842
  persist: true,
843
843
  },
844
- // Apple Watch: draw the glasses' current frame behind the watch trackpad. Frames
845
- // cross WatchConnectivity while the watch app is up, so it stays off unless asked for.
844
+ // Watch (Apple Watch, Wear OS): draw the glasses' current frame behind the watch
845
+ // trackpad. Frames cross the watch link while the watch app is up, so it stays off
846
+ // unless asked for.
847
+ watch_mirror: {
848
+ key: "watch_mirror",
849
+ defaultValue: () => false,
850
+ writable: true,
851
+ saveOnServer: false,
852
+ persist: true,
853
+ },
854
+ // Deprecated: the setting above under its Apple-only name; read once to migrate.
846
855
  apple_watch_mirror: {
847
856
  key: "apple_watch_mirror",
848
857
  defaultValue: () => false,
849
- writable: true,
858
+ writable: false,
850
859
  saveOnServer: false,
851
860
  persist: true,
852
861
  },
@@ -1276,10 +1285,13 @@ export interface SettingsState {
1276
1285
  }
1277
1286
 
1278
1287
  const getDefaultSettings = () =>
1279
- Object.keys(SETTINGS).reduce((acc, key) => {
1280
- acc[key] = SETTINGS[key].defaultValue()
1281
- return acc
1282
- }, {} as Record<string, any>)
1288
+ Object.keys(SETTINGS).reduce(
1289
+ (acc, key) => {
1290
+ acc[key] = SETTINGS[key].defaultValue()
1291
+ return acc
1292
+ },
1293
+ {} as Record<string, any>,
1294
+ )
1283
1295
 
1284
1296
  // Single-flight for loadAllSettings: the host fires it at module load and
1285
1297
  // engine.start()'s device-store hydration awaits it — without the memo the
@@ -1619,6 +1631,22 @@ export const useSettingsStore = create<SettingsState>()(
1619
1631
  }
1620
1632
  if (menuMigrated) storage.save(MENU_SETTINGS_MIGRATION_KEY, true)
1621
1633
  }
1634
+
1635
+ // The watch mirror switch lost its Apple-only name when Wear OS joined: carry a
1636
+ // turned-on `apple_watch_mirror` over to `watch_mirror` once.
1637
+ const WATCH_MIRROR_MIGRATION_KEY = "migration:watch_mirror_v1"
1638
+ const watchMirrorMigrationDone = storage.load<boolean>(WATCH_MIRROR_MIGRATION_KEY)
1639
+ if (watchMirrorMigrationDone.is_error() || !watchMirrorMigrationDone.value) {
1640
+ let watchMirrorMigrated = true
1641
+ if (get().getSetting(SETTINGS.apple_watch_mirror.key) === true) {
1642
+ const result = await get().setSetting(SETTINGS.watch_mirror.key, true, false)
1643
+ if (result.is_error()) {
1644
+ console.log("SETTINGS: watch_mirror migration failed:", result.error)
1645
+ watchMirrorMigrated = false
1646
+ }
1647
+ }
1648
+ if (watchMirrorMigrated) storage.save(WATCH_MIRROR_MIGRATION_KEY, true)
1649
+ }
1622
1650
  })
1623
1651
  loadAllSettingsInFlight = inFlight
1624
1652
  void Promise.resolve(inFlight).then((result) => {
@@ -19,4 +19,5 @@ export enum ControllerTypes {
19
19
  R1 = "Even Realities R1",
20
20
  NEURAL_BAND = "Meta Neural Band",
21
21
  APPLE_WATCH = "Apple Watch",
22
+ WEAR_OS = "Wear OS Watch",
22
23
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppleWatchLink.d.ts","sourceRoot":"","sources":["../../../src/services/appleWatch/AppleWatchLink.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,wBAAwB,CAAA;AAElE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAOpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAA;AAO1C,eAAO,MAAM,cAAc,0BAA0B,CAAA;AACrD,eAAO,MAAM,aAAa,QAAQ,CAAA;AAQlC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAA;IAC7D,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,GAAG;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,CAMhG;AAED,wBAAgB,UAAU,CAAC,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAC,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUpG;AAOD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA4EpF;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,SAAS,SAAS,EAAE,CAAA;IAChC,KAAK,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAWD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,sBAAsB,EAC7B,IAAI,GAAE,qBAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AA6DD,wBAAgB,mBAAmB,IAAI,IAAI,CAqC1C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppleWatchLink.js","sourceRoot":"","sources":["../../../src/services/appleWatch/AppleWatchLink.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in\n * front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames\n * while the setting is on, and the commands the watch sends back. Native adds whether the active\n * watch is paired and keeps the newest frame for when the watch app opens. Gestures ride\n * `touch_event` like any controller's.\n */\nimport type {AppleWatchCommandEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {MirrorSnapshot} from \"@glassly/miniapp\"\nimport {MiniappStreamType} from \"@glassly/miniapp\"\nimport {Platform} from \"react-native\"\n\nimport {useAppStatusStore} from \"../../stores/apps\"\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport type {ClientApp} from \"../../types\"\nimport {BgTimer} from \"../../utils/timers\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\nimport localMiniappRuntime from \"../LocalMiniappRuntime\"\nimport {mirrorService} from \"../MirrorService\"\nimport {keyEventsForTextChange} from \"../neuralBand/NeuralBandInput\"\n\nexport const ASSIST_PACKAGE = \"com.glassly.assistant\"\nexport const ASSIST_ACTION = \"ask\"\n/** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */\nconst MAX_FRAME_BYTES = 48_000\nconst MAX_IMAGE_CHARS = 16_000\nconst MAX_APPS = 24\nconst STATE_DEBOUNCE_MS = 250\nconst MIRROR_INTERVAL_MS = 500\n\nexport interface WatchStateInput {\n glasses: {connected: boolean; model: string; battery: number}\n apps: readonly ClientApp[]\n assist: boolean\n mirror: boolean\n}\n\n/** Miniapps the watch can open: shown, compatible, and drawing on the glasses. */\nexport function launchableApps(apps: readonly ClientApp[]): {packageName: string; name: string}[] {\n return apps\n .filter((app) => app.type === \"standard\" && !app.hidden && app.compatibility?.isCompatible !== false)\n .filter((app) => app.packageName && !app.packageName.includes(\"@empty\"))\n .slice(0, MAX_APPS)\n .map((app) => ({packageName: app.packageName, name: app.name}))\n}\n\nexport function watchState({glasses, apps, assist, mirror}: WatchStateInput): Record<string, unknown> {\n const front = apps.find((app) => app.foregrounded && app.running && app.type === \"standard\")\n const state: Record<string, unknown> = {\n glasses: {connected: glasses.connected, name: glasses.model || \"Glasses\", battery: glasses.battery},\n apps: launchableApps(apps),\n assist,\n mirror,\n }\n if (front) state.app = {packageName: front.packageName, name: front.name}\n return state\n}\n\ntype WatchElement = Record<string, string | number | boolean | number[]>\n\nconst level = (color: unknown, fallback = 15) =>\n typeof color === \"number\" ? Math.max(0, Math.min(15, color)) : fallback\n\n/** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */\nexport function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> | null {\n const canvas = snapshot.canvas\n if (!canvas) return null\n const content = snapshot.content\n if (content.kind === \"bitmap\") {\n if (content.data.length > MAX_FRAME_BYTES) return null\n return {width: canvas.width, height: canvas.height, bitmap: content.data, elements: []}\n }\n if (content.kind !== \"scene\") return null\n const elements: WatchElement[] = []\n for (const element of content.elements) {\n const {x, y, w, h} = element.box\n const style = element.style ?? {}\n switch (element.type) {\n case \"text\":\n if (!element.text) break\n elements.push({\n type: \"text\",\n x,\n y,\n w,\n h,\n text: element.text,\n size: style.fontSize ?? 20,\n color: level(style.color),\n })\n break\n case \"rect\":\n elements.push({\n type: \"rect\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n radius: style.radius ?? 0,\n color: level(style.color),\n })\n break\n case \"circle\":\n elements.push({\n type: \"circle\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n color: level(style.color),\n })\n break\n case \"line\":\n case \"triangle\":\n case \"quad\": {\n const points = (element.points ?? []).flatMap((point) => [point.x, point.y])\n if (points.length < 4) break\n const closed = element.type !== \"line\"\n elements.push({\n type: \"line\",\n points,\n width: style.width ?? style.border ?? 1,\n color: level(style.color),\n closed,\n fill: closed && style.fill === true,\n })\n break\n }\n case \"image\":\n if (element.data && element.data.length <= MAX_IMAGE_CHARS)\n elements.push({type: \"image\", x, y, w, h, data: element.data})\n break\n }\n }\n const frame = {width: canvas.width, height: canvas.height, elements}\n return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame\n}\n\nexport interface AppleWatchCommandDeps {\n apps: () => readonly ClientApp[]\n start: (app: ClientApp) => Promise<unknown>\n stop: (packageName: string) => Promise<unknown>\n ask: (query: string) => Promise<unknown>\n type: (stream: string, payload: unknown) => void\n now: () => number\n}\n\nconst defaultDeps: AppleWatchCommandDeps = {\n apps: () => useAppStatusStore.getState().apps,\n start: (app) => useAppStatusStore.getState().start(app),\n stop: (packageName) => useAppStatusStore.getState().stop(packageName),\n ask: (query) => localMiniappRuntime.invokeActionFromHost(ASSIST_PACKAGE, ASSIST_ACTION, {query}),\n type: (stream, payload) => localMiniappRuntime.forwardEvent(stream, payload),\n now: () => Date.now(),\n}\n\nexport async function handleAppleWatchCommand(\n event: AppleWatchCommandEvent,\n deps: AppleWatchCommandDeps = defaultDeps,\n): Promise<void> {\n switch (event.command) {\n case \"launch\": {\n const app = launchableApps(deps.apps()).some((entry) => entry.packageName === event.packageName)\n ? deps.apps().find((entry) => entry.packageName === event.packageName)\n : undefined\n if (!app) {\n console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`)\n return\n }\n await deps.start(app)\n return\n }\n case \"stop\": {\n const front = deps.apps().find((app) => app.foregrounded && app.running && app.type === \"standard\")\n if (front) await deps.stop(front.packageName)\n return\n }\n case \"assist\": {\n const query = event.text.trim()\n if (query) await deps.ask(query)\n return\n }\n case \"text\":\n for (const key of keyEventsForTextChange(\"\", event.text, deps.now()))\n deps.type(MiniappStreamType.KEYBOARD_EVENT, key)\n return\n }\n}\n\nlet stops: (() => void)[] = []\nlet stopMirror: (() => void) | null = null\n\nfunction currentState(): Record<string, unknown> {\n const glasses = useGlassesStore.getState()\n return watchState({\n glasses: {\n connected: isGlassesConnected(glasses.connection),\n model: glasses.deviceModel,\n battery: glasses.batteryLevel,\n },\n apps: useAppStatusStore.getState().apps,\n assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),\n mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,\n })\n}\n\nfunction syncMirror(enabled: boolean): void {\n if (!enabled) {\n if (stopMirror) {\n stopMirror()\n stopMirror = null\n void BluetoothSdk.setWatchMirror(null).catch(() => {})\n }\n return\n }\n if (stopMirror) return\n let lastSentAt = 0\n let pending: MirrorSnapshot | null = null\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const send = (snapshot: MirrorSnapshot) => {\n lastSentAt = Date.now()\n void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) =>\n console.warn(\"AppleWatch: mirror push failed:\", err),\n )\n }\n const unsubscribe = mirrorService.subscribe((snapshot) => {\n const wait = lastSentAt + MIRROR_INTERVAL_MS - Date.now()\n if (wait <= 0 && !timer) {\n send(snapshot)\n return\n }\n pending = snapshot\n timer ??= BgTimer.setTimeout(\n () => {\n timer = null\n if (pending) send(pending)\n pending = null\n },\n Math.max(wait, 0),\n )\n })\n stopMirror = () => {\n unsubscribe()\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n }\n}\n\nexport function startAppleWatchLink(): void {\n if (Platform.OS !== \"ios\" || stops.length > 0) return\n let lastKey = \"\"\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const push = () => {\n timer = null\n const state = currentState()\n const key = JSON.stringify(state)\n if (key === lastKey) return\n lastKey = key\n void BluetoothSdk.setWatchState(state).catch((err) => console.warn(\"AppleWatch: state push failed:\", err))\n }\n const schedule = () => {\n timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS)\n }\n const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true\n const commands = BluetoothSdk.addListener(\"apple_watch_command\", (event) => {\n handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err))\n })\n stops = [\n () => commands.remove(),\n useGlassesStore.subscribe(schedule),\n useAppStatusStore.subscribe(schedule),\n useSettingsStore.subscribe(\n (s) => s.getSetting(SETTINGS.apple_watch_mirror.key),\n () => {\n syncMirror(mirrorEnabled())\n schedule()\n },\n ),\n () => {\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n },\n ]\n syncMirror(mirrorEnabled())\n schedule()\n}\n\nexport function stopAppleWatchLink(): void {\n stops.splice(0).forEach((stop) => stop())\n syncMirror(false)\n}\n"]}