@glassly/engine 0.1.0-dev.99 → 0.2.9-beta.448

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 (501) hide show
  1. package/README.md +28 -0
  2. package/build/engine.d.ts +32 -10
  3. package/build/engine.d.ts.map +1 -1
  4. package/build/engine.js +24 -16
  5. package/build/engine.js.map +1 -1
  6. package/build/facades/dev.d.ts +9 -0
  7. package/build/facades/dev.d.ts.map +1 -1
  8. package/build/facades/dev.js +9 -0
  9. package/build/facades/dev.js.map +1 -1
  10. package/build/facades/display.d.ts +10 -3
  11. package/build/facades/display.d.ts.map +1 -1
  12. package/build/facades/display.js +22 -8
  13. package/build/facades/display.js.map +1 -1
  14. package/build/facades/displayMirror.d.ts +11 -0
  15. package/build/facades/displayMirror.d.ts.map +1 -1
  16. package/build/facades/displayMirror.js +22 -0
  17. package/build/facades/displayMirror.js.map +1 -1
  18. package/build/facades/glasses.d.ts +31 -1
  19. package/build/facades/glasses.d.ts.map +1 -1
  20. package/build/facades/glasses.js +51 -0
  21. package/build/facades/glasses.js.map +1 -1
  22. package/build/facades/memory.d.ts +0 -6
  23. package/build/facades/memory.d.ts.map +1 -1
  24. package/build/facades/memory.js +1 -9
  25. package/build/facades/memory.js.map +1 -1
  26. package/build/facades/miniapps.d.ts +3 -1
  27. package/build/facades/miniapps.d.ts.map +1 -1
  28. package/build/facades/miniapps.js +3 -1
  29. package/build/facades/miniapps.js.map +1 -1
  30. package/build/facades/pairing.d.ts.map +1 -1
  31. package/build/facades/pairing.js +5 -4
  32. package/build/facades/pairing.js.map +1 -1
  33. package/build/facades/permissions.d.ts +2 -0
  34. package/build/facades/permissions.d.ts.map +1 -1
  35. package/build/facades/permissions.js +48 -0
  36. package/build/facades/permissions.js.map +1 -1
  37. package/build/facades/phoneNotifications.d.ts +20 -9
  38. package/build/facades/phoneNotifications.d.ts.map +1 -1
  39. package/build/facades/phoneNotifications.js +33 -26
  40. package/build/facades/phoneNotifications.js.map +1 -1
  41. package/build/facades/settings.d.ts.map +1 -1
  42. package/build/facades/settings.js +1 -2
  43. package/build/facades/settings.js.map +1 -1
  44. package/build/generated/releaseMetadata.js +4 -4
  45. package/build/generated/releaseMetadata.js.map +1 -1
  46. package/build/index.d.ts +6 -4
  47. package/build/index.d.ts.map +1 -1
  48. package/build/index.js +5 -4
  49. package/build/index.js.map +1 -1
  50. package/build/react/MentraLiveOtaFlow.js +2 -2
  51. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  52. package/build/react/useMentraLiveOta.d.ts.map +1 -1
  53. package/build/react/useMentraLiveOta.js +8 -7
  54. package/build/react/useMentraLiveOta.js.map +1 -1
  55. package/build/runtime/config.d.ts +1 -1
  56. package/build/runtime/config.d.ts.map +1 -1
  57. package/build/runtime/config.js.map +1 -1
  58. package/build/services/AppRegistry.d.ts +4 -1
  59. package/build/services/AppRegistry.d.ts.map +1 -1
  60. package/build/services/AppRegistry.js +21 -1
  61. package/build/services/AppRegistry.js.map +1 -1
  62. package/build/services/BlobStore.d.ts +2 -2
  63. package/build/services/BlobStore.js +3 -3
  64. package/build/services/BlobStore.js.map +1 -1
  65. package/build/services/CloudClientService.d.ts +7 -3
  66. package/build/services/CloudClientService.d.ts.map +1 -1
  67. package/build/services/CloudClientService.js +34 -9
  68. package/build/services/CloudClientService.js.map +1 -1
  69. package/build/services/DevServerBridge.d.ts +1 -18
  70. package/build/services/DevServerBridge.d.ts.map +1 -1
  71. package/build/services/DevServerBridge.js +9 -8
  72. package/build/services/DevServerBridge.js.map +1 -1
  73. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  74. package/build/services/DeviceEventRouter.js +58 -71
  75. package/build/services/DeviceEventRouter.js.map +1 -1
  76. package/build/services/DisplayDiagnostics.d.ts +5 -0
  77. package/build/services/DisplayDiagnostics.d.ts.map +1 -0
  78. package/build/services/DisplayDiagnostics.js +105 -0
  79. package/build/services/DisplayDiagnostics.js.map +1 -0
  80. package/build/services/DisplayProcessor.d.ts +6 -7
  81. package/build/services/DisplayProcessor.d.ts.map +1 -1
  82. package/build/services/DisplayProcessor.js +17 -45
  83. package/build/services/DisplayProcessor.js.map +1 -1
  84. package/build/services/GestureDotsService.d.ts +10 -4
  85. package/build/services/GestureDotsService.d.ts.map +1 -1
  86. package/build/services/GestureDotsService.js +70 -16
  87. package/build/services/GestureDotsService.js.map +1 -1
  88. package/build/services/GlassesKeyboardInput.d.ts +77 -0
  89. package/build/services/GlassesKeyboardInput.d.ts.map +1 -0
  90. package/build/services/GlassesKeyboardInput.js +261 -0
  91. package/build/services/GlassesKeyboardInput.js.map +1 -0
  92. package/build/services/GlassesSensorService.d.ts +47 -0
  93. package/build/services/GlassesSensorService.d.ts.map +1 -0
  94. package/build/services/GlassesSensorService.js +242 -0
  95. package/build/services/GlassesSensorService.js.map +1 -0
  96. package/build/services/GlassesTouchInput.d.ts +14 -0
  97. package/build/services/GlassesTouchInput.d.ts.map +1 -0
  98. package/build/services/GlassesTouchInput.js +107 -0
  99. package/build/services/GlassesTouchInput.js.map +1 -0
  100. package/build/services/HeadingService.d.ts +0 -6
  101. package/build/services/HeadingService.d.ts.map +1 -1
  102. package/build/services/HeadingService.js +0 -27
  103. package/build/services/HeadingService.js.map +1 -1
  104. package/build/services/HotspotShutdown.d.ts.map +1 -1
  105. package/build/services/HotspotShutdown.js +2 -1
  106. package/build/services/HotspotShutdown.js.map +1 -1
  107. package/build/services/JSCrashloopReportService.js +2 -2
  108. package/build/services/JSCrashloopReportService.js.map +1 -1
  109. package/build/services/JSRouter.d.ts +6 -4
  110. package/build/services/JSRouter.d.ts.map +1 -1
  111. package/build/services/JSRouter.js +15 -13
  112. package/build/services/JSRouter.js.map +1 -1
  113. package/build/services/LocalDisplayManager.d.ts +35 -7
  114. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  115. package/build/services/LocalDisplayManager.js +105 -27
  116. package/build/services/LocalDisplayManager.js.map +1 -1
  117. package/build/services/LocalMiniappDataMigration.d.ts +1 -1
  118. package/build/services/LocalMiniappDataMigration.js +1 -1
  119. package/build/services/LocalMiniappDataMigration.js.map +1 -1
  120. package/build/services/LocalMiniappRuntime.d.ts +64 -11
  121. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  122. package/build/services/LocalMiniappRuntime.js +441 -35
  123. package/build/services/LocalMiniappRuntime.js.map +1 -1
  124. package/build/services/LocalSttFallbackCoordinator.d.ts +3 -0
  125. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  126. package/build/services/LocalSttFallbackCoordinator.js +27 -8
  127. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  128. package/build/services/MemoryStore.d.ts +8 -33
  129. package/build/services/MemoryStore.d.ts.map +1 -1
  130. package/build/services/MemoryStore.js +30 -56
  131. package/build/services/MemoryStore.js.map +1 -1
  132. package/build/services/MiniappEngine.d.ts +3 -3
  133. package/build/services/MiniappEngine.js +7 -7
  134. package/build/services/MiniappEngine.js.map +1 -1
  135. package/build/services/MiniappLauncher.d.ts +8 -0
  136. package/build/services/MiniappLauncher.d.ts.map +1 -1
  137. package/build/services/MiniappLauncher.js +19 -1
  138. package/build/services/MiniappLauncher.js.map +1 -1
  139. package/build/services/MirrorService.d.ts +24 -0
  140. package/build/services/MirrorService.d.ts.map +1 -0
  141. package/build/services/MirrorService.js +132 -0
  142. package/build/services/MirrorService.js.map +1 -0
  143. package/build/services/NativeNavigationService.d.ts +2 -3
  144. package/build/services/NativeNavigationService.d.ts.map +1 -1
  145. package/build/services/NativeNavigationService.js +24 -21
  146. package/build/services/NativeNavigationService.js.map +1 -1
  147. package/build/services/NavigationHandlers.d.ts.map +1 -1
  148. package/build/services/NavigationHandlers.js +10 -5
  149. package/build/services/NavigationHandlers.js.map +1 -1
  150. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  151. package/build/services/OtaInstallCoordinator.js +24 -23
  152. package/build/services/OtaInstallCoordinator.js.map +1 -1
  153. package/build/services/PairedGlassesLatch.d.ts +7 -0
  154. package/build/services/PairedGlassesLatch.d.ts.map +1 -0
  155. package/build/services/PairedGlassesLatch.js +38 -0
  156. package/build/services/PairedGlassesLatch.js.map +1 -0
  157. package/build/services/PhoneActivity.d.ts +23 -0
  158. package/build/services/PhoneActivity.d.ts.map +1 -0
  159. package/build/services/PhoneActivity.js +111 -0
  160. package/build/services/PhoneActivity.js.map +1 -0
  161. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -1
  162. package/build/services/PhoneCameraFovCoordinator.js +4 -3
  163. package/build/services/PhoneCameraFovCoordinator.js.map +1 -1
  164. package/build/services/PhoneNotificationsSync.d.ts +40 -6
  165. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  166. package/build/services/PhoneNotificationsSync.js +160 -84
  167. package/build/services/PhoneNotificationsSync.js.map +1 -1
  168. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
  169. package/build/services/PhonePhotoCoordinator.js +6 -5
  170. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  171. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  172. package/build/services/PhoneStreamCoordinator.js +8 -7
  173. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  174. package/build/services/STTModelManager.d.ts +3 -0
  175. package/build/services/STTModelManager.d.ts.map +1 -1
  176. package/build/services/STTModelManager.js +15 -1
  177. package/build/services/STTModelManager.js.map +1 -1
  178. package/build/services/SceneRenderer.d.ts +43 -2
  179. package/build/services/SceneRenderer.d.ts.map +1 -1
  180. package/build/services/SceneRenderer.js +123 -8
  181. package/build/services/SceneRenderer.js.map +1 -1
  182. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  183. package/build/services/StreamLifecycleController.js +6 -14
  184. package/build/services/StreamLifecycleController.js.map +1 -1
  185. package/build/services/TTSModelManager.js +1 -1
  186. package/build/services/TTSModelManager.js.map +1 -1
  187. package/build/services/UIRouter.d.ts +2 -2
  188. package/build/services/UIRouter.js +5 -5
  189. package/build/services/UIRouter.js.map +1 -1
  190. package/build/services/asg/galleryPathMigration.js +1 -1
  191. package/build/services/asg/galleryPathMigration.js.map +1 -1
  192. package/build/services/asg/gallerySyncService.js +2 -2
  193. package/build/services/asg/gallerySyncService.js.map +1 -1
  194. package/build/services/asg/localStorageService.d.ts.map +1 -1
  195. package/build/services/asg/localStorageService.js +3 -3
  196. package/build/services/asg/localStorageService.js.map +1 -1
  197. package/build/services/debug/DashboardOpenTrace.d.ts +32 -0
  198. package/build/services/debug/DashboardOpenTrace.d.ts.map +1 -0
  199. package/build/services/debug/DashboardOpenTrace.js +128 -0
  200. package/build/services/debug/DashboardOpenTrace.js.map +1 -0
  201. package/build/services/debug/dashboardOpenTimeline.d.ts +77 -0
  202. package/build/services/debug/dashboardOpenTimeline.d.ts.map +1 -0
  203. package/build/services/debug/dashboardOpenTimeline.js +195 -0
  204. package/build/services/debug/dashboardOpenTimeline.js.map +1 -0
  205. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  206. package/build/services/effectiveCapabilities.js +25 -13
  207. package/build/services/effectiveCapabilities.js.map +1 -1
  208. package/build/services/gestureClaims.d.ts +34 -15
  209. package/build/services/gestureClaims.d.ts.map +1 -1
  210. package/build/services/gestureClaims.js +42 -21
  211. package/build/services/gestureClaims.js.map +1 -1
  212. package/build/services/manifestFields.d.ts +9 -0
  213. package/build/services/manifestFields.d.ts.map +1 -0
  214. package/build/services/manifestFields.js +26 -0
  215. package/build/services/manifestFields.js.map +1 -0
  216. package/build/services/memoryStoreInstance.d.ts +0 -10
  217. package/build/services/memoryStoreInstance.d.ts.map +1 -1
  218. package/build/services/memoryStoreInstance.js +2 -31
  219. package/build/services/memoryStoreInstance.js.map +1 -1
  220. package/build/services/mirrorSnapshot.d.ts +16 -0
  221. package/build/services/mirrorSnapshot.d.ts.map +1 -0
  222. package/build/services/mirrorSnapshot.js +102 -0
  223. package/build/services/mirrorSnapshot.js.map +1 -0
  224. package/build/services/nativeNavigationGate.d.ts +8 -0
  225. package/build/services/nativeNavigationGate.d.ts.map +1 -0
  226. package/build/services/nativeNavigationGate.js +12 -0
  227. package/build/services/nativeNavigationGate.js.map +1 -0
  228. package/build/services/notificationAppCatalog.d.ts +7 -0
  229. package/build/services/notificationAppCatalog.d.ts.map +1 -0
  230. package/build/services/notificationAppCatalog.js +16 -0
  231. package/build/services/notificationAppCatalog.js.map +1 -0
  232. package/build/services/notifications/notificationPolicy.d.ts +55 -0
  233. package/build/services/notifications/notificationPolicy.d.ts.map +1 -0
  234. package/build/services/notifications/notificationPolicy.js +41 -0
  235. package/build/services/notifications/notificationPolicy.js.map +1 -0
  236. package/build/services/pages/OverlayPages.d.ts +26 -0
  237. package/build/services/pages/OverlayPages.d.ts.map +1 -0
  238. package/build/services/pages/OverlayPages.js +80 -0
  239. package/build/services/pages/OverlayPages.js.map +1 -0
  240. package/build/services/pages/PageHost.d.ts +32 -0
  241. package/build/services/pages/PageHost.d.ts.map +1 -0
  242. package/build/services/pages/PageHost.js +99 -0
  243. package/build/services/pages/PageHost.js.map +1 -0
  244. package/build/services/pages/overlayApps.d.ts +25 -0
  245. package/build/services/pages/overlayApps.d.ts.map +1 -0
  246. package/build/services/pages/overlayApps.js +66 -0
  247. package/build/services/pages/overlayApps.js.map +1 -0
  248. package/build/services/pages/pageEligibility.d.ts +9 -0
  249. package/build/services/pages/pageEligibility.d.ts.map +1 -0
  250. package/build/services/pages/pageEligibility.js +27 -0
  251. package/build/services/pages/pageEligibility.js.map +1 -0
  252. package/build/services/settingsBridge.d.ts +58 -0
  253. package/build/services/settingsBridge.d.ts.map +1 -0
  254. package/build/services/settingsBridge.js +90 -0
  255. package/build/services/settingsBridge.js.map +1 -0
  256. package/build/services/speakerProfileStoreInstance.js +1 -1
  257. package/build/services/speakerProfileStoreInstance.js.map +1 -1
  258. package/build/services/uiShim.d.ts +1 -1
  259. package/build/services/uiShim.js +4 -4
  260. package/build/services/uiShim.js.map +1 -1
  261. package/build/stores/appVisibility.d.ts +28 -0
  262. package/build/stores/appVisibility.d.ts.map +1 -1
  263. package/build/stores/appVisibility.js +34 -0
  264. package/build/stores/appVisibility.js.map +1 -1
  265. package/build/stores/apps.d.ts +13 -0
  266. package/build/stores/apps.d.ts.map +1 -1
  267. package/build/stores/apps.js +66 -12
  268. package/build/stores/apps.js.map +1 -1
  269. package/build/stores/bluetoothSettingKeys.js +1 -1
  270. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  271. package/build/stores/display.d.ts +3 -3
  272. package/build/stores/display.d.ts.map +1 -1
  273. package/build/stores/display.js +16 -4
  274. package/build/stores/display.js.map +1 -1
  275. package/build/stores/glasses.d.ts.map +1 -1
  276. package/build/stores/glasses.js +14 -1
  277. package/build/stores/glasses.js.map +1 -1
  278. package/build/stores/settings.d.ts +1 -0
  279. package/build/stores/settings.d.ts.map +1 -1
  280. package/build/stores/settings.js +170 -44
  281. package/build/stores/settings.js.map +1 -1
  282. package/build/types/applet.d.ts +14 -1
  283. package/build/types/applet.d.ts.map +1 -1
  284. package/build/types/applet.js.map +1 -1
  285. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  286. package/build/types/capabilities/even-realities-g2.js +5 -0
  287. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  288. package/build/types/capabilities/simulated-glasses.d.ts +1 -1
  289. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -1
  290. package/build/types/capabilities/simulated-glasses.js +7 -4
  291. package/build/types/capabilities/simulated-glasses.js.map +1 -1
  292. package/build/types/hardware.d.ts +20 -1
  293. package/build/types/hardware.d.ts.map +1 -1
  294. package/build/types/hardware.js +2 -0
  295. package/build/types/hardware.js.map +1 -1
  296. package/build/types/index.d.ts +1 -0
  297. package/build/types/index.d.ts.map +1 -1
  298. package/build/types/index.js +1 -0
  299. package/build/types/index.js.map +1 -1
  300. package/build/utils/debouncedPatch.d.ts +3 -1
  301. package/build/utils/debouncedPatch.d.ts.map +1 -1
  302. package/build/utils/debouncedPatch.js +6 -3
  303. package/build/utils/debouncedPatch.js.map +1 -1
  304. package/build/utils/devMiniappLaunch.d.ts.map +1 -1
  305. package/build/utils/devMiniappLaunch.js +4 -3
  306. package/build/utils/devMiniappLaunch.js.map +1 -1
  307. package/build/utils/display/measurer/TextMeasurer.d.ts +2 -159
  308. package/build/utils/display/measurer/TextMeasurer.d.ts.map +1 -1
  309. package/build/utils/display/measurer/TextMeasurer.js +2 -272
  310. package/build/utils/display/measurer/TextMeasurer.js.map +1 -1
  311. package/build/utils/display/measurer/script-detection.d.ts +1 -52
  312. package/build/utils/display/measurer/script-detection.d.ts.map +1 -1
  313. package/build/utils/display/measurer/script-detection.js +2 -183
  314. package/build/utils/display/measurer/script-detection.js.map +1 -1
  315. package/build/utils/display/pixelArt.d.ts +3 -0
  316. package/build/utils/display/pixelArt.d.ts.map +1 -0
  317. package/build/utils/display/pixelArt.js +3 -0
  318. package/build/utils/display/pixelArt.js.map +1 -0
  319. package/build/utils/display/profiles/g1.d.ts +1 -32
  320. package/build/utils/display/profiles/g1.d.ts.map +1 -1
  321. package/build/utils/display/profiles/g1.js +2 -212
  322. package/build/utils/display/profiles/g1.js.map +1 -1
  323. package/build/utils/display/profiles/g2.d.ts +1 -12
  324. package/build/utils/display/profiles/g2.d.ts.map +1 -1
  325. package/build/utils/display/profiles/g2.js +2 -33
  326. package/build/utils/display/profiles/g2.js.map +1 -1
  327. package/build/utils/display/profiles/nex.d.ts +1 -9
  328. package/build/utils/display/profiles/nex.d.ts.map +1 -1
  329. package/build/utils/display/profiles/nex.js +2 -149
  330. package/build/utils/display/profiles/nex.js.map +1 -1
  331. package/build/utils/display/profiles/types.d.ts +1 -102
  332. package/build/utils/display/profiles/types.d.ts.map +1 -1
  333. package/build/utils/display/profiles/types.js.map +1 -1
  334. package/build/utils/display/profiles/z100.d.ts +1 -32
  335. package/build/utils/display/profiles/z100.d.ts.map +1 -1
  336. package/build/utils/display/profiles/z100.js +2 -181
  337. package/build/utils/display/profiles/z100.js.map +1 -1
  338. package/build/utils/display/scene/degrade.d.ts.map +1 -1
  339. package/build/utils/display/scene/degrade.js +4 -1
  340. package/build/utils/display/scene/degrade.js.map +1 -1
  341. package/build/utils/display/scene/differ.d.ts +13 -1
  342. package/build/utils/display/scene/differ.d.ts.map +1 -1
  343. package/build/utils/display/scene/differ.js +22 -0
  344. package/build/utils/display/scene/differ.js.map +1 -1
  345. package/build/utils/display/scene/index.d.ts +1 -1
  346. package/build/utils/display/scene/index.d.ts.map +1 -1
  347. package/build/utils/display/scene/index.js.map +1 -1
  348. package/build/utils/display/scene/process.d.ts.map +1 -1
  349. package/build/utils/display/scene/process.js +183 -11
  350. package/build/utils/display/scene/process.js.map +1 -1
  351. package/build/utils/display/scene/store.d.ts +1 -1
  352. package/build/utils/display/scene/store.d.ts.map +1 -1
  353. package/build/utils/display/scene/store.js +0 -0
  354. package/build/utils/display/scene/store.js.map +1 -1
  355. package/build/utils/display/scene/svgPath.d.ts +9 -0
  356. package/build/utils/display/scene/svgPath.d.ts.map +1 -0
  357. package/build/utils/display/scene/svgPath.js +167 -0
  358. package/build/utils/display/scene/svgPath.js.map +1 -0
  359. package/build/utils/display/scene/types.d.ts +2 -142
  360. package/build/utils/display/scene/types.d.ts.map +1 -1
  361. package/build/utils/display/scene/types.js +0 -0
  362. package/build/utils/display/scene/types.js.map +1 -1
  363. package/build/utils/display/wrapper/TextWrapper.d.ts +1 -101
  364. package/build/utils/display/wrapper/TextWrapper.d.ts.map +1 -1
  365. package/build/utils/display/wrapper/TextWrapper.js +2 -470
  366. package/build/utils/display/wrapper/TextWrapper.js.map +1 -1
  367. package/build/utils/display/wrapper/types.d.ts +2 -74
  368. package/build/utils/display/wrapper/types.d.ts.map +1 -1
  369. package/build/utils/display/wrapper/types.js +2 -10
  370. package/build/utils/display/wrapper/types.js.map +1 -1
  371. package/build/utils/hardware/hardware.js +1 -1
  372. package/build/utils/hardware/hardware.js.map +1 -1
  373. package/build/utils/timers/timers.d.ts.map +1 -1
  374. package/build/utils/timers/timers.js +26 -4
  375. package/build/utils/timers/timers.js.map +1 -1
  376. package/package.json +8 -8
  377. package/src/engine.ts +24 -15
  378. package/src/facades/dev.ts +10 -0
  379. package/src/facades/display.ts +21 -8
  380. package/src/facades/displayMirror.ts +29 -0
  381. package/src/facades/glasses.ts +61 -1
  382. package/src/facades/memory.ts +1 -12
  383. package/src/facades/miniapps.ts +4 -1
  384. package/src/facades/pairing.ts +8 -6
  385. package/src/facades/permissions.ts +46 -0
  386. package/src/facades/phoneNotifications.ts +45 -26
  387. package/src/facades/settings.ts +1 -2
  388. package/src/generated/releaseMetadata.ts +4 -4
  389. package/src/index.ts +6 -9
  390. package/src/react/MentraLiveOtaFlow.tsx +2 -2
  391. package/src/react/useMentraLiveOta.ts +14 -8
  392. package/src/runtime/config.ts +1 -1
  393. package/src/services/AppRegistry.ts +43 -7
  394. package/src/services/BlobStore.ts +3 -3
  395. package/src/services/CloudClientService.ts +37 -10
  396. package/src/services/DevServerBridge.ts +12 -10
  397. package/src/services/DeviceEventRouter.ts +73 -74
  398. package/src/services/DisplayDiagnostics.ts +107 -0
  399. package/src/services/DisplayProcessor.ts +20 -55
  400. package/src/services/GestureDotsService.ts +77 -16
  401. package/src/services/GlassesKeyboardInput.ts +306 -0
  402. package/src/services/GlassesSensorService.ts +233 -0
  403. package/src/services/GlassesTouchInput.ts +128 -0
  404. package/src/services/HeadingService.ts +0 -24
  405. package/src/services/HotspotShutdown.ts +3 -1
  406. package/src/services/JSCrashloopReportService.ts +2 -2
  407. package/src/services/JSRouter.ts +29 -14
  408. package/src/services/LocalDisplayManager.ts +112 -26
  409. package/src/services/LocalMiniappDataMigration.ts +1 -1
  410. package/src/services/LocalMiniappRuntime.ts +500 -43
  411. package/src/services/LocalSttFallbackCoordinator.ts +28 -7
  412. package/src/services/MemoryStore.ts +32 -80
  413. package/src/services/MiniappEngine.ts +7 -7
  414. package/src/services/MiniappLauncher.ts +25 -1
  415. package/src/services/MirrorService.ts +139 -0
  416. package/src/services/NativeNavigationService.ts +29 -25
  417. package/src/services/NavigationHandlers.ts +10 -5
  418. package/src/services/OtaInstallCoordinator.ts +36 -34
  419. package/src/services/PairedGlassesLatch.ts +39 -0
  420. package/src/services/PhoneActivity.ts +116 -0
  421. package/src/services/PhoneCameraFovCoordinator.ts +6 -4
  422. package/src/services/PhoneNotificationsSync.ts +185 -103
  423. package/src/services/PhonePhotoCoordinator.ts +9 -10
  424. package/src/services/PhoneStreamCoordinator.ts +13 -11
  425. package/src/services/STTModelManager.ts +15 -1
  426. package/src/services/SceneRenderer.ts +148 -10
  427. package/src/services/StreamLifecycleController.ts +9 -7
  428. package/src/services/TTSModelManager.ts +1 -1
  429. package/src/services/UIRouter.ts +5 -5
  430. package/src/services/asg/galleryPathMigration.ts +1 -1
  431. package/src/services/asg/gallerySyncService.ts +2 -2
  432. package/src/services/asg/localStorageService.ts +3 -3
  433. package/src/services/debug/DashboardOpenTrace.ts +153 -0
  434. package/src/services/debug/dashboardOpenTimeline.ts +237 -0
  435. package/src/services/effectiveCapabilities.ts +32 -13
  436. package/src/services/gestureClaims.ts +72 -23
  437. package/src/services/manifestFields.ts +23 -0
  438. package/src/services/memoryStoreInstance.ts +2 -34
  439. package/src/services/mirrorSnapshot.ts +116 -0
  440. package/src/services/nativeNavigationGate.ts +14 -0
  441. package/src/services/notificationAppCatalog.ts +21 -0
  442. package/src/services/notifications/notificationPolicy.ts +87 -0
  443. package/src/services/pages/OverlayPages.ts +86 -0
  444. package/src/services/pages/PageHost.ts +116 -0
  445. package/src/services/pages/overlayApps.ts +72 -0
  446. package/src/services/pages/pageEligibility.ts +30 -0
  447. package/src/services/settingsBridge.ts +111 -0
  448. package/src/services/speakerProfileStoreInstance.ts +1 -1
  449. package/src/services/uiShim.ts +4 -4
  450. package/src/stores/appVisibility.ts +45 -0
  451. package/src/stores/apps.ts +110 -32
  452. package/src/stores/bluetoothSettingKeys.ts +1 -1
  453. package/src/stores/display.ts +18 -7
  454. package/src/stores/glasses.ts +15 -1
  455. package/src/stores/settings.ts +175 -51
  456. package/src/types/applet.ts +14 -1
  457. package/src/types/capabilities/even-realities-g2.ts +6 -0
  458. package/src/types/capabilities/simulated-glasses.ts +8 -4
  459. package/src/types/hardware.ts +34 -1
  460. package/src/types/index.ts +1 -0
  461. package/src/utils/debouncedPatch.ts +8 -4
  462. package/src/utils/devMiniappLaunch.ts +5 -3
  463. package/src/utils/display/measurer/TextMeasurer.ts +3 -346
  464. package/src/utils/display/measurer/script-detection.ts +10 -218
  465. package/src/utils/display/pixelArt.ts +3 -0
  466. package/src/utils/display/profiles/g1.ts +2 -236
  467. package/src/utils/display/profiles/g2.ts +2 -39
  468. package/src/utils/display/profiles/nex.ts +2 -166
  469. package/src/utils/display/profiles/types.ts +9 -132
  470. package/src/utils/display/profiles/z100.ts +2 -198
  471. package/src/utils/display/scene/degrade.ts +4 -1
  472. package/src/utils/display/scene/differ.ts +25 -1
  473. package/src/utils/display/scene/index.ts +5 -0
  474. package/src/utils/display/scene/process.ts +196 -13
  475. package/src/utils/display/scene/store.ts +0 -0
  476. package/src/utils/display/scene/svgPath.ts +163 -0
  477. package/src/utils/display/scene/types.ts +0 -0
  478. package/src/utils/display/wrapper/TextWrapper.ts +2 -546
  479. package/src/utils/display/wrapper/types.ts +3 -102
  480. package/src/utils/hardware/hardware.ts +1 -1
  481. package/src/utils/timers/timers.ts +32 -4
  482. package/build/services/CustomDashboardModel.d.ts +0 -48
  483. package/build/services/CustomDashboardModel.d.ts.map +0 -1
  484. package/build/services/CustomDashboardModel.js +0 -73
  485. package/build/services/CustomDashboardModel.js.map +0 -1
  486. package/build/services/CustomDashboardService.d.ts +0 -63
  487. package/build/services/CustomDashboardService.d.ts.map +0 -1
  488. package/build/services/CustomDashboardService.js +0 -576
  489. package/build/services/CustomDashboardService.js.map +0 -1
  490. package/build/services/DashboardCoordinator.d.ts +0 -26
  491. package/build/services/DashboardCoordinator.d.ts.map +0 -1
  492. package/build/services/DashboardCoordinator.js +0 -125
  493. package/build/services/DashboardCoordinator.js.map +0 -1
  494. package/build/services/iosNotificationAppCatalog.d.ts +0 -13
  495. package/build/services/iosNotificationAppCatalog.d.ts.map +0 -1
  496. package/build/services/iosNotificationAppCatalog.js +0 -28
  497. package/build/services/iosNotificationAppCatalog.js.map +0 -1
  498. package/src/services/CustomDashboardModel.ts +0 -83
  499. package/src/services/CustomDashboardService.ts +0 -598
  500. package/src/services/DashboardCoordinator.ts +0 -121
  501. package/src/services/iosNotificationAppCatalog.ts +0 -39
