@glassly/engine 0.3.5-dev.706 → 0.3.5-dev.720
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 +1 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +1 -1
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -3
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -3
- package/build/facades/ai.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts +4 -3
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +12 -11
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +12 -4
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +21 -7
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +3 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +31 -6
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +4 -3
- package/build/services/MiniappExitGesture.d.ts.map +1 -1
- package/build/services/MiniappExitGesture.js +3 -3
- package/build/services/MiniappExitGesture.js.map +1 -1
- package/build/services/MiniappWidgetMode.d.ts +2 -1
- package/build/services/MiniappWidgetMode.d.ts.map +1 -1
- package/build/services/MiniappWidgetMode.js +3 -3
- package/build/services/MiniappWidgetMode.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +1 -1
- package/src/facades/ai.ts +3 -3
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/GlassesTouchInput.ts +12 -11
- package/src/services/LocalDisplayManager.ts +22 -8
- package/src/services/LocalMiniappRuntime.ts +28 -6
- package/src/services/MiniappExitGesture.ts +5 -4
- package/src/services/MiniappWidgetMode.ts +5 -4
|
@@ -12,7 +12,8 @@ interface ExitGestureState {
|
|
|
12
12
|
}
|
|
13
13
|
export interface ExitGestureDependencies {
|
|
14
14
|
getState: () => ExitGestureState;
|
|
15
|
-
|
|
15
|
+
/** The miniapp a double tap leaves right now, or null when the tap is the system's. */
|
|
16
|
+
getTargetPackageName: () => string | null;
|
|
16
17
|
beginExitNotice: (packageName: string, text: string) => boolean;
|
|
17
18
|
finishExitNotice: (packageName: string) => void;
|
|
18
19
|
cancelExitNotice: (packageName: string) => void;
|
|
@@ -24,8 +25,8 @@ export declare class MiniappExitGesture {
|
|
|
24
25
|
private readonly deps;
|
|
25
26
|
private pending;
|
|
26
27
|
constructor(deps: ExitGestureDependencies);
|
|
27
|
-
/** Returns true when the system gesture was consumed by a
|
|
28
|
-
|
|
28
|
+
/** Returns true when the system gesture was consumed by a miniapp exit. */
|
|
29
|
+
tryExitMiniapp(): boolean;
|
|
29
30
|
/** Cancel an in-flight handoff when the device-event router shuts down. */
|
|
30
31
|
reset(): void;
|
|
31
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappExitGesture.d.ts","sourceRoot":"","sources":["../../src/services/MiniappExitGesture.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAE1C,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,cAAc,EAAE,CAAA;IACtB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,gBAAgB,CAAA;IAChC,
|
|
1
|
+
{"version":3,"file":"MiniappExitGesture.d.ts","sourceRoot":"","sources":["../../src/services/MiniappExitGesture.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAE1C,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,cAAc,EAAE,CAAA;IACtB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,gBAAgB,CAAA;IAChC,uFAAuF;IACvF,oBAAoB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACzC,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAC/D,gBAAgB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,gBAAgB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3D,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,qEAAqE;AACrE,qBAAa,kBAAkB;IAGV,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFxC,OAAO,CAAC,OAAO,CAAsD;gBAEjC,IAAI,EAAE,uBAAuB;IAEjE,2EAA2E;IACpE,cAAc,IAAI,OAAO;IAmChC,2EAA2E;IACpE,KAAK,IAAI,IAAI;CAOrB"}
|
|
@@ -6,12 +6,12 @@ export class MiniappExitGesture {
|
|
|
6
6
|
constructor(deps) {
|
|
7
7
|
this.deps = deps;
|
|
8
8
|
}
|
|
9
|
-
/** Returns true when the system gesture was consumed by a
|
|
10
|
-
|
|
9
|
+
/** Returns true when the system gesture was consumed by a miniapp exit. */
|
|
10
|
+
tryExitMiniapp() {
|
|
11
11
|
// Swallow repeat taps while the notice is already counting down.
|
|
12
12
|
if (this.pending)
|
|
13
13
|
return true;
|
|
14
|
-
const packageName = this.deps.
|
|
14
|
+
const packageName = this.deps.getTargetPackageName();
|
|
15
15
|
if (!packageName)
|
|
16
16
|
return false;
|
|
17
17
|
const app = this.deps.getState().apps.find((candidate) => candidate.packageName === packageName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappExitGesture.js","sourceRoot":"","sources":["../../src/services/MiniappExitGesture.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"MiniappExitGesture.js","sourceRoot":"","sources":["../../src/services/MiniappExitGesture.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAA;AA0B1C,qEAAqE;AACrE,MAAM,OAAO,kBAAkB;IAGO;IAF5B,OAAO,GAAkD,IAAI,CAAA;IAErE,YAAoC,IAA6B;QAA7B,SAAI,GAAJ,IAAI,CAAyB;IAAG,CAAC;IAErE,2EAA2E;IACpE,cAAc;QACnB,iEAAiE;QACjE,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAA;QACpD,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAA;QAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,KAAK,WAAW,CAAC,CAAA;QAChG,IAAI,CAAC,GAAG,EAAE,OAAO;YAAE,OAAO,KAAK,CAAA;QAE/B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,WAAW,CAAA;QAC3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,WAAW,IAAI,GAAG,CAAC;YAAE,OAAO,KAAK,CAAA;QAE7E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAA;YAClC,IAAI,KAAK,CAAC,mBAAmB,KAAK,WAAW;gBAAE,KAAK,CAAC,eAAe,EAAE,CAAA;YAEtE,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,KAAK,WAAW,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;YAC/G,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;YACvE,KAAK,IAAI;iBACN,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,WAAW,GAAG,EAAE,KAAK,CAAC,CAAA;YAC5E,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,IAAI,CAAC,OAAO,EAAE,WAAW,KAAK,WAAW;oBAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;gBAClE,mEAAmE;gBACnE,iDAAiD;gBACjD,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;YACzC,CAAC,CAAC,CAAA;QACN,CAAC,EAAE,sBAAsB,CAAC,CAAA;QAE1B,IAAI,CAAC,OAAO,GAAG,EAAC,WAAW,EAAE,OAAO,EAAC,CAAA;QACrC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,2EAA2E;IACpE,KAAK;QACV,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QACzB,MAAM,EAAC,WAAW,EAAE,OAAO,EAAC,GAAG,IAAI,CAAC,OAAO,CAAA;QAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACzC,CAAC;CACF","sourcesContent":["export const MINIAPP_EXIT_NOTICE_MS = 1500\n\ninterface ExitGestureApp {\n packageName: string\n name: string\n running: boolean\n}\n\ninterface ExitGestureState {\n apps: ExitGestureApp[]\n foregroundedPackage: string | null\n clearForeground: () => void\n stop: (packageName: string) => Promise<void>\n}\n\nexport interface ExitGestureDependencies {\n getState: () => ExitGestureState\n /** The miniapp a double tap leaves right now, or null when the tap is the system's. */\n getTargetPackageName: () => string | null\n beginExitNotice: (packageName: string, text: string) => boolean\n finishExitNotice: (packageName: string) => void\n cancelExitNotice: (packageName: string) => void\n setTimeout: (callback: () => void, delay: number) => number\n clearTimeout: (timerId: number) => void\n}\n\n/** Owns the short double-tap → exit-notice → normal-stop handoff. */\nexport class MiniappExitGesture {\n private pending: {packageName: string; timerId: number} | null = null\n\n public constructor(private readonly deps: ExitGestureDependencies) {}\n\n /** Returns true when the system gesture was consumed by a miniapp exit. */\n public tryExitMiniapp(): boolean {\n // Swallow repeat taps while the notice is already counting down.\n if (this.pending) return true\n\n const packageName = this.deps.getTargetPackageName()\n if (!packageName) return false\n\n const app = this.deps.getState().apps.find((candidate) => candidate.packageName === packageName)\n if (!app?.running) return false\n\n const name = app.name.trim() || packageName\n if (!this.deps.beginExitNotice(packageName, `Exiting ${name}…`)) return false\n\n const timerId = this.deps.setTimeout(() => {\n const state = this.deps.getState()\n if (state.foregroundedPackage === packageName) state.clearForeground()\n\n const stillRunning = state.apps.some((candidate) => candidate.packageName === packageName && candidate.running)\n const stop = stillRunning ? state.stop(packageName) : Promise.resolve()\n void stop\n .catch((error) => {\n console.error(`MiniappExitGesture: failed to stop ${packageName}:`, error)\n })\n .finally(() => {\n if (this.pending?.packageName === packageName) this.pending = null\n // LocalMiniappRuntime.onUnmount normally finishes the notice. This\n // fallback also handles a failed/no-op teardown.\n this.deps.finishExitNotice(packageName)\n })\n }, MINIAPP_EXIT_NOTICE_MS)\n\n this.pending = {packageName, timerId}\n return true\n }\n\n /** Cancel an in-flight handoff when the device-event router shuts down. */\n public reset(): void {\n if (!this.pending) return\n const {packageName, timerId} = this.pending\n this.pending = null\n this.deps.clearTimeout(timerId)\n this.deps.cancelExitNotice(packageName)\n }\n}\n"]}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* The runtime is the writer (placements come and go through it); the apps
|
|
10
10
|
* store projects `widgetOnly` from here beside `running`.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
/** Called with the app whose mode changed and whether it is now widget-only. */
|
|
13
|
+
type Listener = (packageName: string, widgetOnly: boolean) => void;
|
|
13
14
|
export declare const miniappWidgetMode: {
|
|
14
15
|
/** Mark (or clear) widget mode. Clearing promotes: the app keeps running as a normal app. */
|
|
15
16
|
setWidgetOnly(packageName: string, on: boolean): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappWidgetMode.d.ts","sourceRoot":"","sources":["../../src/services/MiniappWidgetMode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"MiniappWidgetMode.d.ts","sourceRoot":"","sources":["../../src/services/MiniappWidgetMode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,KAAK,QAAQ,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAA;AAiBlE,eAAO,MAAM,iBAAiB;IAC5B,6FAA6F;+BAClE,MAAM,MAAM,OAAO,GAAG,IAAI;8BAM3B,MAAM,GAAG,OAAO;IAI1C,oEAAoE;2BAC7C,MAAM,MAAM,OAAO,GAAG,IAAI;0BAK3B,MAAM,GAAG,OAAO;cAI5B,MAAM,EAAE;kBAIJ,QAAQ,GAAG,MAAM,IAAI;CAMpC,CAAA"}
|
|
@@ -13,10 +13,10 @@ const widgetOnly = new Set();
|
|
|
13
13
|
/** Providers with at least one widget placed by some host. */
|
|
14
14
|
const placed = new Set();
|
|
15
15
|
const listeners = new Set();
|
|
16
|
-
function notify() {
|
|
16
|
+
function notify(packageName, on) {
|
|
17
17
|
for (const fn of listeners) {
|
|
18
18
|
try {
|
|
19
|
-
fn();
|
|
19
|
+
fn(packageName, on);
|
|
20
20
|
}
|
|
21
21
|
catch (e) {
|
|
22
22
|
console.warn("MiniappWidgetMode: listener threw", e);
|
|
@@ -30,7 +30,7 @@ export const miniappWidgetMode = {
|
|
|
30
30
|
if (on)
|
|
31
31
|
widgetOnly.add(packageName);
|
|
32
32
|
if (changed)
|
|
33
|
-
notify();
|
|
33
|
+
notify(packageName, on);
|
|
34
34
|
},
|
|
35
35
|
isWidgetOnly(packageName) {
|
|
36
36
|
return widgetOnly.has(packageName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappWidgetMode.js","sourceRoot":"","sources":["../../src/services/MiniappWidgetMode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"MiniappWidgetMode.js","sourceRoot":"","sources":["../../src/services/MiniappWidgetMode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;AACpC,8DAA8D;AAC9D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;AAChC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAA;AAErC,SAAS,MAAM,CAAC,WAAmB,EAAE,EAAW;IAC9C,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;QACrB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,CAAC,CAAC,CAAA;QACtD,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6FAA6F;IAC7F,aAAa,CAAC,WAAmB,EAAE,EAAW;QAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;QAClF,IAAI,EAAE;YAAE,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACnC,IAAI,OAAO;YAAE,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACtC,CAAC;IAED,YAAY,CAAC,WAAmB;QAC9B,OAAO,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACpC,CAAC;IAED,oEAAoE;IACpE,SAAS,CAAC,WAAmB,EAAE,EAAW;QACxC,IAAI,EAAE;YAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;;YAC1B,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACjC,CAAC;IAED,QAAQ,CAAC,WAAmB;QAC1B,OAAO,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAChC,CAAC;IAED,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC/B,CAAC;IAED,SAAS,CAAC,EAAY;QACpB,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjB,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACtB,CAAC,CAAA;IACH,CAAC;CACF,CAAA","sourcesContent":["/**\n * Widget mode: a miniapp whose background context runs only because a host\n * (the dashboard) placed one of its widgets. It is running, but it has no\n * card in the app switcher, draws nothing of its own on the glasses and\n * hears no touch input; the Widgets pill lists it. Opening the app from the\n * home screen promotes it to a normal run; closing a placed app from the\n * tray demotes it back here instead of stopping it.\n *\n * The runtime is the writer (placements come and go through it); the apps\n * store projects `widgetOnly` from here beside `running`.\n */\n\n/** Called with the app whose mode changed and whether it is now widget-only. */\ntype Listener = (packageName: string, widgetOnly: boolean) => void\n\nconst widgetOnly = new Set<string>()\n/** Providers with at least one widget placed by some host. */\nconst placed = new Set<string>()\nconst listeners = new Set<Listener>()\n\nfunction notify(packageName: string, on: boolean): void {\n for (const fn of listeners) {\n try {\n fn(packageName, on)\n } catch (e) {\n console.warn(\"MiniappWidgetMode: listener threw\", e)\n }\n }\n}\n\nexport const miniappWidgetMode = {\n /** Mark (or clear) widget mode. Clearing promotes: the app keeps running as a normal app. */\n setWidgetOnly(packageName: string, on: boolean): void {\n const changed = on ? !widgetOnly.has(packageName) : widgetOnly.delete(packageName)\n if (on) widgetOnly.add(packageName)\n if (changed) notify(packageName, on)\n },\n\n isWidgetOnly(packageName: string): boolean {\n return widgetOnly.has(packageName)\n },\n\n /** Record whether any host has a widget of this provider placed. */\n setPlaced(packageName: string, on: boolean): void {\n if (on) placed.add(packageName)\n else placed.delete(packageName)\n },\n\n isPlaced(packageName: string): boolean {\n return placed.has(packageName)\n },\n\n getAll(): string[] {\n return Array.from(widgetOnly)\n },\n\n subscribe(fn: Listener): () => void {\n listeners.add(fn)\n return () => {\n listeners.delete(fn)\n }\n },\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.5-dev.
|
|
3
|
+
"version": "0.3.5-dev.720",
|
|
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.5-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.3.5-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.5-dev.
|
|
103
|
-
"@glassly/crust": "0.3.5-dev.
|
|
104
|
-
"@glassly/miniapp": "0.3.5-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.5-dev.720",
|
|
101
|
+
"@glassly/cloud-client": "0.3.5-dev.720",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.5-dev.720",
|
|
103
|
+
"@glassly/crust": "0.3.5-dev.720",
|
|
104
|
+
"@glassly/miniapp": "0.3.5-dev.720",
|
|
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.5-dev.
|
|
110
|
+
"@glassly/llm-providers": "0.3.5-dev.720",
|
|
111
111
|
"@huggingface/jinja": "^0.5.9",
|
|
112
112
|
"jsonrepair": "^3.12.0"
|
|
113
113
|
},
|
package/src/engine.ts
CHANGED
|
@@ -192,7 +192,7 @@ export const engine = {
|
|
|
192
192
|
},
|
|
193
193
|
glasses,
|
|
194
194
|
speech,
|
|
195
|
-
/** On-device LLM model management (Settings >
|
|
195
|
+
/** On-device LLM model management (Settings > Offline Models). */
|
|
196
196
|
ai,
|
|
197
197
|
/** Cloud live-session status + account ops — engine owns the client. */
|
|
198
198
|
session,
|
package/src/facades/ai.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* ai facade — `engine.ai.local`: the on-device LLM model that serves
|
|
3
3
|
* session.ai calls routed to the phone (forceLocal / localFallback). Thin
|
|
4
4
|
* passthrough over LocalLlmModelManager and LocalLlmProvider so Settings >
|
|
5
|
-
*
|
|
6
|
-
* into engine internals.
|
|
5
|
+
* Offline Models can list, pick, download, and delete models without
|
|
6
|
+
* reaching into engine internals.
|
|
7
7
|
*/
|
|
8
8
|
import localLlmModelManager, {
|
|
9
9
|
LocalLlmModelManager,
|
|
@@ -17,7 +17,7 @@ export const ai = {
|
|
|
17
17
|
local: {
|
|
18
18
|
/** The catalog, in dropdown order. */
|
|
19
19
|
models: (): LocalLlmModelConfig[] => localLlmModelManager.models(),
|
|
20
|
-
/** The Settings >
|
|
20
|
+
/** The Settings > Offline Models pick. */
|
|
21
21
|
selectedModelId: (): LocalLlmModelId => localLlmModelManager.selectedModelId(),
|
|
22
22
|
select: (id: LocalLlmModelId): Promise<void> => localLlmModelManager.select(id),
|
|
23
23
|
isDownloaded: (id?: LocalLlmModelId): Promise<boolean> => localLlmModelManager.isModelAvailable(id),
|
|
@@ -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.5",
|
|
15
|
-
"releaseIdentity": "0.3.5-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.3.5-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.5-dev.720",
|
|
16
|
+
"releaseSetId": "glassly-0.3.5-dev.720",
|
|
17
|
+
"sourceCommit": "65e090e9400a78f8fec7163ed9bcc848657fa92c",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -17,7 +17,7 @@ import {pagesEligible} from "./pages/pageEligibility"
|
|
|
17
17
|
|
|
18
18
|
const miniappExitGesture = new MiniappExitGesture({
|
|
19
19
|
getState: () => useAppStatusStore.getState(),
|
|
20
|
-
|
|
20
|
+
getTargetPackageName: () => localDisplayManager.getMiniappToLeave(),
|
|
21
21
|
beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),
|
|
22
22
|
finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),
|
|
23
23
|
cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),
|
|
@@ -47,9 +47,10 @@ function shouldForwardScroll(event: TouchEvent): boolean {
|
|
|
47
47
|
* Route both real glasses input and phone mirror controls through the same
|
|
48
48
|
* policy. A claimed gesture goes to the app holding the claim, alone; an
|
|
49
49
|
* unclaimed one goes to every subscriber and then the engine's default. The
|
|
50
|
-
* one rule ahead of the claims: an unclaimed double tap
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* one rule ahead of the claims: an unclaimed double tap leaves the miniapp
|
|
51
|
+
* the wearer is in — the one visibly on the glasses, else the running
|
|
52
|
+
* standard app on an empty screen — whoever else claimed the gesture; the
|
|
53
|
+
* dashboard's claim is for the screen with no app in it. Head position
|
|
53
54
|
* (head_up / head_down) is a gesture like any other: the dashboard claims it
|
|
54
55
|
* while it runs, and an unclaimed head_up on glasses that defer the dashboard
|
|
55
56
|
* to the phone opens the native one, as it would with no app at all.
|
|
@@ -63,16 +64,16 @@ export function dispatchTouchEvent(event: TouchEvent): void {
|
|
|
63
64
|
const osHeadUp = gesture === "head_up"
|
|
64
65
|
|
|
65
66
|
if (osDoubleTap) {
|
|
66
|
-
//
|
|
67
|
+
// The app being left opts out of OS exit by registering double tap (the
|
|
67
68
|
// terminal's back-to-menu). The latest claimant still receives it. Otherwise
|
|
68
69
|
// the tap means "leave the app". The controller shows the bounded OS
|
|
69
|
-
// notice first, then uses the normal
|
|
70
|
-
//
|
|
71
|
-
const
|
|
70
|
+
// notice first, then uses the normal store stop path (and closes its
|
|
71
|
+
// phone overlay).
|
|
72
|
+
const leaving = localDisplayManager.getMiniappToLeave()
|
|
72
73
|
if (
|
|
73
|
-
|
|
74
|
-
!localMiniappRuntime.hasGestureClaim(
|
|
75
|
-
miniappExitGesture.
|
|
74
|
+
leaving !== null &&
|
|
75
|
+
!localMiniappRuntime.hasGestureClaim(leaving, gesture) &&
|
|
76
|
+
miniappExitGesture.tryExitMiniapp()
|
|
76
77
|
) {
|
|
77
78
|
gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)
|
|
78
79
|
return
|
|
@@ -165,7 +165,7 @@ class LocalDisplayManager {
|
|
|
165
165
|
private overlayOwner: string | null = null
|
|
166
166
|
|
|
167
167
|
/** Miniapp whose visible frame is temporarily covered by an OS exit notice. */
|
|
168
|
-
private exitingApp: {packageName: string; coveredDisplay: ActiveDisplay} | null = null
|
|
168
|
+
private exitingApp: {packageName: string; coveredDisplay: ActiveDisplay | null} | null = null
|
|
169
169
|
|
|
170
170
|
private expiryTimerId: number | null = null
|
|
171
171
|
|
|
@@ -309,16 +309,30 @@ class LocalDisplayManager {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
312
|
+
* The miniapp an unclaimed double tap leaves: the one visibly drawing on
|
|
313
|
+
* the main view, else the core (running standard) app on an empty or
|
|
314
|
+
* system-drawn screen — an app that has drawn nothing yet, or whose frame
|
|
315
|
+
* expired, is still the one the wearer is in. Nothing while a page covers
|
|
316
|
+
* the main view: the page's claims own the tap.
|
|
317
|
+
*/
|
|
318
|
+
public getMiniappToLeave(): string | null {
|
|
319
|
+
if (this.overlayOwner !== null) return null
|
|
320
|
+
return this.getVisibleMiniappPackageName() ?? this.coreApp
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Cover the leaving miniapp with a persistent system exit notice. While it
|
|
325
|
+
* is up, miniapp display traffic is held off so a streaming app cannot
|
|
326
|
+
* immediately overwrite the notice. The caller completes the normal stop
|
|
327
|
+
* flow and then calls finishMiniappExit(); onUnmount does the same as a
|
|
316
328
|
* safety net.
|
|
317
329
|
*/
|
|
318
330
|
public beginMiniappExit(packageName: string, text: string): boolean {
|
|
319
331
|
if (this.exitingApp !== null) return this.exitingApp.packageName === packageName
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
if (this.getMiniappToLeave() !== packageName) return false
|
|
333
|
+
// Only the app's own visible frame comes back on cancel; a system frame
|
|
334
|
+
// or an empty screen does not.
|
|
335
|
+
const coveredDisplay = this.getVisibleMiniappPackageName() === packageName ? this.currentDisplay : null
|
|
322
336
|
|
|
323
337
|
console.log(`${LOG_TAG}: beginMiniappExit(${packageName})`)
|
|
324
338
|
this.exitingApp = {packageName, coveredDisplay}
|
|
@@ -349,7 +363,7 @@ class LocalDisplayManager {
|
|
|
349
363
|
this.exitingApp = null
|
|
350
364
|
|
|
351
365
|
if (this.currentDisplay?.packageName !== SYSTEM_NOTICE_PKG) return
|
|
352
|
-
if (covered.expiresAt === null || covered.expiresAt > this.now()) {
|
|
366
|
+
if (covered && (covered.expiresAt === null || covered.expiresAt > this.now())) {
|
|
353
367
|
this.restoreDisplay(covered)
|
|
354
368
|
} else {
|
|
355
369
|
this.currentDisplay = null
|
|
@@ -641,6 +641,10 @@ class LocalMiniappRuntime {
|
|
|
641
641
|
private connectedApps: Map<string, ConnectedMiniapp> = new Map()
|
|
642
642
|
/** Claimed gestures (CLAIM_GESTURES): a stack per gesture, the most recent claimer holding it. */
|
|
643
643
|
private gestureClaims: GestureClaimState = emptyGestureClaims()
|
|
644
|
+
// The last main-view frame each app asked to render. Widget mode refuses
|
|
645
|
+
// frames and demotion clears them, and an app that has drawn once may not
|
|
646
|
+
// draw again on its own, so promotion replays this.
|
|
647
|
+
private retainedRenders = new Map<string, Record<string, unknown>>()
|
|
644
648
|
|
|
645
649
|
/**
|
|
646
650
|
* Packages that have completed their CONNECT handshake (sent
|
|
@@ -705,7 +709,13 @@ class LocalMiniappRuntime {
|
|
|
705
709
|
private localSecret = `miniapp_${Date.now()}_${Math.random().toString(36).slice(2, 14)}`
|
|
706
710
|
private usedTokens = new Set<string>()
|
|
707
711
|
|
|
708
|
-
private constructor() {
|
|
712
|
+
private constructor() {
|
|
713
|
+
miniappWidgetMode.subscribe((packageName, widgetOnly) => {
|
|
714
|
+
// Promotion happens inside the apps store's start(); the core-app slot
|
|
715
|
+
// settles with that store write, so replay once it has.
|
|
716
|
+
if (!widgetOnly) queueMicrotask(() => this.replayRetainedRender(packageName))
|
|
717
|
+
})
|
|
718
|
+
}
|
|
709
719
|
|
|
710
720
|
/** Snapshot of miniapps actively subscribed to hardware button events. */
|
|
711
721
|
public getButtonPressSubscribers(): string[] {
|
|
@@ -1244,6 +1254,7 @@ class LocalMiniappRuntime {
|
|
|
1244
1254
|
}
|
|
1245
1255
|
// Its gesture claims go back to whoever claimed them before.
|
|
1246
1256
|
this.applyGestureClaimChanges(dropGestureClaims(this.gestureClaims, packageName))
|
|
1257
|
+
this.retainedRenders.delete(packageName)
|
|
1247
1258
|
|
|
1248
1259
|
// Warm-up is a request-owned camera lease. Cancel it even if the app record was already
|
|
1249
1260
|
// removed so a close racing the camera-open promise cannot leave the sensor running.
|
|
@@ -2489,11 +2500,6 @@ class LocalMiniappRuntime {
|
|
|
2489
2500
|
this.sendResult(packageName, requestId, true, {status: "blocked", reason: "app stopped"})
|
|
2490
2501
|
return
|
|
2491
2502
|
}
|
|
2492
|
-
// A widget-only app draws through its widget alone.
|
|
2493
|
-
if (miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
2494
|
-
this.sendResult(packageName, requestId, true, {status: "blocked", reason: "widget mode"})
|
|
2495
|
-
return
|
|
2496
|
-
}
|
|
2497
2503
|
if (!Array.isArray(payload.elements)) {
|
|
2498
2504
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
2499
2505
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -2501,6 +2507,15 @@ class LocalMiniappRuntime {
|
|
|
2501
2507
|
})
|
|
2502
2508
|
return
|
|
2503
2509
|
}
|
|
2510
|
+
if (payload.view !== "dashboard") {
|
|
2511
|
+
if (payload.elements.length === 0) this.retainedRenders.delete(packageName)
|
|
2512
|
+
else this.retainedRenders.set(packageName, payload)
|
|
2513
|
+
}
|
|
2514
|
+
// A widget-only app draws through its widget alone; the frame waits for promotion.
|
|
2515
|
+
if (miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
2516
|
+
this.sendResult(packageName, requestId, true, {status: "blocked", reason: "widget mode"})
|
|
2517
|
+
return
|
|
2518
|
+
}
|
|
2504
2519
|
// The dashboard view is the overlay app's alone; anyone else's frame
|
|
2505
2520
|
// is dropped, reported rather than silently lost.
|
|
2506
2521
|
if (payload.view === "dashboard" && !isOverlayApp(packageName)) {
|
|
@@ -2530,6 +2545,13 @@ class LocalMiniappRuntime {
|
|
|
2530
2545
|
}
|
|
2531
2546
|
}
|
|
2532
2547
|
|
|
2548
|
+
/** Put a promoted app's last main-view frame on the glasses; widget mode had refused it. */
|
|
2549
|
+
private replayRetainedRender(packageName: string): void {
|
|
2550
|
+
const payload = this.retainedRenders.get(packageName)
|
|
2551
|
+
if (!payload || !this.connectedApps.has(packageName) || miniappWidgetMode.isWidgetOnly(packageName)) return
|
|
2552
|
+
this.handleRender(packageName, payload)
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2533
2555
|
/**
|
|
2534
2556
|
* display.showView: the active dashboard takes or releases the overlay
|
|
2535
2557
|
* slot. Refused (NOT_PERMITTED) for an app that is not a dashboard;
|
|
@@ -15,7 +15,8 @@ interface ExitGestureState {
|
|
|
15
15
|
|
|
16
16
|
export interface ExitGestureDependencies {
|
|
17
17
|
getState: () => ExitGestureState
|
|
18
|
-
|
|
18
|
+
/** The miniapp a double tap leaves right now, or null when the tap is the system's. */
|
|
19
|
+
getTargetPackageName: () => string | null
|
|
19
20
|
beginExitNotice: (packageName: string, text: string) => boolean
|
|
20
21
|
finishExitNotice: (packageName: string) => void
|
|
21
22
|
cancelExitNotice: (packageName: string) => void
|
|
@@ -29,12 +30,12 @@ export class MiniappExitGesture {
|
|
|
29
30
|
|
|
30
31
|
public constructor(private readonly deps: ExitGestureDependencies) {}
|
|
31
32
|
|
|
32
|
-
/** Returns true when the system gesture was consumed by a
|
|
33
|
-
public
|
|
33
|
+
/** Returns true when the system gesture was consumed by a miniapp exit. */
|
|
34
|
+
public tryExitMiniapp(): boolean {
|
|
34
35
|
// Swallow repeat taps while the notice is already counting down.
|
|
35
36
|
if (this.pending) return true
|
|
36
37
|
|
|
37
|
-
const packageName = this.deps.
|
|
38
|
+
const packageName = this.deps.getTargetPackageName()
|
|
38
39
|
if (!packageName) return false
|
|
39
40
|
|
|
40
41
|
const app = this.deps.getState().apps.find((candidate) => candidate.packageName === packageName)
|
|
@@ -10,17 +10,18 @@
|
|
|
10
10
|
* store projects `widgetOnly` from here beside `running`.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** Called with the app whose mode changed and whether it is now widget-only. */
|
|
14
|
+
type Listener = (packageName: string, widgetOnly: boolean) => void
|
|
14
15
|
|
|
15
16
|
const widgetOnly = new Set<string>()
|
|
16
17
|
/** Providers with at least one widget placed by some host. */
|
|
17
18
|
const placed = new Set<string>()
|
|
18
19
|
const listeners = new Set<Listener>()
|
|
19
20
|
|
|
20
|
-
function notify(): void {
|
|
21
|
+
function notify(packageName: string, on: boolean): void {
|
|
21
22
|
for (const fn of listeners) {
|
|
22
23
|
try {
|
|
23
|
-
fn()
|
|
24
|
+
fn(packageName, on)
|
|
24
25
|
} catch (e) {
|
|
25
26
|
console.warn("MiniappWidgetMode: listener threw", e)
|
|
26
27
|
}
|
|
@@ -32,7 +33,7 @@ export const miniappWidgetMode = {
|
|
|
32
33
|
setWidgetOnly(packageName: string, on: boolean): void {
|
|
33
34
|
const changed = on ? !widgetOnly.has(packageName) : widgetOnly.delete(packageName)
|
|
34
35
|
if (on) widgetOnly.add(packageName)
|
|
35
|
-
if (changed) notify()
|
|
36
|
+
if (changed) notify(packageName, on)
|
|
36
37
|
},
|
|
37
38
|
|
|
38
39
|
isWidgetOnly(packageName: string): boolean {
|