@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.100

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 (343) hide show
  1. package/README.md +57 -19
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  11. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/ota-transport.js +2 -0
  13. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  14. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  15. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  17. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  18. package/build/bluetooth-sdk/react.d.ts +2 -0
  19. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/react.js +2 -0
  21. package/build/bluetooth-sdk/react.js.map +1 -0
  22. package/build/bluetooth-sdk/types.d.ts +2 -0
  23. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  24. package/build/bluetooth-sdk/types.js +2 -0
  25. package/build/bluetooth-sdk/types.js.map +1 -0
  26. package/build/engine.d.ts +30 -18
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +12 -3
  29. package/build/engine.js.map +1 -1
  30. package/build/facades/dev.d.ts +1 -1
  31. package/build/facades/display.d.ts +11 -4
  32. package/build/facades/display.d.ts.map +1 -1
  33. package/build/facades/display.js +12 -2
  34. package/build/facades/display.js.map +1 -1
  35. package/build/facades/displayMirror.d.ts +2 -6
  36. package/build/facades/displayMirror.d.ts.map +1 -1
  37. package/build/facades/displayMirror.js +7 -14
  38. package/build/facades/displayMirror.js.map +1 -1
  39. package/build/facades/glasses.d.ts +6 -13
  40. package/build/facades/glasses.d.ts.map +1 -1
  41. package/build/facades/glasses.js +7 -3
  42. package/build/facades/glasses.js.map +1 -1
  43. package/build/facades/ota.d.ts +21 -21
  44. package/build/facades/ota.d.ts.map +1 -1
  45. package/build/facades/ota.js +26 -10
  46. package/build/facades/ota.js.map +1 -1
  47. package/build/facades/pairing.d.ts +11 -5
  48. package/build/facades/pairing.d.ts.map +1 -1
  49. package/build/facades/pairing.js +93 -17
  50. package/build/facades/pairing.js.map +1 -1
  51. package/build/facades/phoneNotifications.d.ts +12 -2
  52. package/build/facades/phoneNotifications.d.ts.map +1 -1
  53. package/build/facades/phoneNotifications.js +30 -10
  54. package/build/facades/phoneNotifications.js.map +1 -1
  55. package/build/facades/speakers.d.ts +1 -1
  56. package/build/facades/speakers.d.ts.map +1 -1
  57. package/build/facades/speakers.js.map +1 -1
  58. package/build/facades/speech.d.ts +7 -6
  59. package/build/facades/speech.d.ts.map +1 -1
  60. package/build/facades/speech.js +1 -1
  61. package/build/facades/speech.js.map +1 -1
  62. package/build/generated/releaseMetadata.d.ts +12 -0
  63. package/build/generated/releaseMetadata.d.ts.map +1 -0
  64. package/build/generated/releaseMetadata.js +10 -0
  65. package/build/generated/releaseMetadata.js.map +1 -0
  66. package/build/index.d.ts +9 -6
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +7 -5
  69. package/build/index.js.map +1 -1
  70. package/build/react/MentraLiveOtaFlow.d.ts +44 -0
  71. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  72. package/build/react/MentraLiveOtaFlow.js +322 -0
  73. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  74. package/build/react/index.d.ts +3 -0
  75. package/build/react/index.d.ts.map +1 -0
  76. package/build/react/index.js +3 -0
  77. package/build/react/index.js.map +1 -0
  78. package/build/react/useEngineSnapshot.d.ts +2 -0
  79. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  80. package/build/react/useEngineSnapshot.js +14 -0
  81. package/build/react/useEngineSnapshot.js.map +1 -0
  82. package/build/react/useMentraLiveOta.d.ts +84 -0
  83. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  84. package/build/react/useMentraLiveOta.js +627 -0
  85. package/build/react/useMentraLiveOta.js.map +1 -0
  86. package/build/services/AppRegistry.d.ts +8 -0
  87. package/build/services/AppRegistry.d.ts.map +1 -1
  88. package/build/services/AppRegistry.js +15 -0
  89. package/build/services/AppRegistry.js.map +1 -1
  90. package/build/services/BlobStore.d.ts +2 -0
  91. package/build/services/BlobStore.d.ts.map +1 -1
  92. package/build/services/BlobStore.js +63 -10
  93. package/build/services/BlobStore.js.map +1 -1
  94. package/build/services/CustomDashboardModel.d.ts +48 -0
  95. package/build/services/CustomDashboardModel.d.ts.map +1 -0
  96. package/build/services/CustomDashboardModel.js +73 -0
  97. package/build/services/CustomDashboardModel.js.map +1 -0
  98. package/build/services/CustomDashboardService.d.ts +55 -0
  99. package/build/services/CustomDashboardService.d.ts.map +1 -0
  100. package/build/services/CustomDashboardService.js +492 -0
  101. package/build/services/CustomDashboardService.js.map +1 -0
  102. package/build/services/DashboardCoordinator.d.ts +26 -0
  103. package/build/services/DashboardCoordinator.d.ts.map +1 -0
  104. package/build/services/DashboardCoordinator.js +125 -0
  105. package/build/services/DashboardCoordinator.js.map +1 -0
  106. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  107. package/build/services/DeviceEventRouter.js +45 -6
  108. package/build/services/DeviceEventRouter.js.map +1 -1
  109. package/build/services/DisplayProcessor.d.ts +3 -3
  110. package/build/services/DisplayProcessor.js +3 -3
  111. package/build/services/DisplayProcessor.js.map +1 -1
  112. package/build/services/GestureDotsService.d.ts +40 -0
  113. package/build/services/GestureDotsService.d.ts.map +1 -0
  114. package/build/services/GestureDotsService.js +167 -0
  115. package/build/services/GestureDotsService.js.map +1 -0
  116. package/build/services/GlassesStatusProjection.d.ts +15 -1
  117. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  118. package/build/services/GlassesStatusProjection.js +18 -11
  119. package/build/services/GlassesStatusProjection.js.map +1 -1
  120. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  121. package/build/services/HotspotOtaTransport.js +8 -7
  122. package/build/services/HotspotOtaTransport.js.map +1 -1
  123. package/build/services/LocalDisplayManager.d.ts +55 -3
  124. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  125. package/build/services/LocalDisplayManager.js +196 -16
  126. package/build/services/LocalDisplayManager.js.map +1 -1
  127. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  128. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  129. package/build/services/LocalMiniappRuntime.js +32 -0
  130. package/build/services/LocalMiniappRuntime.js.map +1 -1
  131. package/build/services/MiniappExitGesture.d.ts +33 -0
  132. package/build/services/MiniappExitGesture.d.ts.map +1 -0
  133. package/build/services/MiniappExitGesture.js +54 -0
  134. package/build/services/MiniappExitGesture.js.map +1 -0
  135. package/build/services/OtaAutoChain.d.ts +41 -0
  136. package/build/services/OtaAutoChain.d.ts.map +1 -0
  137. package/build/services/OtaAutoChain.js +91 -0
  138. package/build/services/OtaAutoChain.js.map +1 -0
  139. package/build/services/OtaErrorMapping.d.ts +18 -0
  140. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  141. package/build/services/OtaErrorMapping.js +63 -0
  142. package/build/services/OtaErrorMapping.js.map +1 -0
  143. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  144. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  145. package/build/services/OtaInstallCoordinator.js +38 -13
  146. package/build/services/OtaInstallCoordinator.js.map +1 -1
  147. package/build/services/OtaService.d.ts.map +1 -1
  148. package/build/services/OtaService.js +1 -1
  149. package/build/services/OtaService.js.map +1 -1
  150. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  151. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  152. package/build/services/OtaUpdateCheckService.js +28 -16
  153. package/build/services/OtaUpdateCheckService.js.map +1 -1
  154. package/build/services/PhoneNotificationsSync.d.ts +13 -0
  155. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  156. package/build/services/PhoneNotificationsSync.js +101 -9
  157. package/build/services/PhoneNotificationsSync.js.map +1 -1
  158. package/build/services/PhonePhotoCoordinator.js +1 -1
  159. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  160. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  161. package/build/services/PhoneStreamCoordinator.js +9 -6
  162. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  163. package/build/services/SceneRenderer.d.ts.map +1 -1
  164. package/build/services/SceneRenderer.js +5 -0
  165. package/build/services/SceneRenderer.js.map +1 -1
  166. package/build/services/StreamLifecycleController.d.ts +4 -0
  167. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  168. package/build/services/StreamLifecycleController.js +4 -0
  169. package/build/services/StreamLifecycleController.js.map +1 -1
  170. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  171. package/build/services/asg/localNetworkTransport.js +13 -12
  172. package/build/services/asg/localNetworkTransport.js.map +1 -1
  173. package/build/services/effectiveCapabilities.d.ts +1 -1
  174. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  175. package/build/services/effectiveCapabilities.js +26 -7
  176. package/build/services/effectiveCapabilities.js.map +1 -1
  177. package/build/services/gestureClaims.d.ts +18 -0
  178. package/build/services/gestureClaims.d.ts.map +1 -0
  179. package/build/services/gestureClaims.js +24 -0
  180. package/build/services/gestureClaims.js.map +1 -0
  181. package/build/services/gestureDotGlyphs.d.ts +8 -0
  182. package/build/services/gestureDotGlyphs.d.ts.map +1 -0
  183. package/build/services/gestureDotGlyphs.js +13 -0
  184. package/build/services/gestureDotGlyphs.js.map +1 -0
  185. package/build/services/glassesClockSync.d.ts.map +1 -1
  186. package/build/services/glassesClockSync.js +5 -1
  187. package/build/services/glassesClockSync.js.map +1 -1
  188. package/build/services/iosNotificationAppCatalog.d.ts +13 -0
  189. package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
  190. package/build/services/iosNotificationAppCatalog.js +28 -0
  191. package/build/services/iosNotificationAppCatalog.js.map +1 -0
  192. package/build/services/otaDisplayState.d.ts +1 -1
  193. package/build/services/otaDisplayState.d.ts.map +1 -1
  194. package/build/services/otaDisplayState.js.map +1 -1
  195. package/build/services/otaInstallPolicy.d.ts +5 -1
  196. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  197. package/build/services/otaInstallPolicy.js +4 -0
  198. package/build/services/otaInstallPolicy.js.map +1 -1
  199. package/build/services/otaLegacyMapping.d.ts +1 -1
  200. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  201. package/build/services/otaLegacyMapping.js.map +1 -1
  202. package/build/services/otaManifestPolicy.d.ts +14 -0
  203. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  204. package/build/services/otaManifestPolicy.js +20 -0
  205. package/build/services/otaManifestPolicy.js.map +1 -0
  206. package/build/services/otaManifestUrl.d.ts +2 -1
  207. package/build/services/otaManifestUrl.d.ts.map +1 -1
  208. package/build/services/otaManifestUrl.js +24 -32
  209. package/build/services/otaManifestUrl.js.map +1 -1
  210. package/build/services/otaReleaseVersion.d.ts +10 -0
  211. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  212. package/build/services/otaReleaseVersion.js +14 -0
  213. package/build/services/otaReleaseVersion.js.map +1 -0
  214. package/build/stores/appVisibility.d.ts +3 -0
  215. package/build/stores/appVisibility.d.ts.map +1 -0
  216. package/build/stores/appVisibility.js +3 -0
  217. package/build/stores/appVisibility.js.map +1 -0
  218. package/build/stores/apps.d.ts.map +1 -1
  219. package/build/stores/apps.js +7 -1
  220. package/build/stores/apps.js.map +1 -1
  221. package/build/stores/core.d.ts +3 -3
  222. package/build/stores/core.d.ts.map +1 -1
  223. package/build/stores/core.js.map +1 -1
  224. package/build/stores/display.d.ts +4 -4
  225. package/build/stores/display.d.ts.map +1 -1
  226. package/build/stores/display.js +12 -36
  227. package/build/stores/display.js.map +1 -1
  228. package/build/stores/glasses.d.ts +10 -4
  229. package/build/stores/glasses.d.ts.map +1 -1
  230. package/build/stores/glasses.js +2 -0
  231. package/build/stores/glasses.js.map +1 -1
  232. package/build/stores/settings.d.ts.map +1 -1
  233. package/build/stores/settings.js +140 -5
  234. package/build/stores/settings.js.map +1 -1
  235. package/build/types/applet.d.ts +3 -1
  236. package/build/types/applet.d.ts.map +1 -1
  237. package/build/types/applet.js +0 -3
  238. package/build/types/applet.js.map +1 -1
  239. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  240. package/build/types/capabilities/even-realities-g2.js +2 -2
  241. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  242. package/build/types/enums.d.ts +1 -5
  243. package/build/types/enums.d.ts.map +1 -1
  244. package/build/types/enums.js +1 -8
  245. package/build/types/enums.js.map +1 -1
  246. package/build/types/hardware.d.ts +1 -1
  247. package/build/types/hardware.d.ts.map +1 -1
  248. package/build/types/hardware.js +1 -0
  249. package/build/types/hardware.js.map +1 -1
  250. package/build/types/index.d.ts +3 -7
  251. package/build/types/index.d.ts.map +1 -1
  252. package/build/types/index.js +3 -7
  253. package/build/types/index.js.map +1 -1
  254. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  255. package/build/utils/display/composer/ColumnComposer.js +1 -1
  256. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  257. package/build/utils/display/scene/process.d.ts.map +1 -1
  258. package/build/utils/display/scene/process.js +4 -3
  259. package/build/utils/display/scene/process.js.map +1 -1
  260. package/build/utils/display/scene/types.d.ts +6 -2
  261. package/build/utils/display/scene/types.d.ts.map +1 -1
  262. package/build/utils/display/scene/types.js.map +1 -1
  263. package/package.json +69 -35
  264. package/src/bluetooth-sdk/debug.ts +1 -0
  265. package/src/bluetooth-sdk/index.ts +2 -0
  266. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  267. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  268. package/src/bluetooth-sdk/react.ts +1 -0
  269. package/src/bluetooth-sdk/types.ts +1 -0
  270. package/src/engine.ts +12 -3
  271. package/src/facades/display.ts +12 -2
  272. package/src/facades/displayMirror.ts +7 -15
  273. package/src/facades/glasses.ts +8 -4
  274. package/src/facades/ota.ts +36 -11
  275. package/src/facades/pairing.ts +103 -23
  276. package/src/facades/phoneNotifications.ts +32 -10
  277. package/src/facades/speakers.ts +1 -1
  278. package/src/facades/speech.ts +21 -7
  279. package/src/generated/releaseMetadata.ts +20 -0
  280. package/src/index.ts +31 -5
  281. package/src/react/MentraLiveOtaFlow.tsx +754 -0
  282. package/src/react/index.ts +21 -0
  283. package/src/react/useEngineSnapshot.ts +16 -0
  284. package/src/react/useMentraLiveOta.ts +793 -0
  285. package/src/services/AppRegistry.ts +16 -0
  286. package/src/services/BlobStore.ts +57 -10
  287. package/src/services/CustomDashboardModel.ts +83 -0
  288. package/src/services/CustomDashboardService.ts +516 -0
  289. package/src/services/DashboardCoordinator.ts +121 -0
  290. package/src/services/DeviceEventRouter.ts +48 -8
  291. package/src/services/DisplayProcessor.ts +3 -3
  292. package/src/services/GestureDotsService.ts +164 -0
  293. package/src/services/GlassesStatusProjection.ts +20 -11
  294. package/src/services/HotspotOtaTransport.ts +8 -7
  295. package/src/services/LocalDisplayManager.ts +198 -18
  296. package/src/services/LocalMiniappRuntime.ts +37 -0
  297. package/src/services/MiniappExitGesture.ts +76 -0
  298. package/src/services/OtaAutoChain.ts +130 -0
  299. package/src/services/OtaErrorMapping.ts +80 -0
  300. package/src/services/OtaInstallCoordinator.ts +40 -13
  301. package/src/services/OtaService.ts +1 -2
  302. package/src/services/OtaUpdateCheckService.ts +33 -19
  303. package/src/services/PhoneNotificationsSync.ts +125 -9
  304. package/src/services/PhonePhotoCoordinator.ts +1 -1
  305. package/src/services/PhoneStreamCoordinator.ts +10 -6
  306. package/src/services/SceneRenderer.ts +4 -0
  307. package/src/services/StreamLifecycleController.ts +4 -0
  308. package/src/services/asg/localNetworkTransport.ts +20 -19
  309. package/src/services/effectiveCapabilities.ts +27 -7
  310. package/src/services/gestureClaims.ts +28 -0
  311. package/src/services/gestureDotGlyphs.ts +15 -0
  312. package/src/services/glassesClockSync.ts +5 -1
  313. package/src/services/iosNotificationAppCatalog.ts +39 -0
  314. package/src/services/otaDisplayState.ts +1 -1
  315. package/src/services/otaInstallPolicy.ts +5 -1
  316. package/src/services/otaLegacyMapping.ts +1 -1
  317. package/src/services/otaManifestPolicy.ts +43 -0
  318. package/src/services/otaManifestUrl.ts +27 -36
  319. package/src/services/otaReleaseVersion.ts +22 -0
  320. package/src/stores/appVisibility.ts +3 -0
  321. package/src/stores/apps.ts +7 -1
  322. package/src/stores/core.ts +4 -4
  323. package/src/stores/display.ts +16 -48
  324. package/src/stores/glasses.ts +15 -6
  325. package/src/stores/settings.ts +140 -5
  326. package/src/types/applet.ts +3 -1
  327. package/src/types/capabilities/even-realities-g2.ts +3 -2
  328. package/src/types/enums.ts +1 -9
  329. package/src/types/hardware.ts +2 -1
  330. package/src/types/index.ts +3 -7
  331. package/src/utils/display/composer/ColumnComposer.ts +1 -1
  332. package/src/utils/display/scene/process.ts +4 -3
  333. package/src/utils/display/scene/types.ts +0 -0
  334. package/build/devtools.d.ts +0 -11
  335. package/build/devtools.d.ts.map +0 -1
  336. package/build/devtools.js +0 -13
  337. package/build/devtools.js.map +0 -1
  338. package/build/internal.d.ts +0 -67
  339. package/build/internal.d.ts.map +0 -1
  340. package/build/internal.js +0 -90
  341. package/build/internal.js.map +0 -1
  342. package/src/devtools.ts +0 -13
  343. package/src/internal.ts +0 -155
