@glassly/engine 0.3.5 → 0.3.6-dev.846

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 (206) hide show
  1. package/build/engine.d.ts +11 -1
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +13 -1
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/ai.d.ts +3 -3
  6. package/build/facades/ai.d.ts.map +1 -1
  7. package/build/facades/ai.js +3 -3
  8. package/build/facades/ai.js.map +1 -1
  9. package/build/facades/glasses.d.ts +5 -1
  10. package/build/facades/glasses.d.ts.map +1 -1
  11. package/build/facades/glasses.js +39 -30
  12. package/build/facades/glasses.js.map +1 -1
  13. package/build/facades/memory.d.ts +10 -2
  14. package/build/facades/memory.d.ts.map +1 -1
  15. package/build/facades/memory.js +10 -0
  16. package/build/facades/memory.js.map +1 -1
  17. package/build/facades/miniapps.d.ts +2 -0
  18. package/build/facades/miniapps.d.ts.map +1 -1
  19. package/build/facades/miniapps.js +2 -0
  20. package/build/facades/miniapps.js.map +1 -1
  21. package/build/facades/pairing.d.ts.map +1 -1
  22. package/build/facades/pairing.js +3 -2
  23. package/build/facades/pairing.js.map +1 -1
  24. package/build/facades/providerKeys.d.ts +6 -2
  25. package/build/facades/providerKeys.d.ts.map +1 -1
  26. package/build/facades/providerKeys.js +6 -2
  27. package/build/facades/providerKeys.js.map +1 -1
  28. package/build/generated/releaseMetadata.js +4 -4
  29. package/build/generated/releaseMetadata.js.map +1 -1
  30. package/build/services/AiTierResolution.d.ts +3 -2
  31. package/build/services/AiTierResolution.d.ts.map +1 -1
  32. package/build/services/AiTierResolution.js +7 -2
  33. package/build/services/AiTierResolution.js.map +1 -1
  34. package/build/services/AppRegistry.d.ts +4 -1
  35. package/build/services/AppRegistry.d.ts.map +1 -1
  36. package/build/services/AppRegistry.js +16 -2
  37. package/build/services/AppRegistry.js.map +1 -1
  38. package/build/services/BlobStore.js +3 -3
  39. package/build/services/BlobStore.js.map +1 -1
  40. package/build/services/BluetoothRestoreLaunch.d.ts +8 -0
  41. package/build/services/BluetoothRestoreLaunch.d.ts.map +1 -0
  42. package/build/services/BluetoothRestoreLaunch.js +34 -0
  43. package/build/services/BluetoothRestoreLaunch.js.map +1 -0
  44. package/build/services/CloudClientService.d.ts +4 -4
  45. package/build/services/ForegroundLocationPermission.d.ts +1 -1
  46. package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
  47. package/build/services/ForegroundLocationPermission.js.map +1 -1
  48. package/build/services/GestureDotsService.d.ts.map +1 -1
  49. package/build/services/GestureDotsService.js +2 -1
  50. package/build/services/GestureDotsService.js.map +1 -1
  51. package/build/services/GlassesTouchInput.d.ts +6 -4
  52. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  53. package/build/services/GlassesTouchInput.js +81 -41
  54. package/build/services/GlassesTouchInput.js.map +1 -1
  55. package/build/services/LocalDisplayManager.d.ts +12 -4
  56. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  57. package/build/services/LocalDisplayManager.js +21 -7
  58. package/build/services/LocalDisplayManager.js.map +1 -1
  59. package/build/services/LocalLlmModelManager.d.ts +3 -1
  60. package/build/services/LocalLlmModelManager.d.ts.map +1 -1
  61. package/build/services/LocalLlmModelManager.js +13 -5
  62. package/build/services/LocalLlmModelManager.js.map +1 -1
  63. package/build/services/LocalLlmPrompt.d.ts +10 -9
  64. package/build/services/LocalLlmPrompt.d.ts.map +1 -1
  65. package/build/services/LocalLlmPrompt.js +43 -32
  66. package/build/services/LocalLlmPrompt.js.map +1 -1
  67. package/build/services/LocalLlmProvider.d.ts +1 -0
  68. package/build/services/LocalLlmProvider.d.ts.map +1 -1
  69. package/build/services/LocalLlmProvider.js +62 -22
  70. package/build/services/LocalLlmProvider.js.map +1 -1
  71. package/build/services/LocalMiniappRuntime.d.ts +28 -6
  72. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  73. package/build/services/LocalMiniappRuntime.js +190 -148
  74. package/build/services/LocalMiniappRuntime.js.map +1 -1
  75. package/build/services/MemoryStore.d.ts +13 -1
  76. package/build/services/MemoryStore.d.ts.map +1 -1
  77. package/build/services/MemoryStore.js +98 -1
  78. package/build/services/MemoryStore.js.map +1 -1
  79. package/build/services/MiniappExitGesture.d.ts +6 -3
  80. package/build/services/MiniappExitGesture.d.ts.map +1 -1
  81. package/build/services/MiniappExitGesture.js +4 -4
  82. package/build/services/MiniappExitGesture.js.map +1 -1
  83. package/build/services/MiniappWidgetMode.d.ts +6 -4
  84. package/build/services/MiniappWidgetMode.d.ts.map +1 -1
  85. package/build/services/MiniappWidgetMode.js +7 -6
  86. package/build/services/MiniappWidgetMode.js.map +1 -1
  87. package/build/services/ProviderKeyValidation.d.ts +2 -0
  88. package/build/services/ProviderKeyValidation.d.ts.map +1 -1
  89. package/build/services/ProviderKeyValidation.js +4 -0
  90. package/build/services/ProviderKeyValidation.js.map +1 -1
  91. package/build/services/SceneRenderer.d.ts.map +1 -1
  92. package/build/services/SceneRenderer.js +3 -1
  93. package/build/services/SceneRenderer.js.map +1 -1
  94. package/build/services/ScreenshotFixture.d.ts +56 -0
  95. package/build/services/ScreenshotFixture.d.ts.map +1 -0
  96. package/build/services/ScreenshotFixture.js +72 -0
  97. package/build/services/ScreenshotFixture.js.map +1 -0
  98. package/build/services/actionInvoke.d.ts +37 -0
  99. package/build/services/actionInvoke.d.ts.map +1 -0
  100. package/build/services/actionInvoke.js +89 -0
  101. package/build/services/actionInvoke.js.map +1 -0
  102. package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
  103. package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
  104. package/build/services/appleWatch/AppleWatchLink.js +259 -0
  105. package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
  106. package/build/services/controllers.d.ts.map +1 -1
  107. package/build/services/controllers.js +16 -0
  108. package/build/services/controllers.js.map +1 -1
  109. package/build/services/manifestActions.d.ts +4 -2
  110. package/build/services/manifestActions.d.ts.map +1 -1
  111. package/build/services/manifestActions.js +9 -3
  112. package/build/services/manifestActions.js.map +1 -1
  113. package/build/services/memoryBackupFile.d.ts +9 -0
  114. package/build/services/memoryBackupFile.d.ts.map +1 -0
  115. package/build/services/memoryBackupFile.js +47 -0
  116. package/build/services/memoryBackupFile.js.map +1 -0
  117. package/build/services/settingsBridge.d.ts +4 -4
  118. package/build/services/settingsBridge.d.ts.map +1 -1
  119. package/build/services/settingsBridge.js +9 -4
  120. package/build/services/settingsBridge.js.map +1 -1
  121. package/build/services/widgetPlacements.d.ts +8 -7
  122. package/build/services/widgetPlacements.d.ts.map +1 -1
  123. package/build/services/widgetPlacements.js +7 -6
  124. package/build/services/widgetPlacements.js.map +1 -1
  125. package/build/stores/apps.d.ts +6 -0
  126. package/build/stores/apps.d.ts.map +1 -1
  127. package/build/stores/apps.js +29 -12
  128. package/build/stores/apps.js.map +1 -1
  129. package/build/stores/bluetoothSettingKeys.d.ts +2 -0
  130. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  131. package/build/stores/bluetoothSettingKeys.js +17 -0
  132. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  133. package/build/stores/settings.d.ts.map +1 -1
  134. package/build/stores/settings.js +42 -2
  135. package/build/stores/settings.js.map +1 -1
  136. package/build/types/applet.d.ts +5 -1
  137. package/build/types/applet.d.ts.map +1 -1
  138. package/build/types/applet.js.map +1 -1
  139. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  140. package/build/types/capabilities/rayneo-io.js +2 -0
  141. package/build/types/capabilities/rayneo-io.js.map +1 -1
  142. package/build/types/enums.d.ts +2 -1
  143. package/build/types/enums.d.ts.map +1 -1
  144. package/build/types/enums.js +1 -0
  145. package/build/types/enums.js.map +1 -1
  146. package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -1
  147. package/build/utils/cloudClient/RnUdpAdapter.js +13 -2
  148. package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -1
  149. package/build/utils/display/scene/index.d.ts +1 -1
  150. package/build/utils/display/scene/index.d.ts.map +1 -1
  151. package/build/utils/display/scene/index.js.map +1 -1
  152. package/build/utils/display/scene/process.d.ts.map +1 -1
  153. package/build/utils/display/scene/process.js +20 -1
  154. package/build/utils/display/scene/process.js.map +1 -1
  155. package/build/utils/display/scene/types.d.ts +1 -1
  156. package/build/utils/display/scene/types.d.ts.map +1 -1
  157. package/build/utils/display/scene/types.js.map +1 -1
  158. package/build/utils/miniappGlobals.d.ts +6 -0
  159. package/build/utils/miniappGlobals.d.ts.map +1 -1
  160. package/build/utils/miniappGlobals.js +28 -4
  161. package/build/utils/miniappGlobals.js.map +1 -1
  162. package/package.json +9 -9
  163. package/src/engine.ts +13 -1
  164. package/src/facades/ai.ts +3 -3
  165. package/src/facades/glasses.ts +65 -42
  166. package/src/facades/memory.ts +11 -2
  167. package/src/facades/miniapps.ts +3 -0
  168. package/src/facades/pairing.ts +4 -2
  169. package/src/facades/providerKeys.ts +6 -1
  170. package/src/generated/releaseMetadata.ts +4 -4
  171. package/src/services/AiTierResolution.ts +9 -3
  172. package/src/services/AppRegistry.ts +18 -3
  173. package/src/services/BlobStore.ts +3 -3
  174. package/src/services/BluetoothRestoreLaunch.ts +37 -0
  175. package/src/services/ForegroundLocationPermission.ts +1 -1
  176. package/src/services/GestureDotsService.ts +2 -1
  177. package/src/services/GlassesTouchInput.ts +86 -38
  178. package/src/services/LocalDisplayManager.ts +22 -8
  179. package/src/services/LocalLlmModelManager.ts +16 -6
  180. package/src/services/LocalLlmPrompt.ts +56 -37
  181. package/src/services/LocalLlmProvider.ts +74 -24
  182. package/src/services/LocalMiniappRuntime.ts +228 -147
  183. package/src/services/MemoryStore.ts +111 -2
  184. package/src/services/MiniappExitGesture.ts +8 -5
  185. package/src/services/MiniappWidgetMode.ts +9 -7
  186. package/src/services/ProviderKeyValidation.ts +5 -0
  187. package/src/services/SceneRenderer.ts +3 -1
  188. package/src/services/ScreenshotFixture.ts +113 -0
  189. package/src/services/actionInvoke.ts +105 -0
  190. package/src/services/appleWatch/AppleWatchLink.ts +297 -0
  191. package/src/services/controllers.ts +17 -0
  192. package/src/services/manifestActions.ts +16 -4
  193. package/src/services/memoryBackupFile.ts +50 -0
  194. package/src/services/settingsBridge.ts +9 -4
  195. package/src/services/widgetPlacements.ts +23 -9
  196. package/src/stores/apps.ts +35 -11
  197. package/src/stores/bluetoothSettingKeys.ts +20 -0
  198. package/src/stores/settings.ts +44 -2
  199. package/src/types/applet.ts +5 -1
  200. package/src/types/capabilities/rayneo-io.ts +2 -0
  201. package/src/types/enums.ts +1 -0
  202. package/src/utils/cloudClient/RnUdpAdapter.ts +13 -1
  203. package/src/utils/display/scene/index.ts +1 -0
  204. package/src/utils/display/scene/process.ts +17 -1
  205. package/src/utils/display/scene/types.ts +1 -0
  206. package/src/utils/miniappGlobals.ts +30 -4