@@ -8,7 +8,10 @@
8
8
  * This runtime handles request dispatch, stream fan-out, and lifecycle
9
9
  * management (connect/disconnect/ping).
10
10
  */
11
+ import { mirrorService } from "./MirrorService";
11
12
  import { AppState, Linking } from "react-native";
13
+ import { logBuffer } from "../utils/devLogging";
14
+ import { collectDisplayDiagnostics } from "./DisplayDiagnostics";
12
15
  import Share from "react-native-share";
13
16
  import * as Battery from "expo-battery";
14
17
  import * as Clipboard from "expo-clipboard";
@@ -33,9 +36,17 @@ import { createAnthropicProvider, createGeminiProvider, createOpenAiProvider, }
33
36
  import { normalizeAiTier, resolveAiTier } from "./AiTierResolution";
34
37
  import { HttpError } from "@glassly/cloud-client";
35
38
  import localDisplayManager from "./LocalDisplayManager";
36
- import { claimsGesture } from "./gestureClaims";
39
+ import { dropGestureClaims, emptyGestureClaims, gestureClaimant, hasGestureClaim, setGestureClaims, } from "./gestureClaims";
40
+ import overlayPages from "./pages/OverlayPages";
41
+ import dashboardOpenTrace from "./debug/DashboardOpenTrace";
42
+ import { isAppForegrounded, isPhoneActive, onAppForegroundChange, onPhoneActivityChange } from "./PhoneActivity";
43
+ import { installedNotificationApps } from "./PhoneNotificationsSync";
44
+ import { DERIVED_SETTINGS_KEYS, SETTINGS_BRIDGE_KEYS, isDerivedSettingsKey, ringBatteryFrom, selectSettingsBridgeKeys, validateSettingsSet, } from "./settingsBridge";
45
+ import { addOverlayApp, displayViewsFor, isOverlayApp, removeOverlayApp, setOverlayViewer } from "./pages/overlayApps";
46
+ import { pagesEligible } from "./pages/pageEligibility";
37
47
  import nativeNavigationService from "./NativeNavigationService";
