@glassly/engine 0.3.5-beta.734 → 0.3.5-beta.767

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 (102) hide show
  1. package/build/engine.d.ts +3 -0
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +4 -0
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/glasses.d.ts +5 -1
  6. package/build/facades/glasses.d.ts.map +1 -1
  7. package/build/facades/glasses.js +5 -0
  8. package/build/facades/glasses.js.map +1 -1
  9. package/build/generated/releaseMetadata.js +3 -3
  10. package/build/generated/releaseMetadata.js.map +1 -1
  11. package/build/services/BlobStore.js +3 -3
  12. package/build/services/BlobStore.js.map +1 -1
  13. package/build/services/ForegroundLocationPermission.d.ts +1 -1
  14. package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
  15. package/build/services/ForegroundLocationPermission.js.map +1 -1
  16. package/build/services/GestureDotsService.d.ts.map +1 -1
  17. package/build/services/GestureDotsService.js +2 -1
  18. package/build/services/GestureDotsService.js.map +1 -1
  19. package/build/services/GlassesTouchInput.d.ts +2 -1
  20. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  21. package/build/services/GlassesTouchInput.js +64 -26
  22. package/build/services/GlassesTouchInput.js.map +1 -1
  23. package/build/services/LocalLlmModelManager.d.ts +3 -1
  24. package/build/services/LocalLlmModelManager.d.ts.map +1 -1
  25. package/build/services/LocalLlmModelManager.js +13 -5
  26. package/build/services/LocalLlmModelManager.js.map +1 -1
  27. package/build/services/LocalLlmPrompt.d.ts +10 -9
  28. package/build/services/LocalLlmPrompt.d.ts.map +1 -1
  29. package/build/services/LocalLlmPrompt.js +43 -32
  30. package/build/services/LocalLlmPrompt.js.map +1 -1
  31. package/build/services/LocalLlmProvider.d.ts.map +1 -1
  32. package/build/services/LocalLlmProvider.js +39 -20
  33. package/build/services/LocalLlmProvider.js.map +1 -1
  34. package/build/services/LocalMiniappRuntime.d.ts +19 -4
  35. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  36. package/build/services/LocalMiniappRuntime.js +70 -127
  37. package/build/services/LocalMiniappRuntime.js.map +1 -1
  38. package/build/services/SceneRenderer.d.ts.map +1 -1
  39. package/build/services/SceneRenderer.js +3 -1
  40. package/build/services/SceneRenderer.js.map +1 -1
  41. package/build/services/actionInvoke.d.ts +37 -0
  42. package/build/services/actionInvoke.d.ts.map +1 -0
  43. package/build/services/actionInvoke.js +89 -0
  44. package/build/services/actionInvoke.js.map +1 -0
  45. package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
  46. package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
  47. package/build/services/appleWatch/AppleWatchLink.js +259 -0
  48. package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
  49. package/build/services/controllers.d.ts.map +1 -1
  50. package/build/services/controllers.js +16 -0
  51. package/build/services/controllers.js.map +1 -1
  52. package/build/services/settingsBridge.d.ts +4 -4
  53. package/build/services/settingsBridge.d.ts.map +1 -1
  54. package/build/services/settingsBridge.js +9 -4
  55. package/build/services/settingsBridge.js.map +1 -1
  56. package/build/stores/bluetoothSettingKeys.d.ts +2 -0
  57. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  58. package/build/stores/bluetoothSettingKeys.js +17 -0
  59. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  60. package/build/stores/settings.d.ts.map +1 -1
  61. package/build/stores/settings.js +32 -2
  62. package/build/stores/settings.js.map +1 -1
  63. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  64. package/build/types/capabilities/rayneo-io.js +2 -0
  65. package/build/types/capabilities/rayneo-io.js.map +1 -1
  66. package/build/types/enums.d.ts +2 -1
  67. package/build/types/enums.d.ts.map +1 -1
  68. package/build/types/enums.js +1 -0
  69. package/build/types/enums.js.map +1 -1
  70. package/build/utils/display/scene/index.d.ts +1 -1
  71. package/build/utils/display/scene/index.d.ts.map +1 -1
  72. package/build/utils/display/scene/index.js.map +1 -1
  73. package/build/utils/display/scene/process.d.ts.map +1 -1
  74. package/build/utils/display/scene/process.js +20 -1
  75. package/build/utils/display/scene/process.js.map +1 -1
  76. package/build/utils/display/scene/types.d.ts +1 -1
  77. package/build/utils/display/scene/types.d.ts.map +1 -1
  78. package/build/utils/display/scene/types.js.map +1 -1
  79. package/package.json +9 -9
  80. package/src/engine.ts +4 -0
  81. package/src/facades/glasses.ts +7 -0
  82. package/src/generated/releaseMetadata.ts +3 -3
  83. package/src/services/BlobStore.ts +3 -3
  84. package/src/services/ForegroundLocationPermission.ts +1 -1
  85. package/src/services/GestureDotsService.ts +2 -1
  86. package/src/services/GlassesTouchInput.ts +69 -23
  87. package/src/services/LocalLlmModelManager.ts +16 -6
  88. package/src/services/LocalLlmPrompt.ts +56 -37
  89. package/src/services/LocalLlmProvider.ts +49 -22
  90. package/src/services/LocalMiniappRuntime.ts +105 -127
  91. package/src/services/SceneRenderer.ts +3 -1
  92. package/src/services/actionInvoke.ts +105 -0
  93. package/src/services/appleWatch/AppleWatchLink.ts +297 -0
  94. package/src/services/controllers.ts +17 -0
  95. package/src/services/settingsBridge.ts +9 -4
  96. package/src/stores/bluetoothSettingKeys.ts +20 -0
  97. package/src/stores/settings.ts +34 -2
  98. package/src/types/capabilities/rayneo-io.ts +2 -0
  99. package/src/types/enums.ts +1 -0
  100. package/src/utils/display/scene/index.ts +1 -0
  101. package/src/utils/display/scene/process.ts +17 -1
  102. package/src/utils/display/scene/types.ts +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppleWatchLink.js","sourceRoot":"","sources":["../../../src/services/appleWatch/AppleWatchLink.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in\n * front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames\n * while the setting is on, and the commands the watch sends back. Native adds whether the active\n * watch is paired and keeps the newest frame for when the watch app opens. Gestures ride\n * `touch_event` like any controller's.\n */\nimport type {AppleWatchCommandEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {MirrorSnapshot} from \"@glassly/miniapp\"\nimport {MiniappStreamType} from \"@glassly/miniapp\"\nimport {Platform} from \"react-native\"\n\nimport {useAppStatusStore} from \"../../stores/apps\"\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport type {ClientApp} from \"../../types\"\nimport {BgTimer} from \"../../utils/timers\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\nimport localMiniappRuntime from \"../LocalMiniappRuntime\"\nimport {mirrorService} from \"../MirrorService\"\nimport {keyEventsForTextChange} from \"../neuralBand/NeuralBandInput\"\n\nexport const ASSIST_PACKAGE = \"com.glassly.assistant\"\nexport const ASSIST_ACTION = \"ask\"\n/** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */\nconst MAX_FRAME_BYTES = 48_000\nconst MAX_IMAGE_CHARS = 16_000\nconst MAX_APPS = 24\nconst STATE_DEBOUNCE_MS = 250\nconst MIRROR_INTERVAL_MS = 500\n\nexport interface WatchStateInput {\n glasses: {connected: boolean; model: string; battery: number}\n apps: readonly ClientApp[]\n assist: boolean\n mirror: boolean\n}\n\n/** Miniapps the watch can open: shown, compatible, and drawing on the glasses. */\nexport function launchableApps(apps: readonly ClientApp[]): {packageName: string; name: string}[] {\n return apps\n .filter((app) => app.type === \"standard\" && !app.hidden && app.compatibility?.isCompatible !== false)\n .filter((app) => app.packageName && !app.packageName.includes(\"@empty\"))\n .slice(0, MAX_APPS)\n .map((app) => ({packageName: app.packageName, name: app.name}))\n}\n\nexport function watchState({glasses, apps, assist, mirror}: WatchStateInput): Record<string, unknown> {\n const front = apps.find((app) => app.foregrounded && app.running && app.type === \"standard\")\n const state: Record<string, unknown> = {\n glasses: {connected: glasses.connected, name: glasses.model || \"Glasses\", battery: glasses.battery},\n apps: launchableApps(apps),\n assist,\n mirror,\n }\n if (front) state.app = {packageName: front.packageName, name: front.name}\n return state\n}\n\ntype WatchElement = Record<string, string | number | boolean | number[]>\n\nconst level = (color: unknown, fallback = 15) =>\n typeof color === \"number\" ? Math.max(0, Math.min(15, color)) : fallback\n\n/** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */\nexport function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> | null {\n const canvas = snapshot.canvas\n if (!canvas) return null\n const content = snapshot.content\n if (content.kind === \"bitmap\") {\n if (content.data.length > MAX_FRAME_BYTES) return null\n return {width: canvas.width, height: canvas.height, bitmap: content.data, elements: []}\n }\n if (content.kind !== \"scene\") return null\n const elements: WatchElement[] = []\n for (const element of content.elements) {\n const {x, y, w, h} = element.box\n const style = element.style ?? {}\n switch (element.type) {\n case \"text\":\n if (!element.text) break\n elements.push({\n type: \"text\",\n x,\n y,\n w,\n h,\n text: element.text,\n size: style.fontSize ?? 20,\n color: level(style.color),\n })\n break\n case \"rect\":\n elements.push({\n type: \"rect\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n radius: style.radius ?? 0,\n color: level(style.color),\n })\n break\n case \"circle\":\n elements.push({\n type: \"circle\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n color: level(style.color),\n })\n break\n case \"line\":\n case \"triangle\":\n case \"quad\": {\n const points = (element.points ?? []).flatMap((point) => [point.x, point.y])\n if (points.length < 4) break\n const closed = element.type !== \"line\"\n elements.push({\n type: \"line\",\n points,\n width: style.width ?? style.border ?? 1,\n color: level(style.color),\n closed,\n fill: closed && style.fill === true,\n })\n break\n }\n case \"image\":\n if (element.data && element.data.length <= MAX_IMAGE_CHARS)\n elements.push({type: \"image\", x, y, w, h, data: element.data})\n break\n }\n }\n const frame = {width: canvas.width, height: canvas.height, elements}\n return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame\n}\n\nexport interface AppleWatchCommandDeps {\n apps: () => readonly ClientApp[]\n start: (app: ClientApp) => Promise<unknown>\n stop: (packageName: string) => Promise<unknown>\n ask: (query: string) => Promise<unknown>\n type: (stream: string, payload: unknown) => void\n now: () => number\n}\n\nconst defaultDeps: AppleWatchCommandDeps = {\n apps: () => useAppStatusStore.getState().apps,\n start: (app) => useAppStatusStore.getState().start(app),\n stop: (packageName) => useAppStatusStore.getState().stop(packageName),\n ask: (query) => localMiniappRuntime.invokeActionFromHost(ASSIST_PACKAGE, ASSIST_ACTION, {query}),\n type: (stream, payload) => localMiniappRuntime.forwardEvent(stream, payload),\n now: () => Date.now(),\n}\n\nexport async function handleAppleWatchCommand(\n event: AppleWatchCommandEvent,\n deps: AppleWatchCommandDeps = defaultDeps,\n): Promise<void> {\n switch (event.command) {\n case \"launch\": {\n const app = launchableApps(deps.apps()).some((entry) => entry.packageName === event.packageName)\n ? deps.apps().find((entry) => entry.packageName === event.packageName)\n : undefined\n if (!app) {\n console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`)\n return\n }\n await deps.start(app)\n return\n }\n case \"stop\": {\n const front = deps.apps().find((app) => app.foregrounded && app.running && app.type === \"standard\")\n if (front) await deps.stop(front.packageName)\n return\n }\n case \"assist\": {\n const query = event.text.trim()\n if (query) await deps.ask(query)\n return\n }\n case \"text\":\n for (const key of keyEventsForTextChange(\"\", event.text, deps.now()))\n deps.type(MiniappStreamType.KEYBOARD_EVENT, key)\n return\n }\n}\n\nlet stops: (() => void)[] = []\nlet stopMirror: (() => void) | null = null\n\nfunction currentState(): Record<string, unknown> {\n const glasses = useGlassesStore.getState()\n return watchState({\n glasses: {\n connected: isGlassesConnected(glasses.connection),\n model: glasses.deviceModel,\n battery: glasses.batteryLevel,\n },\n apps: useAppStatusStore.getState().apps,\n assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),\n mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,\n })\n}\n\nfunction syncMirror(enabled: boolean): void {\n if (!enabled) {\n if (stopMirror) {\n stopMirror()\n stopMirror = null\n void BluetoothSdk.setWatchMirror(null).catch(() => {})\n }\n return\n }\n if (stopMirror) return\n let lastSentAt = 0\n let pending: MirrorSnapshot | null = null\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const send = (snapshot: MirrorSnapshot) => {\n lastSentAt = Date.now()\n void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) =>\n console.warn(\"AppleWatch: mirror push failed:\", err),\n )\n }\n const unsubscribe = mirrorService.subscribe((snapshot) => {\n const wait = lastSentAt + MIRROR_INTERVAL_MS - Date.now()\n if (wait <= 0 && !timer) {\n send(snapshot)\n return\n }\n pending = snapshot\n timer ??= BgTimer.setTimeout(\n () => {\n timer = null\n if (pending) send(pending)\n pending = null\n },\n Math.max(wait, 0),\n )\n })\n stopMirror = () => {\n unsubscribe()\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n }\n}\n\nexport function startAppleWatchLink(): void {\n if (Platform.OS !== \"ios\" || stops.length > 0) return\n let lastKey = \"\"\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const push = () => {\n timer = null\n const state = currentState()\n const key = JSON.stringify(state)\n if (key === lastKey) return\n lastKey = key\n void BluetoothSdk.setWatchState(state).catch((err) => console.warn(\"AppleWatch: state push failed:\", err))\n }\n const schedule = () => {\n timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS)\n }\n const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true\n const commands = BluetoothSdk.addListener(\"apple_watch_command\", (event) => {\n handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err))\n })\n stops = [\n () => commands.remove(),\n useGlassesStore.subscribe(schedule),\n useAppStatusStore.subscribe(schedule),\n useSettingsStore.subscribe(\n (s) => s.getSetting(SETTINGS.apple_watch_mirror.key),\n () => {\n syncMirror(mirrorEnabled())\n schedule()\n },\n ),\n () => {\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n },\n ]\n syncMirror(mirrorEnabled())\n schedule()\n}\n\nexport function stopAppleWatchLink(): void {\n stops.splice(0).forEach((stop) => stop())\n syncMirror(false)\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"controllers.d.ts","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAE9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAwB7E,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAUxE;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAE/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAE3G;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,CAKlG"}
