@glassly/engine 0.3.7 → 0.3.8-dev.1000

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 (212) hide show
  1. package/README.md +4 -0
  2. package/build/engine.d.ts +10 -2
  3. package/build/engine.d.ts.map +1 -1
  4. package/build/engine.js +7 -4
  5. package/build/engine.js.map +1 -1
  6. package/build/facades/ai.d.ts +3 -0
  7. package/build/facades/ai.d.ts.map +1 -1
  8. package/build/facades/ai.js +3 -0
  9. package/build/facades/ai.js.map +1 -1
  10. package/build/facades/backup.d.ts +14 -0
  11. package/build/facades/backup.d.ts.map +1 -0
  12. package/build/facades/backup.js +22 -0
  13. package/build/facades/backup.js.map +1 -0
  14. package/build/facades/glasses.d.ts +4 -4
  15. package/build/facades/glasses.d.ts.map +1 -1
  16. package/build/facades/glasses.js +4 -4
  17. package/build/facades/glasses.js.map +1 -1
  18. package/build/generated/releaseMetadata.js +4 -4
  19. package/build/generated/releaseMetadata.js.map +1 -1
  20. package/build/index.d.ts +2 -0
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js.map +1 -1
  23. package/build/services/AppRegistry.d.ts +2 -1
  24. package/build/services/AppRegistry.d.ts.map +1 -1
  25. package/build/services/AppRegistry.js +38 -8
  26. package/build/services/AppRegistry.js.map +1 -1
  27. package/build/services/AppStateBackup.d.ts +98 -0
  28. package/build/services/AppStateBackup.d.ts.map +1 -0
  29. package/build/services/AppStateBackup.js +224 -0
  30. package/build/services/AppStateBackup.js.map +1 -0
  31. package/build/services/BlobStore.d.ts.map +1 -1
  32. package/build/services/BlobStore.js +5 -12
  33. package/build/services/BlobStore.js.map +1 -1
  34. package/build/services/CloudClientService.d.ts +1 -0
  35. package/build/services/CloudClientService.d.ts.map +1 -1
  36. package/build/services/CloudClientService.js +34 -2
  37. package/build/services/CloudClientService.js.map +1 -1
  38. package/build/services/DisplayDiagnostics.d.ts.map +1 -1
  39. package/build/services/DisplayDiagnostics.js +4 -2
  40. package/build/services/DisplayDiagnostics.js.map +1 -1
  41. package/build/services/DisplayProcessor.d.ts +2 -0
  42. package/build/services/DisplayProcessor.d.ts.map +1 -1
  43. package/build/services/DisplayProcessor.js +15 -1
  44. package/build/services/DisplayProcessor.js.map +1 -1
  45. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  46. package/build/services/GlassesTouchInput.js +4 -2
  47. package/build/services/GlassesTouchInput.js.map +1 -1
  48. package/build/services/JSCrashloopReportService.d.ts.map +1 -1
  49. package/build/services/JSCrashloopReportService.js +5 -0
  50. package/build/services/JSCrashloopReportService.js.map +1 -1
  51. package/build/services/JSRouter.d.ts +11 -3
  52. package/build/services/JSRouter.d.ts.map +1 -1
  53. package/build/services/JSRouter.js +36 -6
  54. package/build/services/JSRouter.js.map +1 -1
  55. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  56. package/build/services/LocalDisplayManager.js +5 -2
  57. package/build/services/LocalDisplayManager.js.map +1 -1
  58. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  59. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  60. package/build/services/LocalMiniappRuntime.js +61 -22
  61. package/build/services/LocalMiniappRuntime.js.map +1 -1
  62. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  63. package/build/services/LocalSttFallbackCoordinator.js +2 -0
  64. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  65. package/build/services/LocalTranslationService.d.ts +2 -0
  66. package/build/services/LocalTranslationService.d.ts.map +1 -1
  67. package/build/services/LocalTranslationService.js +22 -0
  68. package/build/services/LocalTranslationService.js.map +1 -1
  69. package/build/services/MiniappEngine.d.ts +9 -0
  70. package/build/services/MiniappEngine.d.ts.map +1 -1
  71. package/build/services/MiniappEngine.js +60 -27
  72. package/build/services/MiniappEngine.js.map +1 -1
  73. package/build/services/MiniappLauncher.d.ts +2 -0
  74. package/build/services/MiniappLauncher.d.ts.map +1 -1
  75. package/build/services/MiniappLauncher.js +6 -0
  76. package/build/services/MiniappLauncher.js.map +1 -1
  77. package/build/services/NavigationService.d.ts +1 -0
  78. package/build/services/NavigationService.d.ts.map +1 -1
  79. package/build/services/NavigationService.js +20 -6
  80. package/build/services/NavigationService.js.map +1 -1
  81. package/build/services/OfflineActivity.d.ts +54 -0
  82. package/build/services/OfflineActivity.d.ts.map +1 -0
  83. package/build/services/OfflineActivity.js +86 -0
  84. package/build/services/OfflineActivity.js.map +1 -0
  85. package/build/services/PhoneCalendarService.d.ts.map +1 -1
  86. package/build/services/PhoneCalendarService.js +8 -0
  87. package/build/services/PhoneCalendarService.js.map +1 -1
  88. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  89. package/build/services/PhoneNotificationsSync.js +7 -3
  90. package/build/services/PhoneNotificationsSync.js.map +1 -1
  91. package/build/services/STTModelManager.d.ts +1 -0
  92. package/build/services/STTModelManager.d.ts.map +1 -1
  93. package/build/services/STTModelManager.js +6 -0
  94. package/build/services/STTModelManager.js.map +1 -1
  95. package/build/services/SceneRenderer.js +1 -1
  96. package/build/services/SceneRenderer.js.map +1 -1
  97. package/build/services/appStateBackupFile.d.ts +32 -0
  98. package/build/services/appStateBackupFile.d.ts.map +1 -0
  99. package/build/services/appStateBackupFile.js +135 -0
  100. package/build/services/appStateBackupFile.js.map +1 -0
  101. package/build/services/controllers.d.ts.map +1 -1
  102. package/build/services/controllers.js +12 -3
  103. package/build/services/controllers.js.map +1 -1
  104. package/build/services/effectiveCapabilities.js +6 -1
  105. package/build/services/effectiveCapabilities.js.map +1 -1
  106. package/build/services/pages/pageEligibility.d.ts.map +1 -1
  107. package/build/services/pages/pageEligibility.js +8 -1
  108. package/build/services/pages/pageEligibility.js.map +1 -1
  109. package/build/services/settingsBridge.d.ts.map +1 -1
  110. package/build/services/settingsBridge.js +1 -0
  111. package/build/services/settingsBridge.js.map +1 -1
  112. package/build/services/{appleWatch/AppleWatchLink.d.ts → watch/WatchLink.d.ts} +8 -7
  113. package/build/services/watch/WatchLink.d.ts.map +1 -0
  114. package/build/services/{appleWatch/AppleWatchLink.js → watch/WatchLink.js} +14 -15
  115. package/build/services/watch/WatchLink.js.map +1 -0
  116. package/build/stores/apps.d.ts.map +1 -1
  117. package/build/stores/apps.js +22 -10
  118. package/build/stores/apps.js.map +1 -1
  119. package/build/stores/settings.d.ts.map +1 -1
  120. package/build/stores/settings.js +63 -6
  121. package/build/stores/settings.js.map +1 -1
  122. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  123. package/build/types/capabilities/even-realities-g1.js +3 -0
  124. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  125. package/build/types/capabilities/memomind-one.d.ts +3 -0
  126. package/build/types/capabilities/memomind-one.d.ts.map +1 -0
  127. package/build/types/capabilities/memomind-one.js +42 -0
  128. package/build/types/capabilities/memomind-one.js.map +1 -0
  129. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  130. package/build/types/capabilities/rayneo-io.js +3 -2
  131. package/build/types/capabilities/rayneo-io.js.map +1 -1
  132. package/build/types/enums.d.ts +7 -2
  133. package/build/types/enums.d.ts.map +1 -1
  134. package/build/types/enums.js +5 -0
  135. package/build/types/enums.js.map +1 -1
  136. package/build/types/hardware.d.ts +3 -1
  137. package/build/types/hardware.d.ts.map +1 -1
  138. package/build/types/hardware.js +3 -1
  139. package/build/types/hardware.js.map +1 -1
  140. package/build/utils/display/index.d.ts +2 -1
  141. package/build/utils/display/index.d.ts.map +1 -1
  142. package/build/utils/display/index.js +3 -2
  143. package/build/utils/display/index.js.map +1 -1
  144. package/build/utils/display/profiles/index.d.ts +2 -1
  145. package/build/utils/display/profiles/index.d.ts.map +1 -1
  146. package/build/utils/display/profiles/index.js +3 -2
  147. package/build/utils/display/profiles/index.js.map +1 -1
  148. package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
  149. package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
  150. package/build/utils/display/profiles/rayneo-io.js +1 -1
  151. package/build/utils/display/profiles/rayneo-io.js.map +1 -1
  152. package/build/utils/display/scene/index.d.ts +1 -1
  153. package/build/utils/display/scene/index.d.ts.map +1 -1
  154. package/build/utils/display/scene/index.js +1 -1
  155. package/build/utils/display/scene/index.js.map +1 -1
  156. package/build/utils/display/scene/process.d.ts +6 -0
  157. package/build/utils/display/scene/process.d.ts.map +1 -1
  158. package/build/utils/display/scene/process.js +22 -7
  159. package/build/utils/display/scene/process.js.map +1 -1
  160. package/build/utils/miniappGlobals.d.ts +13 -17
  161. package/build/utils/miniappGlobals.d.ts.map +1 -1
  162. package/build/utils/miniappGlobals.js +24 -56
  163. package/build/utils/miniappGlobals.js.map +1 -1
  164. package/package.json +7 -7
  165. package/src/engine.ts +7 -4
  166. package/src/facades/ai.ts +4 -0
  167. package/src/facades/backup.ts +28 -0
  168. package/src/facades/glasses.ts +5 -5
  169. package/src/generated/releaseMetadata.ts +4 -4
  170. package/src/index.ts +9 -0
  171. package/src/services/AppRegistry.ts +36 -9
  172. package/src/services/AppStateBackup.ts +283 -0
  173. package/src/services/BlobStore.ts +5 -11
  174. package/src/services/CloudClientService.ts +35 -4
  175. package/src/services/DisplayDiagnostics.ts +4 -2
  176. package/src/services/DisplayProcessor.ts +20 -8
  177. package/src/services/GlassesTouchInput.ts +6 -2
  178. package/src/services/JSCrashloopReportService.ts +5 -0
  179. package/src/services/JSRouter.ts +37 -6
  180. package/src/services/LocalDisplayManager.ts +5 -2
  181. package/src/services/LocalMiniappRuntime.ts +61 -22
  182. package/src/services/LocalSttFallbackCoordinator.ts +4 -0
  183. package/src/services/LocalTranslationService.ts +20 -0
  184. package/src/services/MiniappEngine.ts +62 -27
  185. package/src/services/MiniappLauncher.ts +7 -0
  186. package/src/services/NavigationService.ts +28 -8
  187. package/src/services/OfflineActivity.ts +131 -0
  188. package/src/services/PhoneCalendarService.ts +7 -0
  189. package/src/services/PhoneNotificationsSync.ts +8 -3
  190. package/src/services/STTModelManager.ts +6 -0
  191. package/src/services/SceneRenderer.ts +1 -1
  192. package/src/services/appStateBackupFile.ts +171 -0
  193. package/src/services/controllers.ts +13 -3
  194. package/src/services/effectiveCapabilities.ts +6 -1
  195. package/src/services/pages/pageEligibility.ts +9 -1
  196. package/src/services/settingsBridge.ts +1 -0
  197. package/src/services/{appleWatch/AppleWatchLink.ts → watch/WatchLink.ts} +20 -20
  198. package/src/stores/apps.ts +22 -11
  199. package/src/stores/settings.ts +70 -10
  200. package/src/types/capabilities/even-realities-g1.ts +4 -0
  201. package/src/types/capabilities/memomind-one.ts +43 -0
  202. package/src/types/capabilities/rayneo-io.ts +3 -2
  203. package/src/types/enums.ts +5 -0
  204. package/src/types/hardware.ts +14 -1
  205. package/src/utils/display/index.ts +4 -2
  206. package/src/utils/display/profiles/index.ts +9 -2
  207. package/src/utils/display/profiles/rayneo-io.ts +6 -1
  208. package/src/utils/display/scene/index.ts +1 -1
  209. package/src/utils/display/scene/process.ts +22 -7
  210. package/src/utils/miniappGlobals.ts +32 -64
  211. package/build/services/appleWatch/AppleWatchLink.d.ts.map +0 -1
  212. package/build/services/appleWatch/AppleWatchLink.js.map +0 -1