38
48
  import headingService from "./HeadingService";
49
+ import glassesSensorService from "./GlassesSensorService";
39
50
  import localSttFallbackCoordinator from "./LocalSttFallbackCoordinator";
40
51
  import speakerLabelResolver from "./SpeakerLabelResolver";
41
52
  import micStateCoordinator from "./MicStateCoordinator";
@@ -117,9 +128,12 @@ const SYSTEM_MINIAPP_PACKAGES = new Set([
117
128
  "com.glassly.simulated",
118
129
  "com.glassly.mirror",
119
130
  "com.glassly.ai",
120
- "cloud.augmentos.notify",
121
131
  "com.glassly.feedback",
122
132
  "com.glassly.miniappdev",
133
+ // The dashboard: the settings bridge and the miniapp list/start/stop are its side menu and settings page.
134
+ "com.glassly.dashboard",
135
+ // The notifications viewer: the settings bridge and the notification app list are its settings page.
136
+ "com.glassly.notifications",
123
137
  ]);
124
138
  const PING_INTERVAL_MS = 5_000;
125
139
  // Min gap between "transcription limit reached" notices on the glasses. A
@@ -194,6 +208,7 @@ const PERMISSION_TYPE_TO_CANONICAL = {
194
208
  LOCATION: "location",
195
209
  BACKGROUND_LOCATION: "location",
196
210
  READ_NOTIFICATIONS: "notifications",
211
+ DISPLAY_MIRROR: "displayMirror",
197
212
  POST_NOTIFICATIONS: "notifications",
198
213
  CALENDAR: "calendar",
199
214
  API_KEYS: "apiKeys",
@@ -298,6 +313,7 @@ const ALL_CANONICAL_PERMISSIONS = [
298
313
  "memory",
299
314
  "ai",
300
315
  "speakers",
316
+ "displayMirror",
301
317
  ];
302
318
  /**
303
319
  * Build the {location, microphone, camera, notifications, calendar, apiKeys}
@@ -400,8 +416,8 @@ class LocalMiniappRuntime {
400
416
  llmQuotaBlocked = false;
401
417
  /** Connected miniapps keyed by packageName. */
402
418
  connectedApps = new Map();
403
- /** Per-app claimed gestures (CLAIM_GESTURES) — see foregroundClaimsGesture. */
404
- gestureClaims = new Map();
419
+ /** Claimed gestures (CLAIM_GESTURES): a stack per gesture, the most recent claimer holding it. */
420
+ gestureClaims = emptyGestureClaims();
405
421
  /**
406
422
  * Packages that have completed their CONNECT handshake (sent
407
423
  * `miniapp_connect`, got `CONNECT_ACK`). Distinct from `connectedApps`,
@@ -419,6 +435,10 @@ class LocalMiniappRuntime {
419
435
  cloudResultsWired = false;
420
436
  cloudStatusWired = false;
421
437
  capabilitiesWired = false;
438
+ preferencesWired = false;
439
+ settingsBridgeWired = false;
440
+ /** Last derived read-only values pushed on the settings stream, for change detection. */
441
+ derivedSettings = new Map();
422
442
  cloudAudioSubscriptionSync = new CloudAudioSubscriptionSync();
423
443
  /**
424
444
  * Per-miniapp language hints from TRANSCRIPTION_CONFIG (issue 021 WP3).
@@ -529,6 +549,23 @@ class LocalMiniappRuntime {
529
549
  }
530
550
  }
531
551
  }
552
+ if (this.streamSubscribers.has(MiniappStreamType.MIRROR_UPDATE)) {
553
+ if (!this.mirrorUnsubscribe)
554
+ this.mirrorUnsubscribe = mirrorService.subscribe((snapshot) => {
555
+ for (const pkg of this.streamSubscribers.get(MiniappStreamType.MIRROR_UPDATE) ?? []) {
556
+ if (this.hasMirrorPermission(pkg))
557
+ this.sendToMiniapp(pkg, {
558
+ type: MiniappResponseType.EVENT,
559
+ streamType: MiniappStreamType.MIRROR_UPDATE,
560
+ data: snapshot,
561
+ });
562
+ }
563
+ });
564
+ }
565
+ else {
566
+ this.mirrorUnsubscribe?.();
567
+ this.mirrorUnsubscribe = null;
568
+ }
532
569
  this.publishTranscriptionDebugSubscribers();
533
570
  }
534
571
  /** Mirror the live transcription/translation subscriber sets into the debug store. */
@@ -639,6 +676,11 @@ class LocalMiniappRuntime {
639
676
  }
640
677
  return LocalMiniappRuntime.instance;
641
678
  }