@@ -0,0 +1,26 @@
1
+ declare class DashboardCoordinator {
2
+ private opened;
3
+ private renderer;
4
+ private legacyTimer;
5
+ start(): void;
6
+ stop(): void;
7
+ get isOpen(): boolean;
8
+ /** Head position (real or simulated). Opening respects contextual_dashboard. */
9
+ setHeadUp(up: boolean): void;
10
+ /** Double-tap on a CFW-eligible device. False when not eligible. */
11
+ toggleCustom(): boolean;
12
+ /** Double-tap fallback for devices without a firmware dashboard. */
13
+ toggleLegacy(): void;
14
+ /** True when an open dashboard consumed the gesture. */
15
+ handleGesture(gestureName: string): boolean;
16
+ /** Show the dashboard over the main content (ignores contextual_dashboard). */
17
+ open(): void;
18
+ /** Close and restore the retained main content. */
19
+ close(): void;
20
+ /** Parity with the retired native head-up wall: time, date, battery. */
21
+ private renderLegacy;
22
+ }
23
+ declare const dashboardCoordinator: DashboardCoordinator;
24
+ export default dashboardCoordinator;
25
+ export { DashboardCoordinator };
26
+ //# sourceMappingURL=DashboardCoordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DashboardCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/DashboardCoordinator.ts"],"names":[],"mappings":"AAgBA,cAAM,oBAAoB;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,WAAW,CAAsB;IAEzC,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAKZ,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,gFAAgF;IAChF,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAU5B,oEAAoE;IACpE,YAAY,IAAI,OAAO;IAOvB,oEAAoE;IACpE,YAAY,IAAI,IAAI;IAKpB,wDAAwD;IACxD,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAU3C,+EAA+E;IAC/E,IAAI,IAAI,IAAI;IAeZ,mDAAmD;IACnD,KAAK,IAAI,IAAI;IAYb,wEAAwE;IACxE,OAAO,CAAC,YAAY;CAgBrB;AAED,QAAA,MAAM,oBAAoB,sBAA6B,CAAA;AACvD,eAAe,oBAAoB,CAAA;AACnC,OAAO,EAAC,oBAAoB,EAAC,CAAA"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Phone-side dashboard switching. Native retains a single display state; this
3
+ * service decides when the dashboard covers the main content (head-up,
4
+ * double-tap) and which renderer draws it: the CFW custom dashboard, or the
5
+ * legacy time/battery wall for stock devices.
6
+ */
7
+ import { useGlassesStore } from "../stores/glasses";
8
+ import { SETTINGS, useSettingsStore } from "../stores/settings";
9
+ import { BgTimer } from "../utils/timers";
10
+ import customDashboardService from "./CustomDashboardService";
11
+ import { isGlassesConnected } from "./GlassesReadiness";
12
+ import localDisplayManager from "./LocalDisplayManager";
13
+ const LEGACY_CLOCK_REFRESH_MS = 30_000;
14
+ const LEGACY_DASHBOARD_PKG = "system.dashboard";
15
+ class DashboardCoordinator {
16
+ opened = false;
17
+ renderer = null;
18
+ legacyTimer = null;
19
+ start() {
20
+ customDashboardService.onRequestClose = () => this.close();
21
+ }
22
+ stop() {
23
+ this.close();
24
+ customDashboardService.onRequestClose = null;
25
+ }
26
+ get isOpen() {
27
+ return this.opened;
28
+ }
29
+ /** Head position (real or simulated). Opening respects contextual_dashboard. */
30
+ setHeadUp(up) {
31
+ if (up === this.opened)
32
+ return;
33
+ if (up) {
34
+ if (useSettingsStore.getState().getSetting(SETTINGS.contextual_dashboard.key) === false)
35
+ return;
36
+ this.open();
37
+ }
38
+ else {
39
+ this.close();
40
+ }
41
+ }
42
+ /** Double-tap on a CFW-eligible device. False when not eligible. */
43
+ toggleCustom() {
44
+ if (!customDashboardService.isEligible())
45
+ return false;
46
+ if (this.opened)
47
+ this.close();
48
+ else
49
+ this.open();
50
+ return true;
51
+ }
52
+ /** Double-tap fallback for devices without a firmware dashboard. */
53
+ toggleLegacy() {
54
+ if (this.opened)
55
+ this.close();
56
+ else
57
+ this.open();
58
+ }
59
+ /** True when an open dashboard consumed the gesture. */
60
+ handleGesture(gestureName) {
61
+ if (!this.opened)
62
+ return false;
63
+ if (this.renderer === "custom")
64
+ return customDashboardService.handleGesture(gestureName);
65
+ if (gestureName === "double_tap") {
66
+ this.close();
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+ /** Show the dashboard over the main content (ignores contextual_dashboard). */
72
+ open() {
73
+ if (this.opened)
74
+ return;
75
+ if (!isGlassesConnected(useGlassesStore.getState().connection))
76
+ return;
77
+ this.opened = true;
78
+ localDisplayManager.setDashboardActive(true);
79
+ if (customDashboardService.isEligible()) {
80
+ this.renderer = "custom";
81
+ customDashboardService.show();
82
+ }
83
+ else {
84
+ this.renderer = "legacy";
85
+ this.renderLegacy();
86
+ this.legacyTimer = BgTimer.setInterval(() => this.renderLegacy(), LEGACY_CLOCK_REFRESH_MS);
87
+ }
88
+ }
89
+ /** Close and restore the retained main content. */
90
+ close() {
91
+ if (!this.opened)
92
+ return;
93
+ this.opened = false;
94
+ if (this.legacyTimer !== null) {
95
+ BgTimer.clearInterval(this.legacyTimer);
96
+ this.legacyTimer = null;
97
+ }
98
+ if (this.renderer === "custom")
99
+ customDashboardService.hide();
100
+ this.renderer = null;
101
+ localDisplayManager.setDashboardActive(false);
102
+ }
103
+ /** Parity with the retired native head-up wall: time, date, battery. */
104
+ renderLegacy() {
105
+ if (!this.opened)
106
+ return;
107
+ const now = new Date();
108
+ const time = new Intl.DateTimeFormat(undefined, {
109
+ hour: "2-digit",
110
+ minute: "2-digit",
111
+ hour12: useSettingsStore.getState().getSetting(SETTINGS.twelve_hour_time.key) === true,
112
+ }).format(now);
113
+ const date = `${String(now.getMonth() + 1).padStart(2, "0")}/${String(now.getDate()).padStart(2, "0")}`;
114
+ const level = useGlassesStore.getState().batteryLevel;
115
+ const batt = typeof level === "number" && level >= 0 ? ` ${level}%` : "";
116
+ localDisplayManager.renderDashboard(LEGACY_DASHBOARD_PKG, {
117
+ view: "dashboard",
118
+ layout: { layoutType: "text_wall", text: `${time} ${date}${batt} Connected` },
119
+ });
120
+ }
121
+ }
122
+ const dashboardCoordinator = new DashboardCoordinator();
123
+ export default dashboardCoordinator;
124
+ export { DashboardCoordinator };
125
+ //# sourceMappingURL=DashboardCoordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DashboardCoordinator.js","sourceRoot":"","sources":["../../src/services/DashboardCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone-side dashboard switching. Native retains a single display state; this\n * service decides when the dashboard covers the main content (head-up,\n * double-tap) and which renderer draws it: the CFW custom dashboard, or the\n * legacy time/battery wall for stock devices.\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {BgTimer} from \"../utils/timers\"\nimport customDashboardService from \"./CustomDashboardService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\n\nconst LEGACY_CLOCK_REFRESH_MS = 30_000\nconst LEGACY_DASHBOARD_PKG = \"system.dashboard\"\n\nclass DashboardCoordinator {\n private opened = false\n private renderer: \"custom\" | \"legacy\" | null = null\n private legacyTimer: number | null = null\n\n start(): void {\n customDashboardService.onRequestClose = () => this.close()\n }\n\n stop(): void {\n this.close()\n customDashboardService.onRequestClose = null\n }\n\n get isOpen(): boolean {\n return this.opened\n }\n\n /** Head position (real or simulated). Opening respects contextual_dashboard. */\n setHeadUp(up: boolean): void {\n if (up === this.opened) return\n if (up) {\n if (useSettingsStore.getState().getSetting(SETTINGS.contextual_dashboard.key) === false) return\n this.open()\n } else {\n this.close()\n }\n }\n\n /** Double-tap on a CFW-eligible device. False when not eligible. */\n toggleCustom(): boolean {\n if (!customDashboardService.isEligible()) return false\n if (this.opened) this.close()\n else this.open()\n return true\n }\n\n /** Double-tap fallback for devices without a firmware dashboard. */\n toggleLegacy(): void {\n if (this.opened) this.close()\n else this.open()\n }\n\n /** True when an open dashboard consumed the gesture. */\n handleGesture(gestureName: string): boolean {\n if (!this.opened) return false\n if (this.renderer === \"custom\") return customDashboardService.handleGesture(gestureName)\n if (gestureName === \"double_tap\") {\n this.close()\n return true\n }\n return false\n }\n\n /** Show the dashboard over the main content (ignores contextual_dashboard). */\n open(): void {\n if (this.opened) return\n if (!isGlassesConnected(useGlassesStore.getState().connection)) return\n this.opened = true\n localDisplayManager.setDashboardActive(true)\n if (customDashboardService.isEligible()) {\n this.renderer = \"custom\"\n customDashboardService.show()\n } else {\n this.renderer = \"legacy\"\n this.renderLegacy()\n this.legacyTimer = BgTimer.setInterval(() => this.renderLegacy(), LEGACY_CLOCK_REFRESH_MS)\n }\n }\n\n /** Close and restore the retained main content. */\n close(): void {\n if (!this.opened) return\n this.opened = false\n if (this.legacyTimer !== null) {\n BgTimer.clearInterval(this.legacyTimer)\n this.legacyTimer = null\n }\n if (this.renderer === \"custom\") customDashboardService.hide()\n this.renderer = null\n localDisplayManager.setDashboardActive(false)\n }\n\n /** Parity with the retired native head-up wall: time, date, battery. */\n private renderLegacy(): void {\n if (!this.opened) return\n const now = new Date()\n const time = new Intl.DateTimeFormat(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: useSettingsStore.getState().getSetting(SETTINGS.twelve_hour_time.key) === true,\n }).format(now)\n const date = `${String(now.getMonth() + 1).padStart(2, \"0\")}/${String(now.getDate()).padStart(2, \"0\")}`\n const level = useGlassesStore.getState().batteryLevel\n const batt = typeof level === \"number\" && level >= 0 ? ` ${level}%` : \"\"\n localDisplayManager.renderDashboard(LEGACY_DASHBOARD_PKG, {\n view: \"dashboard\",\n layout: {layoutType: \"text_wall\", text: `${time} ${date}${batt} Connected`},\n })\n }\n}\n\nconst dashboardCoordinator = new DashboardCoordinator()\nexport default dashboardCoordinator\nexport {DashboardCoordinator}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAqCA,wBAAgB,sBAAsB,IAAI,IAAI,CA+N7C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C"}
1
+ {"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAoDA,wBAAgB,sBAAsB,IAAI,IAAI,CAuP7C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C"}
@@ -19,7 +19,9 @@
19
19
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
20
20
  import { shallow } from "zustand/shallow";
21
21
  import localMiniappRuntime from "./LocalMiniappRuntime";
22
+ import localDisplayManager from "./LocalDisplayManager";
22
23
  import localSttFallbackCoordinator from "./LocalSttFallbackCoordinator";
24
+ import { MiniappExitGesture } from "./MiniappExitGesture";
23
25
  import nativeNavigationService from "./NativeNavigationService";
24
26
  import speakerLabelResolver from "./SpeakerLabelResolver";
25
27
  import { phonePhotoCoordinator } from "./PhonePhotoCoordinator";
@@ -30,8 +32,20 @@ import { SETTINGS, useSettingsStore } from "../stores/settings";
30
32
  import { useAppStatusStore } from "../stores/apps";
31
33
  import { retirePendingSelectionOnPromotion } from "./PairingIdentity";
32
34
  import GlobalEventEmitter from "../utils/GlobalEventEmitter";
35
+ import { BgTimer } from "../utils/timers";
33
36
  import { asgCameraApi } from "./asg/asgCameraApi";
37
+ import dashboardCoordinator from "./DashboardCoordinator";
38
+ import gestureDotsService from "./GestureDotsService";
34
39
  let subs = [];
40
+ const miniappExitGesture = new MiniappExitGesture({
41
+ getState: () => useAppStatusStore.getState(),
42
+ getVisiblePackageName: () => localDisplayManager.getVisibleMiniappPackageName(),
43
+ beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),
44
+ finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),
45
+ cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),
46
+ setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),
47
+ clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),
48
+ });
35
49
  export function startDeviceEventRouter() {
36
50
  if (subs.length)
37
51
  return;
@@ -140,11 +154,36 @@ export function startDeviceEventRouter() {
140
154
  localMiniappRuntime.forwardEvent("button_press", event);
141
155
  }));
