@glassly/engine 0.3.7-dev.974 → 0.3.8-beta.984

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.
@@ -1495,6 +1495,9 @@ class LocalMiniappRuntime {
1495
1495
  case MiniappRequestType.SHOW_VIEW:
1496
1496
  this.handleShowView(packageName, payload, requestId)
1497
1497
  break
1498
+ case MiniappRequestType.PREFETCH:
1499
+ this.handlePrefetch(packageName, payload, requestId)
1500
+ break
1498
1501
  case MiniappRequestType.SETTINGS_GET:
1499
1502
  this.handleSettingsGet(packageName, payload, requestId)
1500
1503
  break
@@ -1680,11 +1683,11 @@ class LocalMiniappRuntime {
1680
1683
  this.handleOpenUrl(packageName, payload)
1681
1684
  break
1682
1685
  case MiniappRequestType.DISPLAY_DIAGNOSTICS: {
1683
- // The records are parsed out of the G2PERF lines, which the SDK only logs while g2_perf_logs is on.
1686
+ // The records are parsed out of the G2PERF lines, which the SDK only logs while perf_logs is on.
1684
1687
  const settings = useSettingsStore.getState()
1685
1688
  const enabled =
1686
1689
  settings.getSetting(SETTINGS.debug_console.key) === true &&
1687
- settings.getSetting(SETTINGS.g2_perf_logs.key) === true
1690
+ settings.getSetting(SETTINGS.perf_logs.key) === true
1688
1691
  this.sendResult(packageName, requestId, true, {
1689
1692
  enabled,
1690
1693
  generatedAt: Date.now(),
@@ -2651,6 +2654,27 @@ class LocalMiniappRuntime {
2651
2654
  )
2652
2655
  }
2653
2656
 
2657
+ /**
2658
+ * display.prefetch: the active dashboard hands over the frame its view would
2659
+ * show if opened now. Native keeps its fonts and images on the glasses while
2660
+ * nothing is on screen; nothing is displayed or retained here.
2661
+ */
2662
+ private handlePrefetch(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
2663
+ if (payload.view !== "dashboard" || !Array.isArray(payload.elements)) {
2664
+ this.sendResult(packageName, requestId, false, undefined, {
2665
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2666
+ message: 'prefetch takes the "dashboard" view and an elements array',
2667
+ })
2668
+ return
2669
+ }
2670
+ if (!this.connectedApps.has(packageName) || !isDashboardApp(packageName) || !isOverlayApp(packageName)) {
2671
+ this.sendResult(packageName, requestId, true, {status: "unsupported", reason: "not the active dashboard"})
2672
+ return
2673
+ }
2674
+ const sent = localDisplayManager.prefetch(packageName, "dashboard", payload.elements as NonNullable<DisplayPayload["scene"]>)
2675
+ this.sendResult(packageName, requestId, true, sent ? {status: "queued"} : {status: "unsupported", reason: "no scene display"})
2676
+ }
2677
+
2654
2678
  /**
2655
2679
  * Reject the request with PERMISSION_NOT_DECLARED unless the caller's
2656
2680
  * manifest declares `permission`. System apps pass without declaring it.
@@ -283,6 +283,34 @@ class SceneRenderer {
283
283
  }
284
284
  }
285
285
 
286
+ /**
287
+ * Hand native the frame `elements` would make for (app, view) without
288
+ * showing it or touching the retained scene: drivers with an on-glass asset
289
+ * cache upload its fonts and images while the panel is idle. Every element
290
+ * is "created" and the frame carries seq 0 (no presentation report). Empty
291
+ * elements drop it. False when there is nothing to send it to.
292
+ */
293
+ public prefetchScene(appId: string, view: "main" | "dashboard", elements: SceneElementInput[]): boolean {
294
+ const caps = this.currentCapabilities()
295
+ if (!caps?.canPosition) return false
296
+ const processed = processScene(resolveAnchors(elements, caps.width, caps.height), caps, displayProcessor.getSceneProfile())
297
+ let synthetic = 0
298
+ const {elements: created} = diffScene([], processed.elements, () => `~prefetch${synthetic++}`)
299
+ const frame: SceneFrame = {
300
+ appId,
301
+ view,
302
+ sceneEpoch: 0,
303
+ seq: 0,
304
+ elements: created,
305
+ removed: [],
306
+ canvas: {width: caps.width, height: caps.height},
307
+ }
308
+ void Promise.resolve(BluetoothSdk.displayEvent({view, scene: frame, prefetch: true})).catch((err) => {
309
+ console.error(`${LOG_TAG}: native prefetch failed:`, err)
310
+ })
311
+ return true
312
+ }
313
+
286
314
  /**
287
315
  * Re-emit an app's retained scene (reconnect / arbitration restore). Replay
288
316
  * is always create-based — the device rebuilds from scratch (spec §4).
@@ -388,17 +388,6 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
388
388
  // owns the settings store).
389
389
  useSettingsStore.getState().setSetting(SETTINGS.has_ever_activated_app.key, true)
390
390
 
391
- // If the tapped app is already running, this is a re-open: the nav/foreground
392
- // above already drove the animation, and there's nothing to spawn. Short-
393
- // circuit before the spawn pipeline. A promoted app keeps its context but
394
- // still takes the foreground below.
395
- if (app.running && !app.widgetOnly) {
396
- saveLastOpenTime(packageName)
397
- // The running background only knows the source of its spawn.
398
- if (app.local) miniappLauncher.notifyLaunch(packageName, opts?.launchSource ?? "phone")
399
- return true
400
- }
401
-
402
391
  // Only-one rules: one standard app in the foreground, one dashboard
403
392
  // drawing the overlay. Starting another stops the one running. Widget-only
404
393
  // peers hold no foreground, so they stay.
@@ -411,6 +400,17 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
411
400
  }
412
401
  }
413
402
 
403
+ // If the tapped app is already running, this is a re-open: the nav/foreground
404
+ // above already drove the animation, and there's nothing to spawn. Short-
405
+ // circuit before the spawn pipeline. A promoted app keeps its context but
406
+ // still takes the foreground below.
407
+ if (app.running && !app.widgetOnly) {
408
+ saveLastOpenTime(packageName)
409
+ // The running background only knows the source of its spawn.
410
+ if (app.local) miniappLauncher.notifyLaunch(packageName, opts?.launchSource ?? "phone")
411
+ return true
412
+ }
413
+
414
414
  set((s) => ({
415
415
  apps: s.apps.map((a) => (a.packageName === packageName ? {...a, running: true, loading: false} : a)),
416
416
  }))
@@ -132,6 +132,13 @@ export const SETTINGS: Record<string, Setting> = {
132
132
  saveOnServer: true,
133
133
  persist: true,
134
134
  },
135
+ memomind_accessory_picker: {
136
+ key: "memomind_accessory_picker",
137
+ defaultValue: () => false,
138
+ writable: true,
139
+ saveOnServer: false,
140
+ persist: true,
141
+ },
135
142
  debug_console: {
136
143
  key: "debug_console",
137
144
  defaultValue: () => false,
@@ -803,8 +810,8 @@ export const SETTINGS: Record<string, Setting> = {
803
810
  // OFF by default: they crowd out everything else in the ten-minute buffer.
804
811
  // Synced to the Bluetooth SDK, which keeps counting either way and only
805
812
  // logs while this is on. Device-local debug knob, so not saved on the server.
806
- g2_perf_logs: {
807
- key: "g2_perf_logs",
813
+ perf_logs: {
814
+ key: "perf_logs",
808
815
  defaultValue: () => false,
809
816
  writable: true,
810
817
  saveOnServer: false,
@@ -1231,7 +1238,8 @@ export const BLUETOOTH_SETTING_KEYS: string[] = [
1231
1238
  SETTINGS.calendar_events.key,
1232
1239
  SETTINGS.cfw_scene_renderer.key,
1233
1240
  SETTINGS.cfw_fast_ble.key,
1234
- SETTINGS.g2_perf_logs.key,
1241
+ SETTINGS.perf_logs.key,
1242
+ SETTINGS.memomind_accessory_picker.key,
1235
1243
  SETTINGS.ring_via_glasses.key,
1236
1244
  SETTINGS.display_max_fps.key,
1237
1245
  SETTINGS.twelve_hour_time.key,