@glassly/engine 0.3.0-beta.487 → 0.3.0-dev.460

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 (90) hide show
  1. package/build/engine.d.ts +0 -3
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/facades/miniapps.d.ts +0 -6
  4. package/build/facades/miniapps.d.ts.map +1 -1
  5. package/build/facades/miniapps.js +0 -7
  6. package/build/facades/miniapps.js.map +1 -1
  7. package/build/generated/releaseMetadata.js +3 -3
  8. package/build/generated/releaseMetadata.js.map +1 -1
  9. package/build/index.d.ts +2 -2
  10. package/build/index.d.ts.map +1 -1
  11. package/build/index.js +2 -2
  12. package/build/index.js.map +1 -1
  13. package/build/services/AppRegistry.js +1 -1
  14. package/build/services/AppRegistry.js.map +1 -1
  15. package/build/services/BlobStore.js +2 -2
  16. package/build/services/BlobStore.js.map +1 -1
  17. package/build/services/LocalDisplayManager.d.ts +2 -1
  18. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  19. package/build/services/LocalDisplayManager.js +2 -1
  20. package/build/services/LocalDisplayManager.js.map +1 -1
  21. package/build/services/LocalMiniappRuntime.d.ts +9 -22
  22. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  23. package/build/services/LocalMiniappRuntime.js +52 -117
  24. package/build/services/LocalMiniappRuntime.js.map +1 -1
  25. package/build/services/MiniappLauncher.d.ts +0 -2
  26. package/build/services/MiniappLauncher.d.ts.map +1 -1
  27. package/build/services/MiniappLauncher.js +1 -6
  28. package/build/services/MiniappLauncher.js.map +1 -1
  29. package/build/services/pages/OverlayPages.d.ts +2 -13
  30. package/build/services/pages/OverlayPages.d.ts.map +1 -1
  31. package/build/services/pages/OverlayPages.js +10 -11
  32. package/build/services/pages/OverlayPages.js.map +1 -1
  33. package/build/services/pages/PageHost.d.ts +1 -1
  34. package/build/services/pages/PageHost.d.ts.map +1 -1
  35. package/build/services/pages/PageHost.js +2 -2
  36. package/build/services/pages/PageHost.js.map +1 -1
  37. package/build/services/pages/overlayApps.d.ts +13 -20
  38. package/build/services/pages/overlayApps.d.ts.map +1 -1
  39. package/build/services/pages/overlayApps.js +31 -50
  40. package/build/services/pages/overlayApps.js.map +1 -1
  41. package/build/services/settingsBridge.d.ts +14 -15
  42. package/build/services/settingsBridge.d.ts.map +1 -1
  43. package/build/services/settingsBridge.js +20 -20
  44. package/build/services/settingsBridge.js.map +1 -1
  45. package/build/stores/appVisibility.d.ts +0 -5
  46. package/build/stores/appVisibility.d.ts.map +1 -1
  47. package/build/stores/appVisibility.js +0 -2
  48. package/build/stores/appVisibility.js.map +1 -1
  49. package/build/stores/apps.d.ts +0 -5
  50. package/build/stores/apps.d.ts.map +1 -1
  51. package/build/stores/apps.js +10 -40
  52. package/build/stores/apps.js.map +1 -1
  53. package/build/stores/settings.d.ts.map +1 -1
  54. package/build/stores/settings.js +45 -19
  55. package/build/stores/settings.js.map +1 -1
  56. package/build/types/applet.d.ts +2 -2
  57. package/build/types/applet.d.ts.map +1 -1
  58. package/build/types/applet.js.map +1 -1
  59. package/build/utils/display/scene/anchor.d.ts +2 -1
  60. package/build/utils/display/scene/anchor.d.ts.map +1 -1
  61. package/build/utils/display/scene/anchor.js +2 -1
  62. package/build/utils/display/scene/anchor.js.map +1 -1
  63. package/package.json +7 -7
  64. package/src/facades/miniapps.ts +0 -9
  65. package/src/generated/releaseMetadata.ts +3 -3
  66. package/src/index.ts +1 -3
  67. package/src/services/AppRegistry.ts +1 -1
  68. package/src/services/BlobStore.ts +2 -2
  69. package/src/services/LocalDisplayManager.ts +2 -1
  70. package/src/services/LocalMiniappRuntime.ts +52 -136
  71. package/src/services/MiniappLauncher.ts +1 -6
  72. package/src/services/pages/OverlayPages.ts +10 -11
  73. package/src/services/pages/PageHost.ts +2 -2
  74. package/src/services/pages/overlayApps.ts +30 -51
  75. package/src/services/settingsBridge.ts +20 -20
  76. package/src/stores/appVisibility.ts +0 -4
  77. package/src/stores/apps.ts +12 -44
  78. package/src/stores/settings.ts +45 -19
  79. package/src/types/applet.ts +1 -3
  80. package/src/utils/display/scene/anchor.ts +2 -1
  81. package/build/services/miniappPermissions.d.ts +0 -16
  82. package/build/services/miniappPermissions.d.ts.map +0 -1
  83. package/build/services/miniappPermissions.js +0 -7
  84. package/build/services/miniappPermissions.js.map +0 -1
  85. package/build/services/pages/activeDashboard.d.ts +0 -7
  86. package/build/services/pages/activeDashboard.d.ts.map +0 -1
  87. package/build/services/pages/activeDashboard.js +0 -21
  88. package/build/services/pages/activeDashboard.js.map +0 -1
  89. package/src/services/miniappPermissions.ts +0 -19
  90. package/src/services/pages/activeDashboard.ts +0 -24
@@ -1,71 +1,51 @@
1
1
  /**
2
- * The overlay app: the connected miniapp that holds the dashboard role
3
- * (ADR-0009). It alone may take the overlay slot through
4
- * `session.display.showView("dashboard")` and has "dashboard" in its
5
- * `display.views` capability while the glasses are eligible. The runtime
6
- * records each dashboard miniapp (manifest type "dashboard") as it connects
7
- * and stops, and who holds the role; the engine never knows what a dashboard
8
- * draws. Kept free of imports so the runtime and the notification policy can
9
- * both read it without a cycle.
2
+ * The overlay apps: the connected miniapps whose manifest type is
3
+ * "system_dashboard" (the dashboard). They alone may take the overlay slot
4
+ * through `session.display.showView("dashboard")` and have "dashboard" in
5
+ * their `display.views` capability while the glasses are eligible. The runtime adds an app when it connects and
6
+ * removes it when it stops; the engine never knows what any of them draws.
7
+ * Kept free of imports so the runtime and the notification policy can both
8
+ * read it without a cycle.
10
9
  */
11
10
 
12
11
  export type DisplayView = "main" | "dashboard"
13
12
 
14
- /** Connected dashboard miniapps, role or not. */
15
- const connected = new Set<string>()
16
- /** Connected dashboards listening to the phone's notifications. */
17
- const listening = new Set<string>()
18
- let active: string | null = null
13
+ const apps = new Set<string>()
14
+ /** Overlay apps listening to the phone's notifications: something on the glasses presents them. */
15
+ const viewers = new Set<string>()
19
16
  const listeners = new Set<() => void>()
20
17
 
21
- /** `packageName` holds the dashboard role and is connected. */
22
18
  export function isOverlayApp(packageName: string): boolean {
23
- return packageName === active && connected.has(packageName)
24
- }
25
-
26
- /** `packageName` is a connected dashboard miniapp, whether or not it holds the role. */
27
- export function isDashboardApp(packageName: string): boolean {
28
- return connected.has(packageName)
19
+ return apps.has(packageName)
29
20
  }
30
21
 
31
22
  export function overlayApps(): readonly string[] {
32
- return active !== null && connected.has(active) ? [active] : []
33
- }
34
-
35
- /** The package that holds the dashboard role, connected or not. */
36
- export function activeDashboard(): string | null {
37
- return active
38
- }
39
-
40
- export function setActiveDashboard(packageName: string | null): void {
41
- if (active === packageName) return
42
- active = packageName
43
- emit()
23
+ return [...apps]
44
24
  }
45
25
 
46
- export function addDashboardApp(packageName: string): void {
47
- if (connected.has(packageName)) return
48
- connected.add(packageName)
26
+ export function addOverlayApp(packageName: string): void {
27
+ if (apps.has(packageName)) return
28
+ apps.add(packageName)
49
29
  emit()
50
30
  }
51
31
 
52
- export function removeDashboardApp(packageName: string): void {
53
- listening.delete(packageName)
54
- if (!connected.delete(packageName)) return
32
+ export function removeOverlayApp(packageName: string): void {
33
+ viewers.delete(packageName)
34
+ if (!apps.delete(packageName)) return
55
35
  emit()
56
36
  }
57
37
 
58
- /** Record whether a dashboard subscribes to phone notifications (the overlay app is the viewer while it does). */
59
- export function setOverlayViewer(packageName: string, subscribed: boolean): void {
60
- const was = overlayViewerRunning()
61
- if (subscribed && connected.has(packageName)) listening.add(packageName)
62
- else listening.delete(packageName)
63
- if (was !== overlayViewerRunning()) emit()
38
+ /** Record whether an overlay app subscribes to phone notifications (it is a viewer while it does). */
39
+ export function setOverlayViewer(packageName: string, listening: boolean): void {
40
+ const was = viewers.has(packageName)
41
+ if (listening && apps.has(packageName)) viewers.add(packageName)
42
+ else viewers.delete(packageName)
43
+ if (was !== viewers.has(packageName)) emit()
64
44
  }
65
45
 
66
- /** The overlay app presents phone notifications. */
46
+ /** An overlay app that presents phone notifications is running. */
67
47
  export function overlayViewerRunning(): boolean {
68
- return active !== null && connected.has(active) && listening.has(active)
48
+ return viewers.size > 0
69
49
  }
70
50
 
71
51
  export function onOverlayAppsChange(listener: () => void): () => void {
@@ -75,9 +55,9 @@ export function onOverlayAppsChange(listener: () => void): () => void {
75
55
  }
76
56
  }
77
57
 
78
- /** The views `packageName` may draw: main for everyone, the dashboard for the overlay app on eligible glasses. */
58
+ /** The views `packageName` may draw: main for everyone, the dashboard for overlay apps on eligible glasses. */
79
59
  export function displayViewsFor(packageName: string, eligible: boolean): DisplayView[] {
80
- return isOverlayApp(packageName) && eligible ? ["main", "dashboard"] : ["main"]
60
+ return apps.has(packageName) && eligible ? ["main", "dashboard"] : ["main"]
81
61
  }
82
62
 
83
63
  function emit(): void {
@@ -86,7 +66,6 @@ function emit(): void {
86
66
 
87
67
  /** Tests only. */
88
68
  export function _resetOverlayAppsForTest(): void {
89
- connected.clear()
90
- listening.clear()
91
- active = null
69
+ apps.clear()
70
+ viewers.clear()
92
71
  }
@@ -1,23 +1,28 @@
1
1
  /**
2
2
  * The settings bridge's vocabulary (session.settings): which engine settings
3
- * a miniapp with the PLATFORM_SETTINGS permission may read or write, the
4
- * derived read-only keys the engine computes rather than stores, and the
5
- * request validation. Pure, so the runtime's handlers stay thin and this can
6
- * be tested without the native import surface the runtime pulls in.
3
+ * a system miniapp may read or write, the derived read-only keys the engine
4
+ * computes rather than stores, and the request validation. Pure, so the
5
+ * runtime's handlers stay thin and this can be tested without the native
6
+ * import surface the runtime pulls in.
7
7
  *
8
- * A dashboard miniapp is the usual consumer: it edits the notification and
9
- * glasses-menu keys the phone's settings screens used to, through the store,
10
- * so persistence, server sync and the Bluetooth sync of `menu_apps` behave as
11
- * if a phone screen wrote them. A dashboard's own look is not here; it keeps
12
- * that in its storage.
8
+ * The dashboard miniapp is the consumer: it edits the same dashboard,
9
+ * notification and glasses-menu keys the phone's settings screens used to,
10
+ * through the store, so persistence, server sync and the Bluetooth sync of
11
+ * `menu_apps` behave as if a phone screen wrote them.
13
12
  */
14
13
 
15
14
  export type SettingsBridgeAccess = "read" | "write"
16
15
 
17
- /** Stored settings the bridge serves, by key, with what a permitted app may do to each. */
16
+ /** Stored settings the bridge serves, by key, with what a system app may do to each. */
18
17
  export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {
19
18
  // The dashboard's master switch is a phone-side debug knob.
20
19
  custom_dashboard_enabled: "read",
20
+ custom_dashboard_clock_style: "write",
21
+ custom_dashboard_clock_show_date: "write",
22
+ custom_dashboard_battery_style: "write",
23
+ custom_dashboard_layout: "write",
24
+ custom_dashboard_calendar_event_count: "write",
25
+ custom_dashboard_calendar_days_ahead: "write",
21
26
  contextual_dashboard: "write",
22
27
  twelve_hour_time: "write",
23
28
  menu_apps: "write",
@@ -33,23 +38,18 @@ export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>
33
38
 
34
39
  /**
35
40
  * Read-only keys the engine computes: whether the wearer can see the phone,
36
- * whether the foreground miniapp is drawing on the glasses, the ring's
37
- * battery, and the package that holds the dashboard role (null for nobody).
38
- * They change on their own clocks; the runtime pushes them like stored keys.
41
+ * whether the foreground miniapp is drawing on the glasses, and the ring's
42
+ * battery. They change on their own clocks; the runtime pushes them like
43
+ * stored keys.
39
44
  */
40
- export const DERIVED_SETTINGS_KEYS = [
41
- "phone_active",
42
- "foreground_app_on_screen",
43
- "ring_battery",
44
- "active_dashboard",
45
- ] as const
45
+ export const DERIVED_SETTINGS_KEYS = ["phone_active", "foreground_app_on_screen", "ring_battery"] as const
46
46
  export type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]
47
47
 
48
48
  export function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {
49
49
  return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)
50
50
  }
51
51
 
52
- /** What a permitted app may do with `key`, or null when the bridge does not serve it. */
52
+ /** What a system app may do with `key`, or null when the bridge does not serve it. */
53
53
  export function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {
54
54
  if (isDerivedSettingsKey(key)) return "read"
55
55
  return SETTINGS_BRIDGE_KEYS[key] ?? null
@@ -1,7 +1,3 @@
1
- /** Host built-in phone pages and dashboard-type miniapps: never launched from a glasses menu. */
2
- export const isSystemMiniapp = (app: {offline: boolean; type: string}): boolean =>
3
- app.offline || app.type === "dashboard"
4
-
5
1
  /** Combine registry-enforced visibility with the user's home-screen choice. */
6
2
  export const resolveHiddenStatus = (registryHidden: boolean, userHidden: boolean): boolean =>
7
3
  registryHidden || userHidden
@@ -25,9 +25,9 @@ import {islandNotifications} from "../services/NotificationsEmitter"
25
25
  import sttModelManager from "../services/STTModelManager"
26
26
  import {miniappLauncher} from "../services/MiniappLauncher"
27
27
  import {miniappRunningRegistry} from "../services/MiniappRunningRegistry"
28
- import {activeDashboardAmong, resolveActiveDashboard} from "../services/pages/activeDashboard"
29
28
  import {defaultHiddenStatus, isGatedByDevTools, isGatedUntilPaired, resolveHiddenStatus} from "./appVisibility"
30
29
  import {SETTINGS, useSettingsStore} from "./settings"
30
+ import {PermissionFeatures, permissions} from "../facades/permissions"
31
31
  import BluetoothSdk from "@glassly/bluetooth-sdk"
32
32
  import type {MiniappLaunchSource} from "@glassly/miniapp"
33
33
 
@@ -102,7 +102,6 @@ interface AppStatusState {
102
102
  * off leaves it running. Persists the wearer's choice beside the hidden flag.
103
103
  */
104
104
  setAlwaysOn: (packageName: string, status: boolean) => Promise<void>
105
- setActiveDashboard: (packageName: string) => Promise<void>
106
105
  /** Replace the whole apps array. Hosts use this when their extra source changes. */
107
106
  setApps: (apps: ClientApp[]) => void
108
107
  }
@@ -379,6 +378,11 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
379
378
  return true
380
379
  }
381
380
 
381
+ // The phone mic is asked for here, at first use, not during pairing: the
382
+ // OS prompt lands over the app that needs it. The facade returns at once
383
+ // when the answer is already known, so this only ever prompts once.
384
+ if (declaresMicrophone(app)) await permissions.request(PermissionFeatures.MICROPHONE)
385
+
382
386
  // Foreground-only-one rule: stop other running standard apps.
383
387
  if (app.type === "standard") {
384
388
  const runningForeground = state.apps.filter(
@@ -508,17 +512,9 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
508
512
 
509
513
  uninstall: (packageName, version) => {
510
514
  return Res.try_async(async () => {
511
- const wasActiveDashboard = activeDashboardAmong(get().apps) === packageName
512
515
  const res = await appRegistry.uninstall(packageName, version)
513
516
  if (res.is_error()) throw res.error
514
517
  set((s) => ({apps: s.apps.filter((a) => a.packageName !== packageName)}))
515
- // The dashboard role fell back to another dashboard, which was stopped
516
- // when it lost the role: start it, as launch would have.
517
- if (wasActiveDashboard) {
518
- const apps = get().apps
519
- const heir = apps.find((a) => a.packageName === activeDashboardAmong(apps))
520
- if (heir?.alwaysOn && !heir.running) await get().start(heir, {skipNavigation: true})
521
- }
522
518
  })
523
519
  },
524
520
 
@@ -563,18 +559,6 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
563
559
  if (status && !app.running) await get().start(app, {skipNavigation: true})
564
560
  },
565
561
 
566
- // The dashboard role (ADR-0009) moves to `packageName`: the previous holder
567
- // stops and the new one starts in the background.
568
- setActiveDashboard: async (packageName: string) => {
569
- const apps = get().apps
570
- const next = apps.find((a) => a.packageName === packageName && a.type === "dashboard")
571
- if (!next) return
572
- const previous = apps.find((a) => a.packageName === activeDashboardAmong(apps))
573
- await useSettingsStore.getState().setSetting(SETTINGS.active_dashboard.key, packageName)
574
- if (previous?.running && previous.packageName !== packageName) await get().stop(previous.packageName)
575
- if (!next.running) await get().start(next, {skipNavigation: true})
576
- },
577
-
578
562
  setApps: (apps) => set({apps}),
579
563
  }))
580
564
 
@@ -651,6 +635,10 @@ useSettingsStore.subscribe(
651
635
  },
652
636
  )
653
637
 
638
+ /** Declares the microphone (or everything) in its manifest. */
639
+ const declaresMicrophone = (app: ClientApp): boolean =>
640
+ app.permissions.some((p) => p.type === "MICROPHONE" || p.type === "ALL")
641
+
654
642
  // ---------------------------------------------------------------------------
655
643
  // Public hooks
656
644
  // ---------------------------------------------------------------------------
@@ -677,28 +665,8 @@ export const useRunningAlwaysOnApps = () => {
677
665
  return useMemo(() => apps.filter((app) => app.alwaysOn && app.running), [apps])
678
666
  }
679
667
 
680
- /** Installed miniapps that can hold the dashboard role: manifest type "dashboard". */
681
- export const useDashboards = () => {
682
- const apps = useApps()
683
- return useMemo(() => apps.filter((app) => app.type === "dashboard"), [apps])
684
- }
685
-
686
- /** The miniapp that holds the dashboard role (ADR-0009), or null when none is installed. */
687
- export const useActiveDashboard = (): ClientApp | null => {
688
- const dashboards = useDashboards()
689
- const chosen = useSettingsStore((s) => s.getSetting(SETTINGS.active_dashboard.key))
690
- return useMemo(() => {
691
- const packageName = resolveActiveDashboard(
692
- dashboards.map((app) => app.packageName),
693
- chosen,
694
- SETTINGS.active_dashboard.defaultValue(),
695
- )
696
- return dashboards.find((app) => app.packageName === packageName) ?? null
697
- }, [dashboards, chosen])
698
- }
699
-
700
- /** Apps that run without a foreground surface: background ones and dashboards. */
701
- const isBackgroundApp = (app: ClientApp): boolean => app.type === "background" || app.type === "dashboard"
668
+ /** Apps that run without a foreground surface: background ones and the system dashboard. */
669
+ const isBackgroundApp = (app: ClientApp): boolean => app.type === "background" || app.type === "system_dashboard"
702
670
 
703
671
  export const useActiveBackgroundApps = () => {
704
672
  const apps = useApps()
@@ -730,18 +730,6 @@ export const SETTINGS: Record<string, Setting> = {
730
730
  saveOnServer: false,
731
731
  persist: true,
732
732
  },
733
- // Debug: the G2's G2PERF display performance lines (a sample per busy
734
- // second, plus stalls) in the console and the log buffer bug reports carry.
735
- // OFF by default: they crowd out everything else in the ten-minute buffer.
736
- // Synced to the Bluetooth SDK, which keeps counting either way and only
737
- // logs while this is on. Device-local debug knob, so not saved on the server.
738
- g2_perf_logs: {
739
- key: "g2_perf_logs",
740
- defaultValue: () => false,
741
- writable: true,
742
- saveOnServer: false,
743
- persist: true,
744
- },
745
733
  // Maximum rate at which display updates leave the phone for the glasses.
746
734
  // The Bluetooth SDK paces every scene frame and legacy layout to this,
747
735
  // coalescing faster updates last-wins; clears bypass it. No effect on G2
@@ -806,12 +794,51 @@ export const SETTINGS: Record<string, Setting> = {
806
794
  saveOnServer: false,
807
795
  persist: true,
808
796
  },
809
- // The wearer's choice of dashboard miniapp (ADR-0009). Installs are
810
- // per-phone, so the choice is device-local. The default is the bundled
811
- // dashboard, which the role falls back to when the choice is not installed.
812
- active_dashboard: {
813
- key: "active_dashboard",
814
- defaultValue: () => "com.glassly.dashboard",
797
+ // Device-local preferences for the Scene API dashboard available on G2
798
+ // custom firmware. Eligibility is firmware-reported at runtime, so there is
799
+ // deliberately no enable toggle: eligible devices replace the native
800
+ // dashboard automatically while stock firmware keeps its native dashboard.
801
+ custom_dashboard_clock_style: {
802
+ key: "custom_dashboard_clock_style",
803
+ defaultValue: () => "compact",
804
+ writable: true,
805
+ saveOnServer: false,
806
+ persist: true,
807
+ },
808
+ custom_dashboard_clock_show_date: {
809
+ key: "custom_dashboard_clock_show_date",
810
+ defaultValue: () => true,
811
+ writable: true,
812
+ saveOnServer: false,
813
+ persist: true,
814
+ },
815
+ // "solid" | "two_tone": the battery widgets' G and R.
816
+ custom_dashboard_battery_style: {
817
+ key: "custom_dashboard_battery_style",
818
+ defaultValue: () => "solid",
819
+ writable: true,
820
+ saveOnServer: false,
821
+ persist: true,
822
+ },
823
+ // {preset, slots: {slotId: widgetId}, scratchpad: [widgetId]} — the
824
+ // dashboard miniapp owns the shape and its default; null draws its default layout.
825
+ custom_dashboard_layout: {
826
+ key: "custom_dashboard_layout",
827
+ defaultValue: () => null,
828
+ writable: true,
829
+ saveOnServer: false,
830
+ persist: true,
831
+ },
832
+ custom_dashboard_calendar_event_count: {
833
+ key: "custom_dashboard_calendar_event_count",
834
+ defaultValue: () => 3,
835
+ writable: true,
836
+ saveOnServer: false,
837
+ persist: true,
838
+ },
839
+ custom_dashboard_calendar_days_ahead: {
840
+ key: "custom_dashboard_calendar_days_ahead",
841
+ defaultValue: () => 5,
815
842
  writable: true,
816
843
  saveOnServer: false,
817
844
  persist: true,
@@ -1103,7 +1130,6 @@ export const BLUETOOTH_SETTING_KEYS: string[] = [
1103
1130
  SETTINGS.calendar_events.key,
1104
1131
  SETTINGS.cfw_scene_renderer.key,
1105
1132
  SETTINGS.cfw_fast_ble.key,
1106
- SETTINGS.g2_perf_logs.key,
1107
1133
  SETTINGS.display_max_fps.key,
1108
1134
  SETTINGS.twelve_hour_time.key,
1109
1135
  SETTINGS.metric_system.key,
@@ -10,7 +10,7 @@ import type {CompatibilityResult} from "../utils/hardware/hardware"
10
10
  /**
11
11
  * App execution model types
12
12
  */
13
- export type AppletType = "standard" | "background" | "dashboard"
13
+ export type AppletType = "standard" | "background" | "system_dashboard"
14
14
 
15
15
  /**
16
16
  * Permission types that apps can request
@@ -29,8 +29,6 @@ export type AppPermissionType =
29
29
  | "MEMORY"
30
30
  | "AI"
31
31
  | "SPEAKERS"
32
- | "MANAGE_MINIAPPS"
33
- | "PLATFORM_SETTINGS"
34
32
 
35
33
  /**
36
34
  * Permission object with type and description
@@ -2,7 +2,8 @@
2
2
  * Anchor resolution — turn corner-anchored boxes into plain top-left boxes
3
3
  * against the device's real canvas. Runs in emitScene BEFORE the canPosition
4
4
  * branch so both processScene (clamping) and degradeScene (reading order)
5
- * only ever see concrete coordinates.
5
+ * only ever see concrete coordinates. Spec:
6
+ * notes/superpowers/specs/2026-08-10-anchored-render-boxes.md
6
7
  */
7
8
 
8
9
  import type {SceneBox, SceneBoxInput, SceneElementInput} from "./types"
@@ -1,16 +0,0 @@
1
- /**
2
- * Whether a miniapp may use an API gated on a manifest permission
3
- * (miniapp.json -> permissions): `session.miniapps` needs MANAGE_MINIAPPS,
4
- * `session.settings` PLATFORM_SETTINGS, the notification app list
5
- * READ_NOTIFICATIONS. System apps pass without declaring it. Pure, so the
6
- * runtime only does the reply.
7
- */
8
- export interface PermissionCaller {
9
- system: boolean;
10
- permissions?: ReadonlyArray<{
11
- type?: string;
12
- }>;
13
- }
14
- export declare function declaresPermission(permissions: PermissionCaller["permissions"], permission: string): boolean;
15
- export declare function mayUsePermission(caller: PermissionCaller, permission: string): boolean;
16
- //# sourceMappingURL=miniappPermissions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"miniappPermissions.d.ts","sourceRoot":"","sources":["../../src/services/miniappPermissions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,aAAa,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;CAC7C;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5G;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtF"}
@@ -1,7 +0,0 @@
1
- export function declaresPermission(permissions, permission) {
2
- return (permissions ?? []).some((declared) => declared.type?.toUpperCase() === permission);
3
- }
4
- export function mayUsePermission(caller, permission) {
5
- return caller.system || declaresPermission(caller.permissions, permission);
6
- }
7
- //# sourceMappingURL=miniappPermissions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"miniappPermissions.js","sourceRoot":"","sources":["../../src/services/miniappPermissions.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,kBAAkB,CAAC,WAA4C,EAAE,UAAkB;IACjG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,UAAU,CAAC,CAAA;AAC5F,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAwB,EAAE,UAAkB;IAC3E,OAAO,MAAM,CAAC,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * Whether a miniapp may use an API gated on a manifest permission\n * (miniapp.json -> permissions): `session.miniapps` needs MANAGE_MINIAPPS,\n * `session.settings` PLATFORM_SETTINGS, the notification app list\n * READ_NOTIFICATIONS. System apps pass without declaring it. Pure, so the\n * runtime only does the reply.\n */\nexport interface PermissionCaller {\n system: boolean\n permissions?: ReadonlyArray<{type?: string}>\n}\n\nexport function declaresPermission(permissions: PermissionCaller[\"permissions\"], permission: string): boolean {\n return (permissions ?? []).some((declared) => declared.type?.toUpperCase() === permission)\n}\n\nexport function mayUsePermission(caller: PermissionCaller, permission: string): boolean {\n return caller.system || declaresPermission(caller.permissions, permission)\n}\n"]}
@@ -1,7 +0,0 @@
1
- export declare function resolveActiveDashboard(installed: readonly string[], chosen: unknown, fallback: string): string | null;
2
- /** The active dashboard among `apps`; only miniapps whose manifest type is "dashboard" can hold the role. */
3
- export declare function activeDashboardAmong(apps: ReadonlyArray<{
4
- packageName: string;
5
- type?: string;
6
- }>): string | null;
7
- //# sourceMappingURL=activeDashboard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"activeDashboard.d.ts","sourceRoot":"","sources":["../../../src/services/pages/activeDashboard.ts"],"names":[],"mappings":"AASA,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIrH;AAED,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAO7G"}
@@ -1,21 +0,0 @@
1
- /**
2
- * Who holds the dashboard role (ADR-0009): the wearer's choice when that
3
- * miniapp is installed, else the default (the bundled dashboard), else the
4
- * first installed dashboard by package name, else nobody. Read at decision
5
- * time, never cached, so an uninstall hands the role back without anyone
6
- * rewriting the setting.
7
- */
8
- import { SETTINGS, useSettingsStore } from "../../stores/settings";
9
- export function resolveActiveDashboard(installed, chosen, fallback) {
10
- if (typeof chosen === "string" && installed.includes(chosen))
11
- return chosen;
12
- if (installed.includes(fallback))
13
- return fallback;
14
- return [...installed].sort()[0] ?? null;
15
- }
16
- /** The active dashboard among `apps`; only miniapps whose manifest type is "dashboard" can hold the role. */
17
- export function activeDashboardAmong(apps) {
18
- const installed = apps.filter((app) => app.type === "dashboard").map((app) => app.packageName);
19
- return resolveActiveDashboard(installed, useSettingsStore.getState().getSetting(SETTINGS.active_dashboard.key), SETTINGS.active_dashboard.defaultValue());
20
- }
21
- //# sourceMappingURL=activeDashboard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"activeDashboard.js","sourceRoot":"","sources":["../../../src/services/pages/activeDashboard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAEhE,MAAM,UAAU,sBAAsB,CAAC,SAA4B,EAAE,MAAe,EAAE,QAAgB;IACpG,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAA;IAC3E,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAA;IACjD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACzC,CAAC;AAED,6GAA6G;AAC7G,MAAM,UAAU,oBAAoB,CAAC,IAAyD;IAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC9F,OAAO,sBAAsB,CAC3B,SAAS,EACT,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,EACrE,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,CACzC,CAAA;AACH,CAAC","sourcesContent":["/**\n * Who holds the dashboard role (ADR-0009): the wearer's choice when that\n * miniapp is installed, else the default (the bundled dashboard), else the\n * first installed dashboard by package name, else nobody. Read at decision\n * time, never cached, so an uninstall hands the role back without anyone\n * rewriting the setting.\n */\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\n\nexport function resolveActiveDashboard(installed: readonly string[], chosen: unknown, fallback: string): string | null {\n if (typeof chosen === \"string\" && installed.includes(chosen)) return chosen\n if (installed.includes(fallback)) return fallback\n return [...installed].sort()[0] ?? null\n}\n\n/** The active dashboard among `apps`; only miniapps whose manifest type is \"dashboard\" can hold the role. */\nexport function activeDashboardAmong(apps: ReadonlyArray<{packageName: string; type?: string}>): string | null {\n const installed = apps.filter((app) => app.type === \"dashboard\").map((app) => app.packageName)\n return resolveActiveDashboard(\n installed,\n useSettingsStore.getState().getSetting(SETTINGS.active_dashboard.key),\n SETTINGS.active_dashboard.defaultValue(),\n )\n}\n"]}
@@ -1,19 +0,0 @@
1
- /**
2
- * Whether a miniapp may use an API gated on a manifest permission
3
- * (miniapp.json -> permissions): `session.miniapps` needs MANAGE_MINIAPPS,
4
- * `session.settings` PLATFORM_SETTINGS, the notification app list
5
- * READ_NOTIFICATIONS. System apps pass without declaring it. Pure, so the
6
- * runtime only does the reply.
7
- */
8
- export interface PermissionCaller {
9
- system: boolean
10
- permissions?: ReadonlyArray<{type?: string}>
11
- }
12
-
13
- export function declaresPermission(permissions: PermissionCaller["permissions"], permission: string): boolean {
14
- return (permissions ?? []).some((declared) => declared.type?.toUpperCase() === permission)
15
- }
16
-
17
- export function mayUsePermission(caller: PermissionCaller, permission: string): boolean {
18
- return caller.system || declaresPermission(caller.permissions, permission)
19
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * Who holds the dashboard role (ADR-0009): the wearer's choice when that
3
- * miniapp is installed, else the default (the bundled dashboard), else the
4
- * first installed dashboard by package name, else nobody. Read at decision
5
- * time, never cached, so an uninstall hands the role back without anyone
6
- * rewriting the setting.
7
- */
8
- import {SETTINGS, useSettingsStore} from "../../stores/settings"
9
-
10
- export function resolveActiveDashboard(installed: readonly string[], chosen: unknown, fallback: string): string | null {
11
- if (typeof chosen === "string" && installed.includes(chosen)) return chosen
12
- if (installed.includes(fallback)) return fallback
13
- return [...installed].sort()[0] ?? null
14
- }
15
-
16
- /** The active dashboard among `apps`; only miniapps whose manifest type is "dashboard" can hold the role. */
17
- export function activeDashboardAmong(apps: ReadonlyArray<{packageName: string; type?: string}>): string | null {
18
- const installed = apps.filter((app) => app.type === "dashboard").map((app) => app.packageName)
19
- return resolveActiveDashboard(
20
- installed,
21
- useSettingsStore.getState().getSetting(SETTINGS.active_dashboard.key),
22
- SETTINGS.active_dashboard.defaultValue(),
23
- )
24
- }