@@ -3,8 +3,9 @@ import {AsyncResult, result as Res, Result} from "typesafe-ts"
3
3
  import {create} from "zustand"
4
4
  import {subscribeWithSelector} from "zustand/middleware"
5
5
 
6
+ import {isScreenshotMode} from "../services/ScreenshotFixture"
6
7
  import {storage} from "../utils/storage"
7
- import {getBluetoothSettingKeysForDevice} from "./bluetoothSettingKeys"
8
+ import {getBluetoothSettingKeysForDevice, nativeMenuApps} from "./bluetoothSettingKeys"
8
9
  import {useGlassesStore} from "./glasses"
9
10
 
10
11
  export {MENTRA_LIVE_SETTING_KEYS, getBluetoothSettingKeysForDevice} from "./bluetoothSettingKeys"
@@ -144,6 +145,8 @@ export const SETTINGS: Record<string, Setting> = {
144
145
  home_mirror: {
145
146
  key: "home_mirror",
146
147
  defaultValue: () => true,
148
+ // The store-screenshot build always shows the mirror under its fixture G2.
149
+ override: () => (isScreenshotMode() ? true : undefined),
147
150
  writable: true,
148
151
  saveOnServer: true,
149
152
  persist: true,
@@ -352,6 +355,13 @@ export const SETTINGS: Record<string, Setting> = {
352
355
  saveOnServer: false,
353
356
  persist: true,
354
357
  },
358
+ deepseek_api_key: {
359
+ key: "deepseek_api_key",
360
+ defaultValue: () => "",
361
+ writable: true,
362
+ saveOnServer: false,
363
+ persist: true,
364
+ },
355
365
  elevenlabs_api_key: {
356
366
  key: "elevenlabs_api_key",
357
367
  defaultValue: () => "",
@@ -831,6 +841,15 @@ export const SETTINGS: Record<string, Setting> = {
831
841
  saveOnServer: false,
832
842
  persist: true,
833
843
  },
844
+ // Apple Watch: draw the glasses' current frame behind the watch trackpad. Frames
845
+ // cross WatchConnectivity while the watch app is up, so it stays off unless asked for.
846
+ apple_watch_mirror: {
847
+ key: "apple_watch_mirror",
848
+ defaultValue: () => false,
849
+ writable: true,
850
+ saveOnServer: false,
851
+ persist: true,
852
+ },
834
853
  // Maximum rate at which display updates leave the phone for the glasses.
835
854
  // The Bluetooth SDK paces every scene frame and legacy layout to this,
836
855
  // coalescing faster updates last-wins; clears bypass it. No effect on G2
@@ -1577,6 +1596,29 @@ export const useSettingsStore = create<SettingsState>()(
1577
1596
  // desired value was applied so a transient failure retries on boot.
1578
1597
  if (migrationSucceeded) storage.save(CAMERA_FOV_MIGRATION_KEY, true)
1579
1598
  }
1599
+
1600
+ // Settings joined the Glasses Menu (the dashboard's glasses Settings
1601
+ // page): add it once, last, to a menu the wearer already saved. An
1602
+ // unsaved menu gets it from the default; a full one (10) is left alone.
1603
+ const MENU_SETTINGS_MIGRATION_KEY = "migration:menu_apps_settings_v1"
1604
+ const menuSettingsMigrationDone = storage.load<boolean>(MENU_SETTINGS_MIGRATION_KEY)
1605
+ if (menuSettingsMigrationDone.is_error() || !menuSettingsMigrationDone.value) {
1606
+ const menu = get().getSetting(SETTINGS.menu_apps.key) as Array<{packageName?: string}> | null
1607
+ let menuMigrated = true
1608
+ if (
1609
+ Array.isArray(menu) &&
1610
+ menu.length < 10 &&
1611
+ !menu.some((item) => item?.packageName === "com.glassly.settings")
1612
+ ) {
1613
+ const next = [...menu, {packageName: "com.glassly.settings", name: "Settings"}]
1614
+ const result = await get().setSetting(SETTINGS.menu_apps.key, next, true)
1615
+ if (result.is_error()) {
1616
+ console.log("SETTINGS: menu_apps Settings migration failed:", result.error)
1617
+ menuMigrated = false
1618
+ }
1619
+ }
1620
+ if (menuMigrated) storage.save(MENU_SETTINGS_MIGRATION_KEY, true)
1621
+ }
1580
1622
  })
1581
1623
  loadAllSettingsInFlight = inFlight
1582
1624
  void Promise.resolve(inFlight).then((result) => {
@@ -1598,7 +1640,7 @@ export const useSettingsStore = create<SettingsState>()(
1598
1640
  if (key === SETTINGS.core_token.key && (typeof value !== "string" || value.trim().length === 0)) {
1599
1641
  continue
1600
1642
  }
1601
- bluetoothSettings[key] = value
1643
+ bluetoothSettings[key] = key === SETTINGS.menu_apps.key ? nativeMenuApps(value) : value
1602
1644
  }
1603
1645
  return bluetoothSettings
1604
1646
  },
@@ -89,7 +89,7 @@ export interface DeclaredAction {
89
89
  visibility?: "public" | "system"
90
90
  }
91
91
 
92
- export type DeclaredWidgetSize = "XS" | "SM" | "MD" | "LG"
92
+ export type DeclaredWidgetSize = "XS" | "XSM" | "SM" | "MD" | "LG"
93
93
 
94
94
  /** A dashboard widget a miniapp declares in `miniapp.json` and serves through `session.widgets`. */
95
95
  export interface DeclaredWidget {
@@ -97,6 +97,10 @@ export interface DeclaredWidget {
97
97
  name: string
98
98
  sizes: DeclaredWidgetSize[]
99
99
  defaultSize: DeclaredWidgetSize
100
+ /** Lists the `EX` size: a tap on the selected widget can expand it to the full screen. */
101
+ expandable: boolean
102
+ /** A tap expands it rather than activating the card: expandable, and the wearer left expansion on. */
103
+ expandOnTap: boolean
100
104
  }
101
105
 
102
106
  export interface ClientApp extends AppletInterface {
@@ -32,6 +32,8 @@ export const rayneoIO: Capabilities = {
32
32
  fieldOfView: {horizontal: 30},
33
33
  maxTextLines: 5,
34
34
  adjustBrightness: true,
35
+ // Both drivers map these settings onto the launcher's three heights and two distances.
36
+ positionRange: {height: {min: 0, max: 8}, depth: {min: 1, max: 3}},
35
37
 
36
38
  // Scene display API — the Glassly CFW page canvas.
37
39
  width: 540,
@@ -18,4 +18,5 @@ export enum DeviceTypes {
18
18
  export enum ControllerTypes {
19
19
  R1 = "Even Realities R1",
20
20
  NEURAL_BAND = "Meta Neural Band",
21
+ APPLE_WATCH = "Apple Watch",
21
22
  }
@@ -14,6 +14,8 @@ type CloudUdpSocket = ReturnType<typeof dgram.createSocket> & {
14
14
  on(event: "message", cb: (msg: Uint8Array) => void): void
15
15
  }
16
16
 
17
+ const SEND_FAILURE_WARN_INTERVAL_MS = 60_000
18
+
17
19
  export function createCloudUdpSocket(): UdpSocketLike {
18
20
  const socket = dgram.createSocket({type: "udp4"}) as CloudUdpSocket
19
21
  let onBytes: ((bytes: Uint8Array) => void) | null = null
@@ -27,10 +29,20 @@ export function createCloudUdpSocket(): UdpSocketLike {
27
29
  // Bind to any available port so the socket is ready to send.
28
30
  socket.bind(0)
29
31
 
32
+ // A dead UDP path fails every frame and every 1 s liveness probe for the whole
33
+ // session; one line per minute says the same thing without flooding the log.
34
+ let failures = 0
35
+ let lastWarnAt = 0
36
+
30
37
  return {
31
38
  send(bytes: Uint8Array, host: string, port: number): void {
32
39
  socket.send(bytes, 0, bytes.length, port, host, (err?: Error) => {
33
- if (err) console.warn(`[cloud-client udp] send failed to ${host}:${port}: ${err.message}`)
40
+ if (!err) return
41
+ failures += 1
42
+ const now = Date.now()
43
+ if (now - lastWarnAt < SEND_FAILURE_WARN_INTERVAL_MS) return
44
+ lastWarnAt = now
45
+ console.warn(`[cloud-client udp] send failed to ${host}:${port} (${failures} so far): ${err.message}`)
34
46
  })
35
47
  },
36
48
  onMessage(cb: (bytes: Uint8Array) => void): void {
@@ -17,6 +17,7 @@ export type {
17
17
  SceneStrokeStyle,
18
18
  SceneTextStyle,
19
19
  SceneTransition,
20
+ SceneTransitionEdge,
20
21
  } from "./types"
21
22
  export {boxesEqual, contentHash, elementContentHash} from "./types"
22
23
  export {resolveAnchors} from "./anchor"
@@ -21,6 +21,7 @@ import type {
21
21
  ScenePoint,
22
22
  SceneTextStyle,
23
23
  SceneTransition,
24
+ SceneTransitionEdge,
24
25
  } from "./types"
25
26
  import {MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from "./types"
26
27
 
@@ -66,11 +67,24 @@ function pointsBox(points: ScenePoint[]): SceneBox {
66
67
  return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}
67
68
  }
68
69
 
70
+ function validEdge(e: unknown): SceneTransitionEdge | undefined {
71
+ if (!e || typeof e !== "object") return undefined
72
+ const dx = Math.round(Number((e as SceneTransitionEdge).dx ?? 0))
73
+ const dy = Math.round(Number((e as SceneTransitionEdge).dy ?? 0))
74
+ if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024) return undefined
75
+ return {dx, dy}
76
+ }
77
+
69
78
  function validTransition(t: unknown): SceneTransition | undefined {
70
79
  if (!t || typeof t !== "object") return undefined
71
80
  const {durationMs, easing} = t as SceneTransition
72
81
  if (!isFinite(durationMs) || durationMs <= 0) return undefined
73
- return easing ? {durationMs, easing} : {durationMs}
82
+ const out: SceneTransition = easing ? {durationMs, easing} : {durationMs}
83
+ const enter = validEdge((t as SceneTransition).enter)
84
+ const exit = validEdge((t as SceneTransition).exit)
85
+ if (enter) out.enter = enter
86
+ if (exit) out.exit = exit
87
+ return out
74
88
  }
75
89
 
76
90
  function boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {
@@ -175,6 +189,8 @@ export function processScene(
175
189
 
176
190
  for (const {el, index, box, points} of valid) {
177
191
  const transition = validTransition((el as {transition?: unknown}).transition)
192
+ // Leaving elements only exist to tween away; a device that jumps never shows them.
193
+ if (transition?.exit && !caps.animation) continue
178
194
  const transform: {rotation?: number; pivot?: ScenePoint} = {}
179
195
  if (el.rotation !== undefined || el.pivot !== undefined) {
180
196
  const angle = el.rotation ?? 0
@@ -18,6 +18,7 @@ export type {
18
18
  SceneStrokeStyle,
19
19
  SceneTextStyle,
20
20
  SceneTransition,
21
+ SceneTransitionEdge,
21
22
  } from "@glassly/miniapp/display"
22
23
  export {
23
24
  boxesEqual,
@@ -73,6 +73,35 @@ export interface BuildMiniappGlobalsOptions {
73
73
  colorScheme?: MiniappColorScheme
74
74
  }
75
75
 
76
+ function safeAreaCssVars(insets: MiniappSafeArea): Record<string, string> {
77
+ return {
78
+ "--glassly-safe-top": `${insets.top}px`,
79
+ "--glassly-safe-bottom": `${insets.bottom}px`,
80
+ "--glassly-safe-left": `${insets.left}px`,
81
+ "--glassly-safe-right": `${insets.right}px`,
82
+ }
83
+ }
84
+
85
+ /**
86
+ * JS to inject into a running miniapp when the host's safe area changes (an
87
+ * iPhone Duo folding moves its status rail): updates window.Glassly and the
88
+ * CSS variables, then fires `glassly:safe-area-change`.
89
+ */
90
+ export function buildSafeAreaUpdateScript(insets: MiniappSafeArea): string {
91
+ const sets = Object.entries(safeAreaCssVars(insets))
92
+ .map(([k, v]) => `document.documentElement.style.setProperty(${JSON.stringify(k)}, ${JSON.stringify(v)});`)
93
+ .join("\n ")
94
+ return `
95
+ window.Glassly = window.Glassly || {};
96
+ window.Glassly.safeAreaInsets = ${JSON.stringify(insets)};
97
+ try {
98
+ ${sets}
99
+ } catch (e) { /* ignore */ }
100
+ window.dispatchEvent(new Event("glassly:safe-area-change"));
101
+ true;
102
+ `
103
+ }
104
+
76
105
  /**
77
106
  * Returns the JS string to inject into a miniapp WebView before its content
78
107
  * loads. Sets window.Glassly with the standard fields miniapps read:
@@ -105,10 +134,7 @@ export function buildMiniappGlobalsScript(opts: BuildMiniappGlobalsOptions): str
105
134
  const insets = opts.safeAreaInsets
106
135
  const capsuleCenter = capsule.top + capsule.height / 2
107
136
  const cssVars: Record<string, string> = {
108
- "--glassly-safe-top": `${insets.top}px`,
109
- "--glassly-safe-bottom": `${insets.bottom}px`,
110
- "--glassly-safe-left": `${insets.left}px`,
111
- "--glassly-safe-right": `${insets.right}px`,
137
+ ...safeAreaCssVars(insets),
112
138
  "--glassly-capsule-top": `${capsule.top}px`,
113
139
  "--glassly-capsule-bottom": `${capsule.bottom}px`,
114
140
  "--glassly-capsule-left": `${capsule.left}px`,