679
+ mirrorUnsubscribe = null;
680
+ hasMirrorPermission(packageName) {
681
+ return (this.connectedApps.get(packageName)?.installedManifest?.permissions?.some((p) => p.type === "DISPLAY_MIRROR") ===
682
+ true);
683
+ }
642
684
  initialized = false;
643
685
  /**
644
686
  * Initialize the runtime. Called from MantleManager.init().
@@ -650,6 +692,16 @@ class LocalMiniappRuntime {
650
692
  this.initialized = true;
651
693
  console.log(`${LOG_TAG}: initialize()`);
652
694
  this.ensurePingLoop();
695
+ mirrorService.initialize();
696
+ // The overlay apps' pages relay through the runtime: the slot going to
697
+ // and from an app arrives as its display_view events.
698
+ overlayPages.setSink({
699
+ send: (packageName, event) => this.sendToMiniapp(packageName, {
700
+ type: MiniappResponseType.EVENT,
701
+ streamType: MiniappStreamType.DISPLAY_VIEW,
702
+ data: event,
703
+ }),
704
+ });
653
705
  }
654
706
  /**
655
707
  * Handle an incoming cloud message relayed from the v2 cloud session
@@ -729,7 +781,7 @@ class LocalMiniappRuntime {
729
781
  // ===========================================================================
730
782
  // App registration
731
783
  // ===========================================================================
732
- registerApp(packageName, sendFn, installedManifest) {
784
+ registerApp(packageName, sendFn, installedManifest, launchSource) {
733
785
  console.log(`${LOG_TAG}: registerApp(${packageName})`);
734
786
  this.clearForegroundProbe(packageName);
735
787
  // Fresh spawn → fresh handshake. Clear any prior handshake flag so
@@ -740,6 +792,9 @@ class LocalMiniappRuntime {
740
792
  // dangling references. The WebView will re-subscribe after its CONNECT.
741
793
  if (this.connectedApps.has(packageName)) {
742
794
  const existing = this.connectedApps.get(packageName);
795
+ void glassesSensorService.release(packageName).catch((error) => {
796
+ console.warn(`${LOG_TAG}: sensor cleanup failed on respawn for ${packageName}`, error);
797
+ });
743
798
  this.cancelSpeech(packageName);
744
799
  // A crash respawn replaces the ConnectedMiniapp without going through
745
800
  // unregisterApp(). Abort the old incarnation's native PCM player before
@@ -778,6 +833,7 @@ class LocalMiniappRuntime {
778
833
  authDelivered: false,
779
834
  speakerState: "idle",
780
835
  requestedLocationRate: null,
836
+ launchSource,
781
837
  });
782
838
  // If we just clobbered an existing entry, its requestedLocationRate is
783
839
  // now gone — recompute so the aggregate doesn't include a stale rate.
@@ -785,6 +841,8 @@ class LocalMiniappRuntime {
785
841
  this.recomputeLocationTier();
786
842
  this.ensureCloudStatusWired();
787
843
  this.ensureCapabilitiesWired();
844
+ this.ensurePreferencesWired();
845
+ this.ensureSettingsBridgeWired();
788
846
  this.ensurePingLoop();
789
847
  // Show the system boot message ("Starting <name>…") on the glasses for the
790
848
  // bounded boot window, mirroring the cloud DisplayManager boot screen. The
@@ -810,14 +868,14 @@ class LocalMiniappRuntime {
810
868
  if (settled)
811
869
  return;
812
870
  settled = true;
813
- clearTimeout(timer);
871
+ BgTimer.clearTimeout(timer);
814
872
  this.connectWaiters.get(packageName)?.delete(waiter);
815
873
  if (err)
816
874
  reject(err);
817
875
  else
818
876
  resolve();
819
877
  };
820
- const timer = setTimeout(() => {
878
+ const timer = BgTimer.setTimeout(() => {
821
879
  waiter(new Error(`waitForConnect: ${packageName} did not connect within ${timeoutMs}ms`));
822
880
  }, timeoutMs);
823
881
  let set = this.connectWaiters.get(packageName);
@@ -887,7 +945,7 @@ class LocalMiniappRuntime {
887
945
  // 50 ms: imperceptible to the user, plenty of time for the SDK's
888
946
  // synchronous `beforeDisconnect` handlers to flush a final
889
947
  // sendOneShot through the still-open transport.
890
- await new Promise((resolve) => setTimeout(resolve, 50));
948
+ await new Promise((resolve) => BgTimer.setTimeout(resolve, 50));
891
949
  this.unregisterApp(packageName);
892
950
  }
893
951
  unregisterApp(packageName, opts) {
@@ -904,6 +962,14 @@ class LocalMiniappRuntime {
904
962
  // Drop this app's transcription hints; the cloud subscription rebuild
905
963
  // below (via replaceStreamSubscribers) re-derives the hint union without it.
906
964
  this.transcriptionHintsByApp.delete(packageName);
965
+ // An overlay app is going: release the slot if it holds it, and drop the
966
+ // dashboard view from its capabilities record.
967
+ if (isOverlayApp(packageName)) {
968
+ overlayPages.remove(packageName);
969
+ removeOverlayApp(packageName);
970
+ }
971
+ // Its gesture claims go back to whoever claimed them before.
972
+ this.applyGestureClaimChanges(dropGestureClaims(this.gestureClaims, packageName));
907
973
  // Warm-up is a request-owned camera lease. Cancel it even if the app record was already
908
974
  // removed so a close racing the camera-open promise cannot leave the sensor running.
909
975
  // Keep camera teardown ordered: restoring FOV can restart the HAL, so the warm-up lease must
@@ -962,11 +1028,15 @@ class LocalMiniappRuntime {
962
1028
  else {
963
1029
  this.navigationHandlers.onDisconnect(packageName);
964
1030
  }
1031
+ void glassesSensorService.release(packageName).catch((error) => {
1032
+ console.warn(`${LOG_TAG}: sensor cleanup failed for ${packageName}`, error);
1033
+ });
965
1034
  // Recompute heading subscription — if this app was the last subscriber,
966
1035
  // the sensor will stop.
967
1036
  this.recomputeHeadingSubscription();
968
- // Same for the IMU/accelerometer stream.
1037
+ // Same for the IMU/accelerometer and glasses compass streams.
969
1038
  this.recomputeImuSubscription();
1039
+ this.recomputeCompassSubscription();
970
1040
  // Drop this app's location-tier request before recomputing so the
971
1041
  // aggregate falls back down if it was the strictest. Done before
972
1042
  // `connectedApps.delete` below so it doesn't matter that the iter
@@ -984,7 +1054,6 @@ class LocalMiniappRuntime {
984
1054
  // current on-glasses frame, this clears the glasses (or restores the
985
1055
  // core app's saved frame).
986
1056
  localDisplayManager.onUnmount(packageName);
987
- this.gestureClaims.delete(packageName);
988
1057
  this.connectedApps.delete(packageName);
989
1058
  this.recomputeMicRequirements();
990
1059
  if (releasedMicGateOverride) {
@@ -1061,9 +1130,32 @@ class LocalMiniappRuntime {
1061
1130
  case MiniappRequestType.DISPLAY:
1062
1131
  this.handleDisplay(packageName, payload, requestId);
1063
1132
  break;
1133
+ case MiniappRequestType.MIRROR_GET:
1134
+ if (!this.hasMirrorPermission(packageName)) {
1135
+ this.sendResult(packageName, requestId, false, undefined, {
1136
+ code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
1137
+ message: "DISPLAY_MIRROR permission not declared in miniapp.json",
1138
+ permission: "DISPLAY_MIRROR",
1139
+ });
1140
+ }
1141
+ else
1142
+ this.sendResult(packageName, requestId, true, mirrorService.current());
1143
+ break;
1064
1144
  case MiniappRequestType.RENDER:
1065
1145
  this.handleRender(packageName, payload, requestId);
1066
1146
  break;
1147
+ case MiniappRequestType.SHOW_VIEW:
1148
+ this.handleShowView(packageName, payload, requestId);
1149
+ break;
1150
+ case MiniappRequestType.SETTINGS_GET:
1151
+ this.handleSettingsGet(packageName, payload, requestId);
1152
+ break;
1153
+ case MiniappRequestType.SETTINGS_SET:
1154
+ void this.handleSettingsSet(packageName, payload, requestId);
1155
+ break;
1156
+ case MiniappRequestType.NOTIFICATION_APPS_LIST:
1157
+ void this.handleNotificationAppsList(packageName, requestId);
1158
+ break;
1067
1159
  case MiniappRequestType.NATIVE_NAV:
1068
1160
  this.handleNativeNav(packageName, payload, requestId);
1069
1161
  break;
@@ -1195,6 +1287,19 @@ class LocalMiniappRuntime {
1195
1287
  case MiniappRequestType.SET_WIFI_ADB_STATE:
1196
1288
  void this.handleSetWifiAdbState(packageName, payload, requestId);
1197
1289
  break;
1290
+ case MiniappRequestType.GLASSES_SENSOR_CONTROL:
1291
+ if (!this.connectedApps.has(packageName)) {
1292
+ this.sendResult(packageName, requestId, false, undefined, {
1293
+ code: MiniappErrorCode.NOT_CONNECTED,
1294
+ message: "Miniapp disconnected",
1295
+ });
1296
+ break;
1297
+ }
1298
+ void glassesSensorService.request(packageName, payload).then(() => this.sendResult(packageName, requestId, true), (error) => this.sendResult(packageName, requestId, false, undefined, {
1299
+ code: error.code || MiniappErrorCode.INTERNAL,
1300
+ message: error instanceof Error ? error.message : "Sensor control failed",
1301
+ }));
1302
+ break;
1198
1303
  case MiniappRequestType.IMU_SET_ENABLED:
1199
1304
  this.handleImuSetEnabled(packageName, payload, requestId);
1200
1305
  break;
@@ -1219,6 +1324,15 @@ class LocalMiniappRuntime {
1219
1324
  case MiniappRequestType.OPEN_URL:
1220
1325
  this.handleOpenUrl(packageName, payload);
1221
1326
  break;
1327
+ case MiniappRequestType.DISPLAY_DIAGNOSTICS: {
1328
+ const enabled = useSettingsStore.getState().getSetting(SETTINGS.debug_console.key) === true;
1329
+ this.sendResult(packageName, requestId, true, {
1330
+ enabled,
1331
+ generatedAt: Date.now(),
1332
+ records: enabled ? collectDisplayDiagnostics(logBuffer.getRecentLogs()) : [],
1333
+ });
1334
+ break;
1335
+ }
1222
1336
  case MiniappRequestType.COPY_CLIPBOARD:
1223
1337
  this.handleCopyClipboard(packageName, payload, requestId);
1224
1338
  break;
@@ -1365,7 +1479,11 @@ class LocalMiniappRuntime {
1365
1479
  // Update lastPongAt so it doesn't time out right away
1366
1480
  existing.lastPongAt = Date.now();
1367
1481
  existing.unansweredPingRounds = 0;
1368
- const capabilities = this.currentCapabilities();
1482
+ // A system_dashboard app may take the overlay slot: its capabilities
1483
+ // carry the dashboard view from the start.
1484
+ if (existing.installedManifest?.type === "system_dashboard")
1485
+ addOverlayApp(packageName);
1486
+ const capabilities = this.capabilitiesFor(packageName);
1369
1487
  // Build the declared-permission record for the SDK's session.permissions
1370
1488
  // module. Lower-cased to match v3's PermissionType union (microphone,
1371
1489
  // camera, location, notifications, calendar). Missing permissions default
@@ -1379,7 +1497,7 @@ class LocalMiniappRuntime {
1379
1497
  this.clearMiniappAuthDeliveryRetry(packageName);
1380
1498
  const authPromise = this.requestMiniappAuth(packageName);
1381
1499
  const initialAuth = await withTimeout(authPromise, 1_500);
1382
- const userId = initialAuth?.glasslyUserId ?? "";
1500
+ const userId = initialAuth?.userId ?? "";
1383
1501
  if (initialAuth) {
1384
1502
  existing.authDelivered = true;
1385
1503
  this.scheduleMiniappAuthRefresh(packageName, initialAuth);
@@ -1389,7 +1507,10 @@ class LocalMiniappRuntime {
1389
1507
  userId,
1390
1508
  packageName,
1391
1509
  capabilities,
1510
+ preferences: this.currentPreferences(),
1511
+ visibility: this.currentVisibility(),
1392
1512
  permissions: declaredPermissions,
1513
+ ...(existing.launchSource ? { launchSource: existing.launchSource } : {}),
1393
1514
  ...(initialAuth ? { auth: initialAuth } : {}),
1394
1515
  }, requestId);
1395
1516
  if (!initialAuth) {
@@ -1606,11 +1727,10 @@ class LocalMiniappRuntime {
1606
1727
  }
1607
1728
  /**
1608
1729
  * CLAIM_GESTURES (session.input.setClaimedGestures): the app declares which
1609
- * touch gestures it handles itself, replacing its previous claim set.
1610
- * Claims suppress the host's default behavior for those gestures (today:
1611
- * double_tap → dashboard/head-up in DeviceEventRouter) but ONLY while the
1612
- * claiming app is the one in front of the user — see
1613
- * {@link foregroundClaimsGesture}. Cleared on app stop.
1730
+ * touch gestures it handles itself, replacing its previous claim set. The
1731
+ * most recent claimer of a gesture holds it and gets it alone; releasing
1732
+ * hands it back to the previous claimer, and both hear the change on the
1733
+ * gesture_claim stream. Cleared on app stop.
1614
1734
  */
