@glassly/engine 0.3.7-dev.906 → 0.3.7-dev.910

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/build/generated/releaseMetadata.js +3 -3
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/services/AppRegistry.d.ts.map +1 -1
  4. package/build/services/AppRegistry.js +29 -6
  5. package/build/services/AppRegistry.js.map +1 -1
  6. package/build/services/DisplayProcessor.d.ts +2 -0
  7. package/build/services/DisplayProcessor.d.ts.map +1 -1
  8. package/build/services/DisplayProcessor.js +13 -1
  9. package/build/services/DisplayProcessor.js.map +1 -1
  10. package/build/services/JSCrashloopReportService.d.ts.map +1 -1
  11. package/build/services/JSCrashloopReportService.js +5 -0
  12. package/build/services/JSCrashloopReportService.js.map +1 -1
  13. package/build/services/JSRouter.d.ts.map +1 -1
  14. package/build/services/JSRouter.js +6 -1
  15. package/build/services/JSRouter.js.map +1 -1
  16. package/build/services/SceneRenderer.js +1 -1
  17. package/build/services/SceneRenderer.js.map +1 -1
  18. package/build/stores/apps.d.ts.map +1 -1
  19. package/build/stores/apps.js +11 -2
  20. package/build/stores/apps.js.map +1 -1
  21. package/build/stores/settings.d.ts.map +1 -1
  22. package/build/stores/settings.js +10 -0
  23. package/build/stores/settings.js.map +1 -1
  24. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  25. package/build/types/capabilities/even-realities-g1.js +3 -0
  26. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  27. package/build/utils/display/index.d.ts +1 -1
  28. package/build/utils/display/index.d.ts.map +1 -1
  29. package/build/utils/display/index.js +2 -2
  30. package/build/utils/display/index.js.map +1 -1
  31. package/build/utils/display/profiles/index.d.ts +1 -1
  32. package/build/utils/display/profiles/index.d.ts.map +1 -1
  33. package/build/utils/display/profiles/index.js +2 -2
  34. package/build/utils/display/profiles/index.js.map +1 -1
  35. package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
  36. package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
  37. package/build/utils/display/profiles/rayneo-io.js +1 -1
  38. package/build/utils/display/profiles/rayneo-io.js.map +1 -1
  39. package/build/utils/display/scene/index.d.ts +1 -1
  40. package/build/utils/display/scene/index.d.ts.map +1 -1
  41. package/build/utils/display/scene/index.js +1 -1
  42. package/build/utils/display/scene/index.js.map +1 -1
  43. package/build/utils/display/scene/process.d.ts +6 -0
  44. package/build/utils/display/scene/process.d.ts.map +1 -1
  45. package/build/utils/display/scene/process.js +22 -7
  46. package/build/utils/display/scene/process.js.map +1 -1
  47. package/package.json +7 -7
  48. package/src/generated/releaseMetadata.ts +3 -3
  49. package/src/services/AppRegistry.ts +28 -6
  50. package/src/services/DisplayProcessor.ts +15 -0
  51. package/src/services/JSCrashloopReportService.ts +5 -0
  52. package/src/services/JSRouter.ts +6 -1
  53. package/src/services/SceneRenderer.ts +1 -1
  54. package/src/stores/apps.ts +11 -2
  55. package/src/stores/settings.ts +10 -0
  56. package/src/types/capabilities/even-realities-g1.ts +4 -0
  57. package/src/utils/display/index.ts +2 -2
  58. package/src/utils/display/profiles/index.ts +7 -2
  59. package/src/utils/display/profiles/rayneo-io.ts +6 -1
  60. package/src/utils/display/scene/index.ts +1 -1
  61. package/src/utils/display/scene/process.ts +22 -7
@@ -217,6 +217,7 @@ function projectApps(previousState: AppStatusState, localApps: ClientApp[]): Cli
217
217
  DeviceTypes.NONE
218
218
  const capabilities = getModelCapabilities(defaultWearable)
219
219
  const devToolsEnabled = useSettingsStore.getState().getSetting(SETTINGS.dev_tools.key) === true
220
+ const persistScreenshots = useSettingsStore.getState().getSetting(SETTINGS.persist_miniapp_screenshots.key) !== false
220
221
  const hasPairedGlasses = useSettingsStore.getState().getSetting(SETTINGS.has_paired_glasses.key) === true
221
222
 