142
156
  subs.push(BluetoothSdk.addListener("touch_event", (event) => {
157
+ // While the custom dashboard is visible it owns its focus/navigation
158
+ // gestures; do not leak those into a miniapp running underneath it.
159
+ if (dashboardCoordinator.handleGesture(event.gestureName))
160
+ return;
161
+ // Gesture-dots debug indicator (any device, gated by its setting). It
162
+ // only paints while the screen is otherwise empty; the gesture still
163
+ // flows to miniapps below.
164
+ gestureDotsService.noteGesture(event.gestureName);
143
165
  localMiniappRuntime.forwardEvent("touch_event", event);
144
- // Double-tap policy, moved out of the native G2 handlers: open the native
145
- // dashboard when the setting is on, else toggle head-up. Native only
146
- // reports the gesture now.
166
+ // Double-tap policy, moved out of the native G2 handlers: exit a visible
167
+ // unclaimed miniapp; otherwise open the dashboard or toggle head-up.
168
+ // Native only reports the gesture now.
147
169
  if (event.gestureName === "double_tap") {
170
+ // A foreground miniapp that claimed double_tap (CLAIM_GESTURES) is
171
+ // using it as an in-app gesture (e.g. the terminal's back-to-menu) —
172
+ // the event was already forwarded above; skip the system default.
173
+ if (localMiniappRuntime.foregroundClaimsGesture("double_tap"))
174
+ return;
175
+ // An unclaimed double tap exits only the miniapp whose frame is visibly
176
+ // on the glasses. The controller shows the bounded OS notice first,
177
+ // then uses the normal store stop path (and closes its phone overlay).
178
+ // Checked before the dashboard toggle: while an app owns the screen an
179
+ // unclaimed double-tap always means "leave the app" — the (custom or
180
+ // native) dashboard is for the empty screen, and head-up still opens
181
+ // the custom dashboard mid-app.
182
+ if (miniappExitGesture.tryExitVisibleMiniapp())
183
+ return;
184
+ // Eligible G2 custom firmware replaces the native dashboard.
185
+ if (dashboardCoordinator.toggleCustom())
186
+ return;
148
187
  const useNativeDashboard = useSettingsStore.getState().getSetting(SETTINGS.use_native_dashboard.key) !== false;
149
188
  if (useNativeDashboard) {
150
189
  // The modal nav UI owns the screen; showDashboard would fight it.
@@ -155,9 +194,7 @@ export function startDeviceEventRouter() {
155
194
  }
156
195
  }
157
196
  else {
158
- void Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((err) => {
159
- console.error("DeviceEventRouter: toggleHeadUp failed:", err);
160
- });
197
+ dashboardCoordinator.toggleLegacy();
161
198
  }
162
199
  }
163
200
  }));