1615
1735
  handleClaimGestures(packageName, payload, requestId) {
1616
1736
  if (!this.connectedApps.has(packageName)) {
@@ -1619,15 +1739,29 @@ class LocalMiniappRuntime {
1619
1739
  }
1620
1740
  const raw = payload.gestures;
1621
1741
  const gestures = Array.isArray(raw) ? raw.filter((g) => typeof g === "string") : [];
1622
- if (gestures.length === 0)
1623
- this.gestureClaims.delete(packageName);
1624
- else
1625
- this.gestureClaims.set(packageName, new Set(gestures));
1742
+ if (payload.scope !== undefined && (typeof payload.scope !== "string" || payload.scope.length === 0)) {
1743
+ this.sendResult(packageName, requestId, false, undefined, {
1744
+ code: MiniappErrorCode.INVALID_ARGUMENT,
1745
+ message: "Gesture claim scope must be a nonempty string",
1746
+ });
1747
+ return;
1748
+ }
1749
+ this.applyGestureClaimChanges(setGestureClaims(this.gestureClaims, packageName, gestures, payload.scope));
1626
1750
  this.sendResult(packageName, requestId, true);
1627
1751
  }
1628
- /** True iff the miniapp in front of the user has claimed `gesture`. */
1629
- foregroundClaimsGesture(gesture) {
1630
- return claimsGesture(this.gestureClaims, gesture, localDisplayManager.getGestureOwner(), useAppStatusStore.getState().foregroundedPackage);
1752
+ applyGestureClaimChanges(result) {
1753
+ this.gestureClaims = result.state;
1754
+ for (const change of result.changes) {
1755
+ this.forwardEvent(MiniappStreamType.GESTURE_CLAIM, { gesture: change.gesture, held: change.held }, undefined, change.packageName);
1756
+ }
1757
+ }
1758
+ /** The app holding `gesture` (the most recent claimer), or null when it is unclaimed. */
1759
+ gestureClaimant(gesture) {
1760
+ return gestureClaimant(this.gestureClaims, gesture);
1761
+ }
1762
+ /** Has this app registered the gesture, even if a later claimant currently receives it? */
1763
+ hasGestureClaim(packageName, gesture) {
1764
+ return hasGestureClaim(this.gestureClaims, packageName, gesture);
1631
1765
  }
1632
1766
  handleSubscribe(packageName, payload, requestId) {
1633
1767
  const app = this.connectedApps.get(packageName);
@@ -1674,11 +1808,13 @@ class LocalMiniappRuntime {
1674
1808
  // the whole subscribe with PERMISSION_NOT_DECLARED.
1675
1809
  const declaredTypes = new Set((app.installedManifest?.permissions ?? []).map((p) => p.type?.toUpperCase()));
1676
1810
  const permissionForStream = (s) => {
1811
+ if (s === MiniappStreamType.MIRROR_UPDATE)
1812
+ return "DISPLAY_MIRROR";
1677
1813
  if (s === "audio_chunk" || s === "vad")
1678
1814
  return "MICROPHONE";
1679
1815
  if (s.startsWith("transcription") || s.startsWith("translation"))
1680
1816
  return "MICROPHONE";
1681
- if (s === "location_update")
1817
+ if (s === "location_update" || s === "heading_update")
1682
1818
  return "LOCATION";
1683
1819
  if (s === "phone_notification")
1684
1820
  return "READ_NOTIFICATIONS";
@@ -1701,16 +1837,29 @@ class LocalMiniappRuntime {
1701
1837
  return;
1702
1838
  }
1703
1839
  }
1840
+ // The settings stream carries the wearer's settings: system apps only.
1841
+ if (streams.includes(MiniappStreamType.SETTINGS_CHANGED) && !this.isSystemPackage(packageName)) {
1842
+ this.sendResult(packageName, requestId, false, undefined, {
1843
+ code: MiniappErrorCode.NOT_PERMITTED,
1844
+ message: "The settings stream is restricted to system apps",
1845
+ });
1846
+ return;
1847
+ }
1704
1848
  // Add new subscriptions
1705
1849
  const previousSubscriptions = app.subscriptions;
1706
1850
  app.subscriptions = new Set(streams);
1707
1851
  app.forceLocalTranscriptionStreams = new Set([...forceLocalTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)));
1708
1852
  app.cloudTranscriptionStreams = new Set([...cloudTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)));
1709
1853
  this.replaceStreamSubscribers(packageName, previousSubscriptions, app.subscriptions);
1854
+ // An overlay app that listens to phone notifications presents them on the glasses.
1855
+ if (isOverlayApp(packageName)) {
1856
+ setOverlayViewer(packageName, app.subscriptions.has(MiniappStreamType.PHONE_NOTIFICATION));
1857
+ }
1710
1858
  this.recomputeMicRequirements();
1711
1859
  this.updateCloudSubscriptions();
1712
1860
  this.recomputeHeadingSubscription();
1713
1861
  this.recomputeImuSubscription();
1862
+ this.recomputeCompassSubscription();
1714
1863
  // Persist this app's requested rate (or clear it if SUBSCRIBE didn't
1715
1864
  // include `location_stream` this time), then ask the host for the
1716
1865
  // strictest rate across all connected apps.
@@ -1761,7 +1910,14 @@ class LocalMiniappRuntime {
1761
1910
  };
1762
1911
  const isSimulated = (glassesState.deviceModel || "").toLowerCase().includes("simulated");
1763
1912
  for (const stream of streams) {
1764
- if (stream === "glasses_battery") {
1913
+ if (stream === MiniappStreamType.MIRROR_UPDATE && this.hasMirrorPermission(packageName)) {
1914
+ this.sendToMiniapp(packageName, {
1915
+ type: MiniappResponseType.EVENT,
1916
+ streamType: MiniappStreamType.MIRROR_UPDATE,
1917
+ data: mirrorService.current(),
1918
+ });
1919
+ }
1920
+ else if (stream === "glasses_battery") {
1765
1921
  // Simulated glasses have no real battery; mirror the phone's battery
1766
1922
  // so miniapps see a sensible value during development.
1767
1923
  if (isSimulated) {
@@ -1812,6 +1968,16 @@ class LocalMiniappRuntime {
1812
1968
  },
1813
1969
  });
1814
1970
  }
1971
+ else if (stream === MiniappStreamType.SETTINGS_CHANGED) {
1972
+ // Replay every bridged key so a mirror can be built from the stream alone.
1973
+ for (const key of selectSettingsBridgeKeys(undefined)) {
1974
+ this.sendToMiniapp(packageName, {
1975
+ type: MiniappResponseType.EVENT,
1976
+ streamType: MiniappStreamType.SETTINGS_CHANGED,
1977
+ data: { key, value: this.settingsBridgeValue(key) },
1978
+ });
1979
+ }
1980
+ }
1815
1981
  else if (stream === "head_position") {
1816
1982
  // The glasses store has no headUp field (same on the pre-engine host
1817
1983
  // hook), so this stays a no-emit until head state lands in the store.
@@ -1908,6 +2074,16 @@ class LocalMiniappRuntime {
1908
2074
  });
1909
2075
  return;
1910
2076
  }
2077
+ // The dashboard view is the overlay apps' alone; anyone else's frame
2078
+ // is dropped, reported rather than silently lost.
2079
+ if (payload.view === "dashboard" && !isOverlayApp(packageName)) {
2080
+ this.sendResult(packageName, requestId, true, {
2081
+ status: "displayed",
2082
+ presented: false,
2083
+ reason: "not the dashboard app",
2084
+ });
2085
+ return;
2086
+ }
1911
2087
  localDisplayManager.request(packageName, {
1912
2088
  view: payload.view ?? "main",
1913
2089
  scene: payload.elements,
@@ -1922,6 +2098,160 @@ class LocalMiniappRuntime {
1922
2098
  });
1923
2099
  }
1924
2100
  }
2101
+ /**
2102
+ * display.showView: the designated dashboard app takes or releases the
2103
+ * overlay slot. Refused (NOT_PERMITTED) for any other app; "unsupported"
2104
+ * while the glasses cannot show the dashboard view. Idempotent either way;
2105
+ * the slot's state comes back to the app as a display_view change.
2106
+ */
2107
+ handleShowView(packageName, payload, requestId) {
2108
+ if (!this.connectedApps.has(packageName)) {
2109
+ this.sendResult(packageName, requestId, true, { status: "unsupported", reason: "app stopped" });
2110
+ return;
2111
+ }
2112
+ const view = payload.view;
2113
+ if (view !== "main" && view !== "dashboard") {
2114
+ this.sendResult(packageName, requestId, false, undefined, {
2115
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2116
+ message: 'showView takes "main" or "dashboard"',
2117
+ });
2118
+ return;
2119
+ }
2120
+ if (!isOverlayApp(packageName)) {
2121
+ this.sendResult(packageName, requestId, false, undefined, {
2122
+ code: MiniappErrorCode.NOT_PERMITTED,
2123
+ message: "The dashboard view belongs to system_dashboard apps",
2124
+ });
2125
+ return;
2126
+ }
2127
+ if (view === "main") {
2128
+ overlayPages.hide(packageName);
2129
+ this.sendResult(packageName, requestId, true, { status: "shown" });
2130
+ return;
2131
+ }
2132
+ if (!pagesEligible()) {
2133
+ this.sendResult(packageName, requestId, true, {
2134
+ status: "unsupported",
2135
+ reason: "glasses cannot show the dashboard",
2136
+ });
2137
+ return;
2138
+ }
2139
+ dashboardOpenTrace.showViewRequested(packageName);
2140
+ const shown = overlayPages.show(packageName);
2141
+ if (shown)
2142
+ dashboardOpenTrace.slotGranted();
2143
+ this.sendResult(packageName, requestId, true, shown ? { status: "shown" } : { status: "unsupported", reason: "the overlay slot was refused" });
2144
+ }
2145
+ // ---------------------------------------------------------------------------
2146
+ // Settings bridge (session.settings) — system apps only
2147
+ // ---------------------------------------------------------------------------
2148
+ requireSystemForSettings(packageName, requestId) {
2149
+ if (this.isSystemPackage(packageName))
2150
+ return true;
2151
+ this.sendResult(packageName, requestId, false, undefined, {
2152
+ code: MiniappErrorCode.NOT_PERMITTED,
2153
+ message: "The settings bridge is restricted to system apps",
2154
+ });
2155
+ return false;
2156
+ }
2157
+ /** A bridged key's current value: the store's, or the engine's for a derived key. */
2158
+ settingsBridgeValue(key) {
2159
+ if (isDerivedSettingsKey(key))
2160
+ return this.derivedSettingValue(key);
2161
+ return useSettingsStore.getState().getSetting(key) ?? null;
2162
+ }
2163
+ derivedSettingValue(key) {
2164
+ switch (key) {
2165
+ case "phone_active":
2166
+ return isPhoneActive();
2167
+ case "foreground_app_on_screen":
2168
+ return localDisplayManager.foregroundAppOnScreen();
2169
+ case "ring_battery":
2170
+ return ringBatteryFrom(useGlassesStore.getState());
2171
+ }
2172
+ }
2173
+ handleSettingsGet(packageName, payload, requestId) {
2174
+ if (!this.requireSystemForSettings(packageName, requestId))
2175
+ return;
2176
+ const values = {};
2177
+ for (const key of selectSettingsBridgeKeys(payload.keys))
2178
+ values[key] = this.settingsBridgeValue(key);
2179
+ this.sendResult(packageName, requestId, true, { values });
2180
+ }
2181
+ async handleSettingsSet(packageName, payload, requestId) {
2182
+ if (!this.requireSystemForSettings(packageName, requestId))
2183
+ return;
2184
+ const validated = validateSettingsSet(payload);
2185
+ if (!validated.ok) {
2186
+ this.sendResult(packageName, requestId, false, undefined, { code: validated.code, message: validated.message });
2187
+ return;
2188
+ }
2189
+ // Through the store, so persistence, server sync and the Bluetooth sync
2190
+ // of glasses settings behave as if a phone screen wrote it.
2191
+ const result = await useSettingsStore.getState().setSetting(validated.key, validated.value);
2192
+ if (result.is_error()) {
2193
+ this.sendResult(packageName, requestId, false, undefined, {
2194
+ code: MiniappErrorCode.INTERNAL,
2195
+ message: result.error.message,
2196
+ });
2197
+ return;
2198
+ }
2199
+ this.sendResult(packageName, requestId, true);
2200
+ }
2201
+ async handleNotificationAppsList(packageName, requestId) {
2202
+ if (!this.requireSystemForSettings(packageName, requestId))
2203
+ return;
2204
+ try {
2205
+ this.sendResult(packageName, requestId, true, await installedNotificationApps());
2206
+ }
2207
+ catch (err) {
2208
+ this.sendResult(packageName, requestId, false, undefined, {
2209
+ code: MiniappErrorCode.INTERNAL,
2210
+ message: err instanceof Error ? err.message : "Failed to list notification apps",
2211
+ });
2212
+ }
2213
+ }
2214
+ /**
2215
+ * Push bridged settings as they change: stored keys from the store, the
2216
+ * derived keys whenever their inputs move (phone activity, the display,
2217
+ * the apps store, the ring). Subscribers are system apps only (gated at
2218
+ * SUBSCRIBE), so this is a no-op fan-out for everyone else.
2219
+ */
2220
+ ensureSettingsBridgeWired() {
2221
+ if (this.settingsBridgeWired)
2222
+ return;
2223
+ this.settingsBridgeWired = true;
2224
+ const storedKeys = Object.keys(SETTINGS_BRIDGE_KEYS);
2225
+ const emit = (key, value) => this.forwardEvent(MiniappStreamType.SETTINGS_CHANGED, { key, value });
2226
+ useSettingsStore.subscribe((s) => storedKeys.map((key) => JSON.stringify(s.getSetting(key) ?? null)), (next, previous) => {
2227
+ storedKeys.forEach((key, i) => {
2228
+ if (next[i] !== previous[i])
2229
+ emit(key, this.settingsBridgeValue(key));
2230
+ });
2231
+ }, { equalityFn: (a, b) => a.length === b.length && a.every((value, i) => value === b[i]) });
2232
+ const pushDerived = () => {
2233
+ for (const key of DERIVED_SETTINGS_KEYS) {
2234
+ const value = this.derivedSettingValue(key);
2235
+ const encoded = JSON.stringify(value ?? null);
2236
+ if (this.derivedSettings.get(key) === encoded)
2237
+ continue;
2238
+ this.derivedSettings.set(key, encoded);
2239
+ emit(key, value);
2240
+ }
2241
+ };
2242
+ for (const key of DERIVED_SETTINGS_KEYS) {
2243
+ this.derivedSettings.set(key, JSON.stringify(this.derivedSettingValue(key) ?? null));
2244
+ }
2245
+ onPhoneActivityChange(pushDerived);
2246
+ // App foreground/background drives VISIBILITY_CHANGE. Background execution
2247
+ // limits (timer coalescing, BLE credit polling) follow the app's own
2248
+ // foreground bit, not `isPhoneActive()` — which counts an unlocked phone as
2249
+ // active even while the app is suspended behind it.
2250
+ onAppForegroundChange((foregrounded) => this.broadcastVisibility(foregrounded));
2251
+ localDisplayManager.onActiveDisplayChange(pushDerived);
2252
+ useAppStatusStore.subscribe(pushDerived);
2253
+ useGlassesStore.subscribe((s) => `${s.ringConnected}:${s.ringBatteryLevel}:${s.controllerConnected}:${s.controllerBatteryLevel}`, pushDerived);
2254
+ }
1925
2255
  /**
1926
2256
  * Native nav UI feed (display.sendNativeNav*). Gated on the device's nav
1927
2257
  * mode being active (trip-scoped); the miniapp's first pushes can race the
@@ -1935,6 +2265,8 @@ class LocalMiniappRuntime {
1935
2265
  return;
1936
2266
  }
1937
2267
  const sent = nativeNavigationService.handleMiniappRequest(payload);
2268
+ if (sent)
2269
+ mirrorService.invalidate("firmware-navigation");
1938
2270
  this.sendResult(packageName, requestId, true, {
1939
2271
  status: sent ? "sent" : "unsupported",
1940
2272
  ...(sent ? {} : { reason: "native navigation unavailable" }),
@@ -1964,6 +2296,7 @@ class LocalMiniappRuntime {
1964
2296
  this.sendResult(packageName, requestId, true, { status: "unsupported", reason: "no glasses connected" });
1965
2297
  return;
1966
2298
  }
2299
+ mirrorService.invalidate("firmware-dashboard");
1967
2300
  void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {
1968
2301
  console.error(`${LOG_TAG}: showDashboard failed:`, err);
1969
2302
  });
@@ -2541,12 +2874,12 @@ class LocalMiniappRuntime {
2541
2874
  */
2542
2875
  blobStore = new BlobStore({
2543
2876
  sendResult: (packageName, requestId, ok, result, error) => this.sendResult(packageName, requestId, ok, result, error),
2544
- getUserId: () => cloudClientService.getGlasslyUserId(),
2877
+ getUserId: () => cloudClientService.getUserId(),
2545
2878
  });
2546
2879
  blobRequestQueues = new Map();
2547
2880
  simpleStorage = new LocalMiniappStorage({
2548
2881
  backend: localMiniappStorageBackend,
2549
- getUserId: () => cloudClientService.resolveGlasslyUserId(),
2882
+ getUserId: () => cloudClientService.resolveUserId(),
2550
2883
  });
2551
2884
  /**
2552
2885
  * BlobStore's file helpers are synchronous once a user namespace is selected,
@@ -2558,7 +2891,7 @@ class LocalMiniappRuntime {
2558
2891
  const previous = this.blobRequestQueues.get(packageName) ?? Promise.resolve();
2559
2892
  const queued = previous.then(async () => {
2560
2893
  try {
2561
- await cloudClientService.resolveGlasslyUserId();
2894
+ await cloudClientService.resolveUserId();
2562
2895
  }
2563
2896
  catch (err) {
2564
2897
  console.error(`${LOG_TAG}: blob identity error:`, err);
@@ -2600,7 +2933,7 @@ class LocalMiniappRuntime {
2600
2933
  });
2601
2934
  }
2602
2935
  /**
2603
- * Heading is a sensor stream — start the native compass when any mini
2936
+ * Phone heading is a sensor stream — start the phone compass when any mini
2604
2937
  * app is subscribed to "heading_update", stop it when none are.
2605
2938
  */
2606
2939
  headingUnsub = null;
@@ -2631,6 +2964,17 @@ class LocalMiniappRuntime {
2631
2964
  this.imuEnabled = wantsImu;
2632
2965
  void BluetoothSdk.setImuEnabled(wantsImu);
2633
2966
  }
2967
+ /**
2968
+ * Glasses compass is demand-driven: GlassesSensorService runs it while any
2969
+ * miniapp is subscribed to "glasses_compass" (custom firmware), serving the
2970
+ * tightest sampling any subscriber asked for via session.compass.start().
2971
+ */
2972
+ recomputeCompassSubscription() {
2973
+ const subscribers = this.streamSubscribers.get(MiniappStreamType.GLASSES_COMPASS) ?? new Set();
2974
+ void glassesSensorService.setCompassSubscribers(subscribers).catch((error) => {
2975
+ console.warn(`${LOG_TAG}: compass subscription update failed`, error);
2976
+ });
2977
+ }
2634
2978
  /**
2635
2979
  * Last rate we asked the host for. Lets us skip the cross-process call
2636
2980
  * when nothing actually changed (most SUBSCRIBE / unregister churn
@@ -3298,7 +3642,7 @@ class LocalMiniappRuntime {
3298
3642
  data: { progressToken, activity: status.activity, timestamp: Date.now() },
3299
3643
  });
3300
3644
  }
3301
- await new Promise((resolve) => setTimeout(resolve, GENERATE_JOB_POLL_MS));
3645
+ await new Promise((resolve) => BgTimer.setTimeout(resolve, GENERATE_JOB_POLL_MS));
3302
3646
  }
3303
3647
  }
3304
3648
  // ---------------------------------------------------------------------------
@@ -3847,6 +4191,9 @@ class LocalMiniappRuntime {
3847
4191
  return { ok: false, error: "MICROPHONE permission not declared in miniapp.json" };
3848
4192
  }
3849
4193
  }
4194
+ if (streams.includes(MiniappStreamType.MIRROR_UPDATE) && !this.hasMirrorPermission(packageName)) {
4195
+ return { ok: false, error: "DISPLAY_MIRROR permission not declared" };
4196
+ }
3850
4197
  // Add new subscriptions
3851
4198
  const previousSubscriptions = app.subscriptions;
3852
4199
  app.subscriptions = new Set(streams);
@@ -4209,10 +4556,46 @@ class LocalMiniappRuntime {
4209
4556
  if (this.capabilitiesWired)
4210
4557
  return;
4211
4558
  this.capabilitiesWired = true;
4212
- useGlassesStore.subscribe((s) => `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}`, () => {
4213
- const capabilities = this.currentCapabilities();
4559
+ const broadcast = () => this.broadcastCapabilities();
4560
+ // Connection, model and firmware also decide the dashboard view's eligibility.
4561
+ useGlassesStore.subscribe((s) => `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}:${s.sceneVector === true}:${s.firmwareVersion}:${s.connection.state}:${s.deviceModel}`, broadcast);
4562
+ // The debug knob flips hasNativeNavigation on custom firmware.
4563
+ useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.cfw_native_navigation.key), broadcast);
4564
+ // The dashboard master switch flips the dashboard view.
4565
+ useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.custom_dashboard_enabled.key), broadcast);
4566
+ }
4567
+ /** Capabilities as `packageName` sees them: the model profile plus the views this app may draw. */
4568
+ capabilitiesFor(packageName) {
4569
+ const capabilities = this.currentCapabilities();
4570
+ if (!capabilities.display)
4571
+ return capabilities;
4572
+ const views = displayViewsFor(packageName, pagesEligible());
4573
+ return { ...capabilities, display: { ...capabilities.display, views } };
4574
+ }
4575
+ broadcastCapabilities() {
4576
+ for (const packageName of this.handshookApps) {
4577
+ this.sendToMiniapp(packageName, {
4578
+ type: MiniappResponseType.CAPABILITIES_UPDATE,
4579
+ capabilities: this.capabilitiesFor(packageName),
4580
+ });
4581
+ }
4582
+ }
4583
+ /** Host user preferences miniapps follow by default (Settings > Appearance). */
4584
+ currentPreferences() {
4585
+ const settings = useSettingsStore.getState();
4586
+ return {
4587
+ unitSystem: settings.getSetting(SETTINGS.metric_system.key) === true ? "metric" : "imperial",
4588
+ twelveHourTime: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,
4589
+ };
4590
+ }
4591
+ ensurePreferencesWired() {
4592
+ if (this.preferencesWired)
4593
+ return;
4594
+ this.preferencesWired = true;
4595
+ useSettingsStore.subscribe((s) => `${s.getSetting(SETTINGS.metric_system.key) === true}:${s.getSetting(SETTINGS.twelve_hour_time.key) === true}`, () => {
4596
+ const preferences = this.currentPreferences();
4214
4597
  for (const packageName of this.handshookApps) {
4215
- this.sendToMiniapp(packageName, { type: MiniappResponseType.CAPABILITIES_UPDATE, capabilities });
4598
+ this.sendToMiniapp(packageName, { type: MiniappResponseType.PREFERENCES_UPDATE, preferences });
4216
4599
  }
4217
4600
  });
4218
4601
  }
@@ -4251,7 +4634,9 @@ class LocalMiniappRuntime {
4251
4634
  * - Cloud sends "head_up" → miniapp protocol uses "head_position" (HEAD_POSITION)
4252
4635
  * - Cloud sends "VAD" (uppercase) → miniapp protocol uses "vad" (lowercase)
4253
4636
  */
4254
- forwardEvent(streamType, data, transcriptionSource) {
4637
+ forwardEvent(streamType, data, transcriptionSource,
4638
+ /** Deliver to this subscriber alone (the designated dashboard app's gestures while it shows). */
4639
+ only) {
4255
4640
  // Local sherpa results enter the runtime only through here (DeviceEventRouter's
4256
4641
  // local_transcription listener); cloud/direct are marked at their onTranscript
4257
4642
  // wiring, which never passes "local".
@@ -4364,6 +4749,8 @@ class LocalMiniappRuntime {
4364
4749
  if (matchedSubs.size === 0 && !perGestureStream)
4365
4750
  return;
4366
4751
  for (const packageName of matchedSubs) {
4752
+ if (only !== undefined && packageName !== only)
4753
+ continue;
4367
4754
  let transcriptionRoute;
4368
4755
  if (normalizedStream.startsWith("transcription:") && transcriptionSource) {
4369
4756
  const routes = this.appTranscriptionRoutesForEvent(packageName, normalizedStream);
@@ -4398,6 +4785,8 @@ class LocalMiniappRuntime {
4398
4785
  const gestureSubs = this.streamSubscribers.get(perGestureStream);
4399
4786
  if (gestureSubs) {
4400
4787
  for (const packageName of gestureSubs) {
4788
+ if (only !== undefined && packageName !== only)
4789
+ continue;
4401
4790
  this.sendToMiniapp(packageName, {
4402
4791
  type: MiniappResponseType.EVENT,
4403
4792
  streamType: perGestureStream,
@@ -4624,6 +5013,7 @@ class LocalMiniappRuntime {
4624
5013
  name: app.name,
4625
5014
  version: app.version ?? "",
4626
5015
  running: app.running,
5016
+ hidden: app.hidden === true,
4627
5017
  compatibility,
4628
5018
  actions: app.actions ?? [],
4629
5019
  };
@@ -4970,6 +5360,22 @@ class LocalMiniappRuntime {
4970
5360
  visibility,
4971
5361
  });
4972
5362
  }
5363
+ /**
5364
+ * Push the app's foreground state to every connected miniapp. Miniapps that
5365
+ * pace their own work (streaming a scene, polling a sensor) need to know when
5366
+ * the host process is backgrounded, because that is when iOS coalesces their
5367
+ * timers and the BLE writer falls back to credit polling.
5368
+ */
5369
+ broadcastVisibility(foregrounded) {
5370
+ const visibility = foregrounded ? "foreground" : "background";
5371
+ for (const packageName of this.connectedApps.keys()) {
5372
+ this.sendVisibilityChange(packageName, visibility);
5373
+ }
5374
+ }
5375
+ /** Foreground state a miniapp is given at CONNECT_ACK, before any transition fires. */
5376
+ currentVisibility() {
5377
+ return isAppForegrounded() ? "foreground" : "background";
5378
+ }
4973
5379
  /**
4974
5380
  * Fast foreground liveness check used when a local miniapp UI is opened or
4975
5381
  * resumed. The normal watchdog intentionally waits ~30s to avoid killing a