222
223
  // Single pass: dedupe → screenshot carry-over → compat → hidden, all into
@@ -246,7 +247,10 @@ function projectApps(previousState: AppStatusState, localApps: ClientApp[]): Cli
246
247
  .map((app) => {
247
248
  const next: ClientApp = {
248
249
  ...app,
249
- screenshot: previousByPackage.get(app.packageName)?.screenshot ?? app.screenshot,
250
+ // The registry's screenshot is the one restored from disk; only an
251
+ // in-memory capture carries over when persistence is off.
252
+ screenshot:
253
+ previousByPackage.get(app.packageName)?.screenshot ?? (persistScreenshots ? app.screenshot : undefined),
250
254
  compatibility: HardwareCompatibility.checkCompatibility(app.hardwareRequirements, capabilities),
251
255
  // A registry source can force an app off the primary home screen.
252
256
  // Compose that gate with the user's persisted home-screen layout instead
@@ -543,7 +547,12 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
543
547
  },
544
548
 
545
549
  saveScreenshot: async (packageName: string, screenshot: string) => {
546
- storage.save(`${packageName}_screenshot`, screenshot)
550
+ const key = `${packageName}_screenshot`
551
+ if (useSettingsStore.getState().getSetting(SETTINGS.persist_miniapp_screenshots.key) !== false) {
552
+ storage.save(key, screenshot)
553
+ } else {
554
+ storage.remove(key)
555
+ }
547
556
  set((s) => ({
548
557
  apps: s.apps.map((a) => (a.packageName === packageName ? {...a, screenshot} : a)),
549
558
  }))
@@ -913,6 +913,16 @@ export const SETTINGS: Record<string, Setting> = {
913
913
  saveOnServer: false,
914
914
  persist: true,
915
915
  },
916
+ // Debug: keep each miniapp's switcher-card screenshot on disk so the card
917
+ // comes back after an app restart. Off keeps captures in memory only, for
918
+ // checking the capture path itself. Device-local, default on.
919
+ persist_miniapp_screenshots: {
920
+ key: "persist_miniapp_screenshots",
921
+ defaultValue: () => true,
922
+ writable: true,
923
+ saveOnServer: false,
924
+ persist: true,
925
+ },
916
926
  // Gesture-dots debug indicator: flash a glyph on the glasses when a touch
917
927
  // gesture is received while the display is empty. Works on any
918
928
  // scene-capable device, not only CFW. Device-local debug knob, default off.
@@ -68,4 +68,8 @@ export const evenRealitiesG1: Capabilities = {
68
68
 
69
69
  // OTA capabilities - G1 firmware updates are not driven by the ASG OTA flow
70
70
  hasOta: false,
71
+
72
+ // The app flashes the bundled Glassly CFW over MCUmgr SMP (see
73
+ // bluetooth-sdk/ios/Source/Resources/G1Firmware/README.md).
74
+ hasCustomFirmware: true,
71
75
  }
@@ -70,8 +70,8 @@ export {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from "./profile
70
70
  // Mentra Nex Profile (a.k.a. Mentra Display)
71
71
  export {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from "./profiles"
72
72
 
73
- // RayNeo iO Profile (stock subtitle page)
74
- export {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "./profiles"
73
+ // RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)
74
+ export {RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "./profiles"
75
75
 
76
76
  // =============================================================================
77
77
  // Measurer - Pixel-accurate text measurement
@@ -27,5 +27,10 @@ export {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from "./g2"
27
27
  // Mentra Nex Profile (a.k.a. Mentra Display)
28
28
  export {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from "./nex"
29
29
 
30
- // RayNeo iO Profile (stock subtitle page)
31
- export {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "./rayneo-io"
30
+ // RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)
31
+ export {
32
+ RAYNEO_IO_PROFILE,
33
+ RAYNEO_IO_CFW_PROFILE,
34
+ RAYNEO_IO_HYPHEN_WIDTH_PX,
35
+ RAYNEO_IO_SPACE_WIDTH_PX,
36
+ } from "./rayneo-io"
@@ -1,2 +1,7 @@
1
1
  // Moved into the SDK (@glassly/miniapp/display) so miniapps lay text out the way the host measures it; this path re-exports it for the engine.
2
- export {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "@glassly/miniapp/display"
2
+ export {
3
+ RAYNEO_IO_PROFILE,
4
+ RAYNEO_IO_CFW_PROFILE,
5
+ RAYNEO_IO_HYPHEN_WIDTH_PX,
6
+ RAYNEO_IO_SPACE_WIDTH_PX,
7
+ } from "@glassly/miniapp/display"
@@ -24,7 +24,7 @@ export {resolveAnchors} from "./anchor"
24
24
  export type {DiffResult, DiffableElement} from "./differ"
25
25
  export {diffScene} from "./differ"
26
26
  export type {ProcessedScene} from "./process"
27
- export {processScene, profileLineHeightPx} from "./process"
27
+ export {processScene, profileLineHeightPx, wrapsOnDevice} from "./process"
28
28
  export type {DegradedScene} from "./degrade"
29
29
  export {degradeScene} from "./degrade"
30
30
  export {SceneStore} from "./store"
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import {compileSvgPath} from "./svgPath"
12
+ import {isUnsupportedScript} from "../measurer/script-detection"
12
13
  import {TextMeasurer} from "../measurer/TextMeasurer"
13
14
  import {TextWrapper} from "../wrapper/TextWrapper"
14
15
  import type {DisplayProfile} from "../profiles/types"
@@ -47,6 +48,18 @@ function clampBox(box: SceneBox, width: number, height: number): SceneBox | null
47
48
 
48
49
  const isFinite = (n: unknown): n is number => typeof n === "number" && Number.isFinite(n)
49
50
 
51
+ /**
52
+ * Text the host cannot wrap: a script the measurer has no widths or break
53
+ * rules for (Thai has no spaces between words, for one). It is passed through
54
+ * unwrapped and the device's text engine lays it out in the box.
55
+ */
56
+ export function wrapsOnDevice(text: string): boolean {
57
+ for (const char of text) {
58
+ if (isUnsupportedScript(char)) return true
59
+ }
60
+ return false
61
+ }
62
+
50
63
  /** Integer points, or null when the count/values are invalid for the type. */
51
64
  function validPoints(el: SceneElementInput): ScenePoint[] | null {
52
65
  const raw = (el as {points?: unknown}).points
@@ -372,10 +385,11 @@ export function processScene(
372
385
  // Height clipping only applies with a CALIBRATED line height — otherwise
373
386
  // the firmware clips vertically in-box (legacy behavior).
374
387
  const style: SceneTextStyle = el.style ?? {}
375
- if (style.font === "mono" || style.font === "builtin") {
376
- // Grid text is laid out by the app (cols × rows) and builtin text by the
377
- // glasses' own font engine; the host's proportional wrap metrics don't
378
- // apply to either. Pass lines through and let the device clip.
388
+ if (style.font === "mono" || style.font === "builtin" || wrapsOnDevice(el.text ?? "")) {
389
+ // Grid text is laid out by the app (cols × rows), builtin text by the
390
+ // glasses' own font engine and unsupported scripts by the device's text
391
+ // engine; the host's proportional wrap metrics don't apply to any of
392
+ // them. Pass lines through and let the device wrap and clip.
379
393
  const monoText = el.text ?? ""
380
394
  out.push({
381
395
  id: el.id,
@@ -390,8 +404,9 @@ export function processScene(
390
404
  continue
391
405
  }
392
406
  const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines
407
+ const maxWidthPx = Math.max(1, clamped.w - 2 * (profile.textInsetPx ?? 0))
393
408
  const result = wrapper.wrap(el.text ?? "", {
394
- maxWidthPx: clamped.w,
409
+ maxWidthPx,
395
410
  maxLines,
396
411
  ...(style.breakMode ? {breakMode: style.breakMode} : {}),
397
412
  })
@@ -402,12 +417,12 @@ export function processScene(
402
417
  // Trim the last line until it fits WITH the ellipsis appended —
403
418
  // otherwise the extra glyph can overflow the clamped box width.
404
419
  let last = lines[lines.length - 1]
405
- while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {
420
+ while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, maxWidthPx)) {
406
421
  last = last.slice(0, -1)
407
422
  }
408
423
  // A box narrower than the ellipsis glyph itself gets an empty line —
409
424
  // never emit a line wider than the clamped box.
410
- const ellipsized = last.length > 0 || measurer.fitsInWidth("…", clamped.w) ? `${last}…` : ""
425
+ const ellipsized = last.length > 0 || measurer.fitsInWidth("…", maxWidthPx) ? `${last}…` : ""
411
426
  lines = [...lines.slice(0, -1), ellipsized]
412
427
  }
413
428
  }