@@ -172,6 +209,7 @@ export function startDeviceEventRouter() {
172
209
  }));
173
210
  // Head position — translate native {up:boolean} → SDK {position:"up"|"down"}.
174
211
  subs.push(BluetoothSdk.addListener("head_up", (event) => {
212
+ dashboardCoordinator.setHeadUp(event.up);
175
213
  localMiniappRuntime.forwardEvent("head_up", { position: event.up ? "up" : "down", timestamp: Date.now() });
176
214
  }));
177
215
  // On-device STT transcripts → local miniapps only while the local engine is
@@ -210,5 +248,6 @@ export function stopDeviceEventRouter() {
210
248
  for (const sub of subs)
211
249
  sub.remove();
212
250
  subs = [];
251
+ miniappExitGesture.reset();
213
252
  }
214
253
  //# sourceMappingURL=DeviceEventRouter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport speakerLabelResolver from \"./SpeakerLabelResolver\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const connected = isGlassesConnected(s.connection) && s.wifi.state === \"connected\"\n return {\n connected,\n ssid: s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status / keep-alive → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"keep_alive_ack\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleKeepAliveAck(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n // Double-tap policy, moved out of the native G2 handlers: open the native\n // dashboard when the setting is on, else toggle head-up. Native only\n // reports the gesture now.\n if (event.gestureName === \"double_tap\") {\n const useNativeDashboard =\n useSettingsStore.getState().getSetting(SETTINGS.use_native_dashboard.key) !== false\n if (useNativeDashboard) {\n // The modal nav UI owns the screen; showDashboard would fight it.\n if (!nativeNavigationService.isActive) {\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n } else {\n void Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((err) => {\n console.error(\"DeviceEventRouter: toggleHeadUp failed:\", err)\n })\n }\n }\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n // Native stamps resolvedSpeakerId when the voice matched the enrolled\n // profile; surface that as `isUser` (the native join has no\n // confident-other signal, so unmatched stays unannotated).\n localMiniappRuntime.forwardEvent(\n `transcription:${lang}`,\n speakerLabelResolver.annotateLocalTranscript(event),\n \"local\",\n )\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n}\n"]}
