@glassly/engine 0.1.0-dev.100 → 0.1.0-dev.102

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.
@@ -1,9 +1,9 @@
1
1
  export const ENGINE_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "0.1.0",
4
- "releaseIdentity": "0.1.0-dev.100",
5
- "releaseSetId": "glassly-0.1.0-dev.100",
6
- "sourceCommit": "5759886304d66eec8e3088937b3b650fab1283b7",
4
+ "releaseIdentity": "0.1.0-dev.102",
5
+ "releaseSetId": "glassly-0.1.0-dev.102",
6
+ "sourceCommit": "25f1ae8ddfcd46216e222643d54f498d21e01d59",
7
7
  "otaManifestUrl": null,
8
8
  "otaManifestSha256": null
9
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.1.0\",\n \"releaseIdentity\": \"0.1.0-dev.100\",\n \"releaseSetId\": \"glassly-0.1.0-dev.100\",\n \"sourceCommit\": \"5759886304d66eec8e3088937b3b650fab1283b7\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.1.0\",\n \"releaseIdentity\": \"0.1.0-dev.102\",\n \"releaseSetId\": \"glassly-0.1.0-dev.102\",\n \"sourceCommit\": \"25f1ae8ddfcd46216e222643d54f498d21e01d59\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"CustomDashboardService.d.ts","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"AAsEA,cAAM,sBAAsB;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,IAAI,CAAQ;IACpB,gFAAgF;IACzE,cAAc,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAO;IACjD,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,SAAS,CAAI;IACrB,4DAA4D;IAC5D,OAAO,CAAC,UAAU,CAAI;IACtB,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAA4D;IAClF,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAsB;IAC3C,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IAExB,KAAK,IAAI,IAAI;IA2Cb;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAavB,IAAI,IAAI,IAAI;IAeZ,UAAU,IAAI,OAAO;IAYrB,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,mEAAmE;IACnE,IAAI,IAAI,IAAI;IAMZ,wCAAwC;IACxC,IAAI,IAAI,IAAI;IAKZ,uEAAuE;IACvE,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAiB3C,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,OAAO;YAID,sBAAsB;YAOtB,SAAS;IAcvB,OAAO,CAAC,qBAAqB;YAIf,wBAAwB;IAkCtC,iFAAiF;IACjF,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,YAAY;IAgGpB,OAAO,CAAC,eAAe;CAqDxB;AAED,QAAA,MAAM,sBAAsB,wBAA+B,CAAA;AAC3D,eAAe,sBAAsB,CAAA;AACrC,OAAO,EAAC,sBAAsB,EAAC,CAAA"}
1
+ {"version":3,"file":"CustomDashboardService.d.ts","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"AA6FA,cAAM,sBAAsB;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,IAAI,CAAQ;IACpB,gFAAgF;IACzE,cAAc,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAO;IACjD,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,SAAS,CAAI;IACrB,4DAA4D;IAC5D,OAAO,CAAC,UAAU,CAAI;IACtB,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAA4D;IAClF,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAsB;IAC3C,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IAExB,KAAK,IAAI,IAAI;IA2Cb;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAavB,IAAI,IAAI,IAAI;IAeZ,UAAU,IAAI,OAAO;IAYrB,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,mEAAmE;IACnE,IAAI,IAAI,IAAI;IAMZ,wCAAwC;IACxC,IAAI,IAAI,IAAI;IAKZ,uEAAuE;IACvE,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAiB3C,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,OAAO;YAID,sBAAsB;YAOtB,SAAS;IAcvB,OAAO,CAAC,qBAAqB;YAIf,wBAAwB;IAsCtC,iFAAiF;IACjF,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,YAAY;IAiGpB,OAAO,CAAC,eAAe;CAqDxB;AAED,QAAA,MAAM,sBAAsB,wBAA+B,CAAA;AAC3D,eAAe,sBAAsB,CAAA;AACrC,OAAO,EAAC,sBAAsB,EAAC,CAAA"}
@@ -45,11 +45,34 @@ const BUTTON_H = 34;
45
45
  const UNDERLINE_H = 3;
46
46
  /** Row-top → underline-top: just under the builtin font's 27px line. */
47
47
  const UNDERLINE_OFFSET_Y = BUTTON_H - 4;
48
- function eventLine(event) {
49
- const when = event.allDay
50
- ? "All day"
51
- : new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(new Date(event.startsAt));
52
- return `${when} ${event.title}`;
48
+ function sameLocalDay(a, b) {
49
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
50
+ }
51
+ /** Local midnight after tomorrow: the widget never shows a "day after tomorrow". */
52
+ function endOfTomorrow(now) {
53
+ const end = new Date(now);
54
+ end.setHours(0, 0, 0, 0);
55
+ end.setDate(end.getDate() + 2);
56
+ return end;
57
+ }
58
+ /** Up to three lines per entry: title, location (when set), then the time — "tmr @ …" for tomorrow. */
59
+ function eventLines(event, now) {
60
+ const lines = [event.title];
61
+ const location = event.location?.trim();
62
+ if (location)
63
+ lines.push(location);
64
+ const start = new Date(event.startsAt);
65
+ const tomorrow = new Date(now);
66
+ tomorrow.setDate(tomorrow.getDate() + 1);
67
+ const isTomorrow = sameLocalDay(start, tomorrow);
68
+ if (event.allDay) {
69
+ lines.push(isTomorrow ? "tmr, all day" : "All day");
70
+ }
71
+ else {
72
+ const time = new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(start);
73
+ lines.push(isTomorrow ? `tmr @ ${time}` : time);
74
+ }
75
+ return lines;
53
76
  }
54
77
  class CustomDashboardService {
55
78
  started = false;
@@ -284,7 +307,11 @@ class CustomDashboardService {
284
307
  const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2);
285
308
  const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3);
286
309
  const startsAt = new Date();
287
- const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000);
310
+ // Clamped to local end-of-tomorrow: the widget only ever shows today and
311
+ // tomorrow ("tmr"), whatever the days-ahead setting says.
312
+ const daysEnd = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000);
313
+ const windowEnd = endOfTomorrow(startsAt);
314
+ const endsAt = daysEnd < windowEnd ? daysEnd : windowEnd;
288
315
  try {
289
316
  const result = await listPhoneCalendarEvents({
290
317
  startsAt: startsAt.toISOString(),
@@ -385,11 +412,12 @@ class CustomDashboardService {
385
412
  // Right half, top-aligned, independent of the left stack. A missing
386
413
  // calendar shows a hint instead of silently vanishing — "No access"
387
414
  // strictly for permission denial, "Unavailable" for any other failure.
415
+ const now = new Date();
388
416
  const lines = this.calendarStatus === "ok"
389
417
  ? [
390
418
  "Calendar",
391
419
  ...(this.calendarEvents.length
392
- ? this.calendarEvents.slice(0, 3).map(eventLine)
420
+ ? this.calendarEvents.slice(0, 3).flatMap((event) => eventLines(event, now))
393
421
  : ["No upcoming events"]),
394
422
  ]
395
423
  : this.calendarStatus === "loading"
@@ -1 +1 @@
1
- {"version":3,"file":"CustomDashboardService.js","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene API dashboard for G2 custom firmware.\n *\n * G2.swift marks the published firmware version after it detects the CFW\n * capability advertisement. DashboardCoordinator owns open/close and display\n * switching; this service renders the widgets and handles gestures while open.\n *\n * The dashboard is composed of user-orderable widgets (clock, calendar, apps\n * button, notifications button) stacked vertically on the CFW scene canvas.\n * The apps button switches to a second, dashboard-rendered app menu view.\n */\nimport {BluetoothSdk} from \"@glassly/bluetooth-sdk\"\nimport {MiniappErrorCode, type CalendarEvent} from \"@glassly/miniapp\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {ClientApp} from \"../types/applet\"\nimport {TextMeasurer} from \"../utils/display/measurer\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n appMenuActions,\n dashboardActions,\n formatDashboardClock,\n isCustomDashboardEligible,\n normalizeDashboardWidgets,\n orderedDashboardApps,\n type CustomDashboardClockStyle,\n type DashboardWidgetSetting,\n type MenuAppSetting,\n} from \"./CustomDashboardModel\"\nimport displayProcessor from \"./DisplayProcessor\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport {listPhoneCalendarEvents, PhoneCalendarError} from \"./PhoneCalendarService\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DASHBOARD_APP_ID = \"system.custom-dashboard\"\nconst CLOCK_REFRESH_MS = 30_000\nconst CALENDAR_REFRESH_MS = 5 * 60_000\n/** Matches the boot message's window; stops have no unmount-driven end signal. */\nconst STOP_NOTICE_MS = 1500\n\n// CFW blits the stock compositor's 576×288 canvas; used when the firmware has\n// not reported scene capabilities yet.\nconst FALLBACK_CANVAS = {width: 576, height: 288}\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\nconst WIDGET_GAP = 10\n/** Row height for the glasses' builtin 20px font engine. */\nconst LINE_H = 24\nconst BUTTON_H = 34\n/**\n * Selection marker: a text-hugging bar under the focused label instead of a\n * boxed border. Drawn as a rect (h 3, border 2 — the strokes overlap into a\n * solid bar) because mode 15 renders only glyphs; the bar must be in the\n * raster or the firmware never shows it.\n */\nconst UNDERLINE_H = 3\n/** Row-top → underline-top: just under the builtin font's 27px line. */\nconst UNDERLINE_OFFSET_Y = BUTTON_H - 4\n\nfunction eventLine(event: CalendarEvent): string {\n const when = event.allDay\n ? \"All day\"\n : new Intl.DateTimeFormat(undefined, {hour: \"numeric\", minute: \"2-digit\"}).format(new Date(event.startsAt))\n return `${when} ${event.title}`\n}\n\nclass CustomDashboardService {\n private started = false\n private open = false\n /** Set by DashboardCoordinator; asks it to close (and restore main content). */\n public onRequestClose: (() => void) | null = null\n private view: \"main\" | \"apps\" = \"main\"\n private mainIndex = 0\n private appsIndex = 0\n /** First visible row of the app list's scrolling window. */\n private appsScroll = 0\n /** \"loading\" until the first fetch resolves; \"no-access\" only for a real permission denial. */\n private calendarStatus: \"loading\" | \"ok\" | \"no-access\" | \"unavailable\" = \"loading\"\n private calendarEvents: CalendarEvent[] = []\n private unsubs: Array<() => void> = []\n private clockTimer: number | null = null\n private calendarTimer: number | null = null\n /** Last observed eligibility, for transition detection. */\n private eligible = false\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n useGlassesStore.subscribe(\n (state) => `${state.connection.state}:${state.deviceModel}:${state.firmwareVersion}`,\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n // Selector: only dashboard-relevant settings retrigger a refresh. The\n // whole-store form fired a calendar query on every settings write.\n useSettingsStore.subscribe(\n (state) =>\n JSON.stringify([\n state.getSetting(SETTINGS.custom_dashboard_enabled.key),\n state.getSetting(SETTINGS.custom_dashboard_widgets.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_style.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_show_date.key),\n state.getSetting(SETTINGS.twelve_hour_time.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key),\n state.getSetting(SETTINGS.menu_apps.key),\n ]),\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n useAppStatusStore.subscribe(() => {\n if (this.isEligible()) this.render()\n }),\n )\n this.clockTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) this.render()\n }, CLOCK_REFRESH_MS)\n this.calendarTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) void this.refreshCalendarAndRender()\n }, CALENDAR_REFRESH_MS)\n this.eligible = this.isEligible()\n }\n\n /**\n * Track eligible⇄ineligible transitions from any trigger (setting toggle,\n * connect/disconnect, firmware change). Leaving closes the dashboard via the\n * coordinator (which restores main content) and drops the retained scene so\n * a stale frame can't be replayed after the feature was turned off.\n */\n private syncEligibility(): boolean {\n const eligible = this.isEligible()\n if (eligible === this.eligible) return eligible\n this.eligible = eligible\n if (!eligible) {\n if (this.open) this.onRequestClose?.()\n this.open = false\n this.resetNavigation()\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n return eligible\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n if (this.clockTimer !== null) BgTimer.clearInterval(this.clockTimer)\n if (this.calendarTimer !== null) BgTimer.clearInterval(this.calendarTimer)\n this.clockTimer = null\n this.calendarTimer = null\n if (this.open) this.onRequestClose?.()\n this.open = false\n this.eligible = false\n this.resetNavigation()\n this.started = false\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n\n isEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({\n model: state.deviceModel,\n firmwareVersion: state.firmwareVersion,\n })\n )\n }\n\n get isOpen(): boolean {\n return this.open\n }\n\n /** Coordinator opened the dashboard: render fresh from the top. */\n show(): void {\n this.open = true\n this.resetNavigation()\n void this.refreshCalendarAndRender()\n }\n\n /** Coordinator closed the dashboard. */\n hide(): void {\n this.open = false\n this.resetNavigation()\n }\n\n /** Returns true when an open custom dashboard consumed the gesture. */\n handleGesture(gestureName: string): boolean {\n if (!this.open || !this.isEligible()) return false\n if (gestureName === \"double_tap\") {\n // In the app menu a double tap backs out to the top of the dashboard\n // (there is no Back row); on the main view it closes the dashboard.\n if (this.view === \"apps\") {\n this.resetNavigation()\n this.render()\n return true\n }\n this.onRequestClose?.()\n return true\n }\n if (this.view === \"apps\") return this.handleAppMenuGesture(gestureName)\n return this.handleMainGesture(gestureName)\n }\n\n private resetNavigation(): void {\n this.view = \"main\"\n this.mainIndex = 0\n this.appsIndex = 0\n this.appsScroll = 0\n }\n\n private handleMainGesture(gestureName: string): boolean {\n const actions = dashboardActions(this.widgets())\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n if (actions.length === 0) return true\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.mainIndex = (this.mainIndex + delta + actions.length) % actions.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const action = actions[this.mainIndex]\n if (action?.kind === \"apps-menu\") {\n this.view = \"apps\"\n this.appsIndex = 0\n this.appsScroll = 0\n this.render()\n } else if (action?.kind === \"notifications\") {\n void this.openNotificationsPanel()\n }\n return true\n }\n return false\n }\n\n private handleAppMenuGesture(gestureName: string): boolean {\n const rows = appMenuActions(this.menuApps())\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.appsIndex = (this.appsIndex + delta + rows.length) % rows.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const row = rows[this.appsIndex]\n if (row?.kind === \"app\") {\n void this.toggleApp(row.packageName)\n }\n return true\n }\n return false\n }\n\n private menuApps(): ClientApp[] {\n const menu = useSettingsStore.getState().getSetting(SETTINGS.menu_apps.key) as MenuAppSetting[] | null\n return orderedDashboardApps(menu, useAppStatusStore.getState().apps)\n }\n\n private widgets(): DashboardWidgetSetting[] {\n return normalizeDashboardWidgets(useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_widgets.key))\n }\n\n private async openNotificationsPanel(): Promise<void> {\n this.onRequestClose?.()\n await BluetoothSdk.showNativeNotificationPanel().catch((error) => {\n console.error(\"CustomDashboard: notification panel failed\", error)\n })\n }\n\n private async toggleApp(packageName: string): Promise<void> {\n this.onRequestClose?.()\n const app = useAppStatusStore.getState().apps.find((candidate) => candidate.packageName === packageName)\n if (!app) return\n if (app.running) {\n // Mirror the \"Starting X…\" boot message: without this the dashboard just\n // closes and the stop is invisible on the glasses.\n localDisplayManager.showSystemNotice(`Stopping ${app.name}…`, STOP_NOTICE_MS)\n await useAppStatusStore.getState().stop(app.packageName)\n } else {\n await useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }\n\n private calendarWidgetEnabled(): boolean {\n return this.widgets().some((widget) => widget.id === \"calendar\" && widget.enabled)\n }\n\n private async refreshCalendarAndRender(): Promise<void> {\n if (!this.open) return\n if (!this.calendarWidgetEnabled()) {\n this.calendarStatus = \"loading\"\n this.calendarEvents = []\n this.render()\n return\n }\n const settings = useSettingsStore.getState()\n const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2)\n const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3)\n const startsAt = new Date()\n const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)\n try {\n const result = await listPhoneCalendarEvents({\n startsAt: startsAt.toISOString(),\n endsAt: endsAt.toISOString(),\n limit: Math.max(1, Math.min(3, count)),\n })\n this.calendarStatus = \"ok\"\n this.calendarEvents = result.events\n } catch (error) {\n // Permission is requested from the Custom Dashboard settings screen;\n // only a real denial should read as one — any other failure is\n // \"Unavailable\" (and logged) so it can't masquerade as a permission\n // problem.\n const denied = error instanceof PhoneCalendarError && error.code === MiniappErrorCode.PERMISSION_DENIED\n this.calendarStatus = denied ? \"no-access\" : \"unavailable\"\n this.calendarEvents = []\n if (!denied) console.warn(\"CustomDashboard: calendar fetch failed\", error)\n }\n this.render()\n }\n\n /** Underline hugging a builtin-font label. x/rowY are the label box's origin. */\n private labelUnderline(id: string, x: number, rowY: number, label: string, maxW?: number): SceneElementInput {\n // Same metrics processScene uses to ellipsize builtin text; the raster\n // insets text by 2px, so the bar starts under the first glyph.\n const textW = new TextMeasurer(displayProcessor.getProfile()).measureText(label)\n const w = maxW === undefined ? textW + 4 : Math.min(textW + 4, maxW)\n return {\n type: \"rect\",\n id,\n box: {x: x + 2, y: rowY + UNDERLINE_OFFSET_Y, w, h: UNDERLINE_H},\n style: {border: 2},\n }\n }\n\n private canvas(): {width: number; height: number} {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (sceneCanvasWidth && sceneCanvasWidth > 0 && sceneCanvasHeight && sceneCanvasHeight > 0) {\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n }\n return FALLBACK_CANVAS\n }\n\n private render(): void {\n // Only while open: emitting into the single native view while closed\n // would clobber the main content.\n if (!this.open || !this.isEligible()) return\n const elements = this.view === \"apps\" ? this.appMenuElements() : this.mainElements()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", elements)\n }\n\n private mainElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const settings = useSettingsStore.getState()\n const widgets = this.widgets().filter((widget) => widget.enabled)\n const actions = dashboardActions(widgets)\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n const selected = actions[this.mainIndex]\n\n const clockStyle =\n (settings.getSetting(SETTINGS.custom_dashboard_clock_style.key) as CustomDashboardClockStyle) === \"large\"\n ? \"large\"\n : \"compact\"\n const calendarEnabled = widgets.some((widget) => widget.id === \"calendar\")\n // Calendar lives in the right half; everything else stacks in the left\n // half (full width when the calendar widget is off).\n const halfW = Math.floor(canvas.width / 2) - MARGIN_X - Math.floor(WIDGET_GAP / 2)\n const width = calendarEnabled ? halfW : canvas.width - MARGIN_X * 2\n const rightX = Math.floor(canvas.width / 2) + Math.floor(WIDGET_GAP / 2)\n\n const elements: SceneElementInput[] = []\n let y = MARGIN_TOP\n for (const widget of widgets) {\n if (widget.id === \"clock\") {\n const fontSize = clockStyle === \"large\" ? 48 : 32\n const clock = formatDashboardClock(new Date(), {\n twelveHour: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,\n showDate: settings.getSetting(SETTINGS.custom_dashboard_clock_show_date.key) !== false,\n })\n elements.push({\n type: \"text\",\n id: \"clock\",\n box: {x: MARGIN_X, y, w: width, h: fontSize + 6},\n text: clock.time,\n style: {font: \"mono\", fontSize, breakMode: \"strict-word\"},\n })\n y += fontSize + 6\n if (clock.date) {\n elements.push({\n type: \"text\",\n id: \"clock-date\",\n box: {x: MARGIN_X, y, w: width, h: LINE_H},\n text: clock.date,\n style: {font: \"builtin\", breakMode: \"strict-word\"},\n })\n y += LINE_H\n }\n y += WIDGET_GAP\n } else if (widget.id === \"calendar\") {\n // Right half, top-aligned, independent of the left stack. A missing\n // calendar shows a hint instead of silently vanishing — \"No access\"\n // strictly for permission denial, \"Unavailable\" for any other failure.\n const lines =\n this.calendarStatus === \"ok\"\n ? [\n \"Calendar\",\n ...(this.calendarEvents.length\n ? this.calendarEvents.slice(0, 3).map(eventLine)\n : [\"No upcoming events\"]),\n ]\n : this.calendarStatus === \"loading\"\n ? [\"Calendar\"]\n : [\"Calendar\", this.calendarStatus === \"no-access\" ? \"No access\" : \"Unavailable\"]\n elements.push({\n type: \"text\",\n id: \"calendar\",\n box: {x: rightX, y: MARGIN_TOP, w: halfW, h: lines.length * LINE_H},\n text: lines.join(\"\\n\"),\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n } else if (widget.id === \"apps\") {\n elements.push({\n type: \"text\",\n id: \"apps-button\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n })\n if (selected?.kind === \"apps-menu\") elements.push(this.labelUnderline(\"apps-underline\", MARGIN_X, y, \"Apps\"))\n y += BUTTON_H + WIDGET_GAP\n } else if (widget.id === \"notifications\") {\n elements.push({\n type: \"text\",\n id: \"notifications\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Notifications\",\n style: {font: \"builtin\"},\n })\n if (selected?.kind === \"notifications\") {\n elements.push(this.labelUnderline(\"notifications-underline\", MARGIN_X, y, \"Notifications\"))\n }\n y += BUTTON_H + WIDGET_GAP\n }\n }\n return elements\n }\n\n private appMenuElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const width = canvas.width - MARGIN_X * 2\n const apps = this.menuApps()\n const rows = appMenuActions(apps)\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n\n const elements: SceneElementInput[] = [\n {\n type: \"text\",\n id: \"apps-title\",\n box: {x: MARGIN_X, y: MARGIN_TOP, w: width, h: LINE_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n },\n ]\n const listTop = MARGIN_TOP + LINE_H + 16\n if (apps.length === 0) {\n elements.push({\n type: \"text\",\n id: \"apps-empty\",\n box: {x: MARGIN_X, y: listTop, w: width, h: LINE_H},\n text: \"No apps in Glasses Menu\",\n style: {font: \"builtin\"},\n })\n return elements\n }\n\n // Vertical side menu down the left half, stock-firmware style. The list\n // scrolls as a window: the selection stays visible and rows shift under it.\n const listW = Math.floor(canvas.width / 2) - MARGIN_X\n const visibleRows = Math.max(1, Math.floor((canvas.height - listTop) / BUTTON_H))\n const maxScroll = Math.max(0, apps.length - visibleRows)\n if (this.appsScroll > maxScroll) this.appsScroll = maxScroll\n if (this.appsIndex < this.appsScroll) this.appsScroll = this.appsIndex\n else if (this.appsIndex >= this.appsScroll + visibleRows) this.appsScroll = this.appsIndex - visibleRows + 1\n\n apps.slice(this.appsScroll, this.appsScroll + visibleRows).forEach((app, row) => {\n const y = listTop + row * BUTTON_H\n const label = app.running ? `${app.name} (running)` : app.name\n elements.push({\n type: \"text\",\n id: `app-row:${app.packageName}`,\n box: {x: MARGIN_X, y, w: listW, h: BUTTON_H},\n text: label,\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n if (this.appsScroll + row === this.appsIndex) {\n elements.push(this.labelUnderline(\"apps-selection\", MARGIN_X, y, label, listW - 2))\n }\n })\n return elements\n }\n}\n\nconst customDashboardService = new CustomDashboardService()\nexport default customDashboardService\nexport {CustomDashboardService}\n"]}
1
+ {"version":3,"file":"CustomDashboardService.js","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene API dashboard for G2 custom firmware.\n *\n * G2.swift marks the published firmware version after it detects the CFW\n * capability advertisement. DashboardCoordinator owns open/close and display\n * switching; this service renders the widgets and handles gestures while open.\n *\n * The dashboard is composed of user-orderable widgets (clock, calendar, apps\n * button, notifications button) stacked vertically on the CFW scene canvas.\n * The apps button switches to a second, dashboard-rendered app menu view.\n */\nimport {BluetoothSdk} from \"@glassly/bluetooth-sdk\"\nimport {MiniappErrorCode, type CalendarEvent} from \"@glassly/miniapp\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {ClientApp} from \"../types/applet\"\nimport {TextMeasurer} from \"../utils/display/measurer\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n appMenuActions,\n dashboardActions,\n formatDashboardClock,\n isCustomDashboardEligible,\n normalizeDashboardWidgets,\n orderedDashboardApps,\n type CustomDashboardClockStyle,\n type DashboardWidgetSetting,\n type MenuAppSetting,\n} from \"./CustomDashboardModel\"\nimport displayProcessor from \"./DisplayProcessor\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport {listPhoneCalendarEvents, PhoneCalendarError} from \"./PhoneCalendarService\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DASHBOARD_APP_ID = \"system.custom-dashboard\"\nconst CLOCK_REFRESH_MS = 30_000\nconst CALENDAR_REFRESH_MS = 5 * 60_000\n/** Matches the boot message's window; stops have no unmount-driven end signal. */\nconst STOP_NOTICE_MS = 1500\n\n// CFW blits the stock compositor's 576×288 canvas; used when the firmware has\n// not reported scene capabilities yet.\nconst FALLBACK_CANVAS = {width: 576, height: 288}\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\nconst WIDGET_GAP = 10\n/** Row height for the glasses' builtin 20px font engine. */\nconst LINE_H = 24\nconst BUTTON_H = 34\n/**\n * Selection marker: a text-hugging bar under the focused label instead of a\n * boxed border. Drawn as a rect (h 3, border 2 — the strokes overlap into a\n * solid bar) because mode 15 renders only glyphs; the bar must be in the\n * raster or the firmware never shows it.\n */\nconst UNDERLINE_H = 3\n/** Row-top → underline-top: just under the builtin font's 27px line. */\nconst UNDERLINE_OFFSET_Y = BUTTON_H - 4\n\nfunction sameLocalDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\n/** Local midnight after tomorrow: the widget never shows a \"day after tomorrow\". */\nfunction endOfTomorrow(now: Date): Date {\n const end = new Date(now)\n end.setHours(0, 0, 0, 0)\n end.setDate(end.getDate() + 2)\n return end\n}\n\n/** Up to three lines per entry: title, location (when set), then the time — \"tmr @ …\" for tomorrow. */\nfunction eventLines(event: CalendarEvent, now: Date): string[] {\n const lines = [event.title]\n const location = event.location?.trim()\n if (location) lines.push(location)\n const start = new Date(event.startsAt)\n const tomorrow = new Date(now)\n tomorrow.setDate(tomorrow.getDate() + 1)\n const isTomorrow = sameLocalDay(start, tomorrow)\n if (event.allDay) {\n lines.push(isTomorrow ? \"tmr, all day\" : \"All day\")\n } else {\n const time = new Intl.DateTimeFormat(undefined, {hour: \"numeric\", minute: \"2-digit\"}).format(start)\n lines.push(isTomorrow ? `tmr @ ${time}` : time)\n }\n return lines\n}\n\nclass CustomDashboardService {\n private started = false\n private open = false\n /** Set by DashboardCoordinator; asks it to close (and restore main content). */\n public onRequestClose: (() => void) | null = null\n private view: \"main\" | \"apps\" = \"main\"\n private mainIndex = 0\n private appsIndex = 0\n /** First visible row of the app list's scrolling window. */\n private appsScroll = 0\n /** \"loading\" until the first fetch resolves; \"no-access\" only for a real permission denial. */\n private calendarStatus: \"loading\" | \"ok\" | \"no-access\" | \"unavailable\" = \"loading\"\n private calendarEvents: CalendarEvent[] = []\n private unsubs: Array<() => void> = []\n private clockTimer: number | null = null\n private calendarTimer: number | null = null\n /** Last observed eligibility, for transition detection. */\n private eligible = false\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n useGlassesStore.subscribe(\n (state) => `${state.connection.state}:${state.deviceModel}:${state.firmwareVersion}`,\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n // Selector: only dashboard-relevant settings retrigger a refresh. The\n // whole-store form fired a calendar query on every settings write.\n useSettingsStore.subscribe(\n (state) =>\n JSON.stringify([\n state.getSetting(SETTINGS.custom_dashboard_enabled.key),\n state.getSetting(SETTINGS.custom_dashboard_widgets.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_style.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_show_date.key),\n state.getSetting(SETTINGS.twelve_hour_time.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key),\n state.getSetting(SETTINGS.menu_apps.key),\n ]),\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n useAppStatusStore.subscribe(() => {\n if (this.isEligible()) this.render()\n }),\n )\n this.clockTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) this.render()\n }, CLOCK_REFRESH_MS)\n this.calendarTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) void this.refreshCalendarAndRender()\n }, CALENDAR_REFRESH_MS)\n this.eligible = this.isEligible()\n }\n\n /**\n * Track eligible⇄ineligible transitions from any trigger (setting toggle,\n * connect/disconnect, firmware change). Leaving closes the dashboard via the\n * coordinator (which restores main content) and drops the retained scene so\n * a stale frame can't be replayed after the feature was turned off.\n */\n private syncEligibility(): boolean {\n const eligible = this.isEligible()\n if (eligible === this.eligible) return eligible\n this.eligible = eligible\n if (!eligible) {\n if (this.open) this.onRequestClose?.()\n this.open = false\n this.resetNavigation()\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n return eligible\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n if (this.clockTimer !== null) BgTimer.clearInterval(this.clockTimer)\n if (this.calendarTimer !== null) BgTimer.clearInterval(this.calendarTimer)\n this.clockTimer = null\n this.calendarTimer = null\n if (this.open) this.onRequestClose?.()\n this.open = false\n this.eligible = false\n this.resetNavigation()\n this.started = false\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n\n isEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({\n model: state.deviceModel,\n firmwareVersion: state.firmwareVersion,\n })\n )\n }\n\n get isOpen(): boolean {\n return this.open\n }\n\n /** Coordinator opened the dashboard: render fresh from the top. */\n show(): void {\n this.open = true\n this.resetNavigation()\n void this.refreshCalendarAndRender()\n }\n\n /** Coordinator closed the dashboard. */\n hide(): void {\n this.open = false\n this.resetNavigation()\n }\n\n /** Returns true when an open custom dashboard consumed the gesture. */\n handleGesture(gestureName: string): boolean {\n if (!this.open || !this.isEligible()) return false\n if (gestureName === \"double_tap\") {\n // In the app menu a double tap backs out to the top of the dashboard\n // (there is no Back row); on the main view it closes the dashboard.\n if (this.view === \"apps\") {\n this.resetNavigation()\n this.render()\n return true\n }\n this.onRequestClose?.()\n return true\n }\n if (this.view === \"apps\") return this.handleAppMenuGesture(gestureName)\n return this.handleMainGesture(gestureName)\n }\n\n private resetNavigation(): void {\n this.view = \"main\"\n this.mainIndex = 0\n this.appsIndex = 0\n this.appsScroll = 0\n }\n\n private handleMainGesture(gestureName: string): boolean {\n const actions = dashboardActions(this.widgets())\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n if (actions.length === 0) return true\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.mainIndex = (this.mainIndex + delta + actions.length) % actions.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const action = actions[this.mainIndex]\n if (action?.kind === \"apps-menu\") {\n this.view = \"apps\"\n this.appsIndex = 0\n this.appsScroll = 0\n this.render()\n } else if (action?.kind === \"notifications\") {\n void this.openNotificationsPanel()\n }\n return true\n }\n return false\n }\n\n private handleAppMenuGesture(gestureName: string): boolean {\n const rows = appMenuActions(this.menuApps())\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.appsIndex = (this.appsIndex + delta + rows.length) % rows.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const row = rows[this.appsIndex]\n if (row?.kind === \"app\") {\n void this.toggleApp(row.packageName)\n }\n return true\n }\n return false\n }\n\n private menuApps(): ClientApp[] {\n const menu = useSettingsStore.getState().getSetting(SETTINGS.menu_apps.key) as MenuAppSetting[] | null\n return orderedDashboardApps(menu, useAppStatusStore.getState().apps)\n }\n\n private widgets(): DashboardWidgetSetting[] {\n return normalizeDashboardWidgets(useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_widgets.key))\n }\n\n private async openNotificationsPanel(): Promise<void> {\n this.onRequestClose?.()\n await BluetoothSdk.showNativeNotificationPanel().catch((error) => {\n console.error(\"CustomDashboard: notification panel failed\", error)\n })\n }\n\n private async toggleApp(packageName: string): Promise<void> {\n this.onRequestClose?.()\n const app = useAppStatusStore.getState().apps.find((candidate) => candidate.packageName === packageName)\n if (!app) return\n if (app.running) {\n // Mirror the \"Starting X…\" boot message: without this the dashboard just\n // closes and the stop is invisible on the glasses.\n localDisplayManager.showSystemNotice(`Stopping ${app.name}…`, STOP_NOTICE_MS)\n await useAppStatusStore.getState().stop(app.packageName)\n } else {\n await useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }\n\n private calendarWidgetEnabled(): boolean {\n return this.widgets().some((widget) => widget.id === \"calendar\" && widget.enabled)\n }\n\n private async refreshCalendarAndRender(): Promise<void> {\n if (!this.open) return\n if (!this.calendarWidgetEnabled()) {\n this.calendarStatus = \"loading\"\n this.calendarEvents = []\n this.render()\n return\n }\n const settings = useSettingsStore.getState()\n const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2)\n const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3)\n const startsAt = new Date()\n // Clamped to local end-of-tomorrow: the widget only ever shows today and\n // tomorrow (\"tmr\"), whatever the days-ahead setting says.\n const daysEnd = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)\n const windowEnd = endOfTomorrow(startsAt)\n const endsAt = daysEnd < windowEnd ? daysEnd : windowEnd\n try {\n const result = await listPhoneCalendarEvents({\n startsAt: startsAt.toISOString(),\n endsAt: endsAt.toISOString(),\n limit: Math.max(1, Math.min(3, count)),\n })\n this.calendarStatus = \"ok\"\n this.calendarEvents = result.events\n } catch (error) {\n // Permission is requested from the Custom Dashboard settings screen;\n // only a real denial should read as one — any other failure is\n // \"Unavailable\" (and logged) so it can't masquerade as a permission\n // problem.\n const denied = error instanceof PhoneCalendarError && error.code === MiniappErrorCode.PERMISSION_DENIED\n this.calendarStatus = denied ? \"no-access\" : \"unavailable\"\n this.calendarEvents = []\n if (!denied) console.warn(\"CustomDashboard: calendar fetch failed\", error)\n }\n this.render()\n }\n\n /** Underline hugging a builtin-font label. x/rowY are the label box's origin. */\n private labelUnderline(id: string, x: number, rowY: number, label: string, maxW?: number): SceneElementInput {\n // Same metrics processScene uses to ellipsize builtin text; the raster\n // insets text by 2px, so the bar starts under the first glyph.\n const textW = new TextMeasurer(displayProcessor.getProfile()).measureText(label)\n const w = maxW === undefined ? textW + 4 : Math.min(textW + 4, maxW)\n return {\n type: \"rect\",\n id,\n box: {x: x + 2, y: rowY + UNDERLINE_OFFSET_Y, w, h: UNDERLINE_H},\n style: {border: 2},\n }\n }\n\n private canvas(): {width: number; height: number} {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (sceneCanvasWidth && sceneCanvasWidth > 0 && sceneCanvasHeight && sceneCanvasHeight > 0) {\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n }\n return FALLBACK_CANVAS\n }\n\n private render(): void {\n // Only while open: emitting into the single native view while closed\n // would clobber the main content.\n if (!this.open || !this.isEligible()) return\n const elements = this.view === \"apps\" ? this.appMenuElements() : this.mainElements()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", elements)\n }\n\n private mainElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const settings = useSettingsStore.getState()\n const widgets = this.widgets().filter((widget) => widget.enabled)\n const actions = dashboardActions(widgets)\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n const selected = actions[this.mainIndex]\n\n const clockStyle =\n (settings.getSetting(SETTINGS.custom_dashboard_clock_style.key) as CustomDashboardClockStyle) === \"large\"\n ? \"large\"\n : \"compact\"\n const calendarEnabled = widgets.some((widget) => widget.id === \"calendar\")\n // Calendar lives in the right half; everything else stacks in the left\n // half (full width when the calendar widget is off).\n const halfW = Math.floor(canvas.width / 2) - MARGIN_X - Math.floor(WIDGET_GAP / 2)\n const width = calendarEnabled ? halfW : canvas.width - MARGIN_X * 2\n const rightX = Math.floor(canvas.width / 2) + Math.floor(WIDGET_GAP / 2)\n\n const elements: SceneElementInput[] = []\n let y = MARGIN_TOP\n for (const widget of widgets) {\n if (widget.id === \"clock\") {\n const fontSize = clockStyle === \"large\" ? 48 : 32\n const clock = formatDashboardClock(new Date(), {\n twelveHour: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,\n showDate: settings.getSetting(SETTINGS.custom_dashboard_clock_show_date.key) !== false,\n })\n elements.push({\n type: \"text\",\n id: \"clock\",\n box: {x: MARGIN_X, y, w: width, h: fontSize + 6},\n text: clock.time,\n style: {font: \"mono\", fontSize, breakMode: \"strict-word\"},\n })\n y += fontSize + 6\n if (clock.date) {\n elements.push({\n type: \"text\",\n id: \"clock-date\",\n box: {x: MARGIN_X, y, w: width, h: LINE_H},\n text: clock.date,\n style: {font: \"builtin\", breakMode: \"strict-word\"},\n })\n y += LINE_H\n }\n y += WIDGET_GAP\n } else if (widget.id === \"calendar\") {\n // Right half, top-aligned, independent of the left stack. A missing\n // calendar shows a hint instead of silently vanishing — \"No access\"\n // strictly for permission denial, \"Unavailable\" for any other failure.\n const now = new Date()\n const lines =\n this.calendarStatus === \"ok\"\n ? [\n \"Calendar\",\n ...(this.calendarEvents.length\n ? this.calendarEvents.slice(0, 3).flatMap((event) => eventLines(event, now))\n : [\"No upcoming events\"]),\n ]\n : this.calendarStatus === \"loading\"\n ? [\"Calendar\"]\n : [\"Calendar\", this.calendarStatus === \"no-access\" ? \"No access\" : \"Unavailable\"]\n elements.push({\n type: \"text\",\n id: \"calendar\",\n box: {x: rightX, y: MARGIN_TOP, w: halfW, h: lines.length * LINE_H},\n text: lines.join(\"\\n\"),\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n } else if (widget.id === \"apps\") {\n elements.push({\n type: \"text\",\n id: \"apps-button\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n })\n if (selected?.kind === \"apps-menu\") elements.push(this.labelUnderline(\"apps-underline\", MARGIN_X, y, \"Apps\"))\n y += BUTTON_H + WIDGET_GAP\n } else if (widget.id === \"notifications\") {\n elements.push({\n type: \"text\",\n id: \"notifications\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Notifications\",\n style: {font: \"builtin\"},\n })\n if (selected?.kind === \"notifications\") {\n elements.push(this.labelUnderline(\"notifications-underline\", MARGIN_X, y, \"Notifications\"))\n }\n y += BUTTON_H + WIDGET_GAP\n }\n }\n return elements\n }\n\n private appMenuElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const width = canvas.width - MARGIN_X * 2\n const apps = this.menuApps()\n const rows = appMenuActions(apps)\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n\n const elements: SceneElementInput[] = [\n {\n type: \"text\",\n id: \"apps-title\",\n box: {x: MARGIN_X, y: MARGIN_TOP, w: width, h: LINE_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n },\n ]\n const listTop = MARGIN_TOP + LINE_H + 16\n if (apps.length === 0) {\n elements.push({\n type: \"text\",\n id: \"apps-empty\",\n box: {x: MARGIN_X, y: listTop, w: width, h: LINE_H},\n text: \"No apps in Glasses Menu\",\n style: {font: \"builtin\"},\n })\n return elements\n }\n\n // Vertical side menu down the left half, stock-firmware style. The list\n // scrolls as a window: the selection stays visible and rows shift under it.\n const listW = Math.floor(canvas.width / 2) - MARGIN_X\n const visibleRows = Math.max(1, Math.floor((canvas.height - listTop) / BUTTON_H))\n const maxScroll = Math.max(0, apps.length - visibleRows)\n if (this.appsScroll > maxScroll) this.appsScroll = maxScroll\n if (this.appsIndex < this.appsScroll) this.appsScroll = this.appsIndex\n else if (this.appsIndex >= this.appsScroll + visibleRows) this.appsScroll = this.appsIndex - visibleRows + 1\n\n apps.slice(this.appsScroll, this.appsScroll + visibleRows).forEach((app, row) => {\n const y = listTop + row * BUTTON_H\n const label = app.running ? `${app.name} (running)` : app.name\n elements.push({\n type: \"text\",\n id: `app-row:${app.packageName}`,\n box: {x: MARGIN_X, y, w: listW, h: BUTTON_H},\n text: label,\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n if (this.appsScroll + row === this.appsIndex) {\n elements.push(this.labelUnderline(\"apps-selection\", MARGIN_X, y, label, listW - 2))\n }\n })\n return elements\n }\n}\n\nconst customDashboardService = new CustomDashboardService()\nexport default customDashboardService\nexport {CustomDashboardService}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.1.0-dev.100",
3
+ "version": "0.1.0-dev.102",
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.1.0-dev.100",
101
- "@glassly/cloud-client": "0.1.0-dev.100",
102
- "@glassly/cloud-protocol": "0.1.0-dev.100",
103
- "@glassly/crust": "0.1.0-dev.100",
104
- "@glassly/miniapp": "0.1.0-dev.100",
100
+ "@glassly/bluetooth-sdk": "0.1.0-dev.102",
101
+ "@glassly/cloud-client": "0.1.0-dev.102",
102
+ "@glassly/cloud-protocol": "0.1.0-dev.102",
103
+ "@glassly/crust": "0.1.0-dev.102",
104
+ "@glassly/miniapp": "0.1.0-dev.102",
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.1.0-dev.100"
110
+ "@glassly/llm-providers": "0.1.0-dev.102"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@glassly/llm-providers": "workspace:*",
@@ -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.1.0",
15
- "releaseIdentity": "0.1.0-dev.100",
16
- "releaseSetId": "glassly-0.1.0-dev.100",
17
- "sourceCommit": "5759886304d66eec8e3088937b3b650fab1283b7",
15
+ "releaseIdentity": "0.1.0-dev.102",
16
+ "releaseSetId": "glassly-0.1.0-dev.102",
17
+ "sourceCommit": "25f1ae8ddfcd46216e222643d54f498d21e01d59",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -61,11 +61,34 @@ const UNDERLINE_H = 3
61
61
  /** Row-top → underline-top: just under the builtin font's 27px line. */
