@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
@@ -9,7 +9,10 @@
9
9
  * management (connect/disconnect/ping).
10
10
  */
11
11
 
12
+ import {mirrorService} from "./MirrorService"
12
13
  import {AppState, Linking} from "react-native"
14
+ import {logBuffer} from "../utils/devLogging"
15
+ import {collectDisplayDiagnostics} from "./DisplayDiagnostics"
13
16
  import Share from "react-native-share"
14
17
  import * as Battery from "expo-battery"
15
18
  import * as Clipboard from "expo-clipboard"
@@ -26,7 +29,7 @@ import {
26
29
  parseEnvelope,
27
30
  serializeEnvelope,
28
31
  } from "@glassly/miniapp"
29
- import type {MiniappEnvelope} from "@glassly/miniapp"
32
+ import type {MiniappEnvelope, MiniappLaunchSource, MiniappPreferences} from "@glassly/miniapp"
30
33
 
31
34
  import {DeviceTypes, type Capabilities} from "../types"
32
35
  import {getEffectiveCapabilities} from "./effectiveCapabilities"
@@ -52,10 +55,34 @@ import {normalizeAiTier, resolveAiTier} from "./AiTierResolution"
52
55
  import type {AiTier, AiTierProvider, SavedLlmKeys} from "./AiTierResolution"
53
56
  import {HttpError} from "@glassly/cloud-client"
54
57
  import localDisplayManager from "./LocalDisplayManager"
55
- import {claimsGesture} from "./gestureClaims"
58
+ import {
59
+ dropGestureClaims,
60
+ emptyGestureClaims,
61
+ gestureClaimant,
62
+ hasGestureClaim,
63
+ setGestureClaims,
64
+ type GestureClaimChange,
65
+ type GestureClaimState,
66
+ } from "./gestureClaims"
67
+ import overlayPages from "./pages/OverlayPages"
68
+ import dashboardOpenTrace from "./debug/DashboardOpenTrace"
69
+ import {isAppForegrounded, isPhoneActive, onAppForegroundChange, onPhoneActivityChange} from "./PhoneActivity"
70
+ import {installedNotificationApps} from "./PhoneNotificationsSync"
71
+ import {
72
+ DERIVED_SETTINGS_KEYS,
73
+ SETTINGS_BRIDGE_KEYS,
74
+ isDerivedSettingsKey,
75
+ ringBatteryFrom,
76
+ selectSettingsBridgeKeys,
77
+ validateSettingsSet,
78
+ type DerivedSettingsKey,
79
+ } from "./settingsBridge"
80
+ import {addOverlayApp, displayViewsFor, isOverlayApp, removeOverlayApp, setOverlayViewer} from "./pages/overlayApps"
81
+ import {pagesEligible} from "./pages/pageEligibility"
56
82
  import nativeNavigationService from "./NativeNavigationService"
57
83
  import type {DisplayPayload} from "./LocalDisplayManager"
58
84
  import headingService from "./HeadingService"
85
+ import glassesSensorService from "./GlassesSensorService"
59
86
  import localSttFallbackCoordinator from "./LocalSttFallbackCoordinator"
60
87
  import speakerLabelResolver from "./SpeakerLabelResolver"
61
88
  import micStateCoordinator from "./MicStateCoordinator"
@@ -158,6 +185,8 @@ interface ConnectedMiniapp {
158
185
  * see {@link LOCATION_RATE_PRIORITY}.
159
186
  */
160
187
  requestedLocationRate: string | null
188
+ /** Who started the app; echoed in CONNECT_ACK. */
189
+ launchSource?: MiniappLaunchSource
161
190
  }
162
191
 