1
+ {"version":3,"file":"controllers.d.ts","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAE9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAyC7E,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAUxE;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAE/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAE3G;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,CAKlG"}
@@ -14,9 +14,25 @@ const NEURAL_BAND_SURFACE = {
14
14
  "DIAL_DOWN",
15
15
  ],
16
16
  };
17
+ /** The Apple Watch trackpad's taps and four-way swipes, and the Digital Crown as the dial. */
18
+ const APPLE_WATCH_SURFACE = {
19
+ type: "swipe2d",
20
+ events: [
21
+ "TAP",
22
+ "DOUBLE_TAP",
23
+ "PRESS_HOLD",
24
+ "SWIPE_UP",
25
+ "SWIPE_DOWN",
26
+ "SWIPE_LEFT",
27
+ "SWIPE_RIGHT",
28
+ "DIAL_UP",
29
+ "DIAL_DOWN",
30
+ ],
31
+ };
17
32
  // The R1 swipes up/down like the temples it stands in for, so it adds nothing.
18
33
  const INPUT_SURFACES = {
19
34
  [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
35
+ [ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],
20
36
  [ControllerTypes.R1]: [],
21
37
  };
22
38
  /** The input surfaces a connected controller of `model` adds to the glasses'. */
@@ -1 +1 @@
1
- {"version":3,"file":"controllers.js","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAA;AAI9C,wDAAwD;AACxD,MAAM,mBAAmB,GAAiB;IACxC,IAAI,EAAE,SAAS;IACf,MAAM,EAAE;QACN,KAAK;QACL,YAAY;QACZ,YAAY;QACZ,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,SAAS;QACT,WAAW;KACZ;CACF,CAAA;AAED,+EAA+E;AAC/E,MAAM,cAAc,GAAsD;IACxE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,mBAAmB,CAAC;IACpD,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE;CACzB,CAAA;AAED,iFAAiF;AACjF,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAC,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,OAAO,KAAK,KAAK,SAAS,IAAK,MAAM,CAAC,MAAM,CAAC,eAAe,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC5F,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,OAAO,GAAuB,EAAE,CAAA;IACtC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,MAAM,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAC,GAAG,KAAgC,CAAA;QAC1D,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACxE,OAAO,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAC,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IACpE,OAAO,MAAM,EAAE,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAsD;IACrF,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAChE,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,mBAAmB,CAAC,QAAsD;IACxF,OAAO,gBAAgB,CAAC,QAAQ,CAAC;SAC9B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;SAC/C,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["/**\n * Controller registry read-model: the paired records (the native-authoritative\n * `controllers` setting) and the live driver statuses (glasses status\n * `controllers`), plus what each model adds to the input surfaces. Pure.\n */\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport type {ButtonCapabilities} from \"../types\"\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type InputSurface = NonNullable<ButtonCapabilities[\"buttons\"]>[number]\n\n/** The Neural Band's thumb swipes, pinches and dial. */\nconst NEURAL_BAND_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n// The R1 swipes up/down like the temples it stands in for, so it adds nothing.\nconst INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {\n [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],\n [ControllerTypes.R1]: [],\n}\n\n/** The input surfaces a connected controller of `model` adds to the glasses'. */\nexport function controllerInputSurfaces(model: string): InputSurface[] {\n return (INPUT_SURFACES[model] ?? []).map((surface) => ({...surface, events: [...surface.events]}))\n}\n\n/** The model swipes in two dimensions. */\nexport function hasInput2d(model: string): boolean {\n return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === \"swipe2d\")\n}\n\nexport function isControllerModel(model: string | undefined): boolean {\n return model !== undefined && (Object.values(ControllerTypes) as string[]).includes(model)\n}\n\n/** The `controllers` setting as records; malformed entries are dropped. */\nexport function pairedControllersFrom(value: unknown): PairedController[] {\n if (!Array.isArray(value)) return []\n const records: PairedController[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue\n const {id, model, name} = entry as Record<string, unknown>\n if (typeof id !== \"string\" || !id || typeof model !== \"string\") continue\n records.push({id, model, name: typeof name === \"string\" ? name : id})\n }\n return records\n}\n\n/** Delivering input: link up and the route or claim done. */\nexport function isControllerReady(status: ControllerStatus | undefined): boolean {\n return status?.connected === true && status.fullyBooted === true\n}\n\nexport function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[] {\n return Object.values(statuses ?? {}).filter(isControllerReady)\n}\n\n/** Change key over the controllers delivering input. */\nexport function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string {\n return readyControllers(statuses)\n .map((status) => `${status.id}=${status.model}`)\n .sort()\n .join(\",\")\n}\n"]}
1
+ {"version":3,"file":"controllers.js","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Controller registry read-model: the paired records (the native-authoritative\n * `controllers` setting) and the live driver statuses (glasses status\n * `controllers`), plus what each model adds to the input surfaces. Pure.\n */\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport type {ButtonCapabilities} from \"../types\"\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type InputSurface = NonNullable<ButtonCapabilities[\"buttons\"]>[number]\n\n/** The Neural Band's thumb swipes, pinches and dial. */\nconst NEURAL_BAND_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n/** The Apple Watch trackpad's taps and four-way swipes, and the Digital Crown as the dial. */\nconst APPLE_WATCH_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n// The R1 swipes up/down like the temples it stands in for, so it adds nothing.\nconst INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {\n [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],\n [ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],\n [ControllerTypes.R1]: [],\n}\n\n/** The input surfaces a connected controller of `model` adds to the glasses'. */\nexport function controllerInputSurfaces(model: string): InputSurface[] {\n return (INPUT_SURFACES[model] ?? []).map((surface) => ({...surface, events: [...surface.events]}))\n}\n\n/** The model swipes in two dimensions. */\nexport function hasInput2d(model: string): boolean {\n return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === \"swipe2d\")\n}\n\nexport function isControllerModel(model: string | undefined): boolean {\n return model !== undefined && (Object.values(ControllerTypes) as string[]).includes(model)\n}\n\n/** The `controllers` setting as records; malformed entries are dropped. */\nexport function pairedControllersFrom(value: unknown): PairedController[] {\n if (!Array.isArray(value)) return []\n const records: PairedController[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue\n const {id, model, name} = entry as Record<string, unknown>\n if (typeof id !== \"string\" || !id || typeof model !== \"string\") continue\n records.push({id, model, name: typeof name === \"string\" ? name : id})\n }\n return records\n}\n\n/** Delivering input: link up and the route or claim done. */\nexport function isControllerReady(status: ControllerStatus | undefined): boolean {\n return status?.connected === true && status.fullyBooted === true\n}\n\nexport function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[] {\n return Object.values(statuses ?? {}).filter(isControllerReady)\n}\n\n/** Change key over the controllers delivering input. */\nexport function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string {\n return readyControllers(statuses)\n .map((status) => `${status.id}=${status.model}`)\n .sort()\n .join(\",\")\n}\n"]}
@@ -6,10 +6,10 @@
6
6
  * be tested without the native import surface the runtime pulls in.