62
62
  const UNDERLINE_OFFSET_Y = BUTTON_H - 4
63
63
 
64
- function eventLine(event: CalendarEvent): string {
65
- const when = event.allDay
66
- ? "All day"
67
- : new Intl.DateTimeFormat(undefined, {hour: "numeric", minute: "2-digit"}).format(new Date(event.startsAt))
68
- return `${when} ${event.title}`
64
+ function sameLocalDay(a: Date, b: Date): boolean {
65
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
66
+ }
67
+
68
+ /** Local midnight after tomorrow: the widget never shows a "day after tomorrow". */
69
+ function endOfTomorrow(now: Date): Date {
70
+ const end = new Date(now)
71
+ end.setHours(0, 0, 0, 0)
72
+ end.setDate(end.getDate() + 2)
73
+ return end
74
+ }
75
+
76
+ /** Up to three lines per entry: title, location (when set), then the time — "tmr @ …" for tomorrow. */
77
+ function eventLines(event: CalendarEvent, now: Date): string[] {
78
+ const lines = [event.title]
79
+ const location = event.location?.trim()
80
+ if (location) lines.push(location)
81
+ const start = new Date(event.startsAt)
82
+ const tomorrow = new Date(now)
83
+ tomorrow.setDate(tomorrow.getDate() + 1)
84
+ const isTomorrow = sameLocalDay(start, tomorrow)
85
+ if (event.allDay) {
86
+ lines.push(isTomorrow ? "tmr, all day" : "All day")
87
+ } else {
88
+ const time = new Intl.DateTimeFormat(undefined, {hour: "numeric", minute: "2-digit"}).format(start)
89
+ lines.push(isTomorrow ? `tmr @ ${time}` : time)
90
+ }
91
+ return lines
69
92
  }
70
93
 
71
94
  class CustomDashboardService {
@@ -308,7 +331,11 @@ class CustomDashboardService {
308
331
  const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2)
309
332
  const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3)
310
333
  const startsAt = new Date()
311
- const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)
334
+ // Clamped to local end-of-tomorrow: the widget only ever shows today and
335
+ // tomorrow ("tmr"), whatever the days-ahead setting says.
336
+ const daysEnd = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)
337
+ const windowEnd = endOfTomorrow(startsAt)
338
+ const endsAt = daysEnd < windowEnd ? daysEnd : windowEnd
312
339
  try {
313
340
  const result = await listPhoneCalendarEvents({
314
341
  startsAt: startsAt.toISOString(),
@@ -411,12 +438,13 @@ class CustomDashboardService {
411
438
  // Right half, top-aligned, independent of the left stack. A missing
412
439
  // calendar shows a hint instead of silently vanishing — "No access"
413
440
  // strictly for permission denial, "Unavailable" for any other failure.
441
+ const now = new Date()
414
442
  const lines =
415
443
  this.calendarStatus === "ok"
416
444
  ? [
417
445
  "Calendar",
418
446
  ...(this.calendarEvents.length
419
- ? this.calendarEvents.slice(0, 3).map(eventLine)
447
+ ? this.calendarEvents.slice(0, 3).flatMap((event) => eventLines(event, now))
420
448
  : ["No upcoming events"]),
421
449
  ]
422
450
  : this.calendarStatus === "loading"