1
+ {"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport {MiniappExitGesture} from \"./MiniappExitGesture\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport speakerLabelResolver from \"./SpeakerLabelResolver\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {BgTimer} from \"../utils/timers\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\nimport dashboardCoordinator from \"./DashboardCoordinator\"\nimport gestureDotsService from \"./GestureDotsService\"\n\nlet subs: Array<{remove: () => void}> = []\n\nconst miniappExitGesture = new MiniappExitGesture({\n getState: () => useAppStatusStore.getState(),\n getVisiblePackageName: () => localDisplayManager.getVisibleMiniappPackageName(),\n beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),\n finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),\n cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),\n setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),\n clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),\n})\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const connected = isGlassesConnected(s.connection) && s.wifi.state === \"connected\"\n return {\n connected,\n ssid: s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status / keep-alive → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"keep_alive_ack\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleKeepAliveAck(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n // While the custom dashboard is visible it owns its focus/navigation\n // gestures; do not leak those into a miniapp running underneath it.\n if (dashboardCoordinator.handleGesture(event.gestureName)) return\n\n // Gesture-dots debug indicator (any device, gated by its setting). It\n // only paints while the screen is otherwise empty; the gesture still\n // flows to miniapps below.\n gestureDotsService.noteGesture(event.gestureName)\n\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n // Double-tap policy, moved out of the native G2 handlers: exit a visible\n // unclaimed miniapp; otherwise open the dashboard or toggle head-up.\n // Native only reports the gesture now.\n if (event.gestureName === \"double_tap\") {\n // A foreground miniapp that claimed double_tap (CLAIM_GESTURES) is\n // using it as an in-app gesture (e.g. the terminal's back-to-menu) —\n // the event was already forwarded above; skip the system default.\n if (localMiniappRuntime.foregroundClaimsGesture(\"double_tap\")) return\n\n // An unclaimed double tap exits only the miniapp whose frame is visibly\n // on the glasses. The controller shows the bounded OS notice first,\n // then uses the normal store stop path (and closes its phone overlay).\n // Checked before the dashboard toggle: while an app owns the screen an\n // unclaimed double-tap always means \"leave the app\" — the (custom or\n // native) dashboard is for the empty screen, and head-up still opens\n // the custom dashboard mid-app.\n if (miniappExitGesture.tryExitVisibleMiniapp()) return\n\n // Eligible G2 custom firmware replaces the native dashboard.\n if (dashboardCoordinator.toggleCustom()) return\n\n const useNativeDashboard = useSettingsStore.getState().getSetting(SETTINGS.use_native_dashboard.key) !== false\n if (useNativeDashboard) {\n // The modal nav UI owns the screen; showDashboard would fight it.\n if (!nativeNavigationService.isActive) {\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n } else {\n dashboardCoordinator.toggleLegacy()\n }\n }\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n dashboardCoordinator.setHeadUp(event.up)\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n // Native stamps resolvedSpeakerId when the voice matched the enrolled\n // profile; surface that as `isUser` (the native join has no\n // confident-other signal, so unmatched stays unannotated).\n localMiniappRuntime.forwardEvent(\n `transcription:${lang}`,\n speakerLabelResolver.annotateLocalTranscript(event),\n \"local\",\n )\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n miniappExitGesture.reset()\n}\n"]}
@@ -2,15 +2,15 @@
2
2
  * DisplayProcessor
3
3
  *
4
4
  * Processes display events with pixel-accurate text wrapping using the same
5
- * display-utils library as the cloud. This ensures the mobile preview matches
6
- * exactly what is shown on the glasses.
5
+ * Engine display utilities. This ensures the mobile preview matches exactly
6
+ * what is shown on the glasses.
7
7
  *
8
8
  * Key responsibilities:
9
9
  * 1. Intercept display events before sending to native SGC
10
10
  * 2. Wrap text using the correct device profile (G1, etc.)
11
11
  * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show
12
12
  *
13
- * @see cloud/issues/026-mobile-display-processor for design docs
13
+ * @see notes/miniapp-display-api-redesign.md for the current display design
14
14
  */
15
15
  import { type DisplayProfile, type WrapOptions, type BreakMode } from "../utils/display";
16
16
  /**
@@ -2,15 +2,15 @@
2
2
  * DisplayProcessor
3
3
  *
4
4
  * Processes display events with pixel-accurate text wrapping using the same
5
- * display-utils library as the cloud. This ensures the mobile preview matches
6
- * exactly what is shown on the glasses.
5
+ * Engine display utilities. This ensures the mobile preview matches exactly
6
+ * what is shown on the glasses.
7
7
  *
8
8
  * Key responsibilities:
9
9
  * 1. Intercept display events before sending to native SGC
10
10
  * 2. Wrap text using the correct device profile (G1, etc.)
11
11
  * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show
12
12
  *
13
- * @see cloud/issues/026-mobile-display-processor for design docs
13
+ * @see notes/miniapp-display-api-redesign.md for the current display design
14
14
  */
15
15
  import { createDisplayToolkit, G1_PROFILE, G2_PROFILE, Z100_PROFILE, NEX_PROFILE, ColumnComposer, } from "../utils/display";
16
16
  import { ISLAND_SETTINGS_KEYS } from "../runtime/config";
@@ -1 +1 @@
1
- {"version":3,"file":"DisplayProcessor.js","sourceRoot":"","sources":["../../src/services/DisplayProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DisplayProcessor\n *\n * Processes display events with pixel-accurate text wrapping using the same\n * display-utils library as the cloud. This ensures the mobile preview matches\n * exactly what is shown on the glasses.\n *\n * Key responsibilities:\n * 1. Intercept display events before sending to native SGC\n * 2. Wrap text using the correct device profile (G1, etc.)\n * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show\n *\n * @see cloud/issues/026-mobile-display-processor for design docs\n */\n\nimport {\n createDisplayToolkit,\n G1_PROFILE,\n G2_PROFILE,\n Z100_PROFILE,\n NEX_PROFILE,\n TextMeasurer,\n TextWrapper,\n ColumnComposer,\n type DisplayProfile,\n type WrapOptions,\n type BreakMode,\n} from \"../utils/display\"\n\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Supported device models for display processing\n */\nexport type DeviceModel = \"g1\" | \"g2\" | \"z100\" | \"nex\" | \"mach1\" | \"mentra-live\" | \"simulated\" | \"unknown\"\n\n/**\n * Display event types that we process\n */\nexport type DisplayLayoutType =\n | \"text_wall\"\n | \"text_line\"\n | \"text_rows\"\n | \"reference_card\"\n | \"double_text_wall\"\n | \"bitmap_view\"\n\n/**\n * Raw display event from the cloud/WebSocket\n */\nexport interface DisplayEvent {\n view: \"main\" | \"dashboard\"\n layoutType?: DisplayLayoutType\n layout?: {\n layoutType: DisplayLayoutType\n text?: string | string[] // string for text_wall/text_line, string[] for text_rows\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n data?: string // For bitmap_view\n x?: number // bitmap_view container position/size (forwarded to native, used by G2)\n y?: number\n width?: number\n height?: number\n [key: string]: unknown\n }\n text?: string | string[]\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n [key: string]: unknown\n}\n\n/**\n * Processed display event with guaranteed wrapped text\n */\nexport interface ProcessedDisplayEvent extends DisplayEvent {\n /** Marks this event as processed */\n _processed: true\n /** The device profile used for processing */\n _profile: string\n /** Pre-split lines for text_wall/text_line (for easy rendering) */\n _lines?: string[]\n /** Original layout type before processing (e.g., double_text_wall -> text_wall) */\n _originalLayoutType?: DisplayLayoutType\n /** Column composition metadata for double_text_wall */\n _composedColumns?: {\n leftLines: string[]\n rightLines: string[]\n config: {\n leftColumnWidthPx: number\n rightColumnStartPx: number\n rightColumnWidthPx: number\n maxLines: number\n leftMarginSpaces?: number\n }\n }\n}\n\n/**\n * Options for the DisplayProcessor\n */\nexport interface DisplayProcessorOptions {\n /** Default break mode for text wrapping */\n breakMode?: BreakMode\n /** Whether to log processing details */\n debug?: boolean\n}\n\n// =============================================================================\n// Placeholder Replacement\n// =============================================================================\n\n/**\n * Placeholder values that can be replaced in display text.\n * These match the placeholders used in CoreManager.kt/CoreManager.swift\n */\ninterface PlaceholderValues {\n /** Current time in 12-hour format (e.g., \"2:30 PM\") */\n TIME12: string\n /** Current time in 24-hour format (e.g., \"14:30\") */\n TIME24: string\n /** Current date (e.g., \"1/22\") */\n DATE: string\n /** Glasses battery level (e.g., \"85%\" or \"\" if unknown) */\n GBATT: string\n /** Connection status */\n CONNECTION_STATUS: string\n}\n\n/**\n * Get current placeholder values from device state\n */\nfunction getPlaceholderValues(): PlaceholderValues {\n const now = new Date()\n\n // Format time in 12-hour format\n const hours12 = now.getHours() % 12 || 12\n const minutes = now.getMinutes().toString().padStart(2, \"0\")\n const ampm = now.getHours() >= 12 ? \"PM\" : \"AM\"\n const TIME12 = `${hours12}:${minutes} ${ampm}`\n\n // Format time in 24-hour format\n const hours24 = now.getHours().toString().padStart(2, \"0\")\n const TIME24 = `${hours24}:${minutes}`\n\n // Format date\n const month = now.getMonth() + 1\n const day = now.getDate()\n const DATE = `${month}/${day}`\n\n // Battery + connection read from the engine glasses store directly (was a host\n // glassesStatus hook).\n const batteryLevel = useGlassesStore.getState().batteryLevel ?? -1\n const GBATT = batteryLevel === -1 ? \"\" : `${batteryLevel}%`\n\n // Connection status\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n const CONNECTION_STATUS = connected ? \"Connected\" : \"\"\n\n return {TIME12, TIME24, DATE, GBATT, CONNECTION_STATUS}\n}\n\n/**\n * Replace placeholders in text with actual values.\n * Placeholders are in format $NAME$ (e.g., $GBATT$, $TIME12$)\n *\n * @param text - Text containing placeholders\n * @returns Text with placeholders replaced\n */\nfunction replacePlaceholders(text: string): string {\n if (!text || !text.includes(\"$\")) {\n return text\n }\n\n const values = getPlaceholderValues()\n\n return text\n .replace(/\\$TIME12\\$/g, values.TIME12)\n .replace(/\\$TIME24\\$/g, values.TIME24)\n .replace(/\\$DATE\\$/g, values.DATE)\n .replace(/\\$GBATT\\$/g, values.GBATT)\n .replace(/\\$CONNECTION_STATUS\\$/g, values.CONNECTION_STATUS)\n .replace(/\\$no_datetime\\$/g, `${values.DATE}, ${values.TIME12}`)\n}\n\nfunction isBreakMode(value: unknown): value is BreakMode {\n return value === \"character\" || value === \"character-no-hyphen\" || value === \"word\" || value === \"strict-word\"\n}\n\n// =============================================================================\n// Device Profile Mapping\n// =============================================================================\n\n/**\n * Map device model names to display profiles\n */\nconst DEVICE_PROFILES: Record<DeviceModel, DisplayProfile> = {\n \"g1\": G1_PROFILE,\n \"g2\": G2_PROFILE,\n \"z100\": Z100_PROFILE,\n \"nex\": NEX_PROFILE,\n \"mach1\": Z100_PROFILE, // Mach1 uses same hardware as Vuzix Z100\n \"mentra-live\": G1_PROFILE, // Mentra Live has no display, uses G1 as fallback\n \"simulated\": G1_PROFILE, // Simulated uses G1 profile\n \"unknown\": G1_PROFILE, // Default to G1\n}\n\n/**\n * Normalize various model name strings to our DeviceModel type\n */\nfunction normalizeModelName(modelName: string | null | undefined): DeviceModel {\n if (!modelName) return \"unknown\"\n\n const lower = modelName.toLowerCase()\n\n if (lower.includes(\"g2\") || lower.includes(\"even realities g2\")) {\n return \"g2\"\n }\n if (lower.includes(\"g1\") || lower.includes(\"even realities\")) {\n return \"g1\"\n }\n if (lower.includes(\"z100\") || lower.includes(\"vuzix\")) {\n return \"z100\"\n }\n if (lower.includes(\"nex\") || lower.includes(\"glassly display\")) {\n return \"nex\"\n }\n if (lower.includes(\"mach1\") || lower.includes(\"mach 1\")) {\n return \"mach1\"\n }\n if (lower.includes(\"glassly live\") || lower.includes(\"mentra-live\")) {\n return \"mentra-live\"\n }\n if (lower.includes(\"simulated\") || lower.includes(\"simulator\")) {\n return \"simulated\"\n }\n\n return \"unknown\"\n}\n\n// =============================================================================\n// DisplayProcessor Class\n// =============================================================================\n\n/**\n * Processes display events with pixel-accurate text wrapping.\n *\n * Usage:\n * ```typescript\n * const processor = DisplayProcessor.getInstance()\n *\n * // When glasses connect\n * processor.setDeviceModel(\"Even Realities G1\")\n *\n * // Process display events\n * const processed = processor.processDisplayEvent(rawEvent)\n * ```\n */\nexport class DisplayProcessor {\n private static instance: DisplayProcessor | null = null\n\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private composer: ColumnComposer\n private profile: DisplayProfile\n private deviceModel: DeviceModel = \"unknown\"\n private options: DisplayProcessorOptions\n\n private runtimeAttached = false\n private unsubscribeGlassesStatus: (() => void) | null = null\n\n private constructor(options: DisplayProcessorOptions = {}) {\n this.options = {\n breakMode: \"character-no-hyphen\",\n debug: false,\n ...options,\n }\n\n // Initialize with default G1 profile\n const engine = createDisplayToolkit(G1_PROFILE, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(engine.profile, this.options.breakMode)\n this.profile = engine.profile\n\n // Engine stores may not be hydrated yet at module-load time; attachToRuntime()\n // is called again by the host after app services start.\n this.attachToRuntime()\n }\n\n /**\n * Read the current default wearable and subscribe to engine glasses-status changes.\n * Idempotent — safe to call again after app services start.\n */\n public attachToRuntime(): void {\n if (this.runtimeAttached) {\n return\n }\n\n // Seed the device profile from the saved default wearable, read off the engine\n // settings store directly (was a host settings hook).\n const defaultWearable = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.defaultWearable) as\n | string\n | undefined\n if (defaultWearable) {\n this.setDeviceModel(defaultWearable)\n console.log(`DISPLAY_PROCESSOR: Initialized DisplayProcessor with default wearable: ${defaultWearable}`)\n }\n\n // Track device-model changes off the engine glasses store directly (was a host\n // subscribeGlassesStatus hook).\n this.unsubscribeGlassesStatus = useGlassesStore.subscribe(\n (s) => s.deviceModel,\n (deviceModel) => {\n if (deviceModel) this.setDeviceModel(String(deviceModel))\n },\n )\n\n this.runtimeAttached = true\n }\n\n /** Detach runtime subscriptions installed by attachToRuntime(). */\n public detachFromRuntime(): void {\n this.unsubscribeGlassesStatus?.()\n this.unsubscribeGlassesStatus = null\n this.runtimeAttached = false\n }\n\n /**\n * Get the singleton instance\n */\n public static getInstance(options?: DisplayProcessorOptions): DisplayProcessor {\n if (!DisplayProcessor.instance) {\n DisplayProcessor.instance = new DisplayProcessor(options)\n }\n return DisplayProcessor.instance\n }\n\n /**\n * Reset the singleton (useful for testing)\n */\n public static resetInstance(): void {\n DisplayProcessor.instance?.detachFromRuntime()\n DisplayProcessor.instance = null\n }\n\n // ===========================================================================\n // Device Profile Management\n // ===========================================================================\n\n /**\n * Set the device model and update the display profile accordingly.\n * Call this when glasses connect.\n *\n * @param modelName - The model name from the connected glasses (e.g., \"Even Realities G1\")\n */\n public setDeviceModel(modelName: string | null | undefined): void {\n const normalizedModel = normalizeModelName(modelName)\n\n if (normalizedModel === this.deviceModel) {\n return // No change needed\n }\n\n this.deviceModel = normalizedModel\n const newProfile = DEVICE_PROFILES[normalizedModel]\n\n if (newProfile !== this.profile) {\n this.updateProfile(newProfile)\n }\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Device model set to: ${normalizedModel} (from: ${modelName})`)\n }\n }\n\n /**\n * Update the display profile and recreate engine\n */\n private updateProfile(newProfile: DisplayProfile): void {\n const engine = createDisplayToolkit(newProfile, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(newProfile, this.options.breakMode)\n this.profile = engine.profile\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Profile updated to: ${newProfile.id}`)\n }\n }\n\n /**\n * Get the current device model\n */\n public getDeviceModel(): DeviceModel {\n return this.deviceModel\n }\n\n /**\n * Get the current display profile\n */\n public getProfile(): DisplayProfile {\n return this.profile\n }\n\n // ===========================================================================\n // Display Event Processing\n // ===========================================================================\n\n /**\n * Process a display event, wrapping text as needed.\n *\n * @param event - Raw display event from WebSocket\n * @returns Processed event with wrapped text\n */\n public processDisplayEvent(event: DisplayEvent): ProcessedDisplayEvent {\n // Already processed? Return as-is\n if ((event as ProcessedDisplayEvent)._processed) {\n return event as ProcessedDisplayEvent\n }\n\n // Get layout type from either root or nested layout object\n const layoutType = event.layoutType || event.layout?.layoutType\n const layout = event.layout || event\n\n if (!layoutType) {\n // No layout type - pass through\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n switch (layoutType) {\n case \"text_wall\":\n case \"text_line\":\n return this.processTextWall(event, layout)\n\n case \"text_rows\":\n return this.processTextRows(event, layout)\n\n case \"reference_card\":\n return this.processReferenceCard(event, layout)\n\n case \"double_text_wall\":\n return this.processDoubleTextWall(event, layout)\n\n case \"bitmap_view\":\n // Bitmap views don't need text processing\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n\n default:\n // Unknown layout type - pass through\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Unknown layout type: ${layoutType}`)\n }\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n }\n\n /**\n * Process text_wall or text_line layout\n */\n private processTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n // text_wall/text_line always have string text, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping (so we measure actual content)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap the text\n const lines = this.wrapText(textWithPlaceholders, breakMode ? {breakMode} : undefined)\n const wrappedText = lines.join(\"\\n\")\n\n // Update both root and nested layout if present\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedText,\n }\n : undefined\n\n return {\n ...event,\n // Store original text with placeholders for debugging\n _originalText: text,\n text: wrappedText,\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n _lines: lines,\n }\n }\n\n /**\n * Process text_rows layout\n */\n private processTextRows(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const textField = layout.text\n const rows: string[] = Array.isArray(textField) ? textField : []\n\n // Replace placeholders and wrap each row\n const breakMode = this.getEventBreakMode(event, layout)\n const wrappedRows = rows.map((row: string) => {\n const rowWithPlaceholders = replacePlaceholders(row)\n const lines = this.wrapText(rowWithPlaceholders, breakMode ? {breakMode} : undefined)\n return lines.join(\"\\n\")\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedRows as string | string[],\n }\n : undefined\n\n return {\n ...event,\n text: wrappedRows as string | string[],\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process reference_card layout\n */\n private processReferenceCard(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const title = layout.title || \"\"\n // reference_card text is always string, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping\n const titleWithPlaceholders = replacePlaceholders(title)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap title and text separately\n // Title typically gets 1 line, text gets remaining lines\n const wrappedTitle = this.wrapText(titleWithPlaceholders, {maxLines: 1, ...(breakMode ? {breakMode} : {})})\n const wrappedText = this.wrapText(textWithPlaceholders, {\n maxLines: this.profile.maxLines - 1,\n ...(breakMode ? {breakMode} : {}),\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n }\n : undefined\n\n return {\n ...event,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process double_text_wall layout\n *\n * Uses ColumnComposer to create a fully composed string with both columns\n * merged line-by-line with pixel-precise space alignment. This is the single\n * source of truth for double_text_wall composition, replacing duplicate\n * implementations in native iOS (G1Text.swift) and Android (G1Text.kt).\n *\n * The composed text is sent to native as a regular text_wall - native just\n * chunks and sends it without any re-wrapping or column composition.\n */\n private processDoubleTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const leftText = layout.topText || \"\"\n const rightText = layout.bottomText || \"\"\n\n // Replace placeholders BEFORE composition (so we measure actual content)\n const leftTextWithPlaceholders = replacePlaceholders(leftText)\n const rightTextWithPlaceholders = replacePlaceholders(rightText)\n const breakMode = this.getEventBreakMode(event, layout)\n const composer =\n breakMode && breakMode !== this.options.breakMode ? new ColumnComposer(this.profile, breakMode) : this.composer\n\n // Use ColumnComposer for pixel-precise column composition\n const result = composer.composeDoubleTextWall(leftTextWithPlaceholders, rightTextWithPlaceholders)\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] double_text_wall composed:`)\n console.log(` Left lines: ${result.leftLines.length}`)\n console.log(` Right lines: ${result.rightLines.length}`)\n console.log(` Config: ${JSON.stringify(result.config)}`)\n }\n\n // The composed text is a single string with both columns merged\n // Native will receive this as a text_wall and just chunk & send it\n const processedLayout = event.layout\n ? {\n ...event.layout,\n // Keep original topText/bottomText for reference but add composed text\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // The composed text is what native should actually display\n _composedText: result.composedText,\n }\n : undefined\n\n return {\n ...event,\n // Override the layout type to text_wall since we've pre-composed the columns\n // This tells native to use sendTextWall instead of sendDoubleTextWall\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n // Keep original values for debugging/preview (with placeholders replaced)\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // Store original values with placeholders for debugging\n _originalTopText: leftText,\n _originalBottomText: rightText,\n layout: processedLayout\n ? {\n ...processedLayout,\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n }\n : undefined,\n _processed: true,\n _profile: this.profile.id,\n _originalLayoutType: \"double_text_wall\",\n _composedColumns: {\n leftLines: result.leftLines,\n rightLines: result.rightLines,\n config: result.config,\n },\n }\n }\n\n // ===========================================================================\n // Text Wrapping Utilities\n // ===========================================================================\n\n /**\n * Wrap text using the current profile\n *\n * @param text - Text to wrap\n * @param options - Optional override options\n * @returns Array of wrapped lines\n */\n public wrapText(text: string, options?: WrapOptions): string[] {\n if (!text) return [\"\"]\n\n const result = this.wrapper.wrap(text, options)\n return result.lines\n }\n\n private getEventBreakMode(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): BreakMode | undefined {\n const breakMode = layout.breakMode ?? event.breakMode\n return isBreakMode(breakMode) ? breakMode : undefined\n }\n\n /**\n * Measure the pixel width of text\n *\n * @param text - Text to measure\n * @returns Width in pixels\n */\n public measureText(text: string): number {\n return this.measurer.measureText(text)\n }\n\n /**\n * Check if text fits on a single line\n *\n * @param text - Text to check\n * @returns true if text fits without wrapping\n */\n public fitsOnSingleLine(text: string): boolean {\n return this.measurer.measureText(text) <= this.profile.displayWidthPx\n }\n\n /**\n * Get the display width in pixels\n */\n public getDisplayWidth(): number {\n return this.profile.displayWidthPx\n }\n\n /**\n * Get the maximum number of lines\n */\n public getMaxLines(): number {\n return this.profile.maxLines\n }\n\n /**\n * Get detailed text measurement\n */\n public measureTextDetailed(text: string) {\n return this.measurer.measureTextDetailed(text)\n }\n\n // ===========================================================================\n // Break Mode Control\n // ===========================================================================\n\n /**\n * Set the break mode for text wrapping\n *\n * @param breakMode - 'character' | 'word' | 'strict-word'\n */\n public setBreakMode(breakMode: BreakMode): void {\n if (this.options.breakMode === breakMode) return\n\n this.options.breakMode = breakMode\n this.updateProfile(this.profile) // Recreate wrapper with new break mode\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Break mode set to: ${breakMode}`)\n }\n }\n\n /**\n * Get the current break mode\n */\n public getBreakMode(): BreakMode {\n return this.options.breakMode || \"character\"\n }\n}\n\nexport const displayProcessor = DisplayProcessor.getInstance()\nexport default displayProcessor\n"]}
1
+ {"version":3,"file":"DisplayProcessor.js","sourceRoot":"","sources":["../../src/services/DisplayProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DisplayProcessor\n *\n * Processes display events with pixel-accurate text wrapping using the same\n * Engine display utilities. This ensures the mobile preview matches exactly\n * what is shown on the glasses.\n *\n * Key responsibilities:\n * 1. Intercept display events before sending to native SGC\n * 2. Wrap text using the correct device profile (G1, etc.)\n * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show\n *\n * @see notes/miniapp-display-api-redesign.md for the current display design\n */\n\nimport {\n createDisplayToolkit,\n G1_PROFILE,\n G2_PROFILE,\n Z100_PROFILE,\n NEX_PROFILE,\n TextMeasurer,\n TextWrapper,\n ColumnComposer,\n type DisplayProfile,\n type WrapOptions,\n type BreakMode,\n} from \"../utils/display\"\n\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Supported device models for display processing\n */\nexport type DeviceModel = \"g1\" | \"g2\" | \"z100\" | \"nex\" | \"mach1\" | \"mentra-live\" | \"simulated\" | \"unknown\"\n\n/**\n * Display event types that we process\n */\nexport type DisplayLayoutType =\n | \"text_wall\"\n | \"text_line\"\n | \"text_rows\"\n | \"reference_card\"\n | \"double_text_wall\"\n | \"bitmap_view\"\n\n/**\n * Raw display event from the cloud/WebSocket\n */\nexport interface DisplayEvent {\n view: \"main\" | \"dashboard\"\n layoutType?: DisplayLayoutType\n layout?: {\n layoutType: DisplayLayoutType\n text?: string | string[] // string for text_wall/text_line, string[] for text_rows\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n data?: string // For bitmap_view\n x?: number // bitmap_view container position/size (forwarded to native, used by G2)\n y?: number\n width?: number\n height?: number\n [key: string]: unknown\n }\n text?: string | string[]\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n [key: string]: unknown\n}\n\n/**\n * Processed display event with guaranteed wrapped text\n */\nexport interface ProcessedDisplayEvent extends DisplayEvent {\n /** Marks this event as processed */\n _processed: true\n /** The device profile used for processing */\n _profile: string\n /** Pre-split lines for text_wall/text_line (for easy rendering) */\n _lines?: string[]\n /** Original layout type before processing (e.g., double_text_wall -> text_wall) */\n _originalLayoutType?: DisplayLayoutType\n /** Column composition metadata for double_text_wall */\n _composedColumns?: {\n leftLines: string[]\n rightLines: string[]\n config: {\n leftColumnWidthPx: number\n rightColumnStartPx: number\n rightColumnWidthPx: number\n maxLines: number\n leftMarginSpaces?: number\n }\n }\n}\n\n/**\n * Options for the DisplayProcessor\n */\nexport interface DisplayProcessorOptions {\n /** Default break mode for text wrapping */\n breakMode?: BreakMode\n /** Whether to log processing details */\n debug?: boolean\n}\n\n// =============================================================================\n// Placeholder Replacement\n// =============================================================================\n\n/**\n * Placeholder values that can be replaced in display text.\n * These match the placeholders used in CoreManager.kt/CoreManager.swift\n */\ninterface PlaceholderValues {\n /** Current time in 12-hour format (e.g., \"2:30 PM\") */\n TIME12: string\n /** Current time in 24-hour format (e.g., \"14:30\") */\n TIME24: string\n /** Current date (e.g., \"1/22\") */\n DATE: string\n /** Glasses battery level (e.g., \"85%\" or \"\" if unknown) */\n GBATT: string\n /** Connection status */\n CONNECTION_STATUS: string\n}\n\n/**\n * Get current placeholder values from device state\n */\nfunction getPlaceholderValues(): PlaceholderValues {\n const now = new Date()\n\n // Format time in 12-hour format\n const hours12 = now.getHours() % 12 || 12\n const minutes = now.getMinutes().toString().padStart(2, \"0\")\n const ampm = now.getHours() >= 12 ? \"PM\" : \"AM\"\n const TIME12 = `${hours12}:${minutes} ${ampm}`\n\n // Format time in 24-hour format\n const hours24 = now.getHours().toString().padStart(2, \"0\")\n const TIME24 = `${hours24}:${minutes}`\n\n // Format date\n const month = now.getMonth() + 1\n const day = now.getDate()\n const DATE = `${month}/${day}`\n\n // Battery + connection read from the engine glasses store directly (was a host\n // glassesStatus hook).\n const batteryLevel = useGlassesStore.getState().batteryLevel ?? -1\n const GBATT = batteryLevel === -1 ? \"\" : `${batteryLevel}%`\n\n // Connection status\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n const CONNECTION_STATUS = connected ? \"Connected\" : \"\"\n\n return {TIME12, TIME24, DATE, GBATT, CONNECTION_STATUS}\n}\n\n/**\n * Replace placeholders in text with actual values.\n * Placeholders are in format $NAME$ (e.g., $GBATT$, $TIME12$)\n *\n * @param text - Text containing placeholders\n * @returns Text with placeholders replaced\n */\nfunction replacePlaceholders(text: string): string {\n if (!text || !text.includes(\"$\")) {\n return text\n }\n\n const values = getPlaceholderValues()\n\n return text\n .replace(/\\$TIME12\\$/g, values.TIME12)\n .replace(/\\$TIME24\\$/g, values.TIME24)\n .replace(/\\$DATE\\$/g, values.DATE)\n .replace(/\\$GBATT\\$/g, values.GBATT)\n .replace(/\\$CONNECTION_STATUS\\$/g, values.CONNECTION_STATUS)\n .replace(/\\$no_datetime\\$/g, `${values.DATE}, ${values.TIME12}`)\n}\n\nfunction isBreakMode(value: unknown): value is BreakMode {\n return value === \"character\" || value === \"character-no-hyphen\" || value === \"word\" || value === \"strict-word\"\n}\n\n// =============================================================================\n// Device Profile Mapping\n// =============================================================================\n\n/**\n * Map device model names to display profiles\n */\nconst DEVICE_PROFILES: Record<DeviceModel, DisplayProfile> = {\n \"g1\": G1_PROFILE,\n \"g2\": G2_PROFILE,\n \"z100\": Z100_PROFILE,\n \"nex\": NEX_PROFILE,\n \"mach1\": Z100_PROFILE, // Mach1 uses same hardware as Vuzix Z100\n \"mentra-live\": G1_PROFILE, // Mentra Live has no display, uses G1 as fallback\n \"simulated\": G1_PROFILE, // Simulated uses G1 profile\n \"unknown\": G1_PROFILE, // Default to G1\n}\n\n/**\n * Normalize various model name strings to our DeviceModel type\n */\nfunction normalizeModelName(modelName: string | null | undefined): DeviceModel {\n if (!modelName) return \"unknown\"\n\n const lower = modelName.toLowerCase()\n\n if (lower.includes(\"g2\") || lower.includes(\"even realities g2\")) {\n return \"g2\"\n }\n if (lower.includes(\"g1\") || lower.includes(\"even realities\")) {\n return \"g1\"\n }\n if (lower.includes(\"z100\") || lower.includes(\"vuzix\")) {\n return \"z100\"\n }\n if (lower.includes(\"nex\") || lower.includes(\"glassly display\")) {\n return \"nex\"\n }\n if (lower.includes(\"mach1\") || lower.includes(\"mach 1\")) {\n return \"mach1\"\n }\n if (lower.includes(\"glassly live\") || lower.includes(\"mentra-live\")) {\n return \"mentra-live\"\n }\n if (lower.includes(\"simulated\") || lower.includes(\"simulator\")) {\n return \"simulated\"\n }\n\n return \"unknown\"\n}\n\n// =============================================================================\n// DisplayProcessor Class\n// =============================================================================\n\n/**\n * Processes display events with pixel-accurate text wrapping.\n *\n * Usage:\n * ```typescript\n * const processor = DisplayProcessor.getInstance()\n *\n * // When glasses connect\n * processor.setDeviceModel(\"Even Realities G1\")\n *\n * // Process display events\n * const processed = processor.processDisplayEvent(rawEvent)\n * ```\n */\nexport class DisplayProcessor {\n private static instance: DisplayProcessor | null = null\n\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private composer: ColumnComposer\n private profile: DisplayProfile\n private deviceModel: DeviceModel = \"unknown\"\n private options: DisplayProcessorOptions\n\n private runtimeAttached = false\n private unsubscribeGlassesStatus: (() => void) | null = null\n\n private constructor(options: DisplayProcessorOptions = {}) {\n this.options = {\n breakMode: \"character-no-hyphen\",\n debug: false,\n ...options,\n }\n\n // Initialize with default G1 profile\n const engine = createDisplayToolkit(G1_PROFILE, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(engine.profile, this.options.breakMode)\n this.profile = engine.profile\n\n // Engine stores may not be hydrated yet at module-load time; attachToRuntime()\n // is called again by the host after app services start.\n this.attachToRuntime()\n }\n\n /**\n * Read the current default wearable and subscribe to engine glasses-status changes.\n * Idempotent — safe to call again after app services start.\n */\n public attachToRuntime(): void {\n if (this.runtimeAttached) {\n return\n }\n\n // Seed the device profile from the saved default wearable, read off the engine\n // settings store directly (was a host settings hook).\n const defaultWearable = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.defaultWearable) as\n | string\n | undefined\n if (defaultWearable) {\n this.setDeviceModel(defaultWearable)\n console.log(`DISPLAY_PROCESSOR: Initialized DisplayProcessor with default wearable: ${defaultWearable}`)\n }\n\n // Track device-model changes off the engine glasses store directly (was a host\n // subscribeGlassesStatus hook).\n this.unsubscribeGlassesStatus = useGlassesStore.subscribe(\n (s) => s.deviceModel,\n (deviceModel) => {\n if (deviceModel) this.setDeviceModel(String(deviceModel))\n },\n )\n\n this.runtimeAttached = true\n }\n\n /** Detach runtime subscriptions installed by attachToRuntime(). */\n public detachFromRuntime(): void {\n this.unsubscribeGlassesStatus?.()\n this.unsubscribeGlassesStatus = null\n this.runtimeAttached = false\n }\n\n /**\n * Get the singleton instance\n */\n public static getInstance(options?: DisplayProcessorOptions): DisplayProcessor {\n if (!DisplayProcessor.instance) {\n DisplayProcessor.instance = new DisplayProcessor(options)\n }\n return DisplayProcessor.instance\n }\n\n /**\n * Reset the singleton (useful for testing)\n */\n public static resetInstance(): void {\n DisplayProcessor.instance?.detachFromRuntime()\n DisplayProcessor.instance = null\n }\n\n // ===========================================================================\n // Device Profile Management\n // ===========================================================================\n\n /**\n * Set the device model and update the display profile accordingly.\n * Call this when glasses connect.\n *\n * @param modelName - The model name from the connected glasses (e.g., \"Even Realities G1\")\n */\n public setDeviceModel(modelName: string | null | undefined): void {\n const normalizedModel = normalizeModelName(modelName)\n\n if (normalizedModel === this.deviceModel) {\n return // No change needed\n }\n\n this.deviceModel = normalizedModel\n const newProfile = DEVICE_PROFILES[normalizedModel]\n\n if (newProfile !== this.profile) {\n this.updateProfile(newProfile)\n }\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Device model set to: ${normalizedModel} (from: ${modelName})`)\n }\n }\n\n /**\n * Update the display profile and recreate engine\n */\n private updateProfile(newProfile: DisplayProfile): void {\n const engine = createDisplayToolkit(newProfile, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(newProfile, this.options.breakMode)\n this.profile = engine.profile\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Profile updated to: ${newProfile.id}`)\n }\n }\n\n /**\n * Get the current device model\n */\n public getDeviceModel(): DeviceModel {\n return this.deviceModel\n }\n\n /**\n * Get the current display profile\n */\n public getProfile(): DisplayProfile {\n return this.profile\n }\n\n // ===========================================================================\n // Display Event Processing\n // ===========================================================================\n\n /**\n * Process a display event, wrapping text as needed.\n *\n * @param event - Raw display event from WebSocket\n * @returns Processed event with wrapped text\n */\n public processDisplayEvent(event: DisplayEvent): ProcessedDisplayEvent {\n // Already processed? Return as-is\n if ((event as ProcessedDisplayEvent)._processed) {\n return event as ProcessedDisplayEvent\n }\n\n // Get layout type from either root or nested layout object\n const layoutType = event.layoutType || event.layout?.layoutType\n const layout = event.layout || event\n\n if (!layoutType) {\n // No layout type - pass through\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n switch (layoutType) {\n case \"text_wall\":\n case \"text_line\":\n return this.processTextWall(event, layout)\n\n case \"text_rows\":\n return this.processTextRows(event, layout)\n\n case \"reference_card\":\n return this.processReferenceCard(event, layout)\n\n case \"double_text_wall\":\n return this.processDoubleTextWall(event, layout)\n\n case \"bitmap_view\":\n // Bitmap views don't need text processing\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n\n default:\n // Unknown layout type - pass through\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Unknown layout type: ${layoutType}`)\n }\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n }\n\n /**\n * Process text_wall or text_line layout\n */\n private processTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n // text_wall/text_line always have string text, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping (so we measure actual content)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap the text\n const lines = this.wrapText(textWithPlaceholders, breakMode ? {breakMode} : undefined)\n const wrappedText = lines.join(\"\\n\")\n\n // Update both root and nested layout if present\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedText,\n }\n : undefined\n\n return {\n ...event,\n // Store original text with placeholders for debugging\n _originalText: text,\n text: wrappedText,\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n _lines: lines,\n }\n }\n\n /**\n * Process text_rows layout\n */\n private processTextRows(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const textField = layout.text\n const rows: string[] = Array.isArray(textField) ? textField : []\n\n // Replace placeholders and wrap each row\n const breakMode = this.getEventBreakMode(event, layout)\n const wrappedRows = rows.map((row: string) => {\n const rowWithPlaceholders = replacePlaceholders(row)\n const lines = this.wrapText(rowWithPlaceholders, breakMode ? {breakMode} : undefined)\n return lines.join(\"\\n\")\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedRows as string | string[],\n }\n : undefined\n\n return {\n ...event,\n text: wrappedRows as string | string[],\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process reference_card layout\n */\n private processReferenceCard(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const title = layout.title || \"\"\n // reference_card text is always string, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping\n const titleWithPlaceholders = replacePlaceholders(title)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap title and text separately\n // Title typically gets 1 line, text gets remaining lines\n const wrappedTitle = this.wrapText(titleWithPlaceholders, {maxLines: 1, ...(breakMode ? {breakMode} : {})})\n const wrappedText = this.wrapText(textWithPlaceholders, {\n maxLines: this.profile.maxLines - 1,\n ...(breakMode ? {breakMode} : {}),\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n }\n : undefined\n\n return {\n ...event,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process double_text_wall layout\n *\n * Uses ColumnComposer to create a fully composed string with both columns\n * merged line-by-line with pixel-precise space alignment. This is the single\n * source of truth for double_text_wall composition, replacing duplicate\n * implementations in native iOS (G1Text.swift) and Android (G1Text.kt).\n *\n * The composed text is sent to native as a regular text_wall - native just\n * chunks and sends it without any re-wrapping or column composition.\n */\n private processDoubleTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const leftText = layout.topText || \"\"\n const rightText = layout.bottomText || \"\"\n\n // Replace placeholders BEFORE composition (so we measure actual content)\n const leftTextWithPlaceholders = replacePlaceholders(leftText)\n const rightTextWithPlaceholders = replacePlaceholders(rightText)\n const breakMode = this.getEventBreakMode(event, layout)\n const composer =\n breakMode && breakMode !== this.options.breakMode ? new ColumnComposer(this.profile, breakMode) : this.composer\n\n // Use ColumnComposer for pixel-precise column composition\n const result = composer.composeDoubleTextWall(leftTextWithPlaceholders, rightTextWithPlaceholders)\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] double_text_wall composed:`)\n console.log(` Left lines: ${result.leftLines.length}`)\n console.log(` Right lines: ${result.rightLines.length}`)\n console.log(` Config: ${JSON.stringify(result.config)}`)\n }\n\n // The composed text is a single string with both columns merged\n // Native will receive this as a text_wall and just chunk & send it\n const processedLayout = event.layout\n ? {\n ...event.layout,\n // Keep original topText/bottomText for reference but add composed text\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // The composed text is what native should actually display\n _composedText: result.composedText,\n }\n : undefined\n\n return {\n ...event,\n // Override the layout type to text_wall since we've pre-composed the columns\n // This tells native to use sendTextWall instead of sendDoubleTextWall\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n // Keep original values for debugging/preview (with placeholders replaced)\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // Store original values with placeholders for debugging\n _originalTopText: leftText,\n _originalBottomText: rightText,\n layout: processedLayout\n ? {\n ...processedLayout,\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n }\n : undefined,\n _processed: true,\n _profile: this.profile.id,\n _originalLayoutType: \"double_text_wall\",\n _composedColumns: {\n leftLines: result.leftLines,\n rightLines: result.rightLines,\n config: result.config,\n },\n }\n }\n\n // ===========================================================================\n // Text Wrapping Utilities\n // ===========================================================================\n\n /**\n * Wrap text using the current profile\n *\n * @param text - Text to wrap\n * @param options - Optional override options\n * @returns Array of wrapped lines\n */\n public wrapText(text: string, options?: WrapOptions): string[] {\n if (!text) return [\"\"]\n\n const result = this.wrapper.wrap(text, options)\n return result.lines\n }\n\n private getEventBreakMode(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): BreakMode | undefined {\n const breakMode = layout.breakMode ?? event.breakMode\n return isBreakMode(breakMode) ? breakMode : undefined\n }\n\n /**\n * Measure the pixel width of text\n *\n * @param text - Text to measure\n * @returns Width in pixels\n */\n public measureText(text: string): number {\n return this.measurer.measureText(text)\n }\n\n /**\n * Check if text fits on a single line\n *\n * @param text - Text to check\n * @returns true if text fits without wrapping\n */\n public fitsOnSingleLine(text: string): boolean {\n return this.measurer.measureText(text) <= this.profile.displayWidthPx\n }\n\n /**\n * Get the display width in pixels\n */\n public getDisplayWidth(): number {\n return this.profile.displayWidthPx\n }\n\n /**\n * Get the maximum number of lines\n */\n public getMaxLines(): number {\n return this.profile.maxLines\n }\n\n /**\n * Get detailed text measurement\n */\n public measureTextDetailed(text: string) {\n return this.measurer.measureTextDetailed(text)\n }\n\n // ===========================================================================\n // Break Mode Control\n // ===========================================================================\n\n /**\n * Set the break mode for text wrapping\n *\n * @param breakMode - 'character' | 'word' | 'strict-word'\n */\n public setBreakMode(breakMode: BreakMode): void {\n if (this.options.breakMode === breakMode) return\n\n this.options.breakMode = breakMode\n this.updateProfile(this.profile) // Recreate wrapper with new break mode\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Break mode set to: ${breakMode}`)\n }\n }\n\n /**\n * Get the current break mode\n */\n public getBreakMode(): BreakMode {\n return this.options.breakMode || \"character\"\n }\n}\n\nexport const displayProcessor = DisplayProcessor.getInstance()\nexport default displayProcessor\n"]}