@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
|
@@ -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
|
import type { SceneElementInput } from "./types";
|
|
8
9
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/anchor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/anchor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAA0B,iBAAiB,EAAC,MAAM,SAAS,CAAA;AAcvE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,iBAAiB,EAAE,CAKrB"}
|
|
@@ -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
|
function resolveBox(box, width, height) {
|
|
8
9
|
const { anchor, x, y, w, h } = box;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/anchor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/anchor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,SAAS,UAAU,CAAC,GAAkB,EAAE,KAAa,EAAE,MAAc;IACnE,MAAM,EAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC,GAAG,GAAG,CAAA;IAChC,MAAM,KAAK,GAAG,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,cAAc,CAAA;IACjE,MAAM,MAAM,GAAG,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,cAAc,CAAA;IACpE,OAAO;QACL,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAmC,EACnC,KAAa,EACb,MAAc;IAEd,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACtB,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,EAAE,CAAA;QACzE,OAAO,EAAC,GAAG,EAAE,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,EAAC,CAAA;IACxD,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/**\n * Anchor resolution — turn corner-anchored boxes into plain top-left boxes\n * against the device's real canvas. Runs in emitScene BEFORE the canPosition\n * branch so both processScene (clamping) and degradeScene (reading order)\n * only ever see concrete coordinates. Spec:\n * notes/superpowers/specs/2026-08-10-anchored-render-boxes.md\n */\n\nimport type {SceneBox, SceneBoxInput, SceneElementInput} from \"./types\"\n\nfunction resolveBox(box: SceneBoxInput, width: number, height: number): SceneBox {\n const {anchor, x, y, w, h} = box\n const right = anchor === \"top-right\" || anchor === \"bottom-right\"\n const bottom = anchor === \"bottom-left\" || anchor === \"bottom-right\"\n return {\n x: right ? width - x - w : x,\n y: bottom ? height - y - h : y,\n w,\n h,\n }\n}\n\n/**\n * Resolve every anchored box in a scene to top-left coordinates on a W×H\n * canvas. Unanchored elements pass through unchanged (same reference — no\n * copy churn on the common path), as do malformed ones so processScene's\n * validation still sees and reports them. Non-finite insets survive the\n * subtraction as non-finite and are dropped by the same validation.\n */\nexport function resolveAnchors(\n input: readonly SceneElementInput[],\n width: number,\n height: number,\n): SceneElementInput[] {\n return input.map((el) => {\n if (!el || typeof el !== \"object\" || !el.box || !el.box.anchor) return el\n return {...el, box: resolveBox(el.box, width, height)}\n })\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.0-
|
|
3
|
+
"version": "0.3.0-dev.460",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,17 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.3.0-
|
|
101
|
-
"@glassly/cloud-client": "0.3.0-
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.0-
|
|
103
|
-
"@glassly/crust": "0.3.0-
|
|
104
|
-
"@glassly/miniapp": "0.3.0-
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.0-dev.460",
|
|
101
|
+
"@glassly/cloud-client": "0.3.0-dev.460",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.0-dev.460",
|
|
103
|
+
"@glassly/crust": "0.3.0-dev.460",
|
|
104
|
+
"@glassly/miniapp": "0.3.0-dev.460",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
107
|
"react-native-marked": "8.1.1",
|
|
108
108
|
"semver": "^7.7.4",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.3.0-
|
|
110
|
+
"@glassly/llm-providers": "0.3.0-dev.460"
|
|
111
111
|
},
|
|
112
112
|
"devDependencies": {
|
|
113
113
|
"@glassly/llm-providers": "workspace:*",
|
package/src/facades/miniapps.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import {useAppStatusStore} from "../stores/apps"
|
|
12
12
|
import localMiniappRuntime from "../services/LocalMiniappRuntime"
|
|
13
|
-
import {activeDashboardAmong} from "../services/pages/activeDashboard"
|
|
14
13
|
import type {ClientApp} from "../types"
|
|
15
14
|
|
|
16
15
|
type AppsState = ReturnType<typeof useAppStatusStore.getState>
|
|
@@ -62,12 +61,4 @@ export const miniapps = {
|
|
|
62
61
|
useAppStatusStore.getState().setHiddenStatus(...args),
|
|
63
62
|
/** Mark a miniapp Always On (starts at launch, stops only from the switcher pill), persisted for the next boot. */
|
|
64
63
|
setAlwaysOn: (...args: Parameters<AppsState["setAlwaysOn"]>) => useAppStatusStore.getState().setAlwaysOn(...args),
|
|
65
|
-
|
|
66
|
-
/** Installed miniapps that can hold the dashboard role (manifest type "dashboard"). */
|
|
67
|
-
dashboards: (): ClientApp[] => useAppStatusStore.getState().apps.filter((app) => app.type === "dashboard"),
|
|
68
|
-
/** The package that holds the dashboard role, or null when no dashboard is installed. */
|
|
69
|
-
activeDashboard: (): string | null => activeDashboardAmong(useAppStatusStore.getState().apps),
|
|
70
|
-
/** Give the dashboard role to an installed dashboard; the previous holder stops and the new one starts. */
|
|
71
|
-
setActiveDashboard: (...args: Parameters<AppsState["setActiveDashboard"]>) =>
|
|
72
|
-
useAppStatusStore.getState().setActiveDashboard(...args),
|
|
73
64
|
}
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.3.0",
|
|
15
|
-
"releaseIdentity": "0.3.0-
|
|
16
|
-
"releaseSetId": "glassly-0.3.0-
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.0-dev.460",
|
|
16
|
+
"releaseSetId": "glassly-0.3.0-dev.460",
|
|
17
|
+
"sourceCommit": "65f4349c0a765b45323022f7ea8d794039cfe413",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type {
|
|
|
23
23
|
} from "./facades/reports"
|
|
24
24
|
export type {IslandNotification, IslandNotificationKind} from "./facades/notifications"
|
|
25
25
|
export type {PresentationOwner} from "./services/notifications/notificationPolicy"
|
|
26
|
-
export {DEFAULT_HIDDEN_PACKAGES, isGatedByDevTools
|
|
26
|
+
export {DEFAULT_HIDDEN_PACKAGES, isGatedByDevTools} from "./stores/appVisibility"
|
|
27
27
|
export type {WifiSearchResult} from "./facades/glassesWifi"
|
|
28
28
|
export type {IdentitySnapshot} from "./services/PairingIdentity"
|
|
29
29
|
export type {DisplayMirrorEvent} from "./facades/displayMirror"
|
|
@@ -182,8 +182,6 @@ export {
|
|
|
182
182
|
useUninstall,
|
|
183
183
|
useActiveApps,
|
|
184
184
|
useRunningAlwaysOnApps,
|
|
185
|
-
useDashboards,
|
|
186
|
-
useActiveDashboard,
|
|
187
185
|
useActiveBackgroundApps,
|
|
188
186
|
useBackgroundApps,
|
|
189
187
|
useActiveForegroundApp,
|
|
@@ -76,7 +76,7 @@ export function normalizeManifestPermissions(
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function normalizeManifestType(raw: unknown): AppletType {
|
|
79
|
-
return raw === "background" || raw === "
|
|
79
|
+
return raw === "background" || raw === "system_dashboard" || raw === "standard" ? raw : "standard"
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -528,7 +528,7 @@ export class BlobStore {
|
|
|
528
528
|
return
|
|
529
529
|
}
|
|
530
530
|
const fileName = this.makeId()
|
|
531
|
-
|
|
531
|
+
file.copy(this.fileFor(packageName, fileName))
|
|
532
532
|
const name = basename(file.uri) || key
|
|
533
533
|
const mimeType = mimeFilter || file.type || inferMime(name) || OCTET
|
|
534
534
|
const meta = this.commitFile(
|
|
@@ -729,7 +729,7 @@ export class BlobStore {
|
|
|
729
729
|
const temp = new File(tempDir, shareName)
|
|
730
730
|
if (!temp.exists || temp.size !== meta.bytes) {
|
|
731
731
|
if (temp.exists) temp.delete()
|
|
732
|
-
|
|
732
|
+
file.copy(temp)
|
|
733
733
|
}
|
|
734
734
|
cacheReady = true
|
|
735
735
|
await Share.open({
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* - core-app vs background-app arbitration with a background lock
|
|
11
11
|
*
|
|
12
12
|
* Scope is deliberately local-only. Cloud-originated displays are untouched
|
|
13
|
-
* and may race with local displays during dev; that's acceptable
|
|
13
|
+
* and may race with local displays during dev; that's acceptable per plan
|
|
14
|
+
* (notes/archive/agents/local-display-manager-plan.md).
|
|
14
15
|
*
|
|
15
16
|
* Display pacing is NOT done here. A JS-side ~300ms throttle used to live in
|
|
16
17
|
* this class, but JS timers (and therefore the throttle's trailing flush) do
|
|
@@ -77,19 +77,7 @@ import {
|
|
|
77
77
|
validateSettingsSet,
|
|
78
78
|
type DerivedSettingsKey,
|
|
79
79
|
} from "./settingsBridge"
|
|
80
|
-
import {
|
|
81
|
-
import {activeDashboardAmong} from "./pages/activeDashboard"
|
|
82
|
-
import {
|
|
83
|
-
activeDashboard,
|
|
84
|
-
addDashboardApp,
|
|
85
|
-
displayViewsFor,
|
|
86
|
-
isDashboardApp,
|
|
87
|
-
isOverlayApp,
|
|
88
|
-
onOverlayAppsChange,
|
|
89
|
-
removeDashboardApp,
|
|
90
|
-
setActiveDashboard,
|
|
91
|
-
setOverlayViewer,
|
|
92
|
-
} from "./pages/overlayApps"
|
|
80
|
+
import {addOverlayApp, displayViewsFor, isOverlayApp, removeOverlayApp, setOverlayViewer} from "./pages/overlayApps"
|
|
93
81
|
import {pagesEligible} from "./pages/pageEligibility"
|
|
94
82
|
import nativeNavigationService from "./NativeNavigationService"
|
|
95
83
|
import type {DisplayPayload} from "./LocalDisplayManager"
|
|
@@ -132,7 +120,6 @@ import ttsModelManager from "./TTSModelManager"
|
|
|
132
120
|
import {NavigationHandlers} from "./NavigationHandlers"
|
|
133
121
|
import type {ClientApp} from "../types/applet"
|
|
134
122
|
import {useAppStatusStore} from "../stores/apps"
|
|
135
|
-
import {isSystemMiniapp} from "../stores/appVisibility"
|
|
136
123
|
import {getDevAppAttestation, getDevAppSourcePackage} from "./AppRegistry"
|
|
137
124
|
import {resolveForegroundLocationPermission} from "./ForegroundLocationPermission"
|
|
138
125
|
import {advanceMiniappPingLiveness} from "./MiniappLiveness"
|
|
@@ -266,6 +253,9 @@ const SYSTEM_MINIAPP_PACKAGES = new Set([
|
|
|
266
253
|
"com.glassly.ai",
|
|
267
254
|
"com.glassly.feedback",
|
|
268
255
|
"com.glassly.miniappdev",
|
|
256
|
+
// The dashboard: the settings bridge and the miniapp list/start/stop are its side menu and settings
|
|
257
|
+
// page, and the notification app list is its notification settings' per-app filter.
|
|
258
|
+
"com.glassly.dashboard",
|
|
269
259
|
])
|
|
270
260
|
const PING_INTERVAL_MS = 5_000
|
|
271
261
|
// Min gap between "transcription limit reached" notices on the glasses. A
|
|
@@ -352,8 +342,6 @@ const PERMISSION_TYPE_TO_CANONICAL: Record<string, string> = {
|
|
|
352
342
|
MEMORY: "memory",
|
|
353
343
|
AI: "ai",
|
|
354
344
|
SPEAKERS: "speakers",
|
|
355
|
-
MANAGE_MINIAPPS: "manageMiniapps",
|
|
356
|
-
PLATFORM_SETTINGS: "platformSettings",
|
|
357
345
|
}
|
|
358
346
|
|
|
359
347
|
/**
|
|
@@ -468,8 +456,6 @@ const ALL_CANONICAL_PERMISSIONS = [
|
|
|
468
456
|
"ai",
|
|
469
457
|
"speakers",
|
|
470
458
|
"displayMirror",
|
|
471
|
-
"manageMiniapps",
|
|
472
|
-
"platformSettings",
|
|
473
459
|
] as const
|
|
474
460
|
|
|
475
461
|
/**
|
|
@@ -1177,11 +1163,11 @@ class LocalMiniappRuntime {
|
|
|
1177
1163
|
// Drop this app's transcription hints; the cloud subscription rebuild
|
|
1178
1164
|
// below (via replaceStreamSubscribers) re-derives the hint union without it.
|
|
1179
1165
|
this.transcriptionHintsByApp.delete(packageName)
|
|
1180
|
-
//
|
|
1166
|
+
// An overlay app is going: release the slot if it holds it, and drop the
|
|
1181
1167
|
// dashboard view from its capabilities record.
|
|
1182
|
-
if (
|
|
1168
|
+
if (isOverlayApp(packageName)) {
|
|
1183
1169
|
overlayPages.remove(packageName)
|
|
1184
|
-
|
|
1170
|
+
removeOverlayApp(packageName)
|
|
1185
1171
|
}
|
|
1186
1172
|
// Its gesture claims go back to whoever claimed them before.
|
|
1187
1173
|
this.applyGestureClaimChanges(dropGestureClaims(this.gestureClaims, packageName))
|
|
@@ -1565,11 +1551,7 @@ class LocalMiniappRuntime {
|
|
|
1565
1551
|
this.handleOpenUrl(packageName, payload)
|
|
1566
1552
|
break
|
|
1567
1553
|
case MiniappRequestType.DISPLAY_DIAGNOSTICS: {
|
|
1568
|
-
|
|
1569
|
-
const settings = useSettingsStore.getState()
|
|
1570
|
-
const enabled =
|
|
1571
|
-
settings.getSetting(SETTINGS.debug_console.key) === true &&
|
|
1572
|
-
settings.getSetting(SETTINGS.g2_perf_logs.key) === true
|
|
1554
|
+
const enabled = useSettingsStore.getState().getSetting(SETTINGS.debug_console.key) === true
|
|
1573
1555
|
this.sendResult(packageName, requestId, true, {
|
|
1574
1556
|
enabled,
|
|
1575
1557
|
generatedAt: Date.now(),
|
|
@@ -1766,12 +1748,9 @@ class LocalMiniappRuntime {
|
|
|
1766
1748
|
existing.lastPongAt = Date.now()
|
|
1767
1749
|
existing.unansweredPingRounds = 0
|
|
1768
1750
|
|
|
1769
|
-
//
|
|
1770
|
-
//
|
|
1771
|
-
if (existing.installedManifest?.type === "
|
|
1772
|
-
addDashboardApp(packageName)
|
|
1773
|
-
this.syncActiveDashboard()
|
|
1774
|
-
}
|
|
1751
|
+
// A system_dashboard app may take the overlay slot: its capabilities
|
|
1752
|
+
// carry the dashboard view from the start.
|
|
1753
|
+
if (existing.installedManifest?.type === "system_dashboard") addOverlayApp(packageName)
|
|
1775
1754
|
const capabilities = this.capabilitiesFor(packageName)
|
|
1776
1755
|
|
|
1777
1756
|
// Build the declared-permission record for the SDK's session.permissions
|
|
@@ -2142,26 +2121,24 @@ class LocalMiniappRuntime {
|
|
|
2142
2121
|
const required = permissionForStream(stream)
|
|
2143
2122
|
if (required && !declaredTypes.has(required)) {
|
|
2144
2123
|
logPermissionNotDeclared(packageName, required, `to subscribe to "${stream}"`, `{"type": "${required}"}`)
|
|
2145
|
-
|
|
2124
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2146
2125
|
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
2147
2126
|
message: `${required} permission not declared in miniapp.json (required for "${stream}"). Add {"type": "${required}"} to the "permissions" array.`,
|
|
2148
2127
|
// Extra context fields read by the SDK so authors that subscribe
|
|
2149
2128
|
// to session.on("error") can format their own messages.
|
|
2150
2129
|
permission: required,
|
|
2151
2130
|
subscription: stream,
|
|
2152
|
-
}
|
|
2153
|
-
// The SDK sends SUBSCRIBE without a requestId, so push the rejection.
|
|
2154
|
-
if (requestId) this.sendResult(packageName, requestId, false, undefined, error)
|
|
2155
|
-
else this.sendToMiniapp(packageName, {type: MiniappResponseType.ERROR, ...error})
|
|
2131
|
+
})
|
|
2156
2132
|
return
|
|
2157
2133
|
}
|
|
2158
2134
|
}
|
|
2159
2135
|
|
|
2160
|
-
// The settings stream carries the wearer's settings.
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2136
|
+
// The settings stream carries the wearer's settings: system apps only.
|
|
2137
|
+
if (streams.includes(MiniappStreamType.SETTINGS_CHANGED) && !this.isSystemPackage(packageName)) {
|
|
2138
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2139
|
+
code: MiniappErrorCode.NOT_PERMITTED,
|
|
2140
|
+
message: "The settings stream is restricted to system apps",
|
|
2141
|
+
})
|
|
2165
2142
|
return
|
|
2166
2143
|
}
|
|
2167
2144
|
|
|
@@ -2175,9 +2152,8 @@ class LocalMiniappRuntime {
|
|
|
2175
2152
|
[...cloudTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)),
|
|
2176
2153
|
)
|
|
2177
2154
|
this.replaceStreamSubscribers(packageName, previousSubscriptions, app.subscriptions)
|
|
2178
|
-
//
|
|
2179
|
-
|
|
2180
|
-
if (isDashboardApp(packageName)) {
|
|
2155
|
+
// An overlay app that listens to phone notifications presents them on the glasses.
|
|
2156
|
+
if (isOverlayApp(packageName)) {
|
|
2181
2157
|
setOverlayViewer(packageName, app.subscriptions.has(MiniappStreamType.PHONE_NOTIFICATION))
|
|
2182
2158
|
}
|
|
2183
2159
|
|
|
@@ -2409,13 +2385,13 @@ class LocalMiniappRuntime {
|
|
|
2409
2385
|
})
|
|
2410
2386
|
return
|
|
2411
2387
|
}
|
|
2412
|
-
// The dashboard view is the overlay
|
|
2388
|
+
// The dashboard view is the overlay apps' alone; anyone else's frame
|
|
2413
2389
|
// is dropped, reported rather than silently lost.
|
|
2414
2390
|
if (payload.view === "dashboard" && !isOverlayApp(packageName)) {
|
|
2415
2391
|
this.sendResult(packageName, requestId, true, {
|
|
2416
2392
|
status: "displayed",
|
|
2417
2393
|
presented: false,
|
|
2418
|
-
reason: "not the
|
|
2394
|
+
reason: "not the dashboard app",
|
|
2419
2395
|
})
|
|
2420
2396
|
return
|
|
2421
2397
|
}
|
|
@@ -2439,11 +2415,10 @@ class LocalMiniappRuntime {
|
|
|
2439
2415
|
}
|
|
2440
2416
|
|
|
2441
2417
|
/**
|
|
2442
|
-
* display.showView: the
|
|
2443
|
-
* slot. Refused (NOT_PERMITTED) for
|
|
2444
|
-
*
|
|
2445
|
-
*
|
|
2446
|
-
* comes back to the app as a display_view change.
|
|
2418
|
+
* display.showView: the designated dashboard app takes or releases the
|
|
2419
|
+
* overlay slot. Refused (NOT_PERMITTED) for any other app; "unsupported"
|
|
2420
|
+
* while the glasses cannot show the dashboard view. Idempotent either way;
|
|
2421
|
+
* the slot's state comes back to the app as a display_view change.
|
|
2447
2422
|
*/
|
|
2448
2423
|
private handleShowView(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
|
|
2449
2424
|
if (!this.connectedApps.has(packageName)) {
|
|
@@ -2458,17 +2433,13 @@ class LocalMiniappRuntime {
|
|
|
2458
2433
|
})
|
|
2459
2434
|
return
|
|
2460
2435
|
}
|
|
2461
|
-
if (!
|
|
2436
|
+
if (!isOverlayApp(packageName)) {
|
|
2462
2437
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
2463
2438
|
code: MiniappErrorCode.NOT_PERMITTED,
|
|
2464
|
-
message:
|
|
2439
|
+
message: "The dashboard view belongs to system_dashboard apps",
|
|
2465
2440
|
})
|
|
2466
2441
|
return
|
|
2467
2442
|
}
|
|
2468
|
-
if (!isOverlayApp(packageName)) {
|
|
2469
|
-
this.sendResult(packageName, requestId, true, {status: "unsupported", reason: "not the active dashboard"})
|
|
2470
|
-
return
|
|
2471
|
-
}
|
|
2472
2443
|
if (view === "main") {
|
|
2473
2444
|
overlayPages.hide(packageName)
|
|
2474
2445
|
this.sendResult(packageName, requestId, true, {status: "shown"})
|
|
@@ -2492,36 +2463,17 @@ class LocalMiniappRuntime {
|
|
|
2492
2463
|
)
|
|
2493
2464
|
}
|
|
2494
2465
|
|
|
2495
|
-
/**
|
|
2496
|
-
* Reject the request with PERMISSION_NOT_DECLARED unless the caller's
|
|
2497
|
-
* manifest declares `permission`. System apps pass without declaring it.
|
|
2498
|
-
*/
|
|
2499
|
-
private requirePermission(
|
|
2500
|
-
packageName: string,
|
|
2501
|
-
requestId: string | undefined,
|
|
2502
|
-
permission: string,
|
|
2503
|
-
context: string,
|
|
2504
|
-
): boolean {
|
|
2505
|
-
const caller = {
|
|
2506
|
-
system: this.isSystemPackage(packageName),
|
|
2507
|
-
permissions: this.connectedApps.get(packageName)?.installedManifest?.permissions,
|
|
2508
|
-
}
|
|
2509
|
-
if (mayUsePermission(caller, permission)) return true
|
|
2510
|
-
logPermissionNotDeclared(packageName, permission, context, `{"type": "${permission}"}`)
|
|
2511
|
-
this.sendResult(packageName, requestId, false, undefined, {
|
|
2512
|
-
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
2513
|
-
message: `${permission} permission not declared in miniapp.json. Add {"type": "${permission}"} to the "permissions" array.`,
|
|
2514
|
-
permission,
|
|
2515
|
-
})
|
|
2516
|
-
return false
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
2466
|
// ---------------------------------------------------------------------------
|
|
2520
|
-
// Settings bridge (session.settings) —
|
|
2467
|
+
// Settings bridge (session.settings) — system apps only
|
|
2521
2468
|
// ---------------------------------------------------------------------------
|
|
2522
2469
|
|
|
2523
|
-
private
|
|
2524
|
-
|
|
2470
|
+
private requireSystemForSettings(packageName: string, requestId: string | undefined): boolean {
|
|
2471
|
+
if (this.isSystemPackage(packageName)) return true
|
|
2472
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2473
|
+
code: MiniappErrorCode.NOT_PERMITTED,
|
|
2474
|
+
message: "The settings bridge is restricted to system apps",
|
|
2475
|
+
})
|
|
2476
|
+
return false
|
|
2525
2477
|
}
|
|
2526
2478
|
|
|
2527
2479
|
/** A bridged key's current value: the store's, or the engine's for a derived key. */
|
|
@@ -2538,13 +2490,11 @@ class LocalMiniappRuntime {
|
|
|
2538
2490
|
return localDisplayManager.foregroundAppOnScreen()
|
|
2539
2491
|
case "ring_battery":
|
|
2540
2492
|
return ringBatteryFrom(useGlassesStore.getState())
|
|
2541
|
-
case "active_dashboard":
|
|
2542
|
-
return activeDashboard()
|
|
2543
2493
|
}
|
|
2544
2494
|
}
|
|
2545
2495
|
|
|
2546
2496
|
private handleSettingsGet(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
|
|
2547
|
-
if (!this.
|
|
2497
|
+
if (!this.requireSystemForSettings(packageName, requestId)) return
|
|
2548
2498
|
const values: Record<string, unknown> = {}
|
|
2549
2499
|
for (const key of selectSettingsBridgeKeys(payload.keys)) values[key] = this.settingsBridgeValue(key)
|
|
2550
2500
|
this.sendResult(packageName, requestId, true, {values})
|
|
@@ -2555,7 +2505,7 @@ class LocalMiniappRuntime {
|
|
|
2555
2505
|
payload: Record<string, unknown>,
|
|
2556
2506
|
requestId?: string,
|
|
2557
2507
|
): Promise<void> {
|
|
2558
|
-
if (!this.
|
|
2508
|
+
if (!this.requireSystemForSettings(packageName, requestId)) return
|
|
2559
2509
|
const validated = validateSettingsSet(payload)
|
|
2560
2510
|
if (!validated.ok) {
|
|
2561
2511
|
this.sendResult(packageName, requestId, false, undefined, {code: validated.code, message: validated.message})
|
|
@@ -2575,11 +2525,7 @@ class LocalMiniappRuntime {
|
|
|
2575
2525
|
}
|
|
2576
2526
|
|
|
2577
2527
|
private async handleNotificationAppsList(packageName: string, requestId?: string): Promise<void> {
|
|
2578
|
-
if (
|
|
2579
|
-
!this.requirePermission(packageName, requestId, "READ_NOTIFICATIONS", "to list the apps that post notifications")
|
|
2580
|
-
) {
|
|
2581
|
-
return
|
|
2582
|
-
}
|
|
2528
|
+
if (!this.requireSystemForSettings(packageName, requestId)) return
|
|
2583
2529
|
try {
|
|
2584
2530
|
this.sendResult(packageName, requestId, true, await installedNotificationApps())
|
|
2585
2531
|
} catch (err) {
|
|
@@ -2593,9 +2539,8 @@ class LocalMiniappRuntime {
|
|
|
2593
2539
|
/**
|
|
2594
2540
|
* Push bridged settings as they change: stored keys from the store, the
|
|
2595
2541
|
* derived keys whenever their inputs move (phone activity, the display,
|
|
2596
|
-
* the apps store, the ring
|
|
2597
|
-
*
|
|
2598
|
-
* everyone else.
|
|
2542
|
+
* the apps store, the ring). Subscribers are system apps only (gated at
|
|
2543
|
+
* SUBSCRIBE), so this is a no-op fan-out for everyone else.
|
|
2599
2544
|
*/
|
|
2600
2545
|
private ensureSettingsBridgeWired(): void {
|
|
2601
2546
|
if (this.settingsBridgeWired) return
|
|
@@ -2631,7 +2576,6 @@ class LocalMiniappRuntime {
|
|
|
2631
2576
|
onAppForegroundChange((foregrounded) => this.broadcastVisibility(foregrounded))
|
|
2632
2577
|
localDisplayManager.onActiveDisplayChange(pushDerived)
|
|
2633
2578
|
useAppStatusStore.subscribe(pushDerived)
|
|
2634
|
-
onOverlayAppsChange(pushDerived)
|
|
2635
2579
|
useGlassesStore.subscribe(
|
|
2636
2580
|
(s) => `${s.ringConnected}:${s.ringBatteryLevel}:${s.controllerConnected}:${s.controllerBatteryLevel}`,
|
|
2637
2581
|
pushDerived,
|
|
@@ -5252,31 +5196,6 @@ class LocalMiniappRuntime {
|
|
|
5252
5196
|
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.cfw_native_navigation.key), broadcast)
|
|
5253
5197
|
// The dashboard master switch flips the dashboard view.
|
|
5254
5198
|
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.custom_dashboard_enabled.key), broadcast)
|
|
5255
|
-
// The wearer's choice and the installed dashboards decide who holds the role.
|
|
5256
|
-
const syncRole = () => this.syncActiveDashboard()
|
|
5257
|
-
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.active_dashboard.key), syncRole)
|
|
5258
|
-
useAppStatusStore.subscribe(syncRole)
|
|
5259
|
-
syncRole()
|
|
5260
|
-
}
|
|
5261
|
-
|
|
5262
|
-
/**
|
|
5263
|
-
* Recompute who holds the dashboard role (ADR-0009). The previous holder
|
|
5264
|
-
* gives up the slot first, so it hears the view change while it is still
|
|
5265
|
-
* the overlay app; then every app sees its views again.
|
|
5266
|
-
*/
|
|
5267
|
-
private syncActiveDashboard(): void {
|
|
5268
|
-
const candidates: Array<{packageName: string; type?: string}> = [...useAppStatusStore.getState().apps]
|
|
5269
|
-
// A dashboard can connect at launch before the apps store has listed it.
|
|
5270
|
-
for (const [packageName, app] of this.connectedApps) {
|
|
5271
|
-
if (candidates.some((candidate) => candidate.packageName === packageName)) continue
|
|
5272
|
-
candidates.push({packageName, type: app.installedManifest?.type})
|
|
5273
|
-
}
|
|
5274
|
-
const next = activeDashboardAmong(candidates)
|
|
5275
|
-
const previous = activeDashboard()
|
|
5276
|
-
if (next === previous) return
|
|
5277
|
-
if (previous !== null) overlayPages.hide(previous, "replaced")
|
|
5278
|
-
setActiveDashboard(next)
|
|
5279
|
-
this.broadcastCapabilities()
|
|
5280
5199
|
}
|
|
5281
5200
|
|
|
5282
5201
|
/** Capabilities as `packageName` sees them: the model profile plus the views this app may draw. */
|
|
@@ -5749,20 +5668,18 @@ class LocalMiniappRuntime {
|
|
|
5749
5668
|
}
|
|
5750
5669
|
}
|
|
5751
5670
|
|
|
5752
|
-
/** Reject the request unless the caller
|
|
5753
|
-
private
|
|
5671
|
+
/** Reject the request with NOT_PERMITTED unless the caller is a system app. */
|
|
5672
|
+
private requireSystemCaller(
|
|
5754
5673
|
packageName: string,
|
|
5755
5674
|
requestId: string | undefined,
|
|
5756
5675
|
op: InteropAuditEvent["op"],
|
|
5757
5676
|
target?: string,
|
|
5758
5677
|
): boolean {
|
|
5759
|
-
if (this.
|
|
5760
|
-
this.auditInterop({
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
ok: false,
|
|
5765
|
-
errorCode: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
5678
|
+
if (this.isSystemPackage(packageName)) return true
|
|
5679
|
+
this.auditInterop({caller: packageName, op, target, ok: false, errorCode: MiniappErrorCode.NOT_PERMITTED})
|
|
5680
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
5681
|
+
code: MiniappErrorCode.NOT_PERMITTED,
|
|
5682
|
+
message: "Inter-miniapp APIs are restricted to system apps",
|
|
5766
5683
|
})
|
|
5767
5684
|
return false
|
|
5768
5685
|
}
|
|
@@ -5781,14 +5698,13 @@ class LocalMiniappRuntime {
|
|
|
5781
5698
|
version: app.version ?? "",
|
|
5782
5699
|
running: app.running,
|
|
5783
5700
|
hidden: app.hidden === true,
|
|
5784
|
-
system: isSystemMiniapp(app),
|
|
5785
5701
|
compatibility,
|
|
5786
5702
|
actions: app.actions ?? [],
|
|
5787
5703
|
}
|
|
5788
5704
|
}
|
|
5789
5705
|
|
|
5790
5706
|
private handleMiniappsList(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
|
|
5791
|
-
if (!this.
|
|
5707
|
+
if (!this.requireSystemCaller(packageName, requestId, "list")) return
|
|
5792
5708
|
const includeIncompatible = payload.includeIncompatible === true
|
|
5793
5709
|
const infos = this.interopApps()
|
|
5794
5710
|
.filter(
|
|
@@ -5809,7 +5725,7 @@ class LocalMiniappRuntime {
|
|
|
5809
5725
|
requestId?: string,
|
|
5810
5726
|
): Promise<void> {
|
|
5811
5727
|
const target = payload.packageName as string | undefined
|
|
5812
|
-
if (!this.
|
|
5728
|
+
if (!this.requireSystemCaller(packageName, requestId, "start", target)) return
|
|
5813
5729
|
const app = target ? this.interopApps().find((a) => a.packageName === target) : undefined
|
|
5814
5730
|
if (!target || !app) {
|
|
5815
5731
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
@@ -5901,7 +5817,7 @@ class LocalMiniappRuntime {
|
|
|
5901
5817
|
requestId?: string,
|
|
5902
5818
|
): Promise<void> {
|
|
5903
5819
|
const target = payload.packageName as string | undefined
|
|
5904
|
-
if (!this.
|
|
5820
|
+
if (!this.requireSystemCaller(packageName, requestId, "stop", target)) return
|
|
5905
5821
|
if (!target) {
|
|
5906
5822
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
5907
5823
|
code: MiniappErrorCode.APP_NOT_FOUND,
|
|
@@ -31,7 +31,6 @@ import {storage} from "../utils/storage/storage"
|
|
|
31
31
|
import appRegistry, {getLocalAppRunningState, saveLocalAppRunningState} from "./AppRegistry"
|
|
32
32
|
import devServerBridge from "./DevServerBridge"
|
|
33
33
|
import localMiniappRuntime, {type InstalledMiniappManifest} from "./LocalMiniappRuntime"
|
|
34
|
-
import {activeDashboardAmong} from "./pages/activeDashboard"
|
|
35
34
|
import type {MiniappLaunchSource} from "@glassly/miniapp"
|
|
36
35
|
import type {JSRouter} from "./JSRouter"
|
|
37
36
|
|
|
@@ -307,21 +306,17 @@ class MiniappLauncher {
|
|
|
307
306
|
*
|
|
308
307
|
* Always On apps (the dashboard) start whether or not they were running
|
|
309
308
|
* when the app was last killed, and get a switcher card like any other app.
|
|
310
|
-
* A dashboard that does not hold the role (ADR-0009) is not started by its
|
|
311
|
-
* Always On flag.
|
|
312
309
|
* Both restore paths skip apps missing required OS permissions without
|
|
313
310
|
* prompting or clearing their saved running/Always On choice.
|
|
314
311
|
*/
|
|
315
312
|
async autostartLocalMiniapps(): Promise<void> {
|
|
316
313
|
const apps = await appRegistry.getInstalledMiniapps()
|
|
317
|
-
const activeDashboard = activeDashboardAmong(apps)
|
|
318
314
|
for (const app of apps) {
|
|
319
315
|
// Only phone-hosted miniapps have a JSContext to re-spawn. Offline
|
|
320
316
|
// built-ins (`local:false, offline:true`) restore their native running
|
|
321
317
|
// state elsewhere and have no bundle to launch.
|
|
322
318
|
if (!app.local) continue
|
|
323
|
-
|
|
324
|
-
if (!alwaysOn && !getLocalAppRunningState(app.packageName)) continue
|
|
319
|
+
if (!app.alwaysOn && !getLocalAppRunningState(app.packageName)) continue
|
|
325
320
|
if (this.isRunning(app.packageName)) continue
|
|
326
321
|
try {
|
|
327
322
|
const missing = await permissions.missingRequired(app.permissions ?? [])
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overlay
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* delivers to apps, so this module never imports it.
|
|
2
|
+
* The overlay apps' pages (ADR-0006): one Page per app that has taken the
|
|
3
|
+
* slot, the only Page implementation the page host shows. The engine keeps
|
|
4
|
+
* the slot and the eligibility watch; a page only relays: holding the slot
|
|
5
|
+
* is reported to its app as a view change, and the app's frames reach the
|
|
6
|
+
* slot through `renderOverlay` under its package name. Whichever app asked
|
|
7
|
+
* last holds the slot; the previous one hears it was replaced. The runtime
|
|
8
|
+
* supplies the sink that delivers to apps, so this module never imports it.
|
|
10
9
|
*/
|
|
11
10
|
import pageHost, {type Page, type PageHiddenReason} from "./PageHost"
|
|
12
11
|
import {isOverlayApp} from "./overlayApps"
|
|
@@ -45,10 +44,10 @@ class OverlayPages {
|
|
|
45
44
|
return pageHost.showing === page
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
/** Release the slot if the app holds it
|
|
49
|
-
hide(packageName: string
|
|
47
|
+
/** Release the slot if the app holds it. */
|
|
48
|
+
hide(packageName: string): void {
|
|
50
49
|
const page = this.pages.get(packageName)
|
|
51
|
-
if (page) pageHost.hide(page
|
|
50
|
+
if (page) pageHost.hide(page)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
/** The app stopped: release the slot without telling it, and forget its page. */
|
|
@@ -72,10 +72,10 @@ class PageHost {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** Release the slot if `page` holds it. */
|
|
75
|
-
hide(page: Page
|
|
75
|
+
hide(page: Page): void {
|
|
76
76
|
this.enqueue(() => {
|
|
77
77
|
if (this.showingPage !== page) return
|
|
78
|
-
this.release(
|
|
78
|
+
this.release("hidden")
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
|