163
192
  interface SpeechRun {
@@ -222,9 +251,12 @@ const SYSTEM_MINIAPP_PACKAGES = new Set([
222
251
  "com.glassly.simulated",
223
252
  "com.glassly.mirror",
224
253
  "com.glassly.ai",
225
- "cloud.augmentos.notify",
226
254
  "com.glassly.feedback",
227
255
  "com.glassly.miniappdev",
256
+ // The dashboard: the settings bridge and the miniapp list/start/stop are its side menu and settings page.
257
+ "com.glassly.dashboard",
258
+ // The notifications viewer: the settings bridge and the notification app list are its settings page.
259
+ "com.glassly.notifications",
228
260
  ])
229
261
  const PING_INTERVAL_MS = 5_000
230
262
  // Min gap between "transcription limit reached" notices on the glasses. A
@@ -304,6 +336,7 @@ const PERMISSION_TYPE_TO_CANONICAL: Record<string, string> = {
304
336
  LOCATION: "location",
305
337
  BACKGROUND_LOCATION: "location",
306
338
  READ_NOTIFICATIONS: "notifications",
339
+ DISPLAY_MIRROR: "displayMirror",
307
340
  POST_NOTIFICATIONS: "notifications",
308
341
  CALENDAR: "calendar",
309
342
  API_KEYS: "apiKeys",
@@ -423,6 +456,7 @@ const ALL_CANONICAL_PERMISSIONS = [
423
456
  "memory",
424
457
  "ai",
425
458
  "speakers",
459
+ "displayMirror",
426
460
  ] as const
427
461
 
428
462
  /**
@@ -540,8 +574,8 @@ class LocalMiniappRuntime {
540
574
 
541
575
  /** Connected miniapps keyed by packageName. */
542
576
  private connectedApps: Map<string, ConnectedMiniapp> = new Map()
543
- /** Per-app claimed gestures (CLAIM_GESTURES) — see foregroundClaimsGesture. */
544
- private gestureClaims: Map<string, Set<string>> = new Map()
577
+ /** Claimed gestures (CLAIM_GESTURES): a stack per gesture, the most recent claimer holding it. */
578
+ private gestureClaims: GestureClaimState = emptyGestureClaims()
545
579
 
546
580
  /**
547
581
  * Packages that have completed their CONNECT handshake (sent
@@ -564,6 +598,10 @@ class LocalMiniappRuntime {
564
598
  private cloudResultsWired = false
565
599
  private cloudStatusWired = false
566
600
  private capabilitiesWired = false
601
+ private preferencesWired = false
602
+ private settingsBridgeWired = false
603
+ /** Last derived read-only values pushed on the settings stream, for change detection. */
604
+ private derivedSettings = new Map<DerivedSettingsKey, string>()
567
605
  private cloudAudioSubscriptionSync = new CloudAudioSubscriptionSync()
568
606
 
569
607
  /**
@@ -692,6 +730,22 @@ class LocalMiniappRuntime {
692
730
  }
693
731
  }
694
732
 
733
+ if (this.streamSubscribers.has(MiniappStreamType.MIRROR_UPDATE)) {
734
+ if (!this.mirrorUnsubscribe)
735
+ this.mirrorUnsubscribe = mirrorService.subscribe((snapshot) => {
736
+ for (const pkg of this.streamSubscribers.get(MiniappStreamType.MIRROR_UPDATE) ?? []) {
737
+ if (this.hasMirrorPermission(pkg))
738
+ this.sendToMiniapp(pkg, {
739
+ type: MiniappResponseType.EVENT,
740
+ streamType: MiniappStreamType.MIRROR_UPDATE,
741
+ data: snapshot,
742
+ })
743
+ }
744
+ })
745
+ } else {
746
+ this.mirrorUnsubscribe?.()
747
+ this.mirrorUnsubscribe = null
748
+ }
695
749
  this.publishTranscriptionDebugSubscribers()
696
750
  }
697
751
 
@@ -802,6 +856,15 @@ class LocalMiniappRuntime {
802
856
  return LocalMiniappRuntime.instance
803
857
  }
804
858
 
859
+ private mirrorUnsubscribe: (() => void) | null = null
860
+
861
+ private hasMirrorPermission(packageName: string): boolean {
862
+ return (
863
+ this.connectedApps.get(packageName)?.installedManifest?.permissions?.some((p) => p.type === "DISPLAY_MIRROR") ===
864
+ true
865
+ )
866
+ }
867
+
805
868
  private initialized = false
806
869
 
807
870
  /**
@@ -813,6 +876,17 @@ class LocalMiniappRuntime {
813
876
  this.initialized = true
814
877
  console.log(`${LOG_TAG}: initialize()`)
815
878
  this.ensurePingLoop()
879
+ mirrorService.initialize()
880
+ // The overlay apps' pages relay through the runtime: the slot going to
881
+ // and from an app arrives as its display_view events.
882
+ overlayPages.setSink({
883
+ send: (packageName, event) =>
884
+ this.sendToMiniapp(packageName, {
885
+ type: MiniappResponseType.EVENT,
886
+ streamType: MiniappStreamType.DISPLAY_VIEW,
887
+ data: event,
888
+ }),
889
+ })
816
890
  }
817
891
 
818
892
  /**
@@ -907,6 +981,7 @@ class LocalMiniappRuntime {
907
981
  packageName: string,
908
982
  sendFn: (raw: string) => void,
909
983
  installedManifest?: InstalledMiniappManifest,
984
+ launchSource?: MiniappLaunchSource,
910
985
  ): void {
911
986
  console.log(`${LOG_TAG}: registerApp(${packageName})`)
912
987
  this.clearForegroundProbe(packageName)
@@ -918,6 +993,9 @@ class LocalMiniappRuntime {
918
993
  // dangling references. The WebView will re-subscribe after its CONNECT.
919
994
  if (this.connectedApps.has(packageName)) {
920
995
  const existing = this.connectedApps.get(packageName)!
996
+ void glassesSensorService.release(packageName).catch((error) => {
997
+ console.warn(`${LOG_TAG}: sensor cleanup failed on respawn for ${packageName}`, error)
998
+ })
921
999
  this.cancelSpeech(packageName)
922
1000
  // A crash respawn replaces the ConnectedMiniapp without going through
923
1001
  // unregisterApp(). Abort the old incarnation's native PCM player before
@@ -956,6 +1034,7 @@ class LocalMiniappRuntime {
956
1034
  authDelivered: false,
957
1035
  speakerState: "idle",
958
1036
  requestedLocationRate: null,
1037
+ launchSource,
959
1038
  })
960
1039
  // If we just clobbered an existing entry, its requestedLocationRate is
961
1040
  // now gone — recompute so the aggregate doesn't include a stale rate.
@@ -963,6 +1042,8 @@ class LocalMiniappRuntime {
963
1042
  this.recomputeLocationTier()
964
1043
  this.ensureCloudStatusWired()
965
1044
  this.ensureCapabilitiesWired()
1045
+ this.ensurePreferencesWired()
1046
+ this.ensureSettingsBridgeWired()
966
1047
  this.ensurePingLoop()
967
1048
 
968
1049
  // Show the system boot message ("Starting <name>…") on the glasses for the
@@ -988,12 +1069,12 @@ class LocalMiniappRuntime {
988
1069
  const waiter = (err?: Error) => {
989
1070
  if (settled) return
990
1071
  settled = true
991
- clearTimeout(timer)
1072
+ BgTimer.clearTimeout(timer)
992
1073
  this.connectWaiters.get(packageName)?.delete(waiter)
993
1074
  if (err) reject(err)
994
1075
  else resolve()
995
1076
  }
996
- const timer = setTimeout(() => {
1077
+ const timer = BgTimer.setTimeout(() => {
997
1078
  waiter(new Error(`waitForConnect: ${packageName} did not connect within ${timeoutMs}ms`))
998
1079
  }, timeoutMs)
999
1080
  let set = this.connectWaiters.get(packageName)
@@ -1065,7 +1146,7 @@ class LocalMiniappRuntime {
1065
1146
  // 50 ms: imperceptible to the user, plenty of time for the SDK's
1066
1147
  // synchronous `beforeDisconnect` handlers to flush a final
1067
1148
  // sendOneShot through the still-open transport.
1068
- await new Promise<void>((resolve) => setTimeout(resolve, 50))
1149
+ await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 50))
1069
1150
  this.unregisterApp(packageName)
1070
1151
  }
1071
1152
 
@@ -1083,6 +1164,14 @@ class LocalMiniappRuntime {
1083
1164
  // Drop this app's transcription hints; the cloud subscription rebuild
1084
1165
  // below (via replaceStreamSubscribers) re-derives the hint union without it.
1085
1166
  this.transcriptionHintsByApp.delete(packageName)
1167
+ // An overlay app is going: release the slot if it holds it, and drop the
1168
+ // dashboard view from its capabilities record.
1169
+ if (isOverlayApp(packageName)) {
1170
+ overlayPages.remove(packageName)
1171
+ removeOverlayApp(packageName)
1172
+ }
1173
+ // Its gesture claims go back to whoever claimed them before.
1174
+ this.applyGestureClaimChanges(dropGestureClaims(this.gestureClaims, packageName))
1086
1175
 
1087
1176
  // Warm-up is a request-owned camera lease. Cancel it even if the app record was already
1088
1177
  // removed so a close racing the camera-open promise cannot leave the sensor running.
@@ -1150,11 +1239,16 @@ class LocalMiniappRuntime {
1150
1239
  this.navigationHandlers.onDisconnect(packageName)
1151
1240
  }
1152
1241
 
1242
+ void glassesSensorService.release(packageName).catch((error) => {
1243
+ console.warn(`${LOG_TAG}: sensor cleanup failed for ${packageName}`, error)
1244
+ })
1245
+
1153
1246
  // Recompute heading subscription — if this app was the last subscriber,
1154
1247
  // the sensor will stop.
1155
1248
  this.recomputeHeadingSubscription()
1156
- // Same for the IMU/accelerometer stream.
1249
+ // Same for the IMU/accelerometer and glasses compass streams.
1157
1250
  this.recomputeImuSubscription()
1251
+ this.recomputeCompassSubscription()
1158
1252
 
1159
1253
  // Drop this app's location-tier request before recomputing so the
1160
1254
  // aggregate falls back down if it was the strictest. Done before
@@ -1176,7 +1270,6 @@ class LocalMiniappRuntime {
1176
1270
  // core app's saved frame).
1177
1271
  localDisplayManager.onUnmount(packageName)
1178
1272
 
1179
- this.gestureClaims.delete(packageName)
1180
1273
  this.connectedApps.delete(packageName)
1181
1274
  this.recomputeMicRequirements()
1182
1275
  if (releasedMicGateOverride) {
@@ -1261,9 +1354,30 @@ class LocalMiniappRuntime {
1261
1354
  case MiniappRequestType.DISPLAY:
1262
1355
  this.handleDisplay(packageName, payload, requestId)
1263
1356
  break
1357
+ case MiniappRequestType.MIRROR_GET:
1358
+ if (!this.hasMirrorPermission(packageName)) {
1359
+ this.sendResult(packageName, requestId, false, undefined, {
1360
+ code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
1361
+ message: "DISPLAY_MIRROR permission not declared in miniapp.json",
1362
+ permission: "DISPLAY_MIRROR",
1363
+ })
1364
+ } else this.sendResult(packageName, requestId, true, mirrorService.current())
1365
+ break
1264
1366
  case MiniappRequestType.RENDER:
1265
1367
  this.handleRender(packageName, payload, requestId)
1266
1368
  break
1369
+ case MiniappRequestType.SHOW_VIEW:
1370
+ this.handleShowView(packageName, payload, requestId)
1371
+ break
1372
+ case MiniappRequestType.SETTINGS_GET:
1373
+ this.handleSettingsGet(packageName, payload, requestId)
1374
+ break
1375
+ case MiniappRequestType.SETTINGS_SET:
1376
+ void this.handleSettingsSet(packageName, payload, requestId)
1377
+ break
1378
+ case MiniappRequestType.NOTIFICATION_APPS_LIST:
1379
+ void this.handleNotificationAppsList(packageName, requestId)
1380
+ break
1267
1381
  case MiniappRequestType.NATIVE_NAV:
1268
1382
  this.handleNativeNav(packageName, payload, requestId)
1269
1383
  break
@@ -1395,6 +1509,23 @@ class LocalMiniappRuntime {
1395
1509
  case MiniappRequestType.SET_WIFI_ADB_STATE:
1396
1510
  void this.handleSetWifiAdbState(packageName, payload, requestId)
1397
1511
  break
1512
+ case MiniappRequestType.GLASSES_SENSOR_CONTROL:
1513
+ if (!this.connectedApps.has(packageName)) {
1514
+ this.sendResult(packageName, requestId, false, undefined, {
1515
+ code: MiniappErrorCode.NOT_CONNECTED,
1516
+ message: "Miniapp disconnected",
1517
+ })
1518
+ break
1519
+ }
1520
+ void glassesSensorService.request(packageName, payload).then(
1521
+ () => this.sendResult(packageName, requestId, true),
1522
+ (error: unknown) =>
1523
+ this.sendResult(packageName, requestId, false, undefined, {
1524
+ code: (error as {code?: string}).code || MiniappErrorCode.INTERNAL,
1525
+ message: error instanceof Error ? error.message : "Sensor control failed",
1526
+ }),
1527
+ )
1528
+ break
1398
1529
  case MiniappRequestType.IMU_SET_ENABLED:
1399
1530
  this.handleImuSetEnabled(packageName, payload, requestId)
1400
1531
  break
@@ -1420,6 +1551,15 @@ class LocalMiniappRuntime {
1420
1551
  case MiniappRequestType.OPEN_URL:
1421
1552
  this.handleOpenUrl(packageName, payload)
1422
1553
  break
1554
+ case MiniappRequestType.DISPLAY_DIAGNOSTICS: {
1555
+ const enabled = useSettingsStore.getState().getSetting(SETTINGS.debug_console.key) === true
1556
+ this.sendResult(packageName, requestId, true, {
1557
+ enabled,
1558
+ generatedAt: Date.now(),
1559
+ records: enabled ? collectDisplayDiagnostics(logBuffer.getRecentLogs()) : [],
1560
+ })
1561
+ break
1562
+ }
1423
1563
  case MiniappRequestType.COPY_CLIPBOARD:
1424
1564
  this.handleCopyClipboard(packageName, payload, requestId)
1425
1565
  break
@@ -1609,7 +1749,10 @@ class LocalMiniappRuntime {
1609
1749
  existing.lastPongAt = Date.now()
1610
1750
  existing.unansweredPingRounds = 0
1611
1751
 
1612
- const capabilities = this.currentCapabilities()
1752
+ // A system_dashboard app may take the overlay slot: its capabilities
1753
+ // carry the dashboard view from the start.
1754
+ if (existing.installedManifest?.type === "system_dashboard") addOverlayApp(packageName)
1755
+ const capabilities = this.capabilitiesFor(packageName)
1613
1756
 
1614
1757
  // Build the declared-permission record for the SDK's session.permissions
1615
1758
  // module. Lower-cased to match v3's PermissionType union (microphone,
@@ -1624,7 +1767,7 @@ class LocalMiniappRuntime {
1624
1767
  this.clearMiniappAuthDeliveryRetry(packageName)
1625
1768
  const authPromise = this.requestMiniappAuth(packageName)
1626
1769
  const initialAuth = await withTimeout(authPromise, 1_500)
1627
- const userId = initialAuth?.glasslyUserId ?? ""
1770
+ const userId = initialAuth?.userId ?? ""
1628
1771
  if (initialAuth) {
1629
1772
  existing.authDelivered = true
1630
1773
  this.scheduleMiniappAuthRefresh(packageName, initialAuth)
@@ -1637,7 +1780,10 @@ class LocalMiniappRuntime {
1637
1780
  userId,
1638
1781
  packageName,
1639
1782
  capabilities,
1783
+ preferences: this.currentPreferences(),
1784
+ visibility: this.currentVisibility(),
1640
1785
  permissions: declaredPermissions,
1786
+ ...(existing.launchSource ? {launchSource: existing.launchSource} : {}),
1641
1787
  ...(initialAuth ? {auth: initialAuth} : {}),
1642
1788
  },
1643
1789
  requestId,
@@ -1867,11 +2013,10 @@ class LocalMiniappRuntime {
1867
2013
 
1868
2014
  /**
1869
2015
  * CLAIM_GESTURES (session.input.setClaimedGestures): the app declares which
1870
- * touch gestures it handles itself, replacing its previous claim set.
1871
- * Claims suppress the host's default behavior for those gestures (today:
1872
- * double_tap → dashboard/head-up in DeviceEventRouter) but ONLY while the
1873
- * claiming app is the one in front of the user — see
1874
- * {@link foregroundClaimsGesture}. Cleared on app stop.
2016
+ * touch gestures it handles itself, replacing its previous claim set. The
2017
+ * most recent claimer of a gesture holds it and gets it alone; releasing
2018
+ * hands it back to the previous claimer, and both hear the change on the
2019
+ * gesture_claim stream. Cleared on app stop.
1875
2020
  */
1876
2021
  private handleClaimGestures(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
1877
2022
  if (!this.connectedApps.has(packageName)) {
@@ -1880,19 +2025,39 @@ class LocalMiniappRuntime {
1880
2025
  }
1881
2026
  const raw = payload.gestures
1882
2027
  const gestures = Array.isArray(raw) ? raw.filter((g): g is string => typeof g === "string") : []
1883
- if (gestures.length === 0) this.gestureClaims.delete(packageName)
1884
- else this.gestureClaims.set(packageName, new Set(gestures))
2028
+ if (payload.scope !== undefined && (typeof payload.scope !== "string" || payload.scope.length === 0)) {
2029
+ this.sendResult(packageName, requestId, false, undefined, {
2030
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2031
+ message: "Gesture claim scope must be a nonempty string",
2032
+ })
2033
+ return
2034
+ }
2035
+ this.applyGestureClaimChanges(
2036
+ setGestureClaims(this.gestureClaims, packageName, gestures, payload.scope as string | undefined),
2037
+ )
1885
2038
  this.sendResult(packageName, requestId, true)
1886
2039
  }
1887
2040
 
1888
- /** True iff the miniapp in front of the user has claimed `gesture`. */
1889
- public foregroundClaimsGesture(gesture: string): boolean {
1890
- return claimsGesture(
1891
- this.gestureClaims,
1892
- gesture,
1893
- localDisplayManager.getGestureOwner(),
1894
- useAppStatusStore.getState().foregroundedPackage,
1895
- )
2041
+ private applyGestureClaimChanges(result: {state: GestureClaimState; changes: GestureClaimChange[]}): void {
2042
+ this.gestureClaims = result.state
2043
+ for (const change of result.changes) {
2044
+ this.forwardEvent(
2045
+ MiniappStreamType.GESTURE_CLAIM,
2046
+ {gesture: change.gesture, held: change.held},
2047
+ undefined,
2048
+ change.packageName,
2049
+ )
2050
+ }
2051
+ }
2052
+
2053
+ /** The app holding `gesture` (the most recent claimer), or null when it is unclaimed. */
2054
+ public gestureClaimant(gesture: string): string | null {
2055
+ return gestureClaimant(this.gestureClaims, gesture)
2056
+ }
2057
+
2058
+ /** Has this app registered the gesture, even if a later claimant currently receives it? */
2059
+ public hasGestureClaim(packageName: string, gesture: string): boolean {
2060
+ return hasGestureClaim(this.gestureClaims, packageName, gesture)
1896
2061
  }
1897
2062
 
1898
2063
  private handleSubscribe(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
@@ -1944,9 +2109,10 @@ class LocalMiniappRuntime {
1944
2109
  const declaredTypes = new Set((app.installedManifest?.permissions ?? []).map((p) => p.type?.toUpperCase()))
1945
2110
 
1946
2111
  const permissionForStream = (s: string): string | null => {
2112
+ if (s === MiniappStreamType.MIRROR_UPDATE) return "DISPLAY_MIRROR"
1947
2113
  if (s === "audio_chunk" || s === "vad") return "MICROPHONE"
1948
2114
  if (s.startsWith("transcription") || s.startsWith("translation")) return "MICROPHONE"
1949
- if (s === "location_update") return "LOCATION"
2115
+ if (s === "location_update" || s === "heading_update") return "LOCATION"
1950
2116
  if (s === "phone_notification") return "READ_NOTIFICATIONS"
1951
2117
  if (s === "phone_notification_dismissed") return "READ_NOTIFICATIONS"
1952
2118
  return null
@@ -1968,6 +2134,15 @@ class LocalMiniappRuntime {
1968
2134
  }
1969
2135
  }
1970
2136
 
2137
+ // The settings stream carries the wearer's settings: system apps only.
2138
+ if (streams.includes(MiniappStreamType.SETTINGS_CHANGED) && !this.isSystemPackage(packageName)) {
2139
+ this.sendResult(packageName, requestId, false, undefined, {
2140
+ code: MiniappErrorCode.NOT_PERMITTED,
2141
+ message: "The settings stream is restricted to system apps",
2142
+ })
2143
+ return
2144
+ }
2145
+
1971
2146
  // Add new subscriptions
1972
2147
  const previousSubscriptions = app.subscriptions
1973
2148
  app.subscriptions = new Set(streams)
@@ -1978,11 +2153,16 @@ class LocalMiniappRuntime {
1978
2153
  [...cloudTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)),
1979
2154
  )
1980
2155
  this.replaceStreamSubscribers(packageName, previousSubscriptions, app.subscriptions)
2156
+ // An overlay app that listens to phone notifications presents them on the glasses.
2157
+ if (isOverlayApp(packageName)) {
2158
+ setOverlayViewer(packageName, app.subscriptions.has(MiniappStreamType.PHONE_NOTIFICATION))
2159
+ }
1981
2160
 
1982
2161
  this.recomputeMicRequirements()
1983
2162
  this.updateCloudSubscriptions()
1984
2163
  this.recomputeHeadingSubscription()
1985
2164
  this.recomputeImuSubscription()
2165
+ this.recomputeCompassSubscription()
1986
2166
  // Persist this app's requested rate (or clear it if SUBSCRIBE didn't
1987
2167
  // include `location_stream` this time), then ask the host for the
1988
2168
  // strictest rate across all connected apps.
@@ -2043,7 +2223,13 @@ class LocalMiniappRuntime {
2043
2223
  const isSimulated = (glassesState.deviceModel || "").toLowerCase().includes("simulated")
2044
2224
 
2045
2225
  for (const stream of streams) {
2046
- if (stream === "glasses_battery") {
2226
+ if (stream === MiniappStreamType.MIRROR_UPDATE && this.hasMirrorPermission(packageName)) {
2227
+ this.sendToMiniapp(packageName, {
2228
+ type: MiniappResponseType.EVENT,
2229
+ streamType: MiniappStreamType.MIRROR_UPDATE,
2230
+ data: mirrorService.current(),
2231
+ })
2232
+ } else if (stream === "glasses_battery") {
2047
2233
  // Simulated glasses have no real battery; mirror the phone's battery
2048
2234
  // so miniapps see a sensible value during development.
2049
2235
  if (isSimulated) {
@@ -2089,6 +2275,15 @@ class LocalMiniappRuntime {
2089
2275
  timestamp: Date.now(),
2090
2276
  },
2091
2277
  })
2278
+ } else if (stream === MiniappStreamType.SETTINGS_CHANGED) {
2279
+ // Replay every bridged key so a mirror can be built from the stream alone.
2280
+ for (const key of selectSettingsBridgeKeys(undefined)) {
2281
+ this.sendToMiniapp(packageName, {
2282
+ type: MiniappResponseType.EVENT,
2283
+ streamType: MiniappStreamType.SETTINGS_CHANGED,
2284
+ data: {key, value: this.settingsBridgeValue(key)},
2285
+ })
2286
+ }
2092
2287
  } else if (stream === "head_position") {
2093
2288
  // The glasses store has no headUp field (same on the pre-engine host
2094
2289
  // hook), so this stays a no-emit until head state lands in the store.
@@ -2191,6 +2386,16 @@ class LocalMiniappRuntime {
2191
2386
  })
2192
2387
  return
2193
2388
  }
2389
+ // The dashboard view is the overlay apps' alone; anyone else's frame
2390
+ // is dropped, reported rather than silently lost.
2391
+ if (payload.view === "dashboard" && !isOverlayApp(packageName)) {
2392
+ this.sendResult(packageName, requestId, true, {
2393
+ status: "displayed",
2394
+ presented: false,
2395
+ reason: "not the dashboard app",
2396
+ })
2397
+ return
2398
+ }
2194
2399
 
2195
2400
  localDisplayManager.request(
2196
2401
  packageName,
@@ -2210,6 +2415,174 @@ class LocalMiniappRuntime {
2210
2415
  }
2211
2416
  }
2212
2417
 
2418
+ /**
2419
+ * display.showView: the designated dashboard app takes or releases the
2420
+ * overlay slot. Refused (NOT_PERMITTED) for any other app; "unsupported"
2421
+ * while the glasses cannot show the dashboard view. Idempotent either way;
2422
+ * the slot's state comes back to the app as a display_view change.
2423
+ */
2424
+ private handleShowView(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
2425
+ if (!this.connectedApps.has(packageName)) {
2426
+ this.sendResult(packageName, requestId, true, {status: "unsupported", reason: "app stopped"})
2427
+ return
2428
+ }
2429
+ const view = payload.view
2430
+ if (view !== "main" && view !== "dashboard") {
2431
+ this.sendResult(packageName, requestId, false, undefined, {
2432
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2433
+ message: 'showView takes "main" or "dashboard"',
2434
+ })
2435
+ return
2436
+ }
2437
+ if (!isOverlayApp(packageName)) {
2438
+ this.sendResult(packageName, requestId, false, undefined, {
2439
+ code: MiniappErrorCode.NOT_PERMITTED,
2440
+ message: "The dashboard view belongs to system_dashboard apps",
2441
+ })
2442
+ return
2443
+ }
2444
+ if (view === "main") {
2445
+ overlayPages.hide(packageName)
2446
+ this.sendResult(packageName, requestId, true, {status: "shown"})
2447
+ return
2448
+ }
2449
+ if (!pagesEligible()) {
2450
+ this.sendResult(packageName, requestId, true, {
2451
+ status: "unsupported",
2452
+ reason: "glasses cannot show the dashboard",
2453
+ })
2454
+ return
2455
+ }
2456
+ dashboardOpenTrace.showViewRequested(packageName)
2457
+ const shown = overlayPages.show(packageName)
2458
+ if (shown) dashboardOpenTrace.slotGranted()
2459
+ this.sendResult(
2460
+ packageName,
2461
+ requestId,
2462
+ true,
2463
+ shown ? {status: "shown"} : {status: "unsupported", reason: "the overlay slot was refused"},
2464
+ )
2465
+ }
2466
+
2467
+ // ---------------------------------------------------------------------------
2468
+ // Settings bridge (session.settings) — system apps only
2469
+ // ---------------------------------------------------------------------------
2470
+
2471
+ private requireSystemForSettings(packageName: string, requestId: string | undefined): boolean {
2472
+ if (this.isSystemPackage(packageName)) return true
2473
+ this.sendResult(packageName, requestId, false, undefined, {
2474
+ code: MiniappErrorCode.NOT_PERMITTED,
2475
+ message: "The settings bridge is restricted to system apps",
2476
+ })
2477
+ return false
2478
+ }
2479
+
2480
+ /** A bridged key's current value: the store's, or the engine's for a derived key. */
2481
+ private settingsBridgeValue(key: string): unknown {
2482
+ if (isDerivedSettingsKey(key)) return this.derivedSettingValue(key)
2483
+ return useSettingsStore.getState().getSetting(key) ?? null
2484
+ }
2485
+
2486
+ private derivedSettingValue(key: DerivedSettingsKey): unknown {
2487
+ switch (key) {
2488
+ case "phone_active":
2489
+ return isPhoneActive()
2490
+ case "foreground_app_on_screen":
2491
+ return localDisplayManager.foregroundAppOnScreen()
2492
+ case "ring_battery":
2493
+ return ringBatteryFrom(useGlassesStore.getState())
2494
+ }
2495
+ }
2496
+
2497
+ private handleSettingsGet(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
2498
+ if (!this.requireSystemForSettings(packageName, requestId)) return
2499
+ const values: Record<string, unknown> = {}
2500
+ for (const key of selectSettingsBridgeKeys(payload.keys)) values[key] = this.settingsBridgeValue(key)
2501
+ this.sendResult(packageName, requestId, true, {values})
2502
+ }
2503
+
2504
+ private async handleSettingsSet(
2505
+ packageName: string,
2506
+ payload: Record<string, unknown>,
2507
+ requestId?: string,
2508
+ ): Promise<void> {
2509
+ if (!this.requireSystemForSettings(packageName, requestId)) return
2510
+ const validated = validateSettingsSet(payload)
2511
+ if (!validated.ok) {
2512
+ this.sendResult(packageName, requestId, false, undefined, {code: validated.code, message: validated.message})
2513
+ return
2514
+ }
2515
+ // Through the store, so persistence, server sync and the Bluetooth sync
2516
+ // of glasses settings behave as if a phone screen wrote it.
2517
+ const result = await useSettingsStore.getState().setSetting(validated.key, validated.value)
2518
+ if (result.is_error()) {
2519
+ this.sendResult(packageName, requestId, false, undefined, {
2520
+ code: MiniappErrorCode.INTERNAL,
2521
+ message: result.error.message,
2522
+ })
2523
+ return
2524
+ }
2525
+ this.sendResult(packageName, requestId, true)
2526
+ }
2527
+
2528
+ private async handleNotificationAppsList(packageName: string, requestId?: string): Promise<void> {
2529
+ if (!this.requireSystemForSettings(packageName, requestId)) return
2530
+ try {
2531
+ this.sendResult(packageName, requestId, true, await installedNotificationApps())
2532
+ } catch (err) {
2533
+ this.sendResult(packageName, requestId, false, undefined, {
2534
+ code: MiniappErrorCode.INTERNAL,
2535
+ message: err instanceof Error ? err.message : "Failed to list notification apps",
2536
+ })
2537
+ }
2538
+ }
2539
+
2540
+ /**
2541
+ * Push bridged settings as they change: stored keys from the store, the
2542
+ * derived keys whenever their inputs move (phone activity, the display,
2543
+ * the apps store, the ring). Subscribers are system apps only (gated at
2544
+ * SUBSCRIBE), so this is a no-op fan-out for everyone else.
2545
+ */
2546
+ private ensureSettingsBridgeWired(): void {
2547
+ if (this.settingsBridgeWired) return
2548
+ this.settingsBridgeWired = true
2549
+ const storedKeys = Object.keys(SETTINGS_BRIDGE_KEYS)
2550
+ const emit = (key: string, value: unknown) => this.forwardEvent(MiniappStreamType.SETTINGS_CHANGED, {key, value})
2551
+ useSettingsStore.subscribe(
2552
+ (s) => storedKeys.map((key) => JSON.stringify(s.getSetting(key) ?? null)),
2553
+ (next, previous) => {
2554
+ storedKeys.forEach((key, i) => {
2555
+ if (next[i] !== previous[i]) emit(key, this.settingsBridgeValue(key))
2556
+ })
2557
+ },
2558
+ {equalityFn: (a, b) => a.length === b.length && a.every((value, i) => value === b[i])},
2559
+ )
2560
+ const pushDerived = () => {
2561
+ for (const key of DERIVED_SETTINGS_KEYS) {
2562
+ const value = this.derivedSettingValue(key)
2563
+ const encoded = JSON.stringify(value ?? null)
2564
+ if (this.derivedSettings.get(key) === encoded) continue
2565
+ this.derivedSettings.set(key, encoded)
2566
+ emit(key, value)
2567
+ }
2568
+ }
2569
+ for (const key of DERIVED_SETTINGS_KEYS) {
2570
+ this.derivedSettings.set(key, JSON.stringify(this.derivedSettingValue(key) ?? null))
2571
+ }
2572
+ onPhoneActivityChange(pushDerived)
2573
+ // App foreground/background drives VISIBILITY_CHANGE. Background execution
2574
+ // limits (timer coalescing, BLE credit polling) follow the app's own
2575
+ // foreground bit, not `isPhoneActive()` — which counts an unlocked phone as
2576
+ // active even while the app is suspended behind it.
2577
+ onAppForegroundChange((foregrounded) => this.broadcastVisibility(foregrounded))
2578
+ localDisplayManager.onActiveDisplayChange(pushDerived)
2579
+ useAppStatusStore.subscribe(pushDerived)
2580
+ useGlassesStore.subscribe(
2581
+ (s) => `${s.ringConnected}:${s.ringBatteryLevel}:${s.controllerConnected}:${s.controllerBatteryLevel}`,
2582
+ pushDerived,
2583
+ )
2584
+ }
2585
+
2213
2586
  /**
2214
2587
  * Native nav UI feed (display.sendNativeNav*). Gated on the device's nav
2215
2588
  * mode being active (trip-scoped); the miniapp's first pushes can race the
@@ -2223,6 +2596,7 @@ class LocalMiniappRuntime {
2223
2596
  return
2224
2597
  }
2225
2598
  const sent = nativeNavigationService.handleMiniappRequest(payload)
2599
+ if (sent) mirrorService.invalidate("firmware-navigation")
2226
2600
  this.sendResult(packageName, requestId, true, {
2227
2601
  status: sent ? "sent" : "unsupported",
2228
2602
  ...(sent ? {} : {reason: "native navigation unavailable"}),
@@ -2252,6 +2626,7 @@ class LocalMiniappRuntime {
2252
2626
  this.sendResult(packageName, requestId, true, {status: "unsupported", reason: "no glasses connected"})
2253
2627
  return
2254
2628
  }
2629
+ mirrorService.invalidate("firmware-dashboard")
2255
2630
  void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {
2256
2631
  console.error(`${LOG_TAG}: showDashboard failed:`, err)
2257
2632
  })
@@ -2923,13 +3298,13 @@ class LocalMiniappRuntime {
2923
3298
  private readonly blobStore = new BlobStore({
2924
3299
  sendResult: (packageName, requestId, ok, result, error) =>
2925
3300
  this.sendResult(packageName, requestId, ok, result, error),
2926
- getUserId: () => cloudClientService.getGlasslyUserId(),
3301
+ getUserId: () => cloudClientService.getUserId(),
2927
3302
  })
2928
3303
  private readonly blobRequestQueues = new Map<string, Promise<void>>()
2929
3304
 
2930
3305
  private readonly simpleStorage = new LocalMiniappStorage({
2931
3306
  backend: localMiniappStorageBackend,
2932
- getUserId: () => cloudClientService.resolveGlasslyUserId(),
3307
+ getUserId: () => cloudClientService.resolveUserId(),
2933
3308
  })
2934
3309
 
2935
3310
  /**
@@ -2946,7 +3321,7 @@ class LocalMiniappRuntime {
2946
3321
  const previous = this.blobRequestQueues.get(packageName) ?? Promise.resolve()
2947
3322
  const queued = previous.then(async () => {
2948
3323
  try {
2949
- await cloudClientService.resolveGlasslyUserId()
3324
+ await cloudClientService.resolveUserId()
2950
3325
  } catch (err) {
2951
3326
  console.error(`${LOG_TAG}: blob identity error:`, err)
2952
3327
  this.sendResult(packageName, requestId, false, undefined, {
@@ -2988,7 +3363,7 @@ class LocalMiniappRuntime {
2988
3363
  }
2989
3364
 
2990
3365
  /**
2991
- * Heading is a sensor stream — start the native compass when any mini
3366
+ * Phone heading is a sensor stream — start the phone compass when any mini
2992
3367
  * app is subscribed to "heading_update", stop it when none are.
2993
3368
  */
2994
3369
  private headingUnsub: (() => void) | null = null
@@ -3019,6 +3394,18 @@ class LocalMiniappRuntime {
3019
3394
  void BluetoothSdk.setImuEnabled(wantsImu)
3020
3395
  }
3021
3396
 
3397
+ /**
3398
+ * Glasses compass is demand-driven: GlassesSensorService runs it while any
3399
+ * miniapp is subscribed to "glasses_compass" (custom firmware), serving the
3400
+ * tightest sampling any subscriber asked for via session.compass.start().
3401
+ */
3402
+ private recomputeCompassSubscription(): void {
3403
+ const subscribers = this.streamSubscribers.get(MiniappStreamType.GLASSES_COMPASS) ?? new Set<string>()
3404
+ void glassesSensorService.setCompassSubscribers(subscribers).catch((error) => {
3405
+ console.warn(`${LOG_TAG}: compass subscription update failed`, error)
3406
+ })
3407
+ }
3408
+
3022
3409
  /**
3023
3410
  * Last rate we asked the host for. Lets us skip the cross-process call
3024
3411
  * when nothing actually changed (most SUBSCRIBE / unregister churn
@@ -3767,7 +4154,7 @@ class LocalMiniappRuntime {
3767
4154
  })
3768
4155
  }
3769
4156
 
3770
- await new Promise((resolve) => setTimeout(resolve, GENERATE_JOB_POLL_MS))
4157
+ await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, GENERATE_JOB_POLL_MS))
3771
4158
  }
3772
4159
  }
3773
4160
 
@@ -3935,11 +4322,7 @@ class LocalMiniappRuntime {
3935
4322
  * session.system.scanQr — host camera overlay. Must not clear miniapp
3936
4323
  * foreground; the host seam is responsible for presenting a Modal on top.
3937
4324
  */
3938
- private async handleScanQr(
3939
- packageName: string,
3940
- payload: Record<string, unknown>,
3941
- requestId?: string,
3942
- ): Promise<void> {
4325
+ private async handleScanQr(packageName: string, payload: Record<string, unknown>, requestId?: string): Promise<void> {
3943
4326
  try {
3944
4327
  const result = await invokeScanQrSeam(getUiSeams().scanQr, payload)
3945
4328
  this.sendResult(packageName, requestId, true, result)
@@ -4403,6 +4786,10 @@ class LocalMiniappRuntime {
4403
4786
  }
4404
4787
  }
4405
4788
 
4789
+ if (streams.includes(MiniappStreamType.MIRROR_UPDATE) && !this.hasMirrorPermission(packageName)) {
4790
+ return {ok: false, error: "DISPLAY_MIRROR permission not declared"}
4791
+ }
4792
+
4406
4793
  // Add new subscriptions
4407
4794
  const previousSubscriptions = app.subscriptions
4408
4795
  app.subscriptions = new Set(streams)
@@ -4799,12 +5186,55 @@ class LocalMiniappRuntime {
4799
5186
  private ensureCapabilitiesWired(): void {
4800
5187
  if (this.capabilitiesWired) return
4801
5188
  this.capabilitiesWired = true
5189
+ const broadcast = () => this.broadcastCapabilities()
5190
+ // Connection, model and firmware also decide the dashboard view's eligibility.
4802
5191
  useGlassesStore.subscribe(
4803
- (s) => `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}`,
5192
+ (s) =>
5193
+ `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}:${s.sceneVector === true}:${s.firmwareVersion}:${s.connection.state}:${s.deviceModel}`,
5194
+ broadcast,
5195
+ )
5196
+ // The debug knob flips hasNativeNavigation on custom firmware.
5197
+ useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.cfw_native_navigation.key), broadcast)
5198
+ // The dashboard master switch flips the dashboard view.
5199
+ useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.custom_dashboard_enabled.key), broadcast)
5200
+ }
5201
+
5202
+ /** Capabilities as `packageName` sees them: the model profile plus the views this app may draw. */
5203
+ private capabilitiesFor(packageName: string): Capabilities {
5204
+ const capabilities = this.currentCapabilities()
5205
+ if (!capabilities.display) return capabilities
5206
+ const views = displayViewsFor(packageName, pagesEligible())
5207
+ return {...capabilities, display: {...capabilities.display, views}}
5208
+ }
5209
+
5210
+ private broadcastCapabilities(): void {
5211
+ for (const packageName of this.handshookApps) {
5212
+ this.sendToMiniapp(packageName, {
5213
+ type: MiniappResponseType.CAPABILITIES_UPDATE,
5214
+ capabilities: this.capabilitiesFor(packageName),
5215
+ })
5216
+ }
5217
+ }
5218
+
5219
+ /** Host user preferences miniapps follow by default (Settings > Appearance). */
5220
+ private currentPreferences(): MiniappPreferences {
5221
+ const settings = useSettingsStore.getState()
5222
+ return {
5223
+ unitSystem: settings.getSetting(SETTINGS.metric_system.key) === true ? "metric" : "imperial",
5224
+ twelveHourTime: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,
5225
+ }
5226
+ }
5227
+
5228
+ private ensurePreferencesWired(): void {
5229
+ if (this.preferencesWired) return
5230
+ this.preferencesWired = true
5231
+ useSettingsStore.subscribe(
5232
+ (s) =>
5233
+ `${s.getSetting(SETTINGS.metric_system.key) === true}:${s.getSetting(SETTINGS.twelve_hour_time.key) === true}`,
4804
5234
  () => {
4805
- const capabilities = this.currentCapabilities()
5235
+ const preferences = this.currentPreferences()
4806
5236
  for (const packageName of this.handshookApps) {
4807
- this.sendToMiniapp(packageName, {type: MiniappResponseType.CAPABILITIES_UPDATE, capabilities})
5237
+ this.sendToMiniapp(packageName, {type: MiniappResponseType.PREFERENCES_UPDATE, preferences})
4808
5238
  }
4809
5239
  },
4810
5240
  )
@@ -4850,7 +5280,13 @@ class LocalMiniappRuntime {
4850
5280
  * - Cloud sends "head_up" → miniapp protocol uses "head_position" (HEAD_POSITION)
4851
5281
  * - Cloud sends "VAD" (uppercase) → miniapp protocol uses "vad" (lowercase)
4852
5282
  */
4853
- public forwardEvent(streamType: string, data: unknown, transcriptionSource?: TranscriptionEventSource): void {
5283
+ public forwardEvent(
5284
+ streamType: string,
5285
+ data: unknown,
5286
+ transcriptionSource?: TranscriptionEventSource,
5287
+ /** Deliver to this subscriber alone (the designated dashboard app's gestures while it shows). */
5288
+ only?: string,
5289
+ ): void {
4854
5290
  // Local sherpa results enter the runtime only through here (DeviceEventRouter's
4855
5291
  // local_transcription listener); cloud/direct are marked at their onTranscript
4856
5292
  // wiring, which never passes "local".
@@ -4958,6 +5394,7 @@ class LocalMiniappRuntime {
4958
5394
  if (matchedSubs.size === 0 && !perGestureStream) return
4959
5395
 
4960
5396
  for (const packageName of matchedSubs) {
5397
+ if (only !== undefined && packageName !== only) continue
4961
5398
  let transcriptionRoute: "default" | "forceLocal" | "all" | undefined
4962
5399
  if (normalizedStream.startsWith("transcription:") && transcriptionSource) {
4963
5400
  const routes = this.appTranscriptionRoutesForEvent(packageName, normalizedStream)
@@ -4996,6 +5433,7 @@ class LocalMiniappRuntime {
4996
5433
  const gestureSubs = this.streamSubscribers.get(perGestureStream)
4997
5434
  if (gestureSubs) {
4998
5435
  for (const packageName of gestureSubs) {
5436
+ if (only !== undefined && packageName !== only) continue
4999
5437
  this.sendToMiniapp(packageName, {
5000
5438
  type: MiniappResponseType.EVENT,
5001
5439
  streamType: perGestureStream,
@@ -5260,6 +5698,7 @@ class LocalMiniappRuntime {
5260
5698
  name: app.name,
5261
5699
  version: app.version ?? "",
5262
5700
  running: app.running,
5701
+ hidden: app.hidden === true,
5263
5702
  compatibility,
5264
5703
  actions: app.actions ?? [],
5265
5704
  }
@@ -5636,6 +6075,24 @@ class LocalMiniappRuntime {
5636
6075
  })
5637
6076
  }
5638
6077
 
6078
+ /**
6079
+ * Push the app's foreground state to every connected miniapp. Miniapps that
6080
+ * pace their own work (streaming a scene, polling a sensor) need to know when
6081
+ * the host process is backgrounded, because that is when iOS coalesces their
6082
+ * timers and the BLE writer falls back to credit polling.
6083
+ */
6084
+ private broadcastVisibility(foregrounded: boolean): void {
6085
+ const visibility = foregrounded ? "foreground" : "background"
6086
+ for (const packageName of this.connectedApps.keys()) {
6087
+ this.sendVisibilityChange(packageName, visibility)
6088
+ }
6089
+ }
6090
+
6091
+ /** Foreground state a miniapp is given at CONNECT_ACK, before any transition fires. */
6092
+ public currentVisibility(): "foreground" | "background" {
6093
+ return isAppForegrounded() ? "foreground" : "background"
6094
+ }
6095
+
5639
6096
  /**
5640
6097
  * Fast foreground liveness check used when a local miniapp UI is opened or
5641
6098
  * resumed. The normal watchdog intentionally waits ~30s to avoid killing a