@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.
- package/build/engine.d.ts +0 -3
- package/build/engine.d.ts.map +1 -1
- package/build/facades/miniapps.d.ts +0 -6
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +0 -7
- package/build/facades/miniapps.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/services/AppRegistry.js +1 -1
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.js +2 -2
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +2 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +2 -1
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +9 -22
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +52 -117
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +0 -2
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +1 -6
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/pages/OverlayPages.d.ts +2 -13
- package/build/services/pages/OverlayPages.d.ts.map +1 -1
- package/build/services/pages/OverlayPages.js +10 -11
- package/build/services/pages/OverlayPages.js.map +1 -1
- package/build/services/pages/PageHost.d.ts +1 -1
- package/build/services/pages/PageHost.d.ts.map +1 -1
- package/build/services/pages/PageHost.js +2 -2
- package/build/services/pages/PageHost.js.map +1 -1
- package/build/services/pages/overlayApps.d.ts +13 -20
- package/build/services/pages/overlayApps.d.ts.map +1 -1
- package/build/services/pages/overlayApps.js +31 -50
- package/build/services/pages/overlayApps.js.map +1 -1
- package/build/services/settingsBridge.d.ts +14 -15
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +20 -20
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/appVisibility.d.ts +0 -5
- package/build/stores/appVisibility.d.ts.map +1 -1
- package/build/stores/appVisibility.js +0 -2
- package/build/stores/appVisibility.js.map +1 -1
- package/build/stores/apps.d.ts +0 -5
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +10 -40
- package/build/stores/apps.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +45 -19
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -2
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/utils/display/scene/anchor.d.ts +2 -1
- package/build/utils/display/scene/anchor.d.ts.map +1 -1
- package/build/utils/display/scene/anchor.js +2 -1
- package/build/utils/display/scene/anchor.js.map +1 -1
- package/package.json +7 -7
- package/src/facades/miniapps.ts +0 -9
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +1 -3
- package/src/services/AppRegistry.ts +1 -1
- package/src/services/BlobStore.ts +2 -2
- package/src/services/LocalDisplayManager.ts +2 -1
- package/src/services/LocalMiniappRuntime.ts +52 -136
- package/src/services/MiniappLauncher.ts +1 -6
- package/src/services/pages/OverlayPages.ts +10 -11
- package/src/services/pages/PageHost.ts +2 -2
- package/src/services/pages/overlayApps.ts +30 -51
- package/src/services/settingsBridge.ts +20 -20
- package/src/stores/appVisibility.ts +0 -4
- package/src/stores/apps.ts +12 -44
- package/src/stores/settings.ts +45 -19
- package/src/types/applet.ts +1 -3
- package/src/utils/display/scene/anchor.ts +2 -1
- package/build/services/miniappPermissions.d.ts +0 -16
- package/build/services/miniappPermissions.d.ts.map +0 -1
- package/build/services/miniappPermissions.js +0 -7
- package/build/services/miniappPermissions.js.map +0 -1
- package/build/services/pages/activeDashboard.d.ts +0 -7
- package/build/services/pages/activeDashboard.d.ts.map +0 -1
- package/build/services/pages/activeDashboard.js +0 -21
- package/build/services/pages/activeDashboard.js.map +0 -1
- package/src/services/miniappPermissions.ts +0 -19
- package/src/services/pages/activeDashboard.ts +0 -24
|
@@ -1,71 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overlay
|
|
3
|
-
* (
|
|
4
|
-
* `session.display.showView("dashboard")` and
|
|
5
|
-
* `display.views` capability while the glasses are eligible. The runtime
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
|
47
|
-
if (
|
|
48
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
if (!
|
|
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
|
|
59
|
-
export function setOverlayViewer(packageName: string,
|
|
60
|
-
const was =
|
|
61
|
-
if (
|
|
62
|
-
else
|
|
63
|
-
if (was !==
|
|
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
|
-
/**
|
|
46
|
+
/** An overlay app that presents phone notifications is running. */
|
|
67
47
|
export function overlayViewerRunning(): boolean {
|
|
68
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
* glasses-menu keys the phone's settings screens used to,
|
|
10
|
-
* so persistence, server sync and the Bluetooth sync of
|
|
11
|
-
* if a phone screen wrote them.
|
|
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
|
|
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
|
|
38
|
-
*
|
|
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
|
|
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
|
package/src/stores/apps.ts
CHANGED
|
@@ -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
|
-
/**
|
|
681
|
-
|
|
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()
|
package/src/stores/settings.ts
CHANGED
|
@@ -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
|
-
//
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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,
|
package/src/types/applet.ts
CHANGED
|
@@ -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" | "
|
|
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
|
-
}
|