@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.27
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.
- package/README.md +53 -19
- package/build/bluetooth-sdk/debug.d.ts +2 -0
- package/build/bluetooth-sdk/debug.d.ts.map +1 -0
- package/build/bluetooth-sdk/debug.js +2 -0
- package/build/bluetooth-sdk/debug.js.map +1 -0
- package/build/bluetooth-sdk/index.d.ts +3 -0
- package/build/bluetooth-sdk/index.d.ts.map +1 -0
- package/build/bluetooth-sdk/index.js +3 -0
- package/build/bluetooth-sdk/index.js.map +1 -0
- package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
- package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
- package/build/bluetooth-sdk/ota-transport.js +2 -0
- package/build/bluetooth-sdk/ota-transport.js.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.js +3 -0
- package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
- package/build/bluetooth-sdk/react.d.ts +2 -0
- package/build/bluetooth-sdk/react.d.ts.map +1 -0
- package/build/bluetooth-sdk/react.js +2 -0
- package/build/bluetooth-sdk/react.js.map +1 -0
- package/build/bluetooth-sdk/types.d.ts +2 -0
- package/build/bluetooth-sdk/types.d.ts.map +1 -0
- package/build/bluetooth-sdk/types.js +2 -0
- package/build/bluetooth-sdk/types.js.map +1 -0
- package/build/engine.d.ts +23 -16
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +9 -3
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +1 -1
- package/build/facades/display.d.ts +1 -1
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +1 -1
- package/build/facades/display.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -13
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +5 -3
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/ota.d.ts +19 -21
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +23 -9
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +11 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +93 -17
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/speakers.d.ts +1 -1
- package/build/facades/speakers.d.ts.map +1 -1
- package/build/facades/speakers.js.map +1 -1
- package/build/facades/speech.d.ts +7 -6
- package/build/facades/speech.d.ts.map +1 -1
- package/build/facades/speech.js +1 -1
- package/build/facades/speech.js.map +1 -1
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +7 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -3
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +39 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +200 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +3 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useEngineSnapshot.d.ts +2 -0
- package/build/react/useEngineSnapshot.d.ts.map +1 -0
- package/build/react/useEngineSnapshot.js +14 -0
- package/build/react/useEngineSnapshot.js.map +1 -0
- package/build/react/useMentraLiveOta.d.ts +80 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +550 -0
- package/build/react/useMentraLiveOta.js.map +1 -0
- package/build/services/AppRegistry.d.ts +8 -0
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +15 -0
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -0
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +63 -10
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CustomDashboardModel.d.ts +48 -0
- package/build/services/CustomDashboardModel.d.ts.map +1 -0
- package/build/services/CustomDashboardModel.js +73 -0
- package/build/services/CustomDashboardModel.js.map +1 -0
- package/build/services/CustomDashboardService.d.ts +61 -0
- package/build/services/CustomDashboardService.d.ts.map +1 -0
- package/build/services/CustomDashboardService.js +552 -0
- package/build/services/CustomDashboardService.js.map +1 -0
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +44 -3
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +35 -0
- package/build/services/GestureDotsService.d.ts.map +1 -0
- package/build/services/GestureDotsService.js +149 -0
- package/build/services/GestureDotsService.js.map +1 -0
- package/build/services/GlassesStatusProjection.d.ts +15 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +18 -11
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts.map +1 -1
- package/build/services/HotspotOtaTransport.js +8 -7
- package/build/services/HotspotOtaTransport.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +43 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +128 -2
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +13 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +32 -0
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +33 -0
- package/build/services/MiniappExitGesture.d.ts.map +1 -0
- package/build/services/MiniappExitGesture.js +54 -0
- package/build/services/MiniappExitGesture.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +41 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +91 -0
- package/build/services/OtaAutoChain.js.map +1 -0
- package/build/services/OtaErrorMapping.d.ts +18 -0
- package/build/services/OtaErrorMapping.d.ts.map +1 -0
- package/build/services/OtaErrorMapping.js +63 -0
- package/build/services/OtaErrorMapping.js.map +1 -0
- package/build/services/OtaInstallCoordinator.d.ts +24 -3
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +17 -12
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +1 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +4 -1
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +28 -16
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
- package/build/services/asg/localNetworkTransport.js +13 -12
- package/build/services/asg/localNetworkTransport.js.map +1 -1
- package/build/services/gestureClaims.d.ts +18 -0
- package/build/services/gestureClaims.d.ts.map +1 -0
- package/build/services/gestureClaims.js +24 -0
- package/build/services/gestureClaims.js.map +1 -0
- package/build/services/gestureDotGlyphs.d.ts +8 -0
- package/build/services/gestureDotGlyphs.d.ts.map +1 -0
- package/build/services/gestureDotGlyphs.js +12 -0
- package/build/services/gestureDotGlyphs.js.map +1 -0
- package/build/services/glassesClockSync.d.ts.map +1 -1
- package/build/services/glassesClockSync.js +5 -1
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/otaDisplayState.d.ts +1 -1
- package/build/services/otaDisplayState.d.ts.map +1 -1
- package/build/services/otaDisplayState.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +1 -1
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaLegacyMapping.d.ts +1 -1
- package/build/services/otaLegacyMapping.d.ts.map +1 -1
- package/build/services/otaLegacyMapping.js.map +1 -1
- package/build/services/otaManifestPolicy.d.ts +14 -0
- package/build/services/otaManifestPolicy.d.ts.map +1 -0
- package/build/services/otaManifestPolicy.js +20 -0
- package/build/services/otaManifestPolicy.js.map +1 -0
- package/build/services/otaManifestUrl.d.ts +2 -1
- package/build/services/otaManifestUrl.d.ts.map +1 -1
- package/build/services/otaManifestUrl.js +24 -32
- package/build/services/otaManifestUrl.js.map +1 -1
- package/build/services/otaReleaseVersion.d.ts +10 -0
- package/build/services/otaReleaseVersion.d.ts.map +1 -0
- package/build/services/otaReleaseVersion.js +14 -0
- package/build/services/otaReleaseVersion.js.map +1 -0
- package/build/stores/appVisibility.d.ts +3 -0
- package/build/stores/appVisibility.d.ts.map +1 -0
- package/build/stores/appVisibility.js +3 -0
- package/build/stores/appVisibility.js.map +1 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +7 -1
- package/build/stores/apps.js.map +1 -1
- package/build/stores/core.d.ts +3 -3
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/glasses.d.ts +10 -4
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +87 -3
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js +0 -3
- package/build/types/applet.js.map +1 -1
- package/build/types/enums.d.ts +1 -5
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -8
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +1 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +2 -6
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +2 -6
- package/build/types/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +4 -3
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +6 -2
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js.map +1 -1
- package/package.json +65 -33
- package/src/bluetooth-sdk/debug.ts +1 -0
- package/src/bluetooth-sdk/index.ts +2 -0
- package/src/bluetooth-sdk/ota-transport.ts +1 -0
- package/src/bluetooth-sdk/photo-receiver.ts +2 -0
- package/src/bluetooth-sdk/react.ts +1 -0
- package/src/bluetooth-sdk/types.ts +1 -0
- package/src/engine.ts +9 -3
- package/src/facades/display.ts +1 -1
- package/src/facades/glasses.ts +6 -4
- package/src/facades/ota.ts +33 -10
- package/src/facades/pairing.ts +103 -23
- package/src/facades/speakers.ts +1 -1
- package/src/facades/speech.ts +21 -7
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +29 -4
- package/src/react/MentraLiveOtaFlow.tsx +569 -0
- package/src/react/index.ts +21 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/react/useMentraLiveOta.ts +707 -0
- package/src/services/AppRegistry.ts +16 -0
- package/src/services/BlobStore.ts +57 -10
- package/src/services/CustomDashboardModel.ts +83 -0
- package/src/services/CustomDashboardService.ts +575 -0
- package/src/services/DeviceEventRouter.ts +47 -5
- package/src/services/GestureDotsService.ts +147 -0
- package/src/services/GlassesStatusProjection.ts +20 -11
- package/src/services/HotspotOtaTransport.ts +8 -7
- package/src/services/LocalDisplayManager.ts +130 -2
- package/src/services/LocalMiniappRuntime.ts +37 -0
- package/src/services/MiniappExitGesture.ts +76 -0
- package/src/services/OtaAutoChain.ts +130 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +19 -13
- package/src/services/OtaService.ts +1 -2
- package/src/services/OtaUpdateCheckService.ts +33 -19
- package/src/services/asg/localNetworkTransport.ts +20 -19
- package/src/services/gestureClaims.ts +28 -0
- package/src/services/gestureDotGlyphs.ts +14 -0
- package/src/services/glassesClockSync.ts +5 -1
- package/src/services/otaDisplayState.ts +1 -1
- package/src/services/otaInstallPolicy.ts +1 -1
- package/src/services/otaLegacyMapping.ts +1 -1
- package/src/services/otaManifestPolicy.ts +43 -0
- package/src/services/otaManifestUrl.ts +27 -36
- package/src/services/otaReleaseVersion.ts +22 -0
- package/src/stores/appVisibility.ts +3 -0
- package/src/stores/apps.ts +7 -1
- package/src/stores/core.ts +4 -4
- package/src/stores/glasses.ts +13 -6
- package/src/stores/settings.ts +87 -3
- package/src/types/applet.ts +2 -0
- package/src/types/enums.ts +1 -9
- package/src/types/hardware.ts +1 -1
- package/src/types/index.ts +2 -6
- package/src/utils/display/scene/process.ts +4 -3
- package/src/utils/display/scene/types.ts +0 -0
- package/build/devtools.d.ts +0 -11
- package/build/devtools.d.ts.map +0 -1
- package/build/devtools.js +0 -13
- package/build/devtools.js.map +0 -1
- package/build/internal.d.ts +0 -67
- package/build/internal.d.ts.map +0 -1
- package/build/internal.js +0 -90
- package/build/internal.js.map +0 -1
- package/src/devtools.ts +0 -13
- package/src/internal.ts +0 -155
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;mEACmE;AAGnE,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,eAAe,EAAC,MAAM,SAAS,CAAA;AAG5F,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAA;AAGnB,YAAY,EAAC,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAC,MAAM,UAAU,CAAA"}
|
package/build/types/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* HardwareRequirementLevel, HardwareRequirement) are canonical in
|
|
4
|
-
* @glassly/miniapp and re-exported here; the rest (DeviceTypes, capability
|
|
5
|
-
* profiles under ./capabilities/, applet types) are engine-owned.
|
|
6
|
-
*/
|
|
1
|
+
/** Engine capability types and profiles. Miniapp manifest requirement types
|
|
2
|
+
* are re-exported from @glassly/miniapp, their canonical owner. */
|
|
7
3
|
// Enums (runtime values)
|
|
8
4
|
export { HardwareType, HardwareRequirementLevel, DeviceTypes, ControllerTypes } from "./enums";
|
|
9
5
|
export { HARDWARE_CAPABILITIES, getModelCapabilities, simulatedGlasses, evenRealitiesG1, evenRealitiesG2, mentraLive, vuzixZ100, mentraDisplay, nimo, } from "./hardware";
|
package/build/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;mEACmE;AAEnE,yBAAyB;AACzB,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,eAAe,EAAC,MAAM,SAAS,CAAA;AAgB5F,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAA","sourcesContent":["/** Engine capability types and profiles. Miniapp manifest requirement types\n * are re-exported from @glassly/miniapp, their canonical owner. */\n\n// Enums (runtime values)\nexport {HardwareType, HardwareRequirementLevel, DeviceTypes, ControllerTypes} from \"./enums\"\n\n// Hardware types\nexport type {\n HardwareRequirement,\n CameraCapabilities,\n DisplayCapabilities,\n MicrophoneCapabilities,\n SpeakerCapabilities,\n IMUCapabilities,\n ButtonCapabilities,\n LightCapabilities,\n PowerCapabilities,\n Capabilities,\n} from \"./hardware\"\n\nexport {\n HARDWARE_CAPABILITIES,\n getModelCapabilities,\n simulatedGlasses,\n evenRealitiesG1,\n evenRealitiesG2,\n mentraLive,\n vuzixZ100,\n mentraDisplay,\n nimo,\n} from \"./hardware\"\n\n// Applet types\nexport type {AppletType, AppPermissionType, AppletPermission, AppletInterface, ClientApp} from \"./applet\"\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAW,wBAAwB,EAAE,iBAAiB,EAAiB,MAAM,SAAS,CAAA;AAGlG,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,cAAc,GACtB,cAAc,
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAW,wBAAwB,EAAE,iBAAiB,EAAiB,MAAM,SAAS,CAAA;AAGlG,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,cAAc,GACtB,cAAc,CA+JhB"}
|
|
@@ -150,9 +150,10 @@ export function processScene(input, caps, profile) {
|
|
|
150
150
|
// Height clipping only applies with a CALIBRATED line height — otherwise
|
|
151
151
|
// the firmware clips vertically in-box (legacy behavior).
|
|
152
152
|
const style = el.style ?? {};
|
|
153
|
-
if (style.font === "mono") {
|
|
154
|
-
// Grid text is laid out by the app (cols × rows)
|
|
155
|
-
//
|
|
153
|
+
if (style.font === "mono" || style.font === "builtin") {
|
|
154
|
+
// Grid text is laid out by the app (cols × rows) and builtin text by the
|
|
155
|
+
// glasses' own font engine; the host's proportional wrap metrics don't
|
|
156
|
+
// apply to either. Pass lines through and let the device clip.
|
|
156
157
|
const monoText = el.text ?? "";
|
|
157
158
|
out.push({
|
|
158
159
|
id: el.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {SceneBox, SceneDisplayCapabilities, SceneElementInput, SceneTextStyle} from \"./types\"\nimport {elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\" || !raw.box) {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every((n) => typeof n === \"number\" && Number.isFinite(n))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n\n for (const {el, index} of valid) {\n const clamped = clampBox(el.box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(el.box, clamped)\n if (shrunk) degraded = true\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n contentHash: elementContentHash({type: \"image\", data: el.data}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n contentHash: elementContentHash({type: \"rect\", style: el.style}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\") {\n // Grid text is laid out by the app (cols × rows); the host's proportional\n // wrap metrics don't apply. Pass lines through and let the device clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx: clamped.w,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", clamped.w) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {SceneBox, SceneDisplayCapabilities, SceneElementInput, SceneTextStyle} from \"./types\"\nimport {elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\" || !raw.box) {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every((n) => typeof n === \"number\" && Number.isFinite(n))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n\n for (const {el, index} of valid) {\n const clamped = clampBox(el.box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(el.box, clamped)\n if (shrunk) degraded = true\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n contentHash: elementContentHash({type: \"image\", data: el.data}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n contentHash: elementContentHash({type: \"rect\", style: el.style}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\" || style.font === \"builtin\") {\n // Grid text is laid out by the app (cols × rows) and builtin text by the\n // glasses' own font engine; the host's proportional wrap metrics don't\n // apply to either. Pass lines through and let the device clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx: clamped.w,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", clamped.w) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
|
|
@@ -34,8 +34,12 @@ export interface SceneTextStyle {
|
|
|
34
34
|
overflow?: "clip" | "ellipsis";
|
|
35
35
|
/** Line-break policy for wrapping. */
|
|
36
36
|
breakMode?: SceneBreakMode;
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* "mono": fixed-pitch at `fontSize` px, lines taken as given (no host wrap).
|
|
39
|
+
* "builtin": drawn by the glasses' own 20 px font engine (Glassly CFW G2);
|
|
40
|
+
* lines taken as given, other devices fall back to the default face.
|
|
41
|
+
*/
|
|
42
|
+
font?: "default" | "mono" | "builtin";
|
|
39
43
|
fontSize?: number;
|
|
40
44
|
}
|
|
41
45
|
export interface SceneRectStyle {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAA;AAEnF;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,qBAAqB,GAAG,MAAM,GAAG,aAAa,CAAA;AAEzF,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9B,sCAAsC;IACtC,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuHH,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,GAAG,KAAsC;IACnE,IAAI,CAAC,GAAG,UAAU,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;YACpB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;QAC9B,CAAC;QACD,CAAC,IAAI,IAAI,CAAA;QACT,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAW,EAAE,CAAW;IACjD,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,EAKlC;IACC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;SAChC,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAK,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAA;IACZ,OAAO,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AACzD,CAAC","sourcesContent":["/**\n * Scene pipeline types — the host half of the miniapp `render()` display API.\n *\n * A miniapp describes a full frame (\"scene\") of positioned elements; the host\n * clamps/budgets/wraps it, diffs it against the last frame sent to the device,\n * and ships ONE annotated SceneFrame over the bridge. SGCs either walk the\n * per-element annotations (G2, Mentra Display) or serialize the whole frame\n * (full-frame devices). Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nexport interface SceneBox {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** Canvas corner a box's x/y are measured from (insets grow inward). */\nexport type SceneAnchor = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"\n\n/**\n * A box as it arrives from the miniapp SDK: optionally corner-anchored.\n * `resolveAnchors` turns these into plain top-left SceneBoxes against the\n * device's real canvas before any processing/degrading — anchors never reach\n * FrameElement, the differ, or the wire.\n */\nexport interface SceneBoxInput extends SceneBox {\n anchor?: SceneAnchor\n}\n\nexport type SceneBreakMode = \"character\" | \"character-no-hyphen\" | \"word\" | \"strict-word\"\n\nexport interface SceneTextStyle {\n /** Border width in px (0/absent = none). */\n border?: number\n /** Border corner radius in px. */\n radius?: number\n /** What happens to text that doesn't fit the box after wrapping. Default \"clip\". */\n overflow?: \"clip\" | \"ellipsis\"\n /** Line-break policy for wrapping. */\n breakMode?: SceneBreakMode\n /**\n * \"mono\": fixed-pitch at `fontSize` px, lines taken as given (no host wrap).\n * \"builtin\": drawn by the glasses' own 20 px font engine (Glassly CFW G2);\n * lines taken as given, other devices fall back to the default face.\n */\n font?: \"default\" | \"mono\" | \"builtin\"\n fontSize?: number\n}\n\nexport interface SceneRectStyle {\n border?: number\n radius?: number\n}\n\n/** A scene element as it arrives from the miniapp SDK (pre-processing). */\nexport type SceneElementInput =\n | {type: \"text\"; id?: string; box: SceneBoxInput; text: string; style?: SceneTextStyle}\n | {type: \"image\"; id?: string; box: SceneBoxInput; data: string}\n | {type: \"rect\"; id?: string; box: SceneBoxInput; style?: SceneRectStyle}\n\nexport type SceneElementType = SceneElementInput[\"type\"]\n\nexport type SceneChange = \"created\" | \"updated\" | \"moved\" | \"unchanged\"\n\n/**\n * A processed element inside a SceneFrame: id definitely assigned, box clamped,\n * text pre-wrapped (newlines inserted), annotated with what changed vs the last\n * frame sent to this device.\n */\nexport interface FrameElement {\n id: string\n type: SceneElementType\n box: SceneBox\n text?: string\n /** Image pixels, base64 (PNG documented; SGCs decode → re-encode to wire format and scale to box). */\n data?: string\n style?: SceneTextStyle | SceneRectStyle\n change: SceneChange\n /** Hash of the element's content (text/data + style) — lets SGCs skip BLE re-upload without retaining pixels. */\n contentHash: string\n}\n\n/** The unit that crosses the JS→native bridge: one whole frame per render(). */\nexport interface SceneFrame {\n appId: string\n view: \"main\" | \"dashboard\"\n /** Bumps on replay / app switch / reconnect; a bumped epoch bypasses native dedup. */\n sceneEpoch: number\n /** True when the device is expected to rebuild from scratch (all elements arrive \"created\"). */\n replay?: boolean\n elements: FrameElement[]\n /** Element ids present in the previous frame but absent now — host-computed; apps never send removes. */\n removed: string[]\n /** Canvas the boxes were clamped against — lets a full-frame SGC place a legacy-sized frame correctly. */\n canvas?: {width: number; height: number}\n}\n\n/**\n * Typed display capabilities block (host-side mirror of the SDK's\n * DisplayCapabilities). All limits are DATA — the pipeline acts on them\n * generically; no device names appear in pipeline code.\n */\nexport interface SceneDisplayCapabilities {\n /** Public drawable canvas in px (the device's safe area, not the physical panel). */\n width: number\n height: number\n /** False ⇒ scenes degrade to legacy text layouts host-side; only sugar-shaped content renders. */\n canPosition: boolean\n /** Text-element budget. Rects share this pool on container-based devices. */\n maxTextElements: number\n maxImageElements: number\n /** Max per-image dimensions the device accepts (box-level check), if limited. */\n maxImagePx?: {width: number; height: number}\n shapes: \"rect\"[]\n intensityLevels: number\n partialUpdate: boolean\n}\n\n/** Result of processing a scene — feeds the awaitable render() result. */\nexport interface SceneProcessResult {\n frame: SceneFrame\n degraded: boolean\n /** Ids (or synthesized ids) of elements dropped by budget/bounds/image limits. */\n dropped: string[]\n}\n\n/** Cheap stable string hash (FNV-1a, 32-bit) — content identity for diff + native dedup. */\nexport function contentHash(...parts: (string | number | undefined)[]): string {\n let h = 0x811c9dc5\n for (const part of parts) {\n const s = part === undefined ? \"\u0000\" : String(part)\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n h ^= 0x1f\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n\nexport function boxesEqual(a: SceneBox, b: SceneBox): boolean {\n return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h\n}\n\n/** Content identity of an element (text/data + style), excluding geometry. */\nexport function elementContentHash(el: {\n type: SceneElementType\n text?: string\n data?: string\n style?: SceneTextStyle | SceneRectStyle\n}): string {\n const style = el.style ?? {}\n const styleKey = Object.keys(style)\n .sort()\n .map((k) => `${k}=${(style as Record<string, unknown>)[k]}`)\n .join(\",\")\n return contentHash(el.type, el.text, el.data, styleKey)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.27",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -11,15 +11,40 @@
|
|
|
11
11
|
"react-native": "./src/index.ts",
|
|
12
12
|
"default": "./build/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./
|
|
15
|
-
"types": "./build/
|
|
16
|
-
"react-native": "./src/
|
|
17
|
-
"default": "./build/
|
|
14
|
+
"./ota": {
|
|
15
|
+
"types": "./build/react/index.d.ts",
|
|
16
|
+
"react-native": "./src/react/index.ts",
|
|
17
|
+
"default": "./build/react/index.js"
|
|
18
18
|
},
|
|
19
|
-
"./
|
|
20
|
-
"types": "./build/
|
|
21
|
-
"react-native": "./src/
|
|
22
|
-
"default": "./build/
|
|
19
|
+
"./bluetooth-sdk": {
|
|
20
|
+
"types": "./build/bluetooth-sdk/index.d.ts",
|
|
21
|
+
"react-native": "./src/bluetooth-sdk/index.ts",
|
|
22
|
+
"default": "./build/bluetooth-sdk/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./bluetooth-sdk/react": {
|
|
25
|
+
"types": "./build/bluetooth-sdk/react.d.ts",
|
|
26
|
+
"react-native": "./src/bluetooth-sdk/react.ts",
|
|
27
|
+
"default": "./build/bluetooth-sdk/react.js"
|
|
28
|
+
},
|
|
29
|
+
"./bluetooth-sdk/types": {
|
|
30
|
+
"types": "./build/bluetooth-sdk/types.d.ts",
|
|
31
|
+
"react-native": "./src/bluetooth-sdk/types.ts",
|
|
32
|
+
"default": "./build/bluetooth-sdk/types.js"
|
|
33
|
+
},
|
|
34
|
+
"./bluetooth-sdk/photo-receiver": {
|
|
35
|
+
"types": "./build/bluetooth-sdk/photo-receiver.d.ts",
|
|
36
|
+
"react-native": "./src/bluetooth-sdk/photo-receiver.ts",
|
|
37
|
+
"default": "./build/bluetooth-sdk/photo-receiver.js"
|
|
38
|
+
},
|
|
39
|
+
"./bluetooth-sdk/ota-transport": {
|
|
40
|
+
"types": "./build/bluetooth-sdk/ota-transport.d.ts",
|
|
41
|
+
"react-native": "./src/bluetooth-sdk/ota-transport.ts",
|
|
42
|
+
"default": "./build/bluetooth-sdk/ota-transport.js"
|
|
43
|
+
},
|
|
44
|
+
"./bluetooth-sdk/debug": {
|
|
45
|
+
"types": "./build/bluetooth-sdk/debug.d.ts",
|
|
46
|
+
"react-native": "./src/bluetooth-sdk/debug.ts",
|
|
47
|
+
"default": "./build/bluetooth-sdk/debug.js"
|
|
23
48
|
},
|
|
24
49
|
"./build/*": null,
|
|
25
50
|
"./src/*": null,
|
|
@@ -29,6 +54,10 @@
|
|
|
29
54
|
"build",
|
|
30
55
|
"README.md",
|
|
31
56
|
"src",
|
|
57
|
+
"!build/devtools.*",
|
|
58
|
+
"!build/internal.*",
|
|
59
|
+
"!src/devtools.ts",
|
|
60
|
+
"!src/internal.ts",
|
|
32
61
|
"!src/**/__tests__",
|
|
33
62
|
"!src/**/*.test.ts"
|
|
34
63
|
],
|
|
@@ -36,7 +65,11 @@
|
|
|
36
65
|
"build": "expo-module build",
|
|
37
66
|
"clean": "expo-module clean",
|
|
38
67
|
"lint": "expo-module lint",
|
|
39
|
-
"test": "./scripts/test.sh",
|
|
68
|
+
"test": "./scripts/test.sh && node --test scripts/*.test.mjs",
|
|
69
|
+
"write:release-metadata": "node scripts/write-release-metadata.mjs",
|
|
70
|
+
"verify:release-package": "node scripts/verify-release-package.mjs",
|
|
71
|
+
"test:public-api": "bun test src/bluetooth-sdk/__tests__/publicApi.test.ts",
|
|
72
|
+
"test:public-ota-consumer": "tsc --noEmit --strict --skipLibCheck --jsx react-jsx --moduleResolution bundler --module esnext --target es2022 scripts/fixtures/public-ota-consumer.tsx",
|
|
40
73
|
"build:module": "expo-module build",
|
|
41
74
|
"prepublishOnly": "expo-module prepublishOnly",
|
|
42
75
|
"expo-module": "expo-module"
|
|
@@ -64,16 +97,17 @@
|
|
|
64
97
|
"registry": "https://registry.npmjs.org/"
|
|
65
98
|
},
|
|
66
99
|
"dependencies": {
|
|
67
|
-
"@glassly/
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.1.0-dev.27",
|
|
101
|
+
"@glassly/cloud-client": "0.1.0-dev.27",
|
|
102
|
+
"@glassly/cloud-protocol": "0.1.0-dev.27",
|
|
103
|
+
"@glassly/crust": "0.1.0-dev.27",
|
|
104
|
+
"@glassly/miniapp": "0.1.0-dev.27",
|
|
68
105
|
"buffer": "^6.0.3",
|
|
69
106
|
"events": "^3.3.0",
|
|
70
107
|
"semver": "^7.7.4",
|
|
71
108
|
"typesafe-ts": "^1.7.1"
|
|
72
109
|
},
|
|
73
110
|
"devDependencies": {
|
|
74
|
-
"@glassly/bluetooth-sdk": "workspace:*",
|
|
75
|
-
"@glassly/cloud-client": "workspace:*",
|
|
76
|
-
"@glassly/cloud-protocol": "workspace:*",
|
|
77
111
|
"@glassly/llm-providers": "workspace:*",
|
|
78
112
|
"@types/semver": "^7.7.1",
|
|
79
113
|
"expo-module-scripts": "^56.0.3",
|
|
@@ -82,28 +116,31 @@
|
|
|
82
116
|
"typescript": "~5.9.2"
|
|
83
117
|
},
|
|
84
118
|
"peerDependencies": {
|
|
85
|
-
"expo": "
|
|
86
|
-
"expo-modules-core": "
|
|
119
|
+
"expo": "~55.0.24",
|
|
120
|
+
"expo-modules-core": "~55.0.25",
|
|
87
121
|
"react": "*",
|
|
88
122
|
"react-native": "*",
|
|
123
|
+
"react-native-safe-area-context": "*",
|
|
124
|
+
"react-native-svg": "*",
|
|
89
125
|
"react-native-udp": "*",
|
|
90
126
|
"@types/react": "*",
|
|
91
127
|
"react-native-mmkv": "*",
|
|
92
128
|
"react-native-nitro-bg-timer": "*",
|
|
93
129
|
"react-native-nitro-modules": "*",
|
|
94
|
-
"react-native-reanimated": "
|
|
95
|
-
"
|
|
130
|
+
"react-native-reanimated": "4.2.1",
|
|
131
|
+
"react-native-worklets": "0.7.4",
|
|
132
|
+
"expo-file-system": "~55.0.20",
|
|
96
133
|
"react-native-zip-archive": "*",
|
|
97
134
|
"zustand": "*",
|
|
98
|
-
"expo-battery": "
|
|
99
|
-
"expo-audio": "
|
|
100
|
-
"expo-clipboard": "
|
|
101
|
-
"expo-calendar": "
|
|
102
|
-
"expo-constants": "
|
|
103
|
-
"expo-device": "
|
|
104
|
-
"expo-location": "
|
|
105
|
-
"expo-task-manager": "
|
|
106
|
-
"expo-notifications": "
|
|
135
|
+
"expo-battery": "^55.0.13",
|
|
136
|
+
"expo-audio": "~55.0.14",
|
|
137
|
+
"expo-clipboard": "~55.0.13",
|
|
138
|
+
"expo-calendar": "~55.0.14",
|
|
139
|
+
"expo-constants": "~55.0.16",
|
|
140
|
+
"expo-device": "~55.0.17",
|
|
141
|
+
"expo-location": "~55.1.10",
|
|
142
|
+
"expo-task-manager": "~55.0.16",
|
|
143
|
+
"expo-notifications": "~55.0.23",
|
|
107
144
|
"react-native-share": "*",
|
|
108
145
|
"react-native-localize": "*",
|
|
109
146
|
"react-native-ble-manager": "*",
|
|
@@ -112,11 +149,6 @@
|
|
|
112
149
|
"@react-native-community/netinfo": "*",
|
|
113
150
|
"@dr.pogodin/react-native-fs": "*",
|
|
114
151
|
"@craftzdog/react-native-buffer": "*",
|
|
115
|
-
"axios": "*"
|
|
116
|
-
"@glassly/bluetooth-sdk": "*",
|
|
117
|
-
"@glassly/crust": "^0.1.0-dev.1",
|
|
118
|
-
"@glassly/cloud-client": "^0.1.0-dev.0",
|
|
119
|
-
"@glassly/miniapp": "^0.3.0-dev.2",
|
|
120
|
-
"@glassly/cloud-protocol": "^0.1.0-dev.0"
|
|
152
|
+
"axios": "*"
|
|
121
153
|
}
|
|
122
154
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@glassly/bluetooth-sdk/debug"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@glassly/bluetooth-sdk/ota-transport"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@glassly/bluetooth-sdk/react"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@glassly/bluetooth-sdk/types"
|
package/src/engine.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* name stays `engine` in code; the public API surface is `engine`.)
|
|
6
6
|
*
|
|
7
7
|
* It grows one facade at a time. The migration-era flat store/service exports
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* remain in a private GlasslyOS host package; they shrink as screens move onto
|
|
9
|
+
* `engine.*`.
|
|
10
10
|
*/
|
|
11
11
|
import {configure, start as bootstrapStart, stop as bootstrapStop, updateUiSeams} from "./runtime/bootstrap"
|
|
12
12
|
import {cloudClientService} from "./services/CloudClientService"
|
|
@@ -45,6 +45,8 @@ import {memory} from "./facades/memory"
|
|
|
45
45
|
import {speakers} from "./facades/speakers"
|
|
46
46
|
import {providerKeys} from "./facades/providerKeys"
|
|
47
47
|
import {logBuffer} from "./utils/devLogging"
|
|
48
|
+
import customDashboardService from "./services/CustomDashboardService"
|
|
49
|
+
import gestureDotsService from "./services/GestureDotsService"
|
|
48
50
|
|
|
49
51
|
export const engine = {
|
|
50
52
|
/** Front door — hand engine auth + config, then start/stop the runtime. */
|
|
@@ -65,7 +67,9 @@ export const engine = {
|
|
|
65
67
|
// Project native device status -> the engine stores (the inbound feed the rest
|
|
66
68
|
// of the runtime reads). Established first so the stores are live before the
|
|
67
69
|
// syncs below react to them.
|
|
68
|
-
startGlassesStatusProjection()
|
|
70
|
+
startGlassesStatusProjection((changed) => localMiniappRuntime.forwardEvent("glasses_connection_state", changed))
|
|
71
|
+
customDashboardService.start()
|
|
72
|
+
gestureDotsService.start()
|
|
69
73
|
// Route the rest of the inbound device events (wifi/hotspot/gallery -> stores+bus,
|
|
70
74
|
// photo/stream -> coordinators, button/touch/accel/head -> miniapps, save_setting ->
|
|
71
75
|
// store, miniapp_selected -> launcher) so a bare OEM gets device data, not just the
|
|
@@ -147,6 +151,8 @@ export const engine = {
|
|
|
147
151
|
await safely("glasses settings sync", stopGlassesSettingsSync)
|
|
148
152
|
await safely("glasses status projection", stopGlassesStatusProjection)
|
|
149
153
|
await safely("device event router", stopDeviceEventRouter)
|
|
154
|
+
await safely("custom dashboard service", () => customDashboardService.stop())
|
|
155
|
+
await safely("gesture dots service", () => gestureDotsService.stop())
|
|
150
156
|
await safely("ota service", stopOtaService)
|
|
151
157
|
await safely("audio cloud uplink", stopAudioCloudUplink)
|
|
152
158
|
await safely("soniox direct service", () => sonioxDirectService.stop())
|
package/src/facades/display.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* glasses display for dev tools and escape-hatch use (normal rendering goes through
|
|
5
5
|
* the engine DisplayProcessor / layout system, not these).
|
|
6
6
|
*/
|
|
7
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
7
|
+
import BluetoothSdk from "@glassly/bluetooth-sdk"
|
|
8
8
|
import {displayMirror} from "./displayMirror"
|
|
9
9
|
|
|
10
10
|
export const display = {
|
package/src/facades/glasses.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// surface, not the public entry. Relative path (the alias doesn't resolve in engine's
|
|
12
12
|
// standalone build); jest moduleNameMapper + tsconfig both resolve it.
|
|
13
13
|
import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
|
|
14
|
-
import type {ButtonPressEvent, TouchEvent} from "@glassly/bluetooth-sdk
|
|
14
|
+
import type {ButtonPressEvent, ConnectOptions, Device, TouchEvent} from "@glassly/bluetooth-sdk"
|
|
15
15
|
import {useGlassesStore} from "../stores/glasses"
|
|
16
16
|
import {useSettingsStore, SETTINGS} from "../stores/settings"
|
|
17
17
|
import {hasDefaultDevice} from "../services/DeviceStoreHydration"
|
|
@@ -66,6 +66,8 @@ function projectInfo() {
|
|
|
66
66
|
wifiMac: s.wifiMacAddress,
|
|
67
67
|
leftMac: s.leftMacAddress,
|
|
68
68
|
rightMac: s.rightMacAddress,
|
|
69
|
+
sceneCanvasWidth: s.sceneCanvasWidth,
|
|
70
|
+
sceneCanvasHeight: s.sceneCanvasHeight,
|
|
69
71
|
// Copy: the snapshot must not hand callers a mutable reference into the store.
|
|
70
72
|
wifi: {...s.wifi},
|
|
71
73
|
}
|
|
@@ -94,10 +96,10 @@ export const glasses = {
|
|
|
94
96
|
disconnect: (): Promise<void> => BluetoothSdk.disconnect(),
|
|
95
97
|
forget: (): Promise<void> => BluetoothSdk.forget(),
|
|
96
98
|
/** Connect to a specific (discovered) device. */
|
|
97
|
-
connect: async (
|
|
99
|
+
connect: async (device: Device, options?: ConnectOptions): Promise<void> => {
|
|
98
100
|
try {
|
|
99
101
|
await pushAllBluetoothSettings()
|
|
100
|
-
await BluetoothSdk.connect(
|
|
102
|
+
await BluetoothSdk.connect(device, options)
|
|
101
103
|
} catch (error) {
|
|
102
104
|
recordSupportProfileConnectionFailure(error, "connect")
|
|
103
105
|
throw error
|
|
@@ -106,7 +108,7 @@ export const glasses = {
|
|
|
106
108
|
/** Connect the built-in simulated glasses (dev/testing). */
|
|
107
109
|
connectSimulated: (): Promise<void> => BluetoothSdk.connectSimulated(),
|
|
108
110
|
/** Set a device as the `connectDefault()` target. */
|
|
109
|
-
setDefault: (
|
|
111
|
+
setDefault: (device: Device | null): Promise<void> => BluetoothSdk.setDefaultDevice(device),
|
|
110
112
|
/**
|
|
111
113
|
* Reconnect to the saved default glasses. Resolves `false` when there's nothing to
|
|
112
114
|
* reconnect to (no default paired) or the connect attempt fails (e.g. BLE powered
|
package/src/facades/ota.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ota facade — `engine.ota`: the OEM-facing OTA read/observe surface. Engine's
|
|
3
3
|
* OtaService projects the glasses' OTA BLE events into the engine store; this facade
|
|
4
|
-
* exposes the snapshot + change subscriptions the
|
|
5
|
-
*
|
|
4
|
+
* exposes the snapshot + change subscriptions behind the shared OTA flow exported by
|
|
5
|
+
* `@glassly/engine/ota`. Hosts retain only their surrounding navigation and theme/i18n adapters.
|
|
6
6
|
*
|
|
7
7
|
* Availability checks live in engine. Install orchestration lives in engine too:
|
|
8
8
|
* `installSession` fronts the OtaInstallCoordinator state machine (WP 8B) — the host
|
|
9
9
|
* progress screen is a pure renderer over its snapshot + attach/detach/retry/finish.
|
|
10
10
|
*/
|
|
11
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
12
|
-
import type {
|
|
11
|
+
import BluetoothSdk from "@glassly/bluetooth-sdk"
|
|
12
|
+
import type {
|
|
13
|
+
OtaProgress,
|
|
14
|
+
OtaProgressStatus,
|
|
15
|
+
OtaStartAckEvent,
|
|
16
|
+
OtaStatus,
|
|
17
|
+
OtaUpdateInfo,
|
|
18
|
+
ReleaseChangelog,
|
|
19
|
+
} from "@glassly/bluetooth-sdk"
|
|
13
20
|
import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
|
|
14
21
|
import {resolveOtaManifestUrl} from "../services/otaManifestUrl"
|
|
15
22
|
import {otaInstallCoordinator, type OtaInstallSnapshot} from "../services/OtaInstallCoordinator"
|
|
23
|
+
import {startGlassesStatusProjection} from "../services/GlassesStatusProjection"
|
|
24
|
+
import {startOtaService} from "../services/OtaService"
|
|
16
25
|
import {
|
|
17
26
|
checkCurrentGlassesForUpdate,
|
|
18
27
|
type OtaCheckCurrentGlassesOptions,
|
|
@@ -24,6 +33,7 @@ function projectSnapshot() {
|
|
|
24
33
|
return {
|
|
25
34
|
connected: isGlassesConnected(s.connection),
|
|
26
35
|
buildNumber: s.buildNumber || null,
|
|
36
|
+
appVersion: s.appVersion || null,
|
|
27
37
|
mtkFirmwareVersion: s.mtkFirmwareVersion || null,
|
|
28
38
|
besFirmwareVersion: s.besFirmwareVersion || null,
|
|
29
39
|
hotspotOtaVersion: s.hotspotOtaVersion ?? 0,
|
|
@@ -44,6 +54,7 @@ export type {
|
|
|
44
54
|
OtaProgressStatus,
|
|
45
55
|
OtaStatus,
|
|
46
56
|
OtaUpdateInfo,
|
|
57
|
+
ReleaseChangelog,
|
|
47
58
|
OtaInstallSnapshot,
|
|
48
59
|
OtaCheckCurrentGlassesOptions,
|
|
49
60
|
OtaCheckCurrentGlassesResult,
|
|
@@ -51,13 +62,23 @@ export type {
|
|
|
51
62
|
|
|
52
63
|
export const ota = {
|
|
53
64
|
// --- actions ---
|
|
65
|
+
/**
|
|
66
|
+
* Start the device-status and OTA projections without starting the authenticated
|
|
67
|
+
* cloud/miniapp runtime. This is the explicit entry point for Bluetooth-only hosts.
|
|
68
|
+
* Idempotent and safe when the full engine runtime is already running.
|
|
69
|
+
*/
|
|
70
|
+
initialize: async () => {
|
|
71
|
+
const hydrated = startGlassesStatusProjection()
|
|
72
|
+
startOtaService()
|
|
73
|
+
await hydrated
|
|
74
|
+
},
|
|
54
75
|
/**
|
|
55
76
|
* Start the firmware install with the resolved OTA manifest URL. Progress lands on
|
|
56
|
-
* `status()`/`onStatus()`. (The host resolves the manifest URL —
|
|
57
|
-
* resolution stays with the OTA config; the
|
|
77
|
+
* `status()`/`onStatus()`. (The host resolves the manifest URL — developer override,
|
|
78
|
+
* host pin, or Engine release pin resolution stays with the OTA config; the stuck/retry watchdog is its own
|
|
58
79
|
* resilience layer on top of this command.)
|
|
59
80
|
*/
|
|
60
|
-
install: (
|
|
81
|
+
install: (otaVersionUrl?: string | null): Promise<OtaStartAckEvent> => BluetoothSdk.startOtaUpdate(otaVersionUrl),
|
|
61
82
|
// Deferred: this facade entry was intended to become the engine-owned retry/check
|
|
62
83
|
// action, but BluetoothSdk.checkForOtaUpdate() only returns a boolean. Exposing it
|
|
63
84
|
// here would make callers think the rich otaUpdateAvailable read model is refreshed,
|
|
@@ -72,6 +93,9 @@ export const ota = {
|
|
|
72
93
|
ping: () => BluetoothSdk.ping(),
|
|
73
94
|
/** Resolve and compare the current glasses against the OTA manifest, then update the OTA snapshot. */
|
|
74
95
|
checkForUpdates: (options?: OtaCheckCurrentGlassesOptions) => checkCurrentGlassesForUpdate(options),
|
|
96
|
+
/** Return bundled release changelogs crossed between two coordinated product versions, newest first. */
|
|
97
|
+
getReleaseChangelogs: (fromVersion?: string | null, toVersion?: string | null): ReleaseChangelog[] =>
|
|
98
|
+
BluetoothSdk.getReleaseChangelogs(fromVersion, toVersion),
|
|
75
99
|
/** Clear the available-update prompt state. */
|
|
76
100
|
clearUpdateAvailable: () => useGlassesStore.getState().setOtaUpdateAvailable(null),
|
|
77
101
|
/** Clear active progress/status before entering a fresh install flow. */
|
|
@@ -101,7 +125,6 @@ export const ota = {
|
|
|
101
125
|
* @deprecated Legacy progress route (build < 37) only — removed with WP 8D.
|
|
102
126
|
*/
|
|
103
127
|
clearBuildNumberForNextCheck: () => {
|
|
104
|
-
BluetoothSdk.updateGlasses({buildNumber: ""})
|
|
105
128
|
useGlassesStore.getState().setGlassesInfo({buildNumber: ""})
|
|
106
129
|
},
|
|
107
130
|
/**
|
|
@@ -124,9 +147,9 @@ export const ota = {
|
|
|
124
147
|
detach: () => otaInstallCoordinator.detach(),
|
|
125
148
|
/** Retry after a failure: clear state and re-send ota_start (if connected). */
|
|
126
149
|
retry: () => otaInstallCoordinator.retry(),
|
|
127
|
-
/** Terminal cleanup for Continue/Done
|
|
150
|
+
/** Terminal cleanup for Continue/Done, including hotspot route teardown. */
|
|
128
151
|
finish: () => otaInstallCoordinator.finish(),
|
|
129
|
-
/** Abandon an interrupted session
|
|
152
|
+
/** Abandon an interrupted session: await finish() then drop the session's status/progress. */
|
|
130
153
|
discard: () => otaInstallCoordinator.discard(),
|
|
131
154
|
/** Current install read model (displayState/errorMsg/lockout + glasses OTA data). */
|
|
132
155
|
snapshot: (): OtaInstallSnapshot => otaInstallCoordinator.snapshot(),
|