@mentra/engine 0.1.0-dev.1 → 3.1.0-beta.103

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 (267) hide show
  1. package/README.md +43 -29
  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 +18 -15
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +2 -2
  29. package/build/engine.js.map +1 -1
  30. package/build/facades/display.d.ts +1 -1
  31. package/build/facades/display.d.ts.map +1 -1
  32. package/build/facades/display.js +1 -1
  33. package/build/facades/display.js.map +1 -1
  34. package/build/facades/glasses.d.ts +3 -13
  35. package/build/facades/glasses.d.ts.map +1 -1
  36. package/build/facades/glasses.js +3 -3
  37. package/build/facades/glasses.js.map +1 -1
  38. package/build/facades/ota.d.ts +16 -22
  39. package/build/facades/ota.d.ts.map +1 -1
  40. package/build/facades/ota.js +13 -9
  41. package/build/facades/ota.js.map +1 -1
  42. package/build/facades/pairing.d.ts +8 -5
  43. package/build/facades/pairing.d.ts.map +1 -1
  44. package/build/facades/pairing.js +83 -9
  45. package/build/facades/pairing.js.map +1 -1
  46. package/build/facades/speech.d.ts +7 -6
  47. package/build/facades/speech.d.ts.map +1 -1
  48. package/build/facades/speech.js +1 -1
  49. package/build/facades/speech.js.map +1 -1
  50. package/build/generated/releaseMetadata.d.ts +12 -0
  51. package/build/generated/releaseMetadata.d.ts.map +1 -0
  52. package/build/generated/releaseMetadata.js +10 -0
  53. package/build/generated/releaseMetadata.js.map +1 -0
  54. package/build/index.d.ts +7 -8
  55. package/build/index.d.ts.map +1 -1
  56. package/build/index.js +4 -5
  57. package/build/index.js.map +1 -1
  58. package/build/react/MentraLiveOtaFlow.d.ts +8 -2
  59. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
  60. package/build/react/MentraLiveOtaFlow.js +256 -354
  61. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  62. package/build/react/index.d.ts +1 -0
  63. package/build/react/index.d.ts.map +1 -1
  64. package/build/react/index.js +1 -0
  65. package/build/react/index.js.map +1 -1
  66. package/build/react/useMentraLiveOta.d.ts +84 -0
  67. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  68. package/build/react/useMentraLiveOta.js +627 -0
  69. package/build/react/useMentraLiveOta.js.map +1 -0
  70. package/build/services/AppRegistry.d.ts +8 -0
  71. package/build/services/AppRegistry.d.ts.map +1 -1
  72. package/build/services/AppRegistry.js +15 -0
  73. package/build/services/AppRegistry.js.map +1 -1
  74. package/build/services/BlobStore.d.ts +2 -0
  75. package/build/services/BlobStore.d.ts.map +1 -1
  76. package/build/services/BlobStore.js +63 -10
  77. package/build/services/BlobStore.js.map +1 -1
  78. package/build/services/DisplayProcessor.d.ts +3 -3
  79. package/build/services/DisplayProcessor.js +3 -3
  80. package/build/services/DisplayProcessor.js.map +1 -1
  81. package/build/services/GlassesStatusProjection.d.ts +15 -2
  82. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  83. package/build/services/GlassesStatusProjection.js +3 -3
  84. package/build/services/GlassesStatusProjection.js.map +1 -1
  85. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  86. package/build/services/HotspotOtaTransport.js +8 -7
  87. package/build/services/HotspotOtaTransport.js.map +1 -1
  88. package/build/services/LocalDisplayManager.d.ts +2 -3
  89. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  90. package/build/services/LocalDisplayManager.js +2 -3
  91. package/build/services/LocalDisplayManager.js.map +1 -1
  92. package/build/services/OtaAutoChain.d.ts +9 -2
  93. package/build/services/OtaAutoChain.d.ts.map +1 -1
  94. package/build/services/OtaAutoChain.js +10 -2
  95. package/build/services/OtaAutoChain.js.map +1 -1
  96. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  97. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  98. package/build/services/OtaInstallCoordinator.js +38 -13
  99. package/build/services/OtaInstallCoordinator.js.map +1 -1
  100. package/build/services/OtaService.d.ts.map +1 -1
  101. package/build/services/OtaService.js +1 -1
  102. package/build/services/OtaService.js.map +1 -1
  103. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  104. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  105. package/build/services/OtaUpdateCheckService.js +33 -21
  106. package/build/services/OtaUpdateCheckService.js.map +1 -1
  107. package/build/services/PhonePhotoCoordinator.js +1 -1
  108. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  109. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  110. package/build/services/PhoneStreamCoordinator.js +9 -6
  111. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  112. package/build/services/StreamLifecycleController.d.ts +3 -3
  113. package/build/services/StreamLifecycleController.js +3 -3
  114. package/build/services/StreamLifecycleController.js.map +1 -1
  115. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  116. package/build/services/asg/localNetworkTransport.js +13 -12
  117. package/build/services/asg/localNetworkTransport.js.map +1 -1
  118. package/build/services/glassesClockSync.d.ts.map +1 -1
  119. package/build/services/glassesClockSync.js +5 -1
  120. package/build/services/glassesClockSync.js.map +1 -1
  121. package/build/services/otaDisplayState.d.ts +1 -1
  122. package/build/services/otaDisplayState.d.ts.map +1 -1
  123. package/build/services/otaDisplayState.js.map +1 -1
  124. package/build/services/otaInstallPolicy.d.ts +5 -1
  125. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  126. package/build/services/otaInstallPolicy.js +4 -0
  127. package/build/services/otaInstallPolicy.js.map +1 -1
  128. package/build/services/otaLegacyMapping.d.ts +1 -1
  129. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  130. package/build/services/otaLegacyMapping.js.map +1 -1
  131. package/build/services/otaManifestPolicy.d.ts +14 -0
  132. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  133. package/build/services/otaManifestPolicy.js +19 -0
  134. package/build/services/otaManifestPolicy.js.map +1 -0
  135. package/build/services/otaManifestUrl.d.ts +2 -1
  136. package/build/services/otaManifestUrl.d.ts.map +1 -1
  137. package/build/services/otaManifestUrl.js +24 -32
  138. package/build/services/otaManifestUrl.js.map +1 -1
  139. package/build/services/otaReleaseVersion.d.ts +10 -0
  140. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  141. package/build/services/otaReleaseVersion.js +14 -0
  142. package/build/services/otaReleaseVersion.js.map +1 -0
  143. package/build/stores/appVisibility.d.ts +3 -0
  144. package/build/stores/appVisibility.d.ts.map +1 -0
  145. package/build/stores/appVisibility.js +3 -0
  146. package/build/stores/appVisibility.js.map +1 -0
  147. package/build/stores/apps.d.ts.map +1 -1
  148. package/build/stores/apps.js +7 -1
  149. package/build/stores/apps.js.map +1 -1
  150. package/build/stores/core.d.ts +3 -3
  151. package/build/stores/core.d.ts.map +1 -1
  152. package/build/stores/core.js.map +1 -1
  153. package/build/stores/glasses.d.ts +10 -4
  154. package/build/stores/glasses.d.ts.map +1 -1
  155. package/build/stores/glasses.js.map +1 -1
  156. package/build/stores/settings.js +3 -3
  157. package/build/stores/settings.js.map +1 -1
  158. package/build/types/applet.d.ts +1 -1
  159. package/build/types/applet.js +1 -1
  160. package/build/types/applet.js.map +1 -1
  161. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  162. package/build/types/capabilities/even-realities-g1.js +2 -0
  163. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  164. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  165. package/build/types/capabilities/even-realities-g2.js +2 -0
  166. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  167. package/build/types/capabilities/mentra-display.d.ts.map +1 -1
  168. package/build/types/capabilities/mentra-display.js +2 -0
  169. package/build/types/capabilities/mentra-display.js.map +1 -1
  170. package/build/types/capabilities/mentra-live.d.ts.map +1 -1
  171. package/build/types/capabilities/mentra-live.js +2 -0
  172. package/build/types/capabilities/mentra-live.js.map +1 -1
  173. package/build/types/capabilities/nimo.d.ts +9 -0
  174. package/build/types/capabilities/nimo.d.ts.map +1 -0
  175. package/build/types/capabilities/nimo.js +57 -0
  176. package/build/types/capabilities/nimo.js.map +1 -0
  177. package/build/types/capabilities/none.d.ts.map +1 -1
  178. package/build/types/capabilities/none.js +2 -0
  179. package/build/types/capabilities/none.js.map +1 -1
  180. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -1
  181. package/build/types/capabilities/simulated-glasses.js +2 -0
  182. package/build/types/capabilities/simulated-glasses.js.map +1 -1
  183. package/build/types/capabilities/vuzix-z100.d.ts.map +1 -1
  184. package/build/types/capabilities/vuzix-z100.js +2 -0
  185. package/build/types/capabilities/vuzix-z100.js.map +1 -1
  186. package/build/types/enums.d.ts +3 -26
  187. package/build/types/enums.d.ts.map +1 -1
  188. package/build/types/enums.js +2 -27
  189. package/build/types/enums.js.map +1 -1
  190. package/build/types/hardware.d.ts +6 -15
  191. package/build/types/hardware.d.ts.map +1 -1
  192. package/build/types/hardware.js +4 -4
  193. package/build/types/hardware.js.map +1 -1
  194. package/build/types/index.d.ts +3 -7
  195. package/build/types/index.d.ts.map +1 -1
  196. package/build/types/index.js +3 -7
  197. package/build/types/index.js.map +1 -1
  198. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  199. package/build/utils/display/composer/ColumnComposer.js +1 -1
  200. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  201. package/package.json +47 -22
  202. package/src/bluetooth-sdk/debug.ts +1 -0
  203. package/src/bluetooth-sdk/index.ts +2 -0
  204. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  205. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  206. package/src/bluetooth-sdk/react.ts +1 -0
  207. package/src/bluetooth-sdk/types.ts +1 -0
  208. package/src/engine.ts +2 -2
  209. package/src/facades/display.ts +1 -1
  210. package/src/facades/glasses.ts +4 -4
  211. package/src/facades/ota.ts +23 -10
  212. package/src/facades/pairing.ts +92 -16
  213. package/src/facades/speech.ts +21 -7
  214. package/src/generated/releaseMetadata.ts +20 -0
  215. package/src/index.ts +16 -11
  216. package/src/react/MentraLiveOtaFlow.tsx +378 -480
  217. package/src/react/index.ts +14 -0
  218. package/src/react/useMentraLiveOta.ts +793 -0
  219. package/src/services/AppRegistry.ts +16 -0
  220. package/src/services/BlobStore.ts +57 -10
  221. package/src/services/DisplayProcessor.ts +3 -3
  222. package/src/services/GlassesStatusProjection.ts +7 -6
  223. package/src/services/HotspotOtaTransport.ts +8 -7
  224. package/src/services/LocalDisplayManager.ts +2 -3
  225. package/src/services/OtaAutoChain.ts +26 -2
  226. package/src/services/OtaInstallCoordinator.ts +40 -13
  227. package/src/services/OtaService.ts +1 -2
  228. package/src/services/OtaUpdateCheckService.ts +45 -27
  229. package/src/services/PhonePhotoCoordinator.ts +1 -1
  230. package/src/services/PhoneStreamCoordinator.ts +10 -6
  231. package/src/services/StreamLifecycleController.ts +3 -3
  232. package/src/services/asg/localNetworkTransport.ts +20 -19
  233. package/src/services/glassesClockSync.ts +5 -1
  234. package/src/services/otaDisplayState.ts +1 -1
  235. package/src/services/otaInstallPolicy.ts +5 -1
  236. package/src/services/otaLegacyMapping.ts +1 -1
  237. package/src/services/otaManifestPolicy.ts +42 -0
  238. package/src/services/otaManifestUrl.ts +27 -36
  239. package/src/services/otaReleaseVersion.ts +22 -0
  240. package/src/stores/appVisibility.ts +3 -0
  241. package/src/stores/apps.ts +7 -1
  242. package/src/stores/core.ts +4 -4
  243. package/src/stores/glasses.ts +13 -6
  244. package/src/stores/settings.ts +3 -3
  245. package/src/types/applet.ts +1 -1
  246. package/src/types/capabilities/even-realities-g1.ts +3 -0
  247. package/src/types/capabilities/even-realities-g2.ts +3 -0
  248. package/src/types/capabilities/mentra-display.ts +3 -0
  249. package/src/types/capabilities/mentra-live.ts +3 -0
  250. package/src/types/capabilities/nimo.ts +59 -0
  251. package/src/types/capabilities/none.ts +3 -0
  252. package/src/types/capabilities/simulated-glasses.ts +3 -0
  253. package/src/types/capabilities/vuzix-z100.ts +3 -0
  254. package/src/types/enums.ts +2 -27
  255. package/src/types/hardware.ts +7 -15
  256. package/src/types/index.ts +3 -6
  257. package/src/utils/display/composer/ColumnComposer.ts +1 -1
  258. package/build/devtools.d.ts +0 -11
  259. package/build/devtools.d.ts.map +0 -1
  260. package/build/devtools.js +0 -13
  261. package/build/devtools.js.map +0 -1
  262. package/build/internal.d.ts +0 -67
  263. package/build/internal.d.ts.map +0 -1
  264. package/build/internal.js +0 -90
  265. package/build/internal.js.map +0 -1
  266. package/src/devtools.ts +0 -13
  267. package/src/internal.ts +0 -160
@@ -1,9 +1,9 @@
1
- import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
2
- import type {OtaUpdateInfo} from "@mentra/bluetooth-sdk/internal"
1
+ import BluetoothSdk, {type OtaUpdateInfo} from "@mentra/bluetooth-sdk"
2
+ import {ENGINE_RELEASE_METADATA} from "../generated/releaseMetadata"
3
3
  import {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from "../stores/glasses"
4
- import {SETTINGS, useSettingsStore} from "../stores/settings"
5
4
  import {maybeFixGlassesClockFromVersionInfo} from "./glassesClockSync"
6
- import {resolveOtaManifestUrl} from "./otaManifestUrl"
5
+ import {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from "./otaManifestUrl"
6
+ import {resolveOtaReleaseVersion} from "./otaReleaseVersion"
7
7
 
8
8
  /**
9
9
  * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
@@ -39,6 +39,7 @@ export interface BesFirmware {
39
39
  }
40
40
 
41
41
  export interface VersionJson {
42
+ releaseVersion?: string
42
43
  apps?: {
43
44
  [packageName: string]: VersionInfo
44
45
  }
@@ -67,6 +68,8 @@ export interface OtaCheckResult {
67
68
  * instead of racing it with a second fetch of the same URL.
68
69
  */
69
70
  manifestBody: string | null
71
+ /** Coordinated release identity for the selected OTA pin. */
72
+ releaseVersion: string | null
70
73
  /**
71
74
  * Why the check failed, when it did. "network" is retryable (connection/server
72
75
  * trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
@@ -107,6 +110,7 @@ function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurren
107
110
  besVersion: null,
108
111
  isApkDowngrade: false,
109
112
  manifestBody: null,
113
+ releaseVersion: null,
110
114
  updateInfo: null,
111
115
  isRequired: true,
112
116
  skippedReason,
@@ -123,20 +127,23 @@ async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResul
123
127
  try {
124
128
  const response = await fetch(url)
125
129
  if (!response.ok) {
126
- console.error("Failed to fetch version info:", response.status)
130
+ console.warn("Failed to fetch version info:", response.status)
127
131
  // 4xx: the manifest does not exist (or is gone) — permanent for this app
128
132
  // build, retrying cannot help. Everything else is transient server/network
129
133
  // trouble and retryable.
130
- return {json: null, failureReason: response.status >= 400 && response.status < 500 ? "pin_unavailable" : "network"}
134
+ return {
135
+ json: null,
136
+ failureReason: response.status >= 400 && response.status < 500 ? "pin_unavailable" : "network",
137
+ }
131
138
  }
132
139
  try {
133
140
  return {json: await response.json()}
134
141
  } catch (parseError) {
135
- console.error("OTA: manifest is not valid JSON:", parseError)
142
+ console.warn("OTA: manifest is not valid JSON:", parseError)
136
143
  return {json: null, failureReason: "pin_unavailable"}
137
144
  }
138
145
  } catch (error) {
139
- console.error("OTA: Error fetching version info:", error)
146
+ console.warn("OTA: Error fetching version info:", error)
140
147
  return {json: null, failureReason: "network"}
141
148
  }
142
149
  }
@@ -194,12 +201,7 @@ export function getApkUpdateDirection(
194
201
  // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor
195
202
  // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never
196
203
  // offers what the glasses would refuse.
197
- if (
198
- serverVersion < currentVersion &&
199
- exactPin &&
200
- floorVersionCode > 0 &&
201
- serverVersion >= floorVersionCode
202
- ) {
204
+ if (serverVersion < currentVersion && exactPin && floorVersionCode > 0 && serverVersion >= floorVersionCode) {
203
205
  return "downgrade"
204
206
  }
205
207
  return null
@@ -301,6 +303,7 @@ export async function checkForOtaUpdate(
301
303
  besVersion: null,
302
304
  isApkDowngrade: false,
303
305
  manifestBody: null,
306
+ releaseVersion: null,
304
307
  checkFailureReason: fetched.failureReason ?? "network",
305
308
  }
306
309
  }
@@ -332,6 +335,7 @@ export async function checkForOtaUpdate(
332
335
  besVersion: null,
333
336
  isApkDowngrade: false,
334
337
  manifestBody: null,
338
+ releaseVersion: null,
335
339
  checkFailureReason: "pin_unavailable",
336
340
  }
337
341
  }
@@ -370,6 +374,12 @@ export async function checkForOtaUpdate(
370
374
  besVersion: versionJson?.bes_firmware?.version || null,
371
375
  isApkDowngrade: apkDirection === "downgrade",
372
376
  manifestBody: versionJson ? JSON.stringify(versionJson) : null,
377
+ releaseVersion: resolveOtaReleaseVersion({
378
+ manifestReleaseVersion: versionJson.releaseVersion,
379
+ manifestUrl: otaVersionUrl,
380
+ packagedManifestUrl: ENGINE_RELEASE_METADATA.otaManifestUrl,
381
+ packagedReleaseIdentity: ENGINE_RELEASE_METADATA.releaseIdentity,
382
+ }),
373
383
  }
374
384
  } catch (error) {
375
385
  console.error("Error checking for OTA update:", error)
@@ -382,6 +392,7 @@ export async function checkForOtaUpdate(
382
392
  besVersion: null,
383
393
  isApkDowngrade: false,
384
394
  manifestBody: null,
395
+ releaseVersion: null,
385
396
  checkFailureReason: "network",
386
397
  }
387
398
  }
@@ -403,22 +414,19 @@ export async function checkCurrentGlassesForUpdate(
403
414
  return emptyCheckResult("disconnected")
404
415
  }
405
416
 
406
- const embeddedManifestUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
407
- const superMode = useSettingsStore.getState().getSetting(SETTINGS.super_mode.key)
408
- const overrideUrl = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)
409
- const overrideActive = superMode && typeof overrideUrl === "string" && overrideUrl.trim() !== ""
410
- if (!embeddedManifestUrl && !overrideActive) {
411
- console.log("OTA: check skipped - mobile app has no embedded OTA manifest pin")
417
+ if (!hasConfiguredModernOtaManifestPin()) {
418
+ console.log("OTA: check skipped - this app or Engine package has no embedded OTA manifest pin")
412
419
  return emptyCheckResult("dev_build")
413
420
  }
414
- if (!embeddedManifestUrl) {
415
- console.log("OTA: no embedded manifest pin, but super-mode manifest override is active - checking anyway")
416
- }
417
421
 
418
422
  if (refreshVersionInfo) {
419
- void BluetoothSdk.requestVersionInfo().catch((error) => {
420
- console.warn("OTA: Failed to request version_info from glasses:", error)
421
- })
423
+ void BluetoothSdk.requestVersionInfo()
424
+ .then((versionInfo) => {
425
+ useGlassesStore.getState().setGlassesInfo(versionInfo)
426
+ })
427
+ .catch((error) => {
428
+ console.warn("OTA: Failed to request version_info from glasses:", error)
429
+ })
422
430
  }
423
431
 
424
432
  let buildNumber = useGlassesStore.getState().buildNumber
@@ -472,7 +480,17 @@ export async function checkCurrentGlassesForUpdate(
472
480
  }
473
481
 
474
482
  const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
475
- const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion, floorVersionCode)
483
+ if (!manifestUrl) {
484
+ console.log("OTA: check skipped - no OTA manifest pin resolved")
485
+ return emptyCheckResult("dev_build")
486
+ }
487
+ const result = await checkForOtaUpdate(
488
+ manifestUrl,
489
+ buildNumber,
490
+ mtkFirmwareVersion,
491
+ besFirmwareVersion,
492
+ floorVersionCode,
493
+ )
476
494
 
477
495
  if (!result.hasCheckCompleted) {
478
496
  return {
@@ -166,7 +166,7 @@ export class PhonePhotoCoordinator {
166
166
  // to time out. Fail fast with a typed error.
167
167
  //
168
168
  // We DON'T pre-check `hasCamera` here — the canonical capability data
169
- // lives in `getModelCapabilities(deviceModel)` from @mentra/types and
169
+ // lives in `getModelCapabilities(deviceModel)` from @mentra/engine and
170
170
  // pulling that into this file would add a cross-package import. If a
171
171
  // cameraless device receives the BLE photo command, the glasses-side
172
172
  // handler will return a photo_response error within ~1s and the gated
@@ -813,12 +813,6 @@ export class PhoneStreamCoordinator {
813
813
  for (const reject of entry.hlsReadyRejecters) reject(pendingErr)
814
814
  entry.hlsReadyResolvers = []
815
815
  entry.hlsReadyRejecters = []
816
- // Cloudflare DELETE is fire-and-forget — failing it doesn't undo the
817
- // BLE stop we're about to do, and Cloudflare's idle-input scavenger
818
- // will eventually clean up leaked inputs.
819
- teardownManagedStream(entry.liveInputId).catch((err) => {
820
- console.warn("[STREAM] teardownManagedStream failed:", err)
821
- })
822
816
  }
823
817
 
824
818
  try {
@@ -833,6 +827,16 @@ export class PhoneStreamCoordinator {
833
827
  // Only clear if we're still the active entry (defensive — runExclusive
834
828
  // serializes us, so this should always be true).
835
829
  if (this.current === entry) this.current = null
830
+
831
+ if (entry.kind === "managed") {
832
+ // Start remote cleanup only after the publisher has stopped, but do not
833
+ // hold the local transition lock on an unbounded network request. The
834
+ // runtime's durable cleanup queue owns retries if this request fails or
835
+ // the app exits before Cloudflare finishes finalizing the recording.
836
+ void teardownManagedStream(entry.liveInputId).catch((err) => {
837
+ console.warn("[STREAM] teardownManagedStream failed:", err)
838
+ })
839
+ }
836
840
  }
837
841
  }
838
842
  }
@@ -3,9 +3,9 @@
3
3
  * `setActive(true)` starts the timer; each tick sends a keep-alive with a
4
4
  * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.
5
5
  *
6
- * Mirror of `cloud/packages/cloud/src/services/streaming/StreamLifecycleController.ts`
7
- * — kept byte-equivalent except for the `LifecycleLogger` interface (pino
8
- * isn't a phone dep). Behavior changes must land in both files.
6
+ * Phone-owned successor to the retired cloud stream lifecycle controller.
7
+ * It uses the local `LifecycleLogger` interface because pino is not a phone
8
+ * dependency.
9
9
  */
10
10
 
11
11
  export interface LifecycleLogger {
@@ -1,5 +1,5 @@
1
1
  import * as RNFS from "@dr.pogodin/react-native-fs"
2
- import {MentraLocalNetwork, type LocalNetworkDownloadProgress} from "@mentra/bluetooth-sdk/internal"
2
+ import {otaLocalNetwork, type OtaLocalNetworkDownloadProgress} from "@mentra/bluetooth-sdk/ota-transport"
3
3
  import {Buffer} from "buffer"
4
4
  import {Platform} from "react-native"
5
5
  import WifiManager from "react-native-wifi-reborn"
@@ -17,7 +17,7 @@ export function shouldUseScopedLocalNetwork(os: string, moduleAvailable: boolean
17
17
  }
18
18
 
19
19
  function supportsScopedConnection(): boolean {
20
- return shouldUseScopedLocalNetwork(Platform.OS, MentraLocalNetwork != null)
20
+ return shouldUseScopedLocalNetwork(Platform.OS, otaLocalNetwork.isAvailable())
21
21
  }
22
22
 
23
23
  function normalizeHeaders(headers?: HeadersInit): Record<string, string> {
@@ -48,25 +48,25 @@ export const localNetworkTransport = {
48
48
  await WifiManager.connectToProtectedSSID(ssid, password, false, false)
49
49
  return undefined
50
50
  }
51
- const result = await MentraLocalNetwork!.connect(ssid, password)
51
+ const result = await otaLocalNetwork.connect(ssid, password)
52
52
  scopedConnectionActive = true
53
53
  return result.localAddress
54
54
  },
55
55
 
56
56
  async disconnect(): Promise<void> {
57
57
  if (supportsScopedConnection()) {
58
- await MentraLocalNetwork!.disconnect().catch(() => {})
58
+ await otaLocalNetwork.disconnect().catch(() => {})
59
59
  }
60
60
  scopedConnectionActive = false
61
61
  nativeJobs.clear()
62
62
  },
63
63
 
64
64
  async fetch(url: string | URL | Request, init?: RequestInit, timeoutMs = 30_000): Promise<Response> {
65
- if (!scopedConnectionActive || !MentraLocalNetwork || typeof url !== "string") {
65
+ if (!scopedConnectionActive || !otaLocalNetwork.isAvailable() || typeof url !== "string") {
66
66
  return globalThis.fetch(url, init)
67
67
  }
68
68
 
69
- const nativeModule = MentraLocalNetwork
69
+ const nativeModule = otaLocalNetwork
70
70
  const id = requestId("request")
71
71
  const signal = init?.signal
72
72
  const cancel = () => void nativeModule.cancel(id)
@@ -92,13 +92,13 @@ export const localNetworkTransport = {
92
92
  },
93
93
 
94
94
  downloadFile(options: DownloadOptions): DownloadHandle {
95
- if (!scopedConnectionActive || !MentraLocalNetwork) return RNFS.downloadFile(options)
95
+ if (!scopedConnectionActive || !otaLocalNetwork.isAvailable()) return RNFS.downloadFile(options)
96
96
 
97
97
  const jobId = nextJobId++
98
98
  const id = requestId("download")
99
99
  let began = false
100
100
  nativeJobs.set(jobId, id)
101
- const subscription = MentraLocalNetwork.addListener("downloadProgress", (event: LocalNetworkDownloadProgress) => {
101
+ const subscription = otaLocalNetwork.onDownloadProgress((event: OtaLocalNetworkDownloadProgress) => {
102
102
  if (event.requestId !== id) return
103
103
  if (!began) {
104
104
  began = true
@@ -116,14 +116,15 @@ export const localNetworkTransport = {
116
116
  })
117
117
  })
118
118
 
119
- const promise = MentraLocalNetwork.download(
120
- id,
121
- options.fromUrl,
122
- options.toFile,
123
- options.headers || {},
124
- options.connectionTimeout || 30_000,
125
- options.readTimeout || 30_000,
126
- )
119
+ const promise = otaLocalNetwork
120
+ .download(
121
+ id,
122
+ options.fromUrl,
123
+ options.toFile,
124
+ options.headers || {},
125
+ options.connectionTimeout || 30_000,
126
+ options.readTimeout || 30_000,
127
+ )
127
128
  .then((result) => {
128
129
  if (!began) {
129
130
  options.begin?.({
@@ -154,16 +155,16 @@ export const localNetworkTransport = {
154
155
 
155
156
  stopDownload(jobId: number): void {
156
157
  const nativeId = nativeJobs.get(jobId)
157
- if (nativeId && MentraLocalNetwork) {
158
+ if (nativeId && otaLocalNetwork.isAvailable()) {
158
159
  cancelledNativeJobs.add(jobId)
159
- void MentraLocalNetwork.cancel(nativeId)
160
+ void otaLocalNetwork.cancel(nativeId)
160
161
  return
161
162
  }
162
163
  RNFS.stopDownload(jobId)
163
164
  },
164
165
  }
165
166
 
166
- MentraLocalNetwork?.addListener("networkLost", () => {
167
+ otaLocalNetwork.onNetworkLost(() => {
167
168
  scopedConnectionActive = false
168
169
  nativeJobs.clear()
169
170
  })
@@ -2,7 +2,7 @@
2
2
  * Push phone time to glasses only when clock skew is detected (gallery sync, OTA, etc.).
3
3
  */
4
4
 
5
- import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
5
+ import BluetoothSdk from "@mentra/bluetooth-sdk"
6
6
  import {BgTimer} from "../utils/timers"
7
7
  import {useGlassesStore} from "../stores/glasses"
8
8
 
@@ -87,6 +87,10 @@ export async function handleOtaClockSkewFromGlasses(
87
87
  lastOtaClockFixAt = Date.now()
88
88
  const {buildNumber, otaVersionUrl} = useGlassesStore.getState()
89
89
  const manifestUrl = resolveOtaManifestUrl(otaVersionUrl, buildNumber)
90
+ if (!manifestUrl) {
91
+ console.warn("[GlassesClockSync] OTA restart skipped because this build has no manifest pin")
92
+ return false
93
+ }
90
94
  console.log("[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix")
91
95
  await BluetoothSdk.startOtaUpdate(manifestUrl)
92
96
  return true
@@ -1,4 +1,4 @@
1
- import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk/internal"
1
+ import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk"
2
2
 
3
3
  import {isLegacyShapedOtaStatus} from "./otaInstallPolicy"
4
4
 
@@ -1,5 +1,5 @@
1
1
  /** Shared OTA progress watchdog timings (see progress-legacy.tsx / progress.tsx). */
2
- import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk/internal"
2
+ import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk"
3
3
 
4
4
  /**
5
5
  * First glasses `build_number` (ASG `versionCode`) that uses `progress.tsx` with unified
@@ -32,6 +32,8 @@ export const PING_INTERVAL_MS = 10_000
32
32
  export const QUERY_REPLY_TIMEOUT_MS = 6000
33
33
  /** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */
34
34
  export const BES_CONTINUE_LOCKOUT_MS = 15_000
35
+ /** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */
36
+ export const BES_RESTART_TIMEOUT_MS = 120_000
35
37
 
36
38
  // --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---
37
39
  //
@@ -51,6 +53,7 @@ export const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXT
51
53
  export const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
52
54
  export const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS
53
55
  export const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
56
+ export const LEGACY_BES_RESTART_TIMEOUT_MS = BES_RESTART_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
54
57
  /**
55
58
  * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the
56
59
  * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh
@@ -122,6 +125,7 @@ export function selectOtaProtocolProfile(
122
125
  export const OtaProgressMessages = {
123
126
  stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.",
124
127
  globalTimeout: "Update took too long. Please try again.",
128
+ restartTimeout: "Glasses did not finish restarting. Restart them before trying the update again.",
125
129
  sendOtaStartFailed: "Failed to communicate with glasses.",
126
130
  hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.",
127
131
  hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.",
@@ -1,4 +1,4 @@
1
- import type {OtaProgress, OtaProgressStatus, OtaStatus} from "@mentra/bluetooth-sdk/internal"
1
+ import type {OtaProgress, OtaProgressStatus, OtaStatus} from "@mentra/bluetooth-sdk"
2
2
 
3
3
  /** Normalized ota_status fields (snake_case) after bridging Core/expo (snake or camel). */
4
4
  export type NormalizedOtaStatusEvent = {
@@ -0,0 +1,42 @@
1
+ const OTA_VERSION_URL_LEGACY_PROD = "https://ota.mentraglass.com/prod_live_version.json"
2
+
3
+ function trimmed(value?: string | null): string | null {
4
+ const result = value?.trim()
5
+ return result || null
6
+ }
7
+
8
+ export function isLegacyAsgOtaStartBuild(glassesBuildNumber?: string | null): boolean {
9
+ const buildNumber = Number.parseInt(glassesBuildNumber ?? "", 10)
10
+ return Number.isFinite(buildNumber) && buildNumber < 39
11
+ }
12
+
13
+ export function selectModernOtaManifestPin({
14
+ developerOverride,
15
+ hostReleasePin,
16
+ engineReleasePin,
17
+ }: {
18
+ developerOverride?: string | null
19
+ hostReleasePin?: string | null
20
+ engineReleasePin?: string | null
21
+ }): string | null {
22
+ return trimmed(developerOverride) || trimmed(hostReleasePin) || trimmed(engineReleasePin)
23
+ }
24
+
25
+ export function resolveOtaManifestPolicy({
26
+ glassesUrl,
27
+ glassesBuildNumber,
28
+ developerOverride,
29
+ hostReleasePin,
30
+ engineReleasePin,
31
+ }: {
32
+ glassesUrl?: string | null
33
+ glassesBuildNumber?: string | null
34
+ developerOverride?: string | null
35
+ hostReleasePin?: string | null
36
+ engineReleasePin?: string | null
37
+ }): string | null {
38
+ if (isLegacyAsgOtaStartBuild(glassesBuildNumber)) {
39
+ return trimmed(glassesUrl) || OTA_VERSION_URL_LEGACY_PROD
40
+ }
41
+ return selectModernOtaManifestPin({developerOverride, hostReleasePin, engineReleasePin})
42
+ }
@@ -1,20 +1,6 @@
1
- import {sdkPinnedOtaManifestUrl} from "@mentra/bluetooth-sdk/internal"
2
-
1
+ import {ENGINE_RELEASE_METADATA} from "../generated/releaseMetadata"
3
2
  import {SETTINGS, useSettingsStore} from "../stores/settings"
4
-
5
- // Legacy production manifest — pre-39 glasses install from this (they ignore ota_start's URL).
6
- const OTA_VERSION_URL_LEGACY_PROD = "https://ota.mentraglass.com/prod_live_version.json"
7
- // Modern production fleet manifest (v2). Only a last-resort fallback when the SDK-derived pin
8
- // URL is somehow unavailable; modern glasses honor ota_start's URL, so this must be the v2
9
- // artifact, not the legacy v1 rescue manifest.
10
- const OTA_VERSION_URL_PROD = "https://ota.mentraglass.com/prod_live_version_v2.json"
11
-
12
- function isLegacyAsgOtaStartBuild(glassesBuildNumber?: string | null): boolean {
13
- const buildNumber = Number.parseInt(glassesBuildNumber ?? "", 10)
14
- // ASG builds before 39 ignore ota_start.ota_version_url, so compare against
15
- // the URL they will actually use.
16
- return Number.isFinite(buildNumber) && buildNumber < 39
17
- }
3
+ import {resolveOtaManifestPolicy, selectModernOtaManifestPin} from "./otaManifestPolicy"
18
4
 
19
5
  function getOtaVersionUrlDevOverride(): string | null {
20
6
  // Super mode only: a wrong OTA manifest can brick glasses, so a saved
@@ -27,27 +13,32 @@ function getOtaVersionUrlDevOverride(): string | null {
27
13
  return trimmed || null
28
14
  }
29
15
 
30
- export function resolveOtaManifestUrl(glassesUrl?: string | null, glassesBuildNumber?: string | null): string {
31
- const deviceUrl = glassesUrl?.trim()
32
- if (isLegacyAsgOtaStartBuild(glassesBuildNumber)) {
33
- // Legacy glasses ignore ota_start.ota_version_url and install from their
34
- // compiled default, so the developer override does not apply to them either.
35
- return deviceUrl || OTA_VERSION_URL_LEGACY_PROD
36
- }
16
+ function getHostReleasePin(): string | null {
17
+ const value = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
18
+ return value || null
19
+ }
37
20
 
38
- const devOverrideUrl = getOtaVersionUrlDevOverride()
39
- if (devOverrideUrl) {
40
- return devOverrideUrl
41
- }
21
+ function getEmbeddedEngineReleasePin(): string | null {
22
+ const value = ENGINE_RELEASE_METADATA.otaManifestUrl?.trim()
23
+ return value || null
24
+ }
42
25
 
43
- const envUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
44
- if (envUrl) {
45
- return envUrl
46
- }
26
+ export function hasConfiguredModernOtaManifestPin(): boolean {
27
+ return Boolean(
28
+ selectModernOtaManifestPin({
29
+ developerOverride: getOtaVersionUrlDevOverride(),
30
+ hostReleasePin: getHostReleasePin(),
31
+ engineReleasePin: getEmbeddedEngineReleasePin(),
32
+ }),
33
+ )
34
+ }
47
35
 
48
- // The phone owns manifest selection: modern glasses are driven with the manifest pinned to
49
- // this app's Bluetooth SDK version. Modern ASG builds report no manifest URL of their own;
50
- // a glasses-reported URL (older modern builds advertising their baked fleet default) is only
51
- // a last resort when the SDK version is somehow unavailable.
52
- return sdkPinnedOtaManifestUrl() || deviceUrl || OTA_VERSION_URL_PROD
36
+ export function resolveOtaManifestUrl(glassesUrl?: string | null, glassesBuildNumber?: string | null): string | null {
37
+ return resolveOtaManifestPolicy({
38
+ glassesUrl,
39
+ glassesBuildNumber,
40
+ developerOverride: getOtaVersionUrlDevOverride(),
41
+ hostReleasePin: getHostReleasePin(),
42
+ engineReleasePin: getEmbeddedEngineReleasePin(),
43
+ })
53
44
  }
@@ -0,0 +1,22 @@
1
+ const COORDINATED_RELEASE_VERSION = /^\d+\.\d+\.\d+(?:-(?:dev|beta)\.[1-9]\d*)?$/
2
+
3
+ type OtaReleaseVersionInput = {
4
+ manifestReleaseVersion?: string | null
5
+ manifestUrl: string
6
+ packagedManifestUrl?: string | null
7
+ packagedReleaseIdentity?: string | null
8
+ }
9
+
10
+ function validReleaseVersion(version: string | null | undefined): string | null {
11
+ const trimmed = version?.trim()
12
+ return trimmed && COORDINATED_RELEASE_VERSION.test(trimmed) ? trimmed : null
13
+ }
14
+
15
+ /** Resolve the label for the selected OTA pin, including stable promotion of exact beta OTA bytes. */
16
+ export function resolveOtaReleaseVersion(input: OtaReleaseVersionInput): string | null {
17
+ const packagedRelease = validReleaseVersion(input.packagedReleaseIdentity)
18
+ if (packagedRelease && input.packagedManifestUrl?.trim() === input.manifestUrl.trim()) {
19
+ return packagedRelease
20
+ }
21
+ return validReleaseVersion(input.manifestReleaseVersion)
22
+ }
@@ -0,0 +1,3 @@
1
+ /** Combine registry-enforced visibility with the user's home-screen choice. */
2
+ export const resolveHiddenStatus = (registryHidden: boolean, userHidden: boolean): boolean =>
3
+ registryHidden || userHidden
@@ -25,6 +25,7 @@ import {islandNotifications} from "../services/NotificationsEmitter"
25
25
  import sttModelManager from "../services/STTModelManager"
26
26
  import {miniappLauncher} from "../services/MiniappLauncher"
27
27
  import {miniappRunningRegistry} from "../services/MiniappRunningRegistry"
28
+ import {resolveHiddenStatus} from "./appVisibility"
28
29
  import {SETTINGS, useSettingsStore} from "./settings"
29
30
  import BluetoothSdk from "@mentra/bluetooth-sdk"
30
31
 
@@ -221,7 +222,12 @@ function projectApps(previousState: AppStatusState, localApps: ClientApp[]): Cli
221
222
  ...app,
222
223
  screenshot: previousByPackage.get(app.packageName)?.screenshot ?? app.screenshot,
223
224
  compatibility: HardwareCompatibility.checkCompatibility(app.hardwareRequirements, capabilities),
224
- hidden: previousState.getHiddenStatus(app.packageName),
225
+ // A registry source can force an app off the primary home screen (for
226
+ // example, Miniapp Developer while its home-screen setting is disabled).
227
+ // Compose that gate with the user's persisted home-screen layout instead
228
+ // of replacing it, otherwise a default-false persisted value makes a
229
+ // source-hidden app visible again during projection.
230
+ hidden: resolveHiddenStatus(app.hidden, previousState.getHiddenStatus(app.packageName)),
225
231
  // Derive the foreground flag from the store's single source of truth
226
232
  // (`foregroundedPackage`), NOT from the previous snapshot. Carrying it over
227
233
  // per-app meant a refresh that momentarily omitted an app (e.g. the
@@ -1,13 +1,13 @@
1
1
  import {create} from "zustand"
2
2
  import {subscribeWithSelector} from "zustand/middleware"
3
- import type {BluetoothStatus} from "@mentra/bluetooth-sdk/internal"
3
+ import type {PublicBluetoothStatus} from "@mentra/bluetooth-sdk"
4
4
 
5
- export interface CoreState extends BluetoothStatus {
6
- setCoreInfo: (info: Partial<BluetoothStatus>) => void
5
+ export interface CoreState extends PublicBluetoothStatus {
6
+ setCoreInfo: (info: Partial<PublicBluetoothStatus>) => void
7
7
  reset: () => void
8
8
  }
9
9
 
10
- const initialState: BluetoothStatus = {
10
+ const initialState: PublicBluetoothStatus = {
11
11
  // state:
12
12
  searching: false,
13
13
  searchingController: false,
@@ -1,12 +1,12 @@
1
1
  import type {
2
2
  GlassesConnectionStatus,
3
- GlassesStatus,
4
3
  HotspotStatus,
5
4
  OtaProgress,
6
5
  OtaStatus,
7
6
  OtaUpdateInfo,
7
+ PublicGlassesStatus,
8
8
  WifiStatus,
9
- } from "@mentra/bluetooth-sdk/internal"
9
+ } from "@mentra/bluetooth-sdk"
10
10
  import {isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady} from "../services/GlassesReadiness"
11
11
  import {create} from "zustand"
12
12
  import {subscribeWithSelector} from "zustand/middleware"
@@ -20,7 +20,14 @@ export const selectGlassesConnected = (state: {connection: GlassesConnectionStat
20
20
 
21
21
  export const selectGlassesReady = (state: {connection: GlassesConnectionStatus}) => isGlassesReady(state.connection)
22
22
 
23
- export interface GlassesState extends GlassesStatus {
23
+ type EngineGlassesStatus = PublicGlassesStatus & {
24
+ otaUpdateAvailable: OtaUpdateInfo | null
25
+ otaProgress: OtaProgress | null
26
+ otaInProgress: boolean
27
+ otaVersionUrl: string
28
+ }
29
+
30
+ export interface GlassesState extends EngineGlassesStatus {
24
31
  systemTimeMs: number
25
32
  wifiStatusKnown: boolean
26
33
  setGlassesInfo: (info: GlassesInfoUpdate) => void
@@ -53,7 +60,7 @@ type LegacyHotspotFields = {
53
60
  hotspotLocalIp?: string
54
61
  }
55
62
 
56
- type GlassesInfoUpdate = Partial<GlassesStatus> & LegacyWifiFields & LegacyHotspotFields
63
+ type GlassesInfoUpdate = Partial<EngineGlassesStatus> & LegacyWifiFields & LegacyHotspotFields
57
64
 
58
65
  function wifiFromLegacyFields(info: LegacyWifiFields): WifiStatus | null {
59
66
  if (info.wifiConnected === true) {
@@ -80,7 +87,7 @@ function hotspotFromLegacyFields(info: LegacyHotspotFields): HotspotStatus | nul
80
87
  return null
81
88
  }
82
89
 
83
- export const getGlasesInfoPartial = (state: GlassesStatus) => {
90
+ export const getGlasesInfoPartial = (state: EngineGlassesStatus) => {
84
91
  const wifi = state.wifi
85
92
  const connected = isGlassesConnected(state.connection)
86
93
  return {
@@ -98,7 +105,7 @@ export const getGlasesInfoPartial = (state: GlassesStatus) => {
98
105
  }
99
106
  }
100
107
 
101
- interface GlassesStore extends GlassesStatus {
108
+ interface GlassesStore extends EngineGlassesStatus {
102
109
  systemTimeMs: number
103
110
  mtkUpdatedThisSession: boolean
104
111
  wifiStatusKnown: boolean
@@ -994,9 +994,9 @@ export const useSettingsStore = create<SettingsState>()(
994
994
  // easy to get backwards. TestFlight and the App Store receive the SAME
995
995
  // binary, so promotion does not change EXPO_PUBLIC_BUILD_ENV and this
996
996
  // reset never fires on it. Promotion is safe for a different reason:
997
- // the publishing lane bakes prod URLs (staging-builds.yml defaults
998
- // cloud_env to prod), so a promoted build was pointed at production the
999
- // whole time. The same holds for Android internal-track to production.
997
+ // the publishing lane bakes the selected release cloud environment, and
998
+ // production promotes the already-tested binary. The same holds for
999
+ // Android beta-track to production.
1000
1000
  //
1001
1001
  // The residual gap is therefore an override pinned while already on a
1002
1002
  // prod-labelled build, which no label comparison can see. That is a