@@ -0,0 +1,135 @@
1
+ /**
2
+ * App state backup files: the share-sheet export and file-picker restore
3
+ * behind Settings > Profile > Backup. What goes in the file is decided in
4
+ * AppStateBackup.ts; this side reads and writes the live stores.
5
+ */
6
+ import { File, Paths } from "expo-file-system";
7
+ import { Platform } from "react-native";
8
+ import Share from "react-native-share";
9
+ import { SETTINGS, useSettingsStore } from "../stores/settings";
10
+ import { useAppStatusStore } from "../stores/apps";
11
+ import { storage } from "../utils/storage/storage";
12
+ import appRegistry, { saveAlwaysOnOverride, saveWidgetExpandChoice } from "./AppRegistry";
13
+ import { AppStateBackupError, collectAppStateBackup, miniappPrefs, miniappsWithStorage, parseAppStateBackup, restorableSettings, restoreMiniappStorage, } from "./AppStateBackup";
14
+ import { cloudClientService } from "./CloudClientService";
15
+ import { memoryStore } from "./memoryStoreInstance";
16
+ import { respawnMiniapp, runningMiniappPackages, stopMiniapp } from "./MiniappEngine";
17
+ const PICK_MIME_TYPES = Platform.OS === "android" ? ["application/json", "application/octet-stream"] : ["application/json"];
18
+ const backend = {
19
+ get(key) {
20
+ const result = storage.load(key);
21
+ return result.is_ok() ? result.value : null;
22
+ },
23
+ set(key, value) {
24
+ const result = storage.save(key, value);
25
+ if (result.is_error())
26
+ throw result.error;
27
+ },
28
+ keys: () => storage.getAllKeys(),
29
+ };
30
+ export async function exportAppStateFile() {
31
+ const userId = await cloudClientService.resolveUserId();
32
+ const installed = [];
33
+ for (const app of await appRegistry.getInstalledMiniapps()) {
34
+ const version = await appRegistry.getActiveVersion(app.packageName);
35
+ if (!version)
36
+ continue;
37
+ installed.push({
38
+ packageName: app.packageName,
39
+ version,
40
+ source: appRegistry.getReleaseIdentity(app.packageName, version)?.source,
41
+ });
42
+ }
43
+ const backup = collectAppStateBackup({
44
+ backend,
45
+ userId,
46
+ settings: Object.values(SETTINGS),
47
+ installed,
48
+ memory: await memoryStore.exportBackup(),
49
+ now: Date.now(),
50
+ });
51
+ const filename = `glassly-backup-${localDate(backup.exportedAt)}.json`;
52
+ const file = new File(Paths.cache, filename);
53
+ file.writeSync(JSON.stringify(backup));
54
+ try {
55
+ await Share.open({ url: file.uri, type: "application/json", filename, title: "Glassly backup" });
56
+ return { success: true };
57
+ }
58
+ catch (error) {
59
+ // react-native-share rejects when the user dismisses the sheet.
60
+ if (error?.message?.includes("User did not share"))
61
+ return { success: false, cancelled: true };
62
+ throw error;
63
+ }
64
+ }
65
+ /** Resolves null when the user cancels the picker. */
66
+ export async function importAppStateFile(opts = {}) {
67
+ const picked = await File.pickFileAsync({ mimeTypes: PICK_MIME_TYPES });
68
+ if (picked.canceled)
69
+ return null;
70
+ let data;
71
+ try {
72
+ data = JSON.parse(await picked.result.text());
73
+ }
74
+ catch {
75
+ throw new AppStateBackupError("Not a Glassly app state backup");
76
+ }
77
+ const backup = parseAppStateBackup(data);
78
+ const userId = await cloudClientService.resolveUserId();
79
+ // First, so a memory section this build rejects fails the restore before anything is written.
80
+ const memory = backup.memory ? await memoryStore.importBackup(backup.memory) : null;
81
+ const { plain, indexed } = restorableSettings(backup, Object.values(SETTINGS));
82
+ const written = await useSettingsStore.getState().setManyLocally(plain);
83
+ if (written.is_error())
84
+ throw written.error;
85
+ // setManyLocally only takes registry keys; `key:index` values go to storage and the store directly.
86
+ for (const [key, value] of Object.entries(indexed))
87
+ backend.set(key, value);
88
+ if (Object.keys(indexed).length > 0) {
89
+ useSettingsStore.setState((state) => ({ settings: { ...state.settings, ...indexed } }));
90
+ }
91
+ // A running miniapp holds its old values in memory and could write them back over the
92
+ // restore, so the ones getting storage stop now and start again at the end.
93
+ const running = new Set(runningMiniappPackages());
94
+ const restarting = miniappsWithStorage(backup).filter((packageName) => running.has(packageName));
95
+ for (const packageName of restarting)
96
+ await stopMiniapp(packageName);
97
+ try {
98
+ const restored = restoreMiniappStorage(backend, userId, backup);
99
+ const apps = useAppStatusStore.getState();
100
+ for (const [packageName, record] of Object.entries(backup.miniapps)) {
101
+ const prefs = miniappPrefs(record);
102
+ if (prefs.hidden !== undefined)
103
+ apps.setHiddenStatus(packageName, prefs.hidden);
104
+ if (prefs.alwaysOn !== undefined)
105
+ saveAlwaysOnOverride(packageName, prefs.alwaysOn);
106
+ for (const [widgetId, expand] of Object.entries(prefs.widgetExpand)) {
107
+ saveWidgetExpandChoice(packageName, widgetId, expand);
108
+ }
109
+ }
110
+ appRegistry.markRefreshNeeded();
111
+ await apps.refresh();
112
+ const installedNow = new Set((await appRegistry.getInstalledMiniapps()).map((app) => app.packageName));
113
+ const missing = Object.entries(backup.miniapps)
114
+ .filter(([packageName, record]) => record.installed && !installedNow.has(packageName))
115
+ .map(([packageName]) => packageName);
116
+ if (missing.length > 0)
117
+ await opts.reinstall?.(missing);
118
+ return {
119
+ settings: Object.keys(plain).length + Object.keys(indexed).length,
120
+ miniapps: restored.packages.length,
121
+ memory,
122
+ missing,
123
+ };
124
+ }
125
+ finally {
126
+ for (const packageName of restarting)
127
+ await respawnMiniapp(packageName);
128
+ }
129
+ }
130
+ function localDate(ms) {
131
+ const d = new Date(ms);
132
+ const pad = (n) => String(n).padStart(2, "0");
133
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
134
+ }
135
+ //# sourceMappingURL=appStateBackupFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appStateBackupFile.js","sourceRoot":"","sources":["../../src/services/appStateBackupFile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * App state backup files: the share-sheet export and file-picker restore\n * behind Settings > Profile > Backup. What goes in the file is decided in\n * AppStateBackup.ts; this side reads and writes the live stores.\n */\n\nimport type {MemoryImportResult} from \"@glassly/miniapp\"\nimport {File, Paths} from \"expo-file-system\"\nimport {Platform} from \"react-native\"\nimport Share from \"react-native-share\"\n\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {storage} from \"../utils/storage/storage\"\nimport appRegistry, {saveAlwaysOnOverride, saveWidgetExpandChoice} from \"./AppRegistry\"\nimport {\n AppStateBackupError,\n collectAppStateBackup,\n miniappPrefs,\n miniappsWithStorage,\n parseAppStateBackup,\n restorableSettings,\n restoreMiniappStorage,\n type AppStateBackupBackend,\n} from \"./AppStateBackup\"\nimport {cloudClientService} from \"./CloudClientService\"\nimport {memoryStore} from \"./memoryStoreInstance\"\nimport {respawnMiniapp, runningMiniappPackages, stopMiniapp} from \"./MiniappEngine\"\n\nexport interface AppStateExportResult {\n success: boolean\n cancelled?: boolean\n}\n\nexport interface AppStateRestoreResult {\n /** Settings written. */\n settings: number\n /** Miniapps whose storage was restored. */\n miniapps: number\n memory: MemoryImportResult | null\n /** Installed when the backup was made but not on this phone now. */\n missing: string[]\n}\n\nexport interface AppStateRestoreOptions {\n /**\n * Install what the backup had installed and this phone lacks. Runs before\n * the restored miniapps start again, so the dashboard comes back with every\n * widget provider its saved layout names: it drops unknown widgets from the\n * layout the next time it saves it.\n */\n reinstall?: (packageNames: string[]) => Promise<void>\n}\n\nconst PICK_MIME_TYPES =\n Platform.OS === \"android\" ? [\"application/json\", \"application/octet-stream\"] : [\"application/json\"]\n\nconst backend: AppStateBackupBackend = {\n get(key) {\n const result = storage.load<unknown>(key)\n return result.is_ok() ? result.value : null\n },\n set(key, value) {\n const result = storage.save(key, value)\n if (result.is_error()) throw result.error\n },\n keys: () => storage.getAllKeys(),\n}\n\nexport async function exportAppStateFile(): Promise<AppStateExportResult> {\n const userId = await cloudClientService.resolveUserId()\n const installed: Array<{packageName: string; version: string; source?: string}> = []\n for (const app of await appRegistry.getInstalledMiniapps()) {\n const version = await appRegistry.getActiveVersion(app.packageName)\n if (!version) continue\n installed.push({\n packageName: app.packageName,\n version,\n source: appRegistry.getReleaseIdentity(app.packageName, version)?.source,\n })\n }\n const backup = collectAppStateBackup({\n backend,\n userId,\n settings: Object.values(SETTINGS),\n installed,\n memory: await memoryStore.exportBackup(),\n now: Date.now(),\n })\n\n const filename = `glassly-backup-${localDate(backup.exportedAt)}.json`\n const file = new File(Paths.cache, filename)\n file.writeSync(JSON.stringify(backup))\n try {\n await Share.open({url: file.uri, type: \"application/json\", filename, title: \"Glassly backup\"})\n return {success: true}\n } catch (error: any) {\n // react-native-share rejects when the user dismisses the sheet.\n if (error?.message?.includes(\"User did not share\")) return {success: false, cancelled: true}\n throw error\n }\n}\n\n/** Resolves null when the user cancels the picker. */\nexport async function importAppStateFile(opts: AppStateRestoreOptions = {}): Promise<AppStateRestoreResult | null> {\n const picked = await File.pickFileAsync({mimeTypes: PICK_MIME_TYPES})\n if (picked.canceled) return null\n let data: unknown\n try {\n data = JSON.parse(await picked.result.text())\n } catch {\n throw new AppStateBackupError(\"Not a Glassly app state backup\")\n }\n const backup = parseAppStateBackup(data)\n const userId = await cloudClientService.resolveUserId()\n\n // First, so a memory section this build rejects fails the restore before anything is written.\n const memory = backup.memory ? await memoryStore.importBackup(backup.memory) : null\n\n const {plain, indexed} = restorableSettings(backup, Object.values(SETTINGS))\n const written = await useSettingsStore.getState().setManyLocally(plain)\n if (written.is_error()) throw written.error\n // setManyLocally only takes registry keys; `key:index` values go to storage and the store directly.\n for (const [key, value] of Object.entries(indexed)) backend.set(key, value)\n if (Object.keys(indexed).length > 0) {\n useSettingsStore.setState((state) => ({settings: {...state.settings, ...indexed}}))\n }\n\n // A running miniapp holds its old values in memory and could write them back over the\n // restore, so the ones getting storage stop now and start again at the end.\n const running = new Set(runningMiniappPackages())\n const restarting = miniappsWithStorage(backup).filter((packageName) => running.has(packageName))\n for (const packageName of restarting) await stopMiniapp(packageName)\n\n try {\n const restored = restoreMiniappStorage(backend, userId, backup)\n\n const apps = useAppStatusStore.getState()\n for (const [packageName, record] of Object.entries(backup.miniapps)) {\n const prefs = miniappPrefs(record)\n if (prefs.hidden !== undefined) apps.setHiddenStatus(packageName, prefs.hidden)\n if (prefs.alwaysOn !== undefined) saveAlwaysOnOverride(packageName, prefs.alwaysOn)\n for (const [widgetId, expand] of Object.entries(prefs.widgetExpand)) {\n saveWidgetExpandChoice(packageName, widgetId, expand)\n }\n }\n appRegistry.markRefreshNeeded()\n await apps.refresh()\n\n const installedNow = new Set((await appRegistry.getInstalledMiniapps()).map((app) => app.packageName))\n const missing = Object.entries(backup.miniapps)\n .filter(([packageName, record]) => record.installed && !installedNow.has(packageName))\n .map(([packageName]) => packageName)\n if (missing.length > 0) await opts.reinstall?.(missing)\n\n return {\n settings: Object.keys(plain).length + Object.keys(indexed).length,\n miniapps: restored.packages.length,\n memory,\n missing,\n }\n } finally {\n for (const packageName of restarting) await respawnMiniapp(packageName)\n }\n}\n\nfunction localDate(ms: number): string {\n const d = new Date(ms)\n const pad = (n: number) => String(n).padStart(2, \"0\")\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\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;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"}
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;AAmD7E,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,8 +14,8 @@ 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 = {
17
+ /** A watch trackpad's taps and four-way swipes, and the crown (Apple) or rotary bezel (Wear OS) as the dial. */
18
+ const WATCH_SURFACE = {
19
19
  type: "swipe2d",
20
20
  events: [
21
21
  "TAP",
@@ -29,10 +29,19 @@ const APPLE_WATCH_SURFACE = {
29
29
  "DIAL_DOWN",
30
30
  ],
31
31
  };
32
+ /** The Gamebrick's D-pad is four absolute swipes; A is the press lifecycle (tap and hold) and B a double tap. */
33
+ const GAMEBRICK_SURFACE = {
34
+ type: "swipe2d",
35
+ events: ["TAP", "DOUBLE_TAP", "PRESS_HOLD", "SWIPE_UP", "SWIPE_DOWN", "SWIPE_LEFT", "SWIPE_RIGHT"],
36
+ };
32
37
  // The R1 swipes up/down like the temples it stands in for, so it adds nothing.
33
38
  const INPUT_SURFACES = {
34
39
  [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
35
- [ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],
40
+ [ControllerTypes.APPLE_WATCH]: [WATCH_SURFACE],
41
+ [ControllerTypes.WEAR_OS]: [WATCH_SURFACE],
42
+ [ControllerTypes.GAMEBRICK]: [GAMEBRICK_SURFACE],
43
+ [ControllerTypes.IINE_ANANKE]: [GAMEBRICK_SURFACE],
44
+ [ControllerTypes.ZERO2]: [GAMEBRICK_SURFACE],
36
45
  [ControllerTypes.R1]: [],
37
46
  };
38
47
  /** 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":"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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"]}
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/** A watch trackpad's taps and four-way swipes, and the crown (Apple) or rotary bezel (Wear OS) as the dial. */\nconst 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 Gamebrick's D-pad is four absolute swipes; A is the press lifecycle (tap and hold) and B a double tap. */\nconst GAMEBRICK_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\"TAP\", \"DOUBLE_TAP\", \"PRESS_HOLD\", \"SWIPE_UP\", \"SWIPE_DOWN\", \"SWIPE_LEFT\", \"SWIPE_RIGHT\"],\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]: [WATCH_SURFACE],\n [ControllerTypes.WEAR_OS]: [WATCH_SURFACE],\n [ControllerTypes.GAMEBRICK]: [GAMEBRICK_SURFACE],\n [ControllerTypes.IINE_ANANKE]: [GAMEBRICK_SURFACE],\n [ControllerTypes.ZERO2]: [GAMEBRICK_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"]}
@@ -53,6 +53,8 @@ function glassesCapabilities(model) {
53
53
  // Glassly CFW supports the complete vector contract, including SVG and rotation.
54
54
  // Upstream g2flash publishes a raster canvas with sceneVector=false.
55
55
  const vector = useGlassesStore.getState().sceneVector === true;
56
+ // Depth is a disparity between the lenses; one panel (MemoMind One) draws every element flat.
57
+ const stereo = (base.display.count ?? 1) >= 2;
56
58
  return {
57
59
  ...(volumeGate ? withVolumeGate(base) : base),
58
60
  hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),
@@ -60,11 +62,14 @@ function glassesCapabilities(model) {
60
62
  ...base.display,
61
63
  width: canvas.width,
62
64
  height: canvas.height,
65
+ // A Glassly CFW that publishes a canvas takes positioned elements, whatever
66
+ // the stock profile says (the G1's stock firmware degrades scenes to text walls).
67
+ canPosition: true,
63
68
  maxTextElements: Math.max(base.display.maxTextElements ?? 0, vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_TEXT_ELEMENTS),
64
69
  // Vector firmware draws an image as one cached slot, the same as a shape.
65
70
  maxImageElements: Math.max(base.display.maxImageElements ?? 0, vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS),
66
71
  ...(vector
67
- ? { shapes: [...ALL_DISPLAY_SHAPES, "svg"], animation: true, rotation: true, depth: true }
72
+ ? { shapes: [...ALL_DISPLAY_SHAPES, "svg"], animation: true, rotation: true, depth: stereo }
68
73
  : {}),
69
74
  },
70
75
  };
@@ -1 +1 @@
1
- {"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Effective device capabilities — the static model profile plus what the\n * connected firmware reports at runtime. The runtime override is the scene\n * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into\n * the 640×480 panel, so the drawable canvas grows past stock's 576×288; on\n * the RayNeo iO it opens a 540×180 page that stock firmware has no\n * equivalent of. Any model whose profile declares `hasCustomFirmware` reads it.\n * Native publishes the canvas into the glasses status (0 = none). Because\n * that path is phone-side rasterization, the stock firmware's container\n * pools (6 text / 4 image) don't bound a CFW frame either — element caps are\n * lifted alongside the canvas, bounded only to keep frames sane. Firmware\n * that draws vector shapes keeps 128 scene slots, so shape-heavy frames\n * (a game board, a chart) get a larger shared text/shape budget.\n *\n * Every connected controller adds its model's input surfaces to\n * `button.buttons` (the Meta Neural Band a `swipe2d` entry), so a miniapp can\n * tell four-way swipes from the temples' and the R1 ring's up/down.\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {ALL_DISPLAY_SHAPES, DeviceTypes, getModelCapabilities, type Capabilities} from \"../types\"\nimport {controllerInputSurfaces, readyControllers, type InputSurface} from \"./controllers\"\nimport {resolveNativeNavigation} from \"./nativeNavigationGate\"\n\nconst CFW_MAX_TEXT_ELEMENTS = 24\nconst CFW_MAX_IMAGE_ELEMENTS = 16\nconst CFW_VECTOR_MAX_ELEMENTS = 120\n\nexport interface SceneCanvasSize {\n width: number\n height: number\n}\n\nexport function runtimeSceneCanvas(): SceneCanvasSize | null {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0) return null\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n}\n\nfunction forceNativeNavigationOnCfw(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.cfw_native_navigation.key) === true\n}\n\n/** The connected firmware gates microphone streaming on volume (G2 Glassly CFW revision 40+). */\nexport function runtimeVolumeGate(): boolean {\n return useGlassesStore.getState().volumeGate === true\n}\n\nexport function getEffectiveCapabilities(model: DeviceTypes): Capabilities {\n return withControllerInput(glassesCapabilities(model))\n}\n\nfunction glassesCapabilities(model: DeviceTypes): Capabilities {\n const base = getModelCapabilities(model)\n // Only models with a Glassly CFW publish a canvas or a volume gate; never\n // let a stale value reshape another model.\n const canvas = base.hasCustomFirmware ? runtimeSceneCanvas() : null\n const volumeGate = base.hasCustomFirmware && base.hasMicrophone && runtimeVolumeGate()\n if (!canvas) return volumeGate ? withVolumeGate(base) : base\n if (!base.display) return volumeGate ? withVolumeGate(base) : base\n // Glassly CFW supports the complete vector contract, including SVG and rotation.\n // Upstream g2flash publishes a raster canvas with sceneVector=false.\n const vector = useGlassesStore.getState().sceneVector === true\n return {\n ...(volumeGate ? withVolumeGate(base) : base),\n hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),\n display: {\n ...base.display,\n width: canvas.width,\n height: canvas.height,\n maxTextElements: Math.max(\n base.display.maxTextElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_TEXT_ELEMENTS,\n ),\n // Vector firmware draws an image as one cached slot, the same as a shape.\n maxImageElements: Math.max(\n base.display.maxImageElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS,\n ),\n ...(vector\n ? {shapes: [...ALL_DISPLAY_SHAPES, \"svg\" as const], animation: true, rotation: true, depth: true}\n : {}),\n },\n }\n}\n\nfunction withControllerInput(caps: Capabilities): Capabilities {\n const buttons = caps.button?.buttons ?? []\n const types = new Set(buttons.map((surface) => surface.type))\n const added: InputSurface[] = []\n for (const status of readyControllers(useGlassesStore.getState().controllers)) {\n for (const surface of controllerInputSurfaces(status.model)) {\n if (types.has(surface.type)) continue\n types.add(surface.type)\n added.push(surface)\n }\n }\n if (added.length === 0) return caps\n return {\n ...caps,\n hasButton: true,\n button: {\n ...caps.button,\n count: (caps.button?.count ?? buttons.length) + added.length,\n buttons: [...buttons, ...added],\n },\n }\n}\n\nfunction withVolumeGate(base: Capabilities): Capabilities {\n return {...base, microphone: {...(base.microphone ?? {}), volumeGate: true}}\n}\n"]}
1
+ {"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Effective device capabilities — the static model profile plus what the\n * connected firmware reports at runtime. The runtime override is the scene\n * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into\n * the 640×480 panel, so the drawable canvas grows past stock's 576×288; on\n * the RayNeo iO it opens a 540×180 page that stock firmware has no\n * equivalent of. Any model whose profile declares `hasCustomFirmware` reads it.\n * Native publishes the canvas into the glasses status (0 = none). Because\n * that path is phone-side rasterization, the stock firmware's container\n * pools (6 text / 4 image) don't bound a CFW frame either — element caps are\n * lifted alongside the canvas, bounded only to keep frames sane. Firmware\n * that draws vector shapes keeps 128 scene slots, so shape-heavy frames\n * (a game board, a chart) get a larger shared text/shape budget.\n *\n * Every connected controller adds its model's input surfaces to\n * `button.buttons` (the Meta Neural Band a `swipe2d` entry), so a miniapp can\n * tell four-way swipes from the temples' and the R1 ring's up/down.\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {ALL_DISPLAY_SHAPES, DeviceTypes, getModelCapabilities, type Capabilities} from \"../types\"\nimport {controllerInputSurfaces, readyControllers, type InputSurface} from \"./controllers\"\nimport {resolveNativeNavigation} from \"./nativeNavigationGate\"\n\nconst CFW_MAX_TEXT_ELEMENTS = 24\nconst CFW_MAX_IMAGE_ELEMENTS = 16\nconst CFW_VECTOR_MAX_ELEMENTS = 120\n\nexport interface SceneCanvasSize {\n width: number\n height: number\n}\n\nexport function runtimeSceneCanvas(): SceneCanvasSize | null {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0) return null\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n}\n\nfunction forceNativeNavigationOnCfw(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.cfw_native_navigation.key) === true\n}\n\n/** The connected firmware gates microphone streaming on volume (G2 Glassly CFW revision 40+). */\nexport function runtimeVolumeGate(): boolean {\n return useGlassesStore.getState().volumeGate === true\n}\n\nexport function getEffectiveCapabilities(model: DeviceTypes): Capabilities {\n return withControllerInput(glassesCapabilities(model))\n}\n\nfunction glassesCapabilities(model: DeviceTypes): Capabilities {\n const base = getModelCapabilities(model)\n // Only models with a Glassly CFW publish a canvas or a volume gate; never\n // let a stale value reshape another model.\n const canvas = base.hasCustomFirmware ? runtimeSceneCanvas() : null\n const volumeGate = base.hasCustomFirmware && base.hasMicrophone && runtimeVolumeGate()\n if (!canvas) return volumeGate ? withVolumeGate(base) : base\n if (!base.display) return volumeGate ? withVolumeGate(base) : base\n // Glassly CFW supports the complete vector contract, including SVG and rotation.\n // Upstream g2flash publishes a raster canvas with sceneVector=false.\n const vector = useGlassesStore.getState().sceneVector === true\n // Depth is a disparity between the lenses; one panel (MemoMind One) draws every element flat.\n const stereo = (base.display.count ?? 1) >= 2\n return {\n ...(volumeGate ? withVolumeGate(base) : base),\n hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),\n display: {\n ...base.display,\n width: canvas.width,\n height: canvas.height,\n // A Glassly CFW that publishes a canvas takes positioned elements, whatever\n // the stock profile says (the G1's stock firmware degrades scenes to text walls).\n canPosition: true,\n maxTextElements: Math.max(\n base.display.maxTextElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_TEXT_ELEMENTS,\n ),\n // Vector firmware draws an image as one cached slot, the same as a shape.\n maxImageElements: Math.max(\n base.display.maxImageElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS,\n ),\n ...(vector\n ? {shapes: [...ALL_DISPLAY_SHAPES, \"svg\" as const], animation: true, rotation: true, depth: stereo}\n : {}),\n },\n }\n}\n\nfunction withControllerInput(caps: Capabilities): Capabilities {\n const buttons = caps.button?.buttons ?? []\n const types = new Set(buttons.map((surface) => surface.type))\n const added: InputSurface[] = []\n for (const status of readyControllers(useGlassesStore.getState().controllers)) {\n for (const surface of controllerInputSurfaces(status.model)) {\n if (types.has(surface.type)) continue\n types.add(surface.type)\n added.push(surface)\n }\n }\n if (added.length === 0) return caps\n return {\n ...caps,\n hasButton: true,\n button: {\n ...caps.button,\n count: (caps.button?.count ?? buttons.length) + added.length,\n buttons: [...buttons, ...added],\n },\n }\n}\n\nfunction withVolumeGate(base: Capabilities): Capabilities {\n return {...base, microphone: {...(base.microphone ?? {}), volumeGate: true}}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"pageEligibility.d.ts","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAaA,0GAA0G;AAC1G,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAAG,OAAO,CAMjH;AAED,wBAAgB,aAAa,IAAI,OAAO,CAOvC"}
1
+ {"version":3,"file":"pageEligibility.d.ts","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAaA,0GAA0G;AAC1G,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAKxF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAAG,OAAO,CAWjH;AAED,wBAAgB,aAAa,IAAI,OAAO,CAOvC"}
@@ -7,11 +7,13 @@
7
7
  */
8
8
  import { useGlassesStore } from "../../stores/glasses";
9
9
  import { SETTINGS, useSettingsStore } from "../../stores/settings";
10
+ import { DeviceTypes } from "../../types/enums";
10
11
  import { getModelCapabilities } from "../../types/hardware";
11
12
  import { isGlassesConnected } from "../GlassesReadiness";
12
13
  /** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */
13
14
  export function isCfwFirmwareVersion(firmwareVersion) {
14
- return firmwareVersion?.toLowerCase().includes("glassly cfw") === true;
15
+ return (firmwareVersion?.toLowerCase().includes("glassly cfw") === true ||
16
+ /^GLASSLYCFW\/[0-9]+$/.test(firmwareVersion ?? ""));
15
17
  }
16
18
  /**
17
19
  * Only models that ship a Glassly custom firmware, and only while they run it.
@@ -21,6 +23,11 @@ export function isCfwFirmwareVersion(firmwareVersion) {
21
23
  export function isCustomDashboardEligible(info) {
22
24
  if (!info.model)
23
25
  return false;
26
+ if (info.model === DeviceTypes.MEMOMIND_ONE) {
27
+ const match = /^GLASSLYCFW\/([0-9]+)$/.exec(info.firmwareVersion ?? "");
28
+ const revision = match ? Number(match[1]) : 0;
29
+ return Number.isSafeInteger(revision) && revision >= 10;
30
+ }
24
31
  return (getModelCapabilities(info.model).hasCustomFirmware === true &&
25
32
  isCfwFirmwareVersion(info.firmwareVersion));
26
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pageEligibility.js","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAEhE,OAAO,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEtD,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,eAA0C;IAC7E,OAAO,eAAe,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAA8D;IACtG,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAC7B,OAAO,CACL,oBAAoB,CAAC,IAAI,CAAC,KAAoB,CAAC,CAAC,iBAAiB,KAAK,IAAI;QAC1E,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,CAC3C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IACzG,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxC,OAAO,CACL,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;QACpC,yBAAyB,CAAC,EAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAC,CAAC,CAC9F,CAAA;AACH,CAAC","sourcesContent":["/**\n * Whether the dashboard view may show right now: connected Glassly CFW\n * glasses with the dashboard enabled. The page host reads it before showing\n * a page and hides the showing page when it turns false; the miniapp runtime\n * folds it into the designated app's `display.views` capability. Read at\n * decision time, never cached.\n */\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport {DeviceTypes} from \"../../types/enums\"\nimport {getModelCapabilities} from \"../../types/hardware\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\n\n/** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */\nexport function isCfwFirmwareVersion(firmwareVersion: string | null | undefined): boolean {\n return firmwareVersion?.toLowerCase().includes(\"glassly cfw\") === true\n}\n\n/**\n * Only models that ship a Glassly custom firmware, and only while they run it.\n * Stock firmware on those models and every other model (NIMO, G1, ...) are\n * intentionally excluded.\n */\nexport function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {\n if (!info.model) return false\n return (\n getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&\n isCfwFirmwareVersion(info.firmwareVersion)\n )\n}\n\nexport function pagesEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({model: state.deviceModel, firmwareVersion: state.firmwareVersion})\n )\n}\n"]}
1
+ {"version":3,"file":"pageEligibility.js","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEtD,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,eAA0C;IAC7E,OAAO,CACL,eAAe,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI;QAC/D,sBAAsB,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CACnD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAA8D;IACtG,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,YAAY,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAA;QACvE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,EAAE,CAAA;IACzD,CAAC;IACD,OAAO,CACL,oBAAoB,CAAC,IAAI,CAAC,KAAoB,CAAC,CAAC,iBAAiB,KAAK,IAAI;QAC1E,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,CAC3C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IACzG,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxC,OAAO,CACL,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;QACpC,yBAAyB,CAAC,EAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAC,CAAC,CAC9F,CAAA;AACH,CAAC","sourcesContent":["/**\n * Whether the dashboard view may show right now: connected Glassly CFW\n * glasses with the dashboard enabled. The page host reads it before showing\n * a page and hides the showing page when it turns false; the miniapp runtime\n * folds it into the designated app's `display.views` capability. Read at\n * decision time, never cached.\n */\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport {DeviceTypes} from \"../../types/enums\"\nimport {getModelCapabilities} from \"../../types/hardware\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\n\n/** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */\nexport function isCfwFirmwareVersion(firmwareVersion: string | null | undefined): boolean {\n return (\n firmwareVersion?.toLowerCase().includes(\"glassly cfw\") === true ||\n /^GLASSLYCFW\\/[0-9]+$/.test(firmwareVersion ?? \"\")\n )\n}\n\n/**\n * Only models that ship a Glassly custom firmware, and only while they run it.\n * Stock firmware on those models and every other model (NIMO, G1, ...) are\n * intentionally excluded.\n */\nexport function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {\n if (!info.model) return false\n if (info.model === DeviceTypes.MEMOMIND_ONE) {\n const match = /^GLASSLYCFW\\/([0-9]+)$/.exec(info.firmwareVersion ?? \"\")\n const revision = match ? Number(match[1]) : 0\n return Number.isSafeInteger(revision) && revision >= 10\n }\n return (\n getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&\n isCfwFirmwareVersion(info.firmwareVersion)\n )\n}\n\nexport function pagesEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({model: state.deviceModel, firmwareVersion: state.firmwareVersion})\n )\n}\n"]}
@@ -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,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"}
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,CAwB/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"}
@@ -32,6 +32,7 @@ export const SETTINGS_BRIDGE_KEYS = {
32
32
  notifications_enabled: "write",
33
33
  notifications_popups: "write",
34
34
  notifications_popup_seconds: "write",
35
+ notifications_batch_seconds: "write",
35
36
  notifications_hold_during_apps: "write",
36
37
  notifications_ignore_while_active: "write",
37
38
  notifications_blocklist: "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, 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
+ {"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_batch_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,11 +1,12 @@
1
1
  /**
2
- * The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in
2
+ * The watch remote's phone half, for the Apple Watch (iOS, WatchConnectivity) and a Wear OS
3
+ * watch (Android, Wearable Data Layer) alike: the watch app's status (glasses, the miniapp in
3
4
  * front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames
4
5
  * while the setting is on, and the commands the watch sends back. Native adds whether the active
5
6
  * watch is paired and keeps the newest frame for when the watch app opens. Gestures ride
6
7
  * `touch_event` like any controller's.
7
8
  */
8
- import type { AppleWatchCommandEvent } from "@glassly/bluetooth-sdk";
9
+ import type { WatchCommandEvent } from "@glassly/bluetooth-sdk";
9
10
  import type { MirrorSnapshot } from "@glassly/miniapp";
10
11
  import type { ClientApp } from "../../types";
11
12
  export declare const ASSIST_PACKAGE = "com.glassly.assistant";
@@ -28,7 +29,7 @@ export declare function launchableApps(apps: readonly ClientApp[]): {
28
29
  export declare function watchState({ glasses, apps, assist, mirror }: WatchStateInput): Record<string, unknown>;
29
30
  /** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */
30
31
  export declare function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> | null;
31
- export interface AppleWatchCommandDeps {
32
+ export interface WatchCommandDeps {
32
33
  apps: () => readonly ClientApp[];
33
34
  start: (app: ClientApp) => Promise<unknown>;
34
35
  stop: (packageName: string) => Promise<unknown>;
@@ -36,7 +37,7 @@ export interface AppleWatchCommandDeps {
36
37
  type: (stream: string, payload: unknown) => void;
37
38
  now: () => number;
38
39
  }
39
- export declare function handleAppleWatchCommand(event: AppleWatchCommandEvent, deps?: AppleWatchCommandDeps): Promise<void>;
40
- export declare function startAppleWatchLink(): void;
41
- export declare function stopAppleWatchLink(): void;
42
- //# sourceMappingURL=AppleWatchLink.d.ts.map
40
+ export declare function handleWatchCommand(event: WatchCommandEvent, deps?: WatchCommandDeps): Promise<void>;
41
+ export declare function startWatchLink(): void;
42
+ export declare function stopWatchLink(): void;
43
+ //# sourceMappingURL=WatchLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchLink.d.ts","sourceRoot":"","sources":["../../../src/services/watch/WatchLink.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,wBAAwB,CAAA;AAE7D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAMpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAA;AAO1C,eAAO,MAAM,cAAc,0BAA0B,CAAA;AACrD,eAAO,MAAM,aAAa,QAAQ,CAAA;AAQlC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAA;IAC7D,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,GAAG;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,CAMhG;AAED,wBAAgB,UAAU,CAAC,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAC,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUpG;AAOD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA4EpF;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,SAAS,SAAS,EAAE,CAAA;IAChC,KAAK,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAWD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,gBAA8B,GACnC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AA6DD,wBAAgB,cAAc,IAAI,IAAI,CAqCrC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}
@@ -1,6 +1,5 @@
1
1
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
2
2
  import { MiniappStreamType } from "@glassly/miniapp";
3
- import { Platform } from "react-native";
4
3
  import { useAppStatusStore } from "../../stores/apps";
5
4
  import { useGlassesStore } from "../../stores/glasses";
6
5
  import { SETTINGS, useSettingsStore } from "../../stores/settings";
@@ -11,7 +10,7 @@ import { mirrorService } from "../MirrorService";
11
10
  import { keyEventsForTextChange } from "../neuralBand/NeuralBandInput";
12
11
  export const ASSIST_PACKAGE = "com.glassly.assistant";
13
12
  export const ASSIST_ACTION = "ask";
14
- /** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */
13
+ /** WatchConnectivity messages top out near 64 KB (Data Layer messages at 100 KB); frames past this are skipped, not truncated. */
15
14
  const MAX_FRAME_BYTES = 48_000;
16
15
  const MAX_IMAGE_CHARS = 16_000;
17
16
  const MAX_APPS = 24;
@@ -129,14 +128,14 @@ const defaultDeps = {
129
128
  type: (stream, payload) => localMiniappRuntime.forwardEvent(stream, payload),
130
129
  now: () => Date.now(),
131
130
  };
132
- export async function handleAppleWatchCommand(event, deps = defaultDeps) {
131
+ export async function handleWatchCommand(event, deps = defaultDeps) {
133
132
  switch (event.command) {
134
133
  case "launch": {
135
134
  const app = launchableApps(deps.apps()).some((entry) => entry.packageName === event.packageName)
136
135
  ? deps.apps().find((entry) => entry.packageName === event.packageName)
137
136
  : undefined;
138
137
  if (!app) {
139
- console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`);
138
+ console.warn(`Watch: launch of unknown miniapp ${event.packageName}`);
140
139
  return;
141
140
  }
142
141
  await deps.start(app);
@@ -172,7 +171,7 @@ function currentState() {
172
171
  },
173
172
  apps: useAppStatusStore.getState().apps,
174
173
  assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),
175
- mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,
174
+ mirror: useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true,
176
175
  });
177
176
  }
178
177
  function syncMirror(enabled) {
@@ -191,7 +190,7 @@ function syncMirror(enabled) {
191
190
  let timer = null;
192
191
  const send = (snapshot) => {
193
192
  lastSentAt = Date.now();
194
- void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) => console.warn("AppleWatch: mirror push failed:", err));
193
+ void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) => console.warn("Watch: mirror push failed:", err));
195
194
  };
196
195
  const unsubscribe = mirrorService.subscribe((snapshot) => {
197
196
  const wait = lastSentAt + MIRROR_INTERVAL_MS - Date.now();
@@ -214,8 +213,8 @@ function syncMirror(enabled) {
214
213
  timer = null;
215
214
  };
216
215
  }
217
- export function startAppleWatchLink() {
218
- if (Platform.OS !== "ios" || stops.length > 0)
216
+ export function startWatchLink() {
217
+ if (stops.length > 0)
219
218
  return;
220
219
  let lastKey = "";
221
220
  let timer = null;
@@ -226,20 +225,20 @@ export function startAppleWatchLink() {
226
225
  if (key === lastKey)
227
226
  return;
228
227
  lastKey = key;
229
- void BluetoothSdk.setWatchState(state).catch((err) => console.warn("AppleWatch: state push failed:", err));
228
+ void BluetoothSdk.setWatchState(state).catch((err) => console.warn("Watch: state push failed:", err));
230
229
  };
231
230
  const schedule = () => {
232
231
  timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS);
233
232
  };
234
- const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true;
235
- const commands = BluetoothSdk.addListener("apple_watch_command", (event) => {
236
- handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err));
233
+ const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true;
234
+ const commands = BluetoothSdk.addListener("watch_command", (event) => {
235
+ handleWatchCommand(event).catch((err) => console.warn(`Watch: ${event.command} failed:`, err));
237
236
  });
238
237
  stops = [
239
238
  () => commands.remove(),
240
239
  useGlassesStore.subscribe(schedule),
241
240
  useAppStatusStore.subscribe(schedule),
242
- useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.apple_watch_mirror.key), () => {
241
+ useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.watch_mirror.key), () => {
243
242
  syncMirror(mirrorEnabled());
244
243
  schedule();
245
244
  }),
@@ -252,8 +251,8 @@ export function startAppleWatchLink() {
252
251
  syncMirror(mirrorEnabled());
253
252
  schedule();
254
253
  }
255
- export function stopAppleWatchLink() {
254
+ export function stopWatchLink() {
256
255
  stops.splice(0).forEach((stop) => stop());
257
256
  syncMirror(false);
258
257
  }
259
- //# sourceMappingURL=AppleWatchLink.js.map
258
+ //# sourceMappingURL=WatchLink.js.map