7
7
  *
8
8
  * A dashboard miniapp is the usual consumer: it edits the notification,
9
- * glasses-menu and display-position keys the phone's settings screens used
10
- * to, through the store, so persistence, server sync and the Bluetooth sync
11
- * of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps
12
- * that in its storage.
9
+ * glasses-menu, display-position, brightness and microphone keys the phone's
10
+ * settings screens do, through the store, so persistence, server sync and the
11
+ * Bluetooth sync behave as if a phone screen wrote them. A dashboard's own
12
+ * look is not here; it keeps that in its storage.
13
13
  */
14
14
  import type { ControllerStatus, PairedController } from "@glassly/bluetooth-sdk";
15
15
  export type SettingsBridgeAccess = "read" | "write";
@@ -1 +1 @@
1
- {"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAK9E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAkB/E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sEAAuE,CAAA;AACzG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAG7E;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,GACvC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5E,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,CAY3F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAA;IAC3C,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,WAAW,GACnB,sBAAsB,EAAE,CAqB1B"}
1
+ {"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAK9E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAuB/E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sEAAuE,CAAA;AACzG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAG7E;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,GACvC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5E,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,CAY3F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAA;IAC3C,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,WAAW,GACnB,sBAAsB,EAAE,CAqB1B"}
@@ -6,10 +6,10 @@
6
6
  * be tested without the native import surface the runtime pulls in.
7
7
  *
8
8
  * A dashboard miniapp is the usual consumer: it edits the notification,
9
- * glasses-menu and display-position keys the phone's settings screens used
10
- * to, through the store, so persistence, server sync and the Bluetooth sync
11
- * of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps
12
- * that in its storage.
9
+ * glasses-menu, display-position, brightness and microphone keys the phone's
10
+ * settings screens do, through the store, so persistence, server sync and the
11
+ * Bluetooth sync behave as if a phone screen wrote them. A dashboard's own
12
+ * look is not here; it keeps that in its storage.
13
13
  */
14
14
  import { ControllerTypes } from "../types/enums";
15
15
  import { hasInput2d, isControllerReady } from "./controllers";
@@ -22,6 +22,11 @@ export const SETTINGS_BRIDGE_KEYS = {
22
22
  // Where the glasses draw; each model's steps are its display.positionRange.
23
23
  dashboard_depth: "write",
24
24
  dashboard_height: "write",
25
+ // Device settings the dashboard's glasses Settings page edits.
26
+ brightness: "write",
27
+ auto_brightness: "write",
28
+ // Per paired model: the store resolves the indexed key.
29
+ preferred_mic: "write",
25
30
  twelve_hour_time: "write",
26
31
  menu_apps: "write",
27
32
  notifications_enabled: "write",
@@ -1 +1 @@
1
- {"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu and display-position keys the phone's settings screens used\n * to, through the store, so persistence, server sync and the Bluetooth sync\n * of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps\n * that in its storage.\n */\n\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport {ControllerTypes} from \"../types/enums\"\nimport {hasInput2d, isControllerReady} from \"./controllers\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, and the paired\n * controllers with their battery and input. They change on their own clocks;\n * the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"controllers\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** One paired controller as the `controllers` derived key serves it. */\nexport interface ControllerSettingEntry {\n id: string\n model: string\n name: string\n /** Delivering input. */\n connected: boolean\n /** level -1 when unknown. */\n battery: {level: number; charging: boolean}\n /** Swipes sideways as well as up and down (the Meta Neural Band). */\n input2d: boolean\n}\n\n/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */\nexport interface RelayedRing {\n ringConnected: boolean\n ringBatteryLevel: number\n ringCharging: boolean\n}\n\n/**\n * The R1 the glasses' ring report is about: a ring routed through the G2 keeps\n * its phone link for telemetry, so prefer a linked ring, else the first one.\n */\nfunction relayedRingId(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n): string | undefined {\n const rings = records.filter((record) => record.model === ControllerTypes.R1)\n return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id\n}\n\n/**\n * One entry per paired record, registry order. While the glasses report a\n * linked ring, that report is that ring's link and battery: a ring routed\n * through the G2 reports there, not to the phone.\n */\nexport function controllersSettingFrom(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n relayed: RelayedRing,\n): ControllerSettingEntry[] {\n const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined\n return records.map((record) => {\n const status = statuses?.[record.id]\n let connected = isControllerReady(status)\n let battery = {level: status?.batteryLevel ?? -1, charging: status?.charging === true}\n if (record.id === relayedId) {\n connected = true\n if (relayed.ringBatteryLevel >= 0) {\n battery = {level: relayed.ringBatteryLevel, charging: relayed.ringCharging}\n }\n }\n return {\n id: record.id,\n model: record.model,\n name: record.name,\n connected,\n battery,\n input2d: connected && hasInput2d(record.model),\n }\n })\n}\n"]}
1
+ {"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu, display-position, brightness and microphone keys the phone's\n * settings screens do, through the store, so persistence, server sync and the\n * Bluetooth sync behave as if a phone screen wrote them. A dashboard's own\n * look is not here; it keeps that in its storage.\n */\n\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport {ControllerTypes} from \"../types/enums\"\nimport {hasInput2d, isControllerReady} from \"./controllers\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n // Device settings the dashboard's glasses Settings page edits.\n brightness: \"write\",\n auto_brightness: \"write\",\n // Per paired model: the store resolves the indexed key.\n preferred_mic: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, and the paired\n * controllers with their battery and input. They change on their own clocks;\n * the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"controllers\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** One paired controller as the `controllers` derived key serves it. */\nexport interface ControllerSettingEntry {\n id: string\n model: string\n name: string\n /** Delivering input. */\n connected: boolean\n /** level -1 when unknown. */\n battery: {level: number; charging: boolean}\n /** Swipes sideways as well as up and down (the Meta Neural Band). */\n input2d: boolean\n}\n\n/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */\nexport interface RelayedRing {\n ringConnected: boolean\n ringBatteryLevel: number\n ringCharging: boolean\n}\n\n/**\n * The R1 the glasses' ring report is about: a ring routed through the G2 keeps\n * its phone link for telemetry, so prefer a linked ring, else the first one.\n */\nfunction relayedRingId(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n): string | undefined {\n const rings = records.filter((record) => record.model === ControllerTypes.R1)\n return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id\n}\n\n/**\n * One entry per paired record, registry order. While the glasses report a\n * linked ring, that report is that ring's link and battery: a ring routed\n * through the G2 reports there, not to the phone.\n */\nexport function controllersSettingFrom(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n relayed: RelayedRing,\n): ControllerSettingEntry[] {\n const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined\n return records.map((record) => {\n const status = statuses?.[record.id]\n let connected = isControllerReady(status)\n let battery = {level: status?.batteryLevel ?? -1, charging: status?.charging === true}\n if (record.id === relayedId) {\n connected = true\n if (relayed.ringBatteryLevel >= 0) {\n battery = {level: relayed.ringBatteryLevel, charging: relayed.ringCharging}\n }\n }\n return {\n id: record.id,\n model: record.model,\n name: record.name,\n connected,\n battery,\n input2d: connected && hasInput2d(record.model),\n }\n })\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  /** Mentra Live has no display — omit dashboard/brightness keys from BLE sync. */
2
2
  export declare const MENTRA_LIVE_SETTING_KEYS: string[];
3
3
  export declare function getBluetoothSettingKeysForDevice(deviceModel: string | undefined, fullKeys: string[]): string[];
4
+ /** `menu_apps` as the glasses get it; the same array in gives the same array out, so the change-push stays quiet. */
5
+ export declare function nativeMenuApps(value: unknown): unknown;
4
6
  //# sourceMappingURL=bluetoothSettingKeys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bluetoothSettingKeys.d.ts","sourceRoot":"","sources":["../../src/stores/bluetoothSettingKeys.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAyB5C,CAAA;AAMD,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAE9G"}
1
+ {"version":3,"file":"bluetoothSettingKeys.d.ts","sourceRoot":"","sources":["../../src/stores/bluetoothSettingKeys.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAyB5C,CAAA;AAMD,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAE9G;AASD,qHAAqH;AACrH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUtD"}
@@ -32,4 +32,21 @@ function isMentraLiveDevice(model) {
32
32
  export function getBluetoothSettingKeysForDevice(deviceModel, fullKeys) {
33
33
  return isMentraLiveDevice(deviceModel) ? MENTRA_LIVE_SETTING_KEYS : fullKeys;
34
34
  }
35
+ /**
36
+ * Glasses Menu entries the firmware's own menu cannot open: Settings is the
37
+ * dashboard's glasses page, which only the dashboard's Apps menu shows.
38
+ */
39
+ const NATIVE_MENU_EXCLUDED_PACKAGES = new Set(["com.glassly.settings"]);
40
+ const nativeMenuAppsCache = new WeakMap();
41
+ /** `menu_apps` as the glasses get it; the same array in gives the same array out, so the change-push stays quiet. */
42
+ export function nativeMenuApps(value) {
43
+ if (!Array.isArray(value))
44
+ return value;
45
+ let filtered = nativeMenuAppsCache.get(value);
46
+ if (!filtered) {
47
+ filtered = value.filter((item) => !NATIVE_MENU_EXCLUDED_PACKAGES.has(item?.packageName ?? ""));
48
+ nativeMenuAppsCache.set(value, filtered);
49
+ }
50
+ return filtered;
51
+ }
35
52
  //# sourceMappingURL=bluetoothSettingKeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bluetoothSettingKeys.js","sourceRoot":"","sources":["../../src/stores/bluetoothSettingKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,UAAU,CAAA;AAEpC,iFAAiF;AACjF,MAAM,CAAC,MAAM,wBAAwB,GAAa;IAChD,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,kCAAkC;IAClC,uBAAuB;IACvB,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,uBAAuB;IACvB,2BAA2B;IAC3B,YAAY;IACZ,cAAc;IACd,2BAA2B;IAC3B,cAAc;IACd,kBAAkB;IAClB,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,yDAAyD;IACzD,oBAAoB;IACpB,wBAAwB;IACxB,oBAAoB;CACrB,CAAA;AAED,SAAS,kBAAkB,CAAC,KAAyB;IACnD,OAAO,KAAK,KAAK,WAAW,CAAC,IAAI,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,WAA+B,EAAE,QAAkB;IAClG,OAAO,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC9E,CAAC","sourcesContent":["import {DeviceTypes} from \"../types\"\n\n/** Mentra Live has no display — omit dashboard/brightness keys from BLE sync. */\nexport const MENTRA_LIVE_SETTING_KEYS: string[] = [\n \"sensing_enabled\",\n \"lc3_frame_size\",\n \"preferred_mic\",\n \"voice_activity_detection_enabled\",\n \"loudness_gate_enabled\",\n \"core_token\",\n \"auth_email\",\n \"button_photo_size\",\n \"button_video_settings\",\n \"button_max_recording_time\",\n \"camera_fov\",\n \"offline_mode\",\n \"local_stt_fallback_active\",\n \"gallery_mode\",\n \"default_wearable\",\n \"pending_wearable\",\n \"device_name\",\n \"device_address\",\n \"controllers\",\n \"pending_controller\",\n // Deprecated, kept so native can migrate and clear them:\n \"default_controller\",\n \"controller_device_name\",\n \"controller_address\",\n]\n\nfunction isMentraLiveDevice(model: string | undefined): boolean {\n return model === DeviceTypes.LIVE\n}\n\nexport function getBluetoothSettingKeysForDevice(deviceModel: string | undefined, fullKeys: string[]): string[] {\n return isMentraLiveDevice(deviceModel) ? MENTRA_LIVE_SETTING_KEYS : fullKeys\n}\n"]}
1
+ {"version":3,"file":"bluetoothSettingKeys.js","sourceRoot":"","sources":["../../src/stores/bluetoothSettingKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,UAAU,CAAA;AAEpC,iFAAiF;AACjF,MAAM,CAAC,MAAM,wBAAwB,GAAa;IAChD,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,kCAAkC;IAClC,uBAAuB;IACvB,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,uBAAuB;IACvB,2BAA2B;IAC3B,YAAY;IACZ,cAAc;IACd,2BAA2B;IAC3B,cAAc;IACd,kBAAkB;IAClB,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,yDAAyD;IACzD,oBAAoB;IACpB,wBAAwB;IACxB,oBAAoB;CACrB,CAAA;AAED,SAAS,kBAAkB,CAAC,KAAyB;IACnD,OAAO,KAAK,KAAK,WAAW,CAAC,IAAI,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,WAA+B,EAAE,QAAkB;IAClG,OAAO,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAA;AACvE,MAAM,mBAAmB,GAAG,IAAI,OAAO,EAAqB,CAAA;AAE5D,qHAAqH;AACrH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACvC,IAAI,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,KAAK,CAAC,MAAM,CACrB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,6BAA6B,CAAC,GAAG,CAAE,IAAsC,EAAE,WAAW,IAAI,EAAE,CAAC,CACzG,CAAA;QACD,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC","sourcesContent":["import {DeviceTypes} from \"../types\"\n\n/** Mentra Live has no display — omit dashboard/brightness keys from BLE sync. */\nexport const MENTRA_LIVE_SETTING_KEYS: string[] = [\n \"sensing_enabled\",\n \"lc3_frame_size\",\n \"preferred_mic\",\n \"voice_activity_detection_enabled\",\n \"loudness_gate_enabled\",\n \"core_token\",\n \"auth_email\",\n \"button_photo_size\",\n \"button_video_settings\",\n \"button_max_recording_time\",\n \"camera_fov\",\n \"offline_mode\",\n \"local_stt_fallback_active\",\n \"gallery_mode\",\n \"default_wearable\",\n \"pending_wearable\",\n \"device_name\",\n \"device_address\",\n \"controllers\",\n \"pending_controller\",\n // Deprecated, kept so native can migrate and clear them:\n \"default_controller\",\n \"controller_device_name\",\n \"controller_address\",\n]\n\nfunction isMentraLiveDevice(model: string | undefined): boolean {\n return model === DeviceTypes.LIVE\n}\n\nexport function getBluetoothSettingKeysForDevice(deviceModel: string | undefined, fullKeys: string[]): string[] {\n return isMentraLiveDevice(deviceModel) ? MENTRA_LIVE_SETTING_KEYS : fullKeys\n}\n\n/**\n * Glasses Menu entries the firmware's own menu cannot open: Settings is the\n * dashboard's glasses page, which only the dashboard's Apps menu shows.\n */\nconst NATIVE_MENU_EXCLUDED_PACKAGES = new Set([\"com.glassly.settings\"])\nconst nativeMenuAppsCache = new WeakMap<object, unknown[]>()\n\n/** `menu_apps` as the glasses get it; the same array in gives the same array out, so the change-push stays quiet. */\nexport function nativeMenuApps(value: unknown): unknown {\n if (!Array.isArray(value)) return value\n let filtered = nativeMenuAppsCache.get(value)\n if (!filtered) {\n filtered = value.filter(\n (item) => !NATIVE_MENU_EXCLUDED_PACKAGES.has((item as {packageName?: string} | null)?.packageName ?? \"\"),\n )\n nativeMenuAppsCache.set(value, filtered)\n }\n return filtered\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAQ9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CA6kCnC,CAAA;AAEV,eAAO,MAAM,eAAe,EAAE,MAAM,EAAuD,CAAA;AAI3F,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA2D1C,CAAA;AAeD,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAEV,CAAA;AAIhC,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,aAAa,EAAE,OAAO,CAAA;IAEtB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,CAAA;IAEhC,eAAe,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAE/C,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/C,uBAAuB,EAAE,MAAM,IAAI,CAAA;CACpC;AAoBD,eAAO,MAAM,gBAAgB;;;;;;;;EA+U5B,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAI3F,CAAA"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAQ9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAslCnC,CAAA;AAEV,eAAO,MAAM,eAAe,EAAE,MAAM,EAAuD,CAAA;AAI3F,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA2D1C,CAAA;AAeD,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAEV,CAAA;AAIhC,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,aAAa,EAAE,OAAO,CAAA;IAEtB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,CAAA;IAEhC,eAAe,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAE/C,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/C,uBAAuB,EAAE,MAAM,IAAI,CAAA;CACpC;AAoBD,eAAO,MAAM,gBAAgB;;;;;;;;EAsW5B,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAI3F,CAAA"}
@@ -3,7 +3,7 @@ import { result as Res } from "typesafe-ts";
3
3
  import { create } from "zustand";
4
4
  import { subscribeWithSelector } from "zustand/middleware";
5
5
  import { storage } from "../utils/storage";
6
- import { getBluetoothSettingKeysForDevice } from "./bluetoothSettingKeys";
6
+ import { getBluetoothSettingKeysForDevice, nativeMenuApps } from "./bluetoothSettingKeys";
7
7
  import { useGlassesStore } from "./glasses";
8
8
  export { MENTRA_LIVE_SETTING_KEYS, getBluetoothSettingKeysForDevice } from "./bluetoothSettingKeys";
9
9
  // The provider list the data-consent screen currently shows. Stored per
@@ -787,6 +787,15 @@ export const SETTINGS = {
787
787
  saveOnServer: false,
788
788
  persist: true,
789
789
  },
790
+ // Apple Watch: draw the glasses' current frame behind the watch trackpad. Frames
791
+ // cross WatchConnectivity while the watch app is up, so it stays off unless asked for.
792
+ apple_watch_mirror: {
793
+ key: "apple_watch_mirror",
794
+ defaultValue: () => false,
795
+ writable: true,
796
+ saveOnServer: false,
797
+ persist: true,
798
+ },
790
799
  // Maximum rate at which display updates leave the phone for the glasses.
791
800
  // The Bluetooth SDK paces every scene frame and legacy layout to this,
792
801
  // coalescing faster updates last-wins; clears bypass it. No effect on G2
@@ -1481,6 +1490,27 @@ export const useSettingsStore = create()(subscribeWithSelector((set, get) => ({
1481
1490
  if (migrationSucceeded)
1482
1491
  storage.save(CAMERA_FOV_MIGRATION_KEY, true);
1483
1492
  }
1493
+ // Settings joined the Glasses Menu (the dashboard's glasses Settings
1494
+ // page): add it once, last, to a menu the wearer already saved. An
1495
+ // unsaved menu gets it from the default; a full one (10) is left alone.
1496
+ const MENU_SETTINGS_MIGRATION_KEY = "migration:menu_apps_settings_v1";
1497
+ const menuSettingsMigrationDone = storage.load(MENU_SETTINGS_MIGRATION_KEY);
1498
+ if (menuSettingsMigrationDone.is_error() || !menuSettingsMigrationDone.value) {
1499
+ const menu = get().getSetting(SETTINGS.menu_apps.key);
1500
+ let menuMigrated = true;
1501
+ if (Array.isArray(menu) &&
1502
+ menu.length < 10 &&
1503
+ !menu.some((item) => item?.packageName === "com.glassly.settings")) {
1504
+ const next = [...menu, { packageName: "com.glassly.settings", name: "Settings" }];
1505
+ const result = await get().setSetting(SETTINGS.menu_apps.key, next, true);
1506
+ if (result.is_error()) {
1507
+ console.log("SETTINGS: menu_apps Settings migration failed:", result.error);
1508
+ menuMigrated = false;
1509
+ }
1510
+ }
1511
+ if (menuMigrated)
1512
+ storage.save(MENU_SETTINGS_MIGRATION_KEY, true);
1513
+ }
1484
1514
  });
1485
1515
  loadAllSettingsInFlight = inFlight;
1486
1516
  void Promise.resolve(inFlight).then((result) => {
@@ -1502,7 +1532,7 @@ export const useSettingsStore = create()(subscribeWithSelector((set, get) => ({
1502
1532
  if (key === SETTINGS.core_token.key && (typeof value !== "string" || value.trim().length === 0)) {
1503
1533
  continue;
1504
1534
  }
1505
- bluetoothSettings[key] = value;
1535
+ bluetoothSettings[key] = key === SETTINGS.menu_apps.key ? nativeMenuApps(value) : value;
1506
1536
  }
1507
1537
  return bluetoothSettings;
1508
1538
  },