@glassly/engine 0.3.7-dev.894 → 0.3.7-dev.897

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.3.7",
4
- "releaseIdentity": "0.3.7-dev.894",
5
- "releaseSetId": "glassly-0.3.7-dev.894",
6
- "sourceCommit": "c85c8d91f155af711ef6315801faef9652a5a789",
4
+ "releaseIdentity": "0.3.7-dev.897",
5
+ "releaseSetId": "glassly-0.3.7-dev.897",
6
+ "sourceCommit": "bae11161ee13fff66d3120dc51fd8593e80c6d56",
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.3.7\",\n \"releaseIdentity\": \"0.3.7-dev.894\",\n \"releaseSetId\": \"glassly-0.3.7-dev.894\",\n \"sourceCommit\": \"c85c8d91f155af711ef6315801faef9652a5a789\",\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.3.7\",\n \"releaseIdentity\": \"0.3.7-dev.897\",\n \"releaseSetId\": \"glassly-0.3.7-dev.897\",\n \"sourceCommit\": \"bae11161ee13fff66d3120dc51fd8593e80c6d56\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
@@ -12,6 +12,11 @@ export interface MiniappSafeArea {
12
12
  left: number;
13
13
  right: number;
14
14
  }
15
+ /**
16
+ * The host's capsule menu in the WebView's own coordinate space. `corner`
17
+ * floats over the page's top-right; `rail` sits in the iPhone Duo's status
18
+ * rail, inside the side safe-area inset and clear of the page content.
19
+ */
15
20
  export interface CapsuleMenuRect {
16
21
  top: number;
17
22
  right: number;
@@ -19,14 +24,8 @@ export interface CapsuleMenuRect {
19
24
  left: number;
20
25
  width: number;
21
26
  height: number;
27
+ placement: "corner" | "rail";
22
28
  }
23
- /**
24
- * @param topInsetOffset extra top offset to add when the WebView container does
25
- * NOT already pad by the safe-area inset (i.e. WebView fills edge-to-edge under
26
- * the status bar). Pass `insets.top` in that case, or 0 when the container
27
- * already applies top padding.
28
- */
29
- export declare function getCapsuleMenuRect(topInsetOffset?: number): CapsuleMenuRect;
30
29
  export type MiniappColorScheme = "light" | "dark";
31
30
  export interface BuildMiniappGlobalsOptions {
32
31
  packageName?: string;
@@ -34,21 +33,18 @@ export interface BuildMiniappGlobalsOptions {
34
33
  miniappLocal?: boolean;
35
34
  miniappDeveloperMode?: boolean;
36
35
  safeAreaInsets: MiniappSafeArea;
37
- /**
38
- * True when the WebView container renders edge-to-edge (no top padding for
39
- * the status bar). In that case the capsule menu rect needs to shift down by
40
- * the top inset so its coords match where it's actually drawn on screen.
41
- */
42
- webviewFillsStatusBar?: boolean;
36
+ /** Where the host draws its capsule menu over this WebView. */
37
+ capsuleMenu: CapsuleMenuRect;
43
38
  /** Current host color scheme. Miniapps may follow this to match the phone. */
44
39
  colorScheme?: MiniappColorScheme;
45
40
  }
46
41
  /**
47
- * JS to inject into a running miniapp when the host's safe area changes (an
48
- * iPhone Duo folding moves its status rail): updates window.Glassly and the
49
- * CSS variables, then fires `glassly:safe-area-change`.
42
+ * JS to inject into a running miniapp when the host's safe area or capsule
43
+ * menu moves (an iPhone Duo folding, or its rail's reserved regions growing):
44
+ * updates window.Glassly and the CSS variables, then fires
45
+ * `glassly:safe-area-change`.
50
46
  */
51
- export declare function buildSafeAreaUpdateScript(insets: MiniappSafeArea): string;
47
+ export declare function buildSafeAreaUpdateScript(insets: MiniappSafeArea, capsuleMenu: CapsuleMenuRect): string;
52
48
  /**
53
49
  * Returns the JS string to inject into a miniapp WebView before its content
54
50
  * loads. Sets window.Glassly with the standard fields miniapps read:
@@ -1 +1 @@
1
- {"version":3,"file":"miniappGlobals.d.ts","sourceRoot":"","sources":["../../src/utils/miniappGlobals.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,SAAI,GAAG,eAAe,CAWtE;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AAEjD,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,cAAc,EAAE,eAAe,CAAA;IAC/B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,kBAAkB,CAAA;CACjC;AAWD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAazE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B,GAAG,MAAM,CAwKlF"}
1
+ {"version":3,"file":"miniappGlobals.d.ts","sourceRoot":"","sources":["../../src/utils/miniappGlobals.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,QAAQ,GAAG,MAAM,CAAA;CAC7B;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AAEjD,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,cAAc,EAAE,eAAe,CAAA;IAC/B,+DAA+D;IAC/D,WAAW,EAAE,eAAe,CAAA;IAC5B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,kBAAkB,CAAA;CACjC;AAwBD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,GAAG,MAAM,CAcvG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B,GAAG,MAAM,CAqJlF"}
@@ -6,55 +6,42 @@
6
6
  * this util is the one place that builds the globals so every WebView gets the
7
7
  * same API surface.
8
8
  */
9
- import { Dimensions, Platform } from "react-native";
10
- /**
11
- * The capsule menu bounding rect in the WebView's own coordinate space.
12
- *
13
- * Keep in sync with CapsuleMenu.tsx:
14
- * - CapsuleButton height ≈ h-7.5 (30px), width ≈ 73px
15
- * - Positioned at right-2 (8px), top = theme.spacing.s2 (8px) below insets.top
16
- */
17
- const CAPSULE_MENU_HEIGHT = 30;
18
- const CAPSULE_MENU_WIDTH = 73;
19
- const CAPSULE_MENU_PADDING = 8;
20
- /**
21
- * @param topInsetOffset extra top offset to add when the WebView container does
22
- * NOT already pad by the safe-area inset (i.e. WebView fills edge-to-edge under
23
- * the status bar). Pass `insets.top` in that case, or 0 when the container
24
- * already applies top padding.
25
- */
26
- export function getCapsuleMenuRect(topInsetOffset = 0) {
27
- const screenWidth = Dimensions.get("window").width;
28
- const top = topInsetOffset + CAPSULE_MENU_PADDING;
29
- return {
30
- top,
31
- right: CAPSULE_MENU_PADDING,
32
- bottom: top + CAPSULE_MENU_HEIGHT,
33
- left: screenWidth - CAPSULE_MENU_PADDING - CAPSULE_MENU_WIDTH,
34
- width: CAPSULE_MENU_WIDTH,
35
- height: CAPSULE_MENU_HEIGHT,
36
- };
37
- }
38
- function safeAreaCssVars(insets) {
9
+ import { Platform } from "react-native";
10
+ // CSS custom properties that mirror the safe-area and capsule menu data, so
11
+ // miniapp CSS (or Tailwind arbitrary values) can read them without touching
12
+ // JS: e.g. `style="margin-top: var(--glassly-capsule-top)"`.
13
+ function layoutCssVars(insets, capsule) {
39
14
  return {
40
15
  "--glassly-safe-top": `${insets.top}px`,
41
16
  "--glassly-safe-bottom": `${insets.bottom}px`,
42
17
  "--glassly-safe-left": `${insets.left}px`,
43
18
  "--glassly-safe-right": `${insets.right}px`,
19
+ "--glassly-capsule-top": `${capsule.top}px`,
20
+ "--glassly-capsule-bottom": `${capsule.bottom}px`,
21
+ "--glassly-capsule-left": `${capsule.left}px`,
22
+ "--glassly-capsule-right": `${capsule.right}px`,
23
+ "--glassly-capsule-width": `${capsule.width}px`,
24
+ "--glassly-capsule-height": `${capsule.height}px`,
25
+ "--glassly-capsule-center-y": `${capsule.top + capsule.height / 2}px`,
26
+ // Right-side gutter to reserve so content doesn't slide under the
27
+ // capsule: its width + 16px breathing room, none when it sits in the rail.
28
+ "--glassly-capsule-gutter": `${capsule.placement === "rail" ? 0 : capsule.width + 16}px`,
44
29
  };
45
30
  }
46
31
  /**
47
- * JS to inject into a running miniapp when the host's safe area changes (an
48
- * iPhone Duo folding moves its status rail): updates window.Glassly and the
49
- * CSS variables, then fires `glassly:safe-area-change`.
32
+ * JS to inject into a running miniapp when the host's safe area or capsule
33
+ * menu moves (an iPhone Duo folding, or its rail's reserved regions growing):
34
+ * updates window.Glassly and the CSS variables, then fires
35
+ * `glassly:safe-area-change`.
50
36
  */
51
- export function buildSafeAreaUpdateScript(insets) {
52
- const sets = Object.entries(safeAreaCssVars(insets))
37
+ export function buildSafeAreaUpdateScript(insets, capsuleMenu) {
38
+ const sets = Object.entries(layoutCssVars(insets, capsuleMenu))
53
39
  .map(([k, v]) => `document.documentElement.style.setProperty(${JSON.stringify(k)}, ${JSON.stringify(v)});`)
54
40
  .join("\n ");
55
41
  return `
56
42
  window.Glassly = window.Glassly || {};
57
43
  window.Glassly.safeAreaInsets = ${JSON.stringify(insets)};
44
+ window.Glassly.capsuleMenu = ${JSON.stringify(capsuleMenu)};
58
45
  try {
59
46
  ${sets}
60
47
  } catch (e) { /* ignore */ }
@@ -75,12 +62,11 @@ export function buildSafeAreaUpdateScript(insets) {
75
62
  * assign to it before the native bridge wires in.
76
63
  */
77
64
  export function buildMiniappGlobalsScript(opts) {
78
- const capsuleTopOffset = opts.webviewFillsStatusBar ? opts.safeAreaInsets.top : 0;
79
65
  const globals = {
80
66
  platform: Platform.OS,
81
67
  capabilities: opts.capabilities ?? ["share", "open_url", "copy_clipboard", "download"],
82
68
  safeAreaInsets: opts.safeAreaInsets,
83
- capsuleMenu: getCapsuleMenuRect(capsuleTopOffset),
69
+ capsuleMenu: opts.capsuleMenu,
84
70
  };
85
71
  if (opts.packageName)
86
72
  globals.packageName = opts.packageName;
@@ -90,25 +76,7 @@ export function buildMiniappGlobalsScript(opts) {
90
76
  globals.miniappDeveloperMode = true;
91
77
  if (opts.colorScheme)
92
78
  globals.colorScheme = opts.colorScheme;
93
- // CSS custom properties that mirror the capsule menu / safe-area data.
94
- // Miniapp CSS (or Tailwind arbitrary values) can read these without
95
- // touching JS: e.g. `style="margin-top: var(--glassly-capsule-top)"`.
96
- const capsule = globals.capsuleMenu;
97
- const insets = opts.safeAreaInsets;
98
- const capsuleCenter = capsule.top + capsule.height / 2;
99
- const cssVars = {
100
- ...safeAreaCssVars(insets),
101
- "--glassly-capsule-top": `${capsule.top}px`,
102
- "--glassly-capsule-bottom": `${capsule.bottom}px`,
103
- "--glassly-capsule-left": `${capsule.left}px`,
104
- "--glassly-capsule-right": `${capsule.right}px`,
105
- "--glassly-capsule-width": `${capsule.width}px`,
106
- "--glassly-capsule-height": `${capsule.height}px`,
107
- "--glassly-capsule-center-y": `${capsuleCenter}px`,
108
- // Right-side gutter to reserve so content doesn't slide under the
109
- // capsule: capsule width + 16px breathing room.
110
- "--glassly-capsule-gutter": `${capsule.width + 16}px`,
111
- };
79
+ const cssVars = layoutCssVars(opts.safeAreaInsets, opts.capsuleMenu);
112
80
  const cssVarsBlock = Object.entries(cssVars)
113
81
  .map(([k, v]) => `${k}: ${v};`)
114
82
  .join(" ");
@@ -1 +1 @@
1
- {"version":3,"file":"miniappGlobals.js","sourceRoot":"","sources":["../../src/utils/miniappGlobals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * miniappGlobals — shared builder for the window.Glassly globals injected\n * into every miniapp WebView.\n *\n * Miniapps run on-device, and LocalMiniappView is the single injection path;\n * this util is the one place that builds the globals so every WebView gets the\n * same API surface.\n */\n\nimport {Dimensions, Platform} from \"react-native\"\n\nexport interface MiniappSafeArea {\n top: number\n bottom: number\n left: number\n right: number\n}\n\nexport interface CapsuleMenuRect {\n top: number\n right: number\n bottom: number\n left: number\n width: number\n height: number\n}\n\n/**\n * The capsule menu bounding rect in the WebView's own coordinate space.\n *\n * Keep in sync with CapsuleMenu.tsx:\n * - CapsuleButton height ≈ h-7.5 (30px), width ≈ 73px\n * - Positioned at right-2 (8px), top = theme.spacing.s2 (8px) below insets.top\n */\nconst CAPSULE_MENU_HEIGHT = 30\nconst CAPSULE_MENU_WIDTH = 73\nconst CAPSULE_MENU_PADDING = 8\n\n/**\n * @param topInsetOffset extra top offset to add when the WebView container does\n * NOT already pad by the safe-area inset (i.e. WebView fills edge-to-edge under\n * the status bar). Pass `insets.top` in that case, or 0 when the container\n * already applies top padding.\n */\nexport function getCapsuleMenuRect(topInsetOffset = 0): CapsuleMenuRect {\n const screenWidth = Dimensions.get(\"window\").width\n const top = topInsetOffset + CAPSULE_MENU_PADDING\n return {\n top,\n right: CAPSULE_MENU_PADDING,\n bottom: top + CAPSULE_MENU_HEIGHT,\n left: screenWidth - CAPSULE_MENU_PADDING - CAPSULE_MENU_WIDTH,\n width: CAPSULE_MENU_WIDTH,\n height: CAPSULE_MENU_HEIGHT,\n }\n}\n\nexport type MiniappColorScheme = \"light\" | \"dark\"\n\nexport interface BuildMiniappGlobalsOptions {\n packageName?: string\n capabilities?: string[]\n miniappLocal?: boolean\n miniappDeveloperMode?: boolean\n safeAreaInsets: MiniappSafeArea\n /**\n * True when the WebView container renders edge-to-edge (no top padding for\n * the status bar). In that case the capsule menu rect needs to shift down by\n * the top inset so its coords match where it's actually drawn on screen.\n */\n webviewFillsStatusBar?: boolean\n /** Current host color scheme. Miniapps may follow this to match the phone. */\n colorScheme?: MiniappColorScheme\n}\n\nfunction safeAreaCssVars(insets: MiniappSafeArea): Record<string, string> {\n return {\n \"--glassly-safe-top\": `${insets.top}px`,\n \"--glassly-safe-bottom\": `${insets.bottom}px`,\n \"--glassly-safe-left\": `${insets.left}px`,\n \"--glassly-safe-right\": `${insets.right}px`,\n }\n}\n\n/**\n * JS to inject into a running miniapp when the host's safe area changes (an\n * iPhone Duo folding moves its status rail): updates window.Glassly and the\n * CSS variables, then fires `glassly:safe-area-change`.\n */\nexport function buildSafeAreaUpdateScript(insets: MiniappSafeArea): string {\n const sets = Object.entries(safeAreaCssVars(insets))\n .map(([k, v]) => `document.documentElement.style.setProperty(${JSON.stringify(k)}, ${JSON.stringify(v)});`)\n .join(\"\\n \")\n return `\n window.Glassly = window.Glassly || {};\n window.Glassly.safeAreaInsets = ${JSON.stringify(insets)};\n try {\n ${sets}\n } catch (e) { /* ignore */ }\n window.dispatchEvent(new Event(\"glassly:safe-area-change\"));\n true;\n `\n}\n\n/**\n * Returns the JS string to inject into a miniapp WebView before its content\n * loads. Sets window.Glassly with the standard fields miniapps read:\n *\n * window.Glassly = {\n * platform, packageName?, capabilities, safeAreaInsets, capsuleMenu,\n * miniappLocal?, miniappDeveloperMode?\n * }\n *\n * Also defines a stub window.receiveNativeMessage so the miniapp can safely\n * assign to it before the native bridge wires in.\n */\nexport function buildMiniappGlobalsScript(opts: BuildMiniappGlobalsOptions): string {\n const capsuleTopOffset = opts.webviewFillsStatusBar ? opts.safeAreaInsets.top : 0\n const globals: Record<string, unknown> = {\n platform: Platform.OS,\n capabilities: opts.capabilities ?? [\"share\", \"open_url\", \"copy_clipboard\", \"download\"],\n safeAreaInsets: opts.safeAreaInsets,\n capsuleMenu: getCapsuleMenuRect(capsuleTopOffset),\n }\n if (opts.packageName) globals.packageName = opts.packageName\n if (opts.miniappLocal) globals.miniappLocal = true\n if (opts.miniappDeveloperMode) globals.miniappDeveloperMode = true\n if (opts.colorScheme) globals.colorScheme = opts.colorScheme\n\n // CSS custom properties that mirror the capsule menu / safe-area data.\n // Miniapp CSS (or Tailwind arbitrary values) can read these without\n // touching JS: e.g. `style=\"margin-top: var(--glassly-capsule-top)\"`.\n const capsule = globals.capsuleMenu as CapsuleMenuRect\n const insets = opts.safeAreaInsets\n const capsuleCenter = capsule.top + capsule.height / 2\n const cssVars: Record<string, string> = {\n ...safeAreaCssVars(insets),\n \"--glassly-capsule-top\": `${capsule.top}px`,\n \"--glassly-capsule-bottom\": `${capsule.bottom}px`,\n \"--glassly-capsule-left\": `${capsule.left}px`,\n \"--glassly-capsule-right\": `${capsule.right}px`,\n \"--glassly-capsule-width\": `${capsule.width}px`,\n \"--glassly-capsule-height\": `${capsule.height}px`,\n \"--glassly-capsule-center-y\": `${capsuleCenter}px`,\n // Right-side gutter to reserve so content doesn't slide under the\n // capsule: capsule width + 16px breathing room.\n \"--glassly-capsule-gutter\": `${capsule.width + 16}px`,\n }\n const cssVarsBlock = Object.entries(cssVars)\n .map(([k, v]) => `${k}: ${v};`)\n .join(\" \")\n\n // Console-tap shim: wrap console.log/warn/error/info/debug so each call\n // also forwards a `dev_log` envelope back to the phone via\n // ReactNativeWebView.postMessage. Dev mode forwards to the laptop's\n // `glassly dev` terminal; non-dev mode falls back to the React\n // Native log stream (Metro / Xcode console / adb logcat) so installed-\n // miniapp errors are still inspectable when there's no laptop sidecar.\n //\n // Also tap window.onerror and window.onunhandledrejection so synchronous\n // throws and unhandled promise rejections surface even when the bundle\n // never gets a chance to call console.error itself.\n const consoleTapBlock = `\n (function() {\n try {\n var levels = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var seen = new WeakSet();\n function safeSerialize(arg) {\n if (arg instanceof Error) return {__error: true, message: arg.message, stack: arg.stack};\n if (arg === null || typeof arg !== \"object\") return arg;\n try {\n return JSON.parse(JSON.stringify(arg, function(k, v) {\n if (v !== null && typeof v === \"object\") {\n if (seen.has(v)) return \"[Circular]\";\n seen.add(v);\n }\n return v;\n }));\n } catch (e) {\n try { return String(arg); } catch (_) { return \"[unserializable]\"; }\n }\n }\n levels.forEach(function(level) {\n var original = console[level];\n if (typeof original !== \"function\") return;\n console[level] = function() {\n try { original.apply(console, arguments); } catch (_) {}\n try {\n var args = Array.prototype.slice.call(arguments).map(safeSerialize);\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: level,\n args: args,\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) { /* swallow */ }\n };\n });\n } catch (_) { /* ignore */ }\n\n // Surface synchronous errors and unhandled promise rejections via the\n // same dev_log channel — many bundle-startup crashes never get to call\n // console.error themselves.\n try {\n window.addEventListener(\"error\", function(e) {\n try {\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: \"error\",\n args: [\"[window.error] \" + (e.message || \"unknown\"), {filename: e.filename, lineno: e.lineno, colno: e.colno, stack: e.error && e.error.stack}],\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) {}\n });\n window.addEventListener(\"unhandledrejection\", function(e) {\n try {\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n var reason = e.reason;\n var serialized = reason instanceof Error\n ? {__error: true, message: reason.message, stack: reason.stack}\n : (typeof reason === \"object\" ? JSON.stringify(reason) : String(reason));\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: \"error\",\n args: [\"[unhandledrejection]\", serialized],\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) {}\n });\n } catch (_) {}\n })();\n `\n\n // Lock zoom so miniapps feel like apps, not pages: force a non-scalable\n // viewport meta (overriding whatever the miniapp shipped) and disable\n // double-tap-zoom via CSS. Re-applies on DOMContentLoaded in case the\n // miniapp re-writes <head> during boot.\n return `\n window.Glassly = ${JSON.stringify(globals)};\n window.receiveNativeMessage = window.receiveNativeMessage || function() {};\n (function() {\n try {\n var styleEl = document.createElement(\"style\");\n styleEl.setAttribute(\"data-glassly-injected\", \"1\");\n styleEl.textContent =\n \":root { ${cssVarsBlock} }\" +\n \"html, body { touch-action: manipulation; -ms-content-zooming: none; }\";\n (document.head || document.documentElement).appendChild(styleEl);\n } catch (e) { /* ignore */ }\n\n function lockViewport() {\n try {\n var content = \"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover\";\n var existing = document.querySelector('meta[name=\"viewport\"]');\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n var meta = document.createElement(\"meta\");\n meta.setAttribute(\"name\", \"viewport\");\n meta.setAttribute(\"content\", content);\n (document.head || document.documentElement).appendChild(meta);\n }\n } catch (e) { /* ignore */ }\n }\n lockViewport();\n document.addEventListener(\"DOMContentLoaded\", lockViewport);\n })();\n ${consoleTapBlock}\n true;\n `\n}\n"]}
1
+ {"version":3,"file":"miniappGlobals.js","sourceRoot":"","sources":["../../src/utils/miniappGlobals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * miniappGlobals — shared builder for the window.Glassly globals injected\n * into every miniapp WebView.\n *\n * Miniapps run on-device, and LocalMiniappView is the single injection path;\n * this util is the one place that builds the globals so every WebView gets the\n * same API surface.\n */\n\nimport {Platform} from \"react-native\"\n\nexport interface MiniappSafeArea {\n top: number\n bottom: number\n left: number\n right: number\n}\n\n/**\n * The host's capsule menu in the WebView's own coordinate space. `corner`\n * floats over the page's top-right; `rail` sits in the iPhone Duo's status\n * rail, inside the side safe-area inset and clear of the page content.\n */\nexport interface CapsuleMenuRect {\n top: number\n right: number\n bottom: number\n left: number\n width: number\n height: number\n placement: \"corner\" | \"rail\"\n}\n\nexport type MiniappColorScheme = \"light\" | \"dark\"\n\nexport interface BuildMiniappGlobalsOptions {\n packageName?: string\n capabilities?: string[]\n miniappLocal?: boolean\n miniappDeveloperMode?: boolean\n safeAreaInsets: MiniappSafeArea\n /** Where the host draws its capsule menu over this WebView. */\n capsuleMenu: CapsuleMenuRect\n /** Current host color scheme. Miniapps may follow this to match the phone. */\n colorScheme?: MiniappColorScheme\n}\n\n// CSS custom properties that mirror the safe-area and capsule menu data, so\n// miniapp CSS (or Tailwind arbitrary values) can read them without touching\n// JS: e.g. `style=\"margin-top: var(--glassly-capsule-top)\"`.\nfunction layoutCssVars(insets: MiniappSafeArea, capsule: CapsuleMenuRect): Record<string, string> {\n return {\n \"--glassly-safe-top\": `${insets.top}px`,\n \"--glassly-safe-bottom\": `${insets.bottom}px`,\n \"--glassly-safe-left\": `${insets.left}px`,\n \"--glassly-safe-right\": `${insets.right}px`,\n \"--glassly-capsule-top\": `${capsule.top}px`,\n \"--glassly-capsule-bottom\": `${capsule.bottom}px`,\n \"--glassly-capsule-left\": `${capsule.left}px`,\n \"--glassly-capsule-right\": `${capsule.right}px`,\n \"--glassly-capsule-width\": `${capsule.width}px`,\n \"--glassly-capsule-height\": `${capsule.height}px`,\n \"--glassly-capsule-center-y\": `${capsule.top + capsule.height / 2}px`,\n // Right-side gutter to reserve so content doesn't slide under the\n // capsule: its width + 16px breathing room, none when it sits in the rail.\n \"--glassly-capsule-gutter\": `${capsule.placement === \"rail\" ? 0 : capsule.width + 16}px`,\n }\n}\n\n/**\n * JS to inject into a running miniapp when the host's safe area or capsule\n * menu moves (an iPhone Duo folding, or its rail's reserved regions growing):\n * updates window.Glassly and the CSS variables, then fires\n * `glassly:safe-area-change`.\n */\nexport function buildSafeAreaUpdateScript(insets: MiniappSafeArea, capsuleMenu: CapsuleMenuRect): string {\n const sets = Object.entries(layoutCssVars(insets, capsuleMenu))\n .map(([k, v]) => `document.documentElement.style.setProperty(${JSON.stringify(k)}, ${JSON.stringify(v)});`)\n .join(\"\\n \")\n return `\n window.Glassly = window.Glassly || {};\n window.Glassly.safeAreaInsets = ${JSON.stringify(insets)};\n window.Glassly.capsuleMenu = ${JSON.stringify(capsuleMenu)};\n try {\n ${sets}\n } catch (e) { /* ignore */ }\n window.dispatchEvent(new Event(\"glassly:safe-area-change\"));\n true;\n `\n}\n\n/**\n * Returns the JS string to inject into a miniapp WebView before its content\n * loads. Sets window.Glassly with the standard fields miniapps read:\n *\n * window.Glassly = {\n * platform, packageName?, capabilities, safeAreaInsets, capsuleMenu,\n * miniappLocal?, miniappDeveloperMode?\n * }\n *\n * Also defines a stub window.receiveNativeMessage so the miniapp can safely\n * assign to it before the native bridge wires in.\n */\nexport function buildMiniappGlobalsScript(opts: BuildMiniappGlobalsOptions): string {\n const globals: Record<string, unknown> = {\n platform: Platform.OS,\n capabilities: opts.capabilities ?? [\"share\", \"open_url\", \"copy_clipboard\", \"download\"],\n safeAreaInsets: opts.safeAreaInsets,\n capsuleMenu: opts.capsuleMenu,\n }\n if (opts.packageName) globals.packageName = opts.packageName\n if (opts.miniappLocal) globals.miniappLocal = true\n if (opts.miniappDeveloperMode) globals.miniappDeveloperMode = true\n if (opts.colorScheme) globals.colorScheme = opts.colorScheme\n\n const cssVars = layoutCssVars(opts.safeAreaInsets, opts.capsuleMenu)\n const cssVarsBlock = Object.entries(cssVars)\n .map(([k, v]) => `${k}: ${v};`)\n .join(\" \")\n\n // Console-tap shim: wrap console.log/warn/error/info/debug so each call\n // also forwards a `dev_log` envelope back to the phone via\n // ReactNativeWebView.postMessage. Dev mode forwards to the laptop's\n // `glassly dev` terminal; non-dev mode falls back to the React\n // Native log stream (Metro / Xcode console / adb logcat) so installed-\n // miniapp errors are still inspectable when there's no laptop sidecar.\n //\n // Also tap window.onerror and window.onunhandledrejection so synchronous\n // throws and unhandled promise rejections surface even when the bundle\n // never gets a chance to call console.error itself.\n const consoleTapBlock = `\n (function() {\n try {\n var levels = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var seen = new WeakSet();\n function safeSerialize(arg) {\n if (arg instanceof Error) return {__error: true, message: arg.message, stack: arg.stack};\n if (arg === null || typeof arg !== \"object\") return arg;\n try {\n return JSON.parse(JSON.stringify(arg, function(k, v) {\n if (v !== null && typeof v === \"object\") {\n if (seen.has(v)) return \"[Circular]\";\n seen.add(v);\n }\n return v;\n }));\n } catch (e) {\n try { return String(arg); } catch (_) { return \"[unserializable]\"; }\n }\n }\n levels.forEach(function(level) {\n var original = console[level];\n if (typeof original !== \"function\") return;\n console[level] = function() {\n try { original.apply(console, arguments); } catch (_) {}\n try {\n var args = Array.prototype.slice.call(arguments).map(safeSerialize);\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: level,\n args: args,\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) { /* swallow */ }\n };\n });\n } catch (_) { /* ignore */ }\n\n // Surface synchronous errors and unhandled promise rejections via the\n // same dev_log channel — many bundle-startup crashes never get to call\n // console.error themselves.\n try {\n window.addEventListener(\"error\", function(e) {\n try {\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: \"error\",\n args: [\"[window.error] \" + (e.message || \"unknown\"), {filename: e.filename, lineno: e.lineno, colno: e.colno, stack: e.error && e.error.stack}],\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) {}\n });\n window.addEventListener(\"unhandledrejection\", function(e) {\n try {\n if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === \"function\") {\n var reason = e.reason;\n var serialized = reason instanceof Error\n ? {__error: true, message: reason.message, stack: reason.stack}\n : (typeof reason === \"object\" ? JSON.stringify(reason) : String(reason));\n window.ReactNativeWebView.postMessage(JSON.stringify({\n payload: {\n type: \"dev_log\",\n level: \"error\",\n args: [\"[unhandledrejection]\", serialized],\n packageName: (window.Glassly && window.Glassly.packageName) || null,\n timestamp: Date.now()\n }\n }));\n }\n } catch (_) {}\n });\n } catch (_) {}\n })();\n `\n\n // Lock zoom so miniapps feel like apps, not pages: force a non-scalable\n // viewport meta (overriding whatever the miniapp shipped) and disable\n // double-tap-zoom via CSS. Re-applies on DOMContentLoaded in case the\n // miniapp re-writes <head> during boot.\n return `\n window.Glassly = ${JSON.stringify(globals)};\n window.receiveNativeMessage = window.receiveNativeMessage || function() {};\n (function() {\n try {\n var styleEl = document.createElement(\"style\");\n styleEl.setAttribute(\"data-glassly-injected\", \"1\");\n styleEl.textContent =\n \":root { ${cssVarsBlock} }\" +\n \"html, body { touch-action: manipulation; -ms-content-zooming: none; }\";\n (document.head || document.documentElement).appendChild(styleEl);\n } catch (e) { /* ignore */ }\n\n function lockViewport() {\n try {\n var content = \"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover\";\n var existing = document.querySelector('meta[name=\"viewport\"]');\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n var meta = document.createElement(\"meta\");\n meta.setAttribute(\"name\", \"viewport\");\n meta.setAttribute(\"content\", content);\n (document.head || document.documentElement).appendChild(meta);\n }\n } catch (e) { /* ignore */ }\n }\n lockViewport();\n document.addEventListener(\"DOMContentLoaded\", lockViewport);\n })();\n ${consoleTapBlock}\n true;\n `\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.3.7-dev.894",
3
+ "version": "0.3.7-dev.897",
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.7-dev.894",
101
- "@glassly/cloud-client": "0.3.7-dev.894",
102
- "@glassly/cloud-protocol": "0.3.7-dev.894",
103
- "@glassly/crust": "0.3.7-dev.894",
104
- "@glassly/miniapp": "0.3.7-dev.894",
100
+ "@glassly/bluetooth-sdk": "0.3.7-dev.897",
101
+ "@glassly/cloud-client": "0.3.7-dev.897",
102
+ "@glassly/cloud-protocol": "0.3.7-dev.897",
103
+ "@glassly/crust": "0.3.7-dev.897",
104
+ "@glassly/miniapp": "0.3.7-dev.897",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.3.2",
108
108
  "semver": "^7.7.4",
109
109
  "typesafe-ts": "^1.7.1",
110
- "@glassly/llm-providers": "0.3.7-dev.894",
110
+ "@glassly/llm-providers": "0.3.7-dev.897",
111
111
  "@huggingface/jinja": "^0.5.9",
112
112
  "jsonrepair": "^3.12.0"
113
113
  },
@@ -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.7",
15
- "releaseIdentity": "0.3.7-dev.894",
16
- "releaseSetId": "glassly-0.3.7-dev.894",
17
- "sourceCommit": "c85c8d91f155af711ef6315801faef9652a5a789",
15
+ "releaseIdentity": "0.3.7-dev.897",
16
+ "releaseSetId": "glassly-0.3.7-dev.897",
17
+ "sourceCommit": "bae11161ee13fff66d3120dc51fd8593e80c6d56",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -7,7 +7,7 @@
7
7
  * same API surface.
8
8
  */
9
9
 
10
- import {Dimensions, Platform} from "react-native"
10
+ import {Platform} from "react-native"
11
11
 
12
12
  export interface MiniappSafeArea {
13
13
  top: number
@@ -16,6 +16,11 @@ export interface MiniappSafeArea {
16
16
  right: number
17
17
  }
18
18
 
19
+ /**
20
+ * The host's capsule menu in the WebView's own coordinate space. `corner`
21
+ * floats over the page's top-right; `rail` sits in the iPhone Duo's status
22
+ * rail, inside the side safe-area inset and clear of the page content.
23
+ */
19
24
  export interface CapsuleMenuRect {
20
25
  top: number
21
26
  right: number
@@ -23,36 +28,7 @@ export interface CapsuleMenuRect {
23
28
  left: number
24
29
  width: number
25
30
  height: number
26
- }
27
-
28
- /**
29
- * The capsule menu bounding rect in the WebView's own coordinate space.
30
- *
31
- * Keep in sync with CapsuleMenu.tsx:
32
- * - CapsuleButton height ≈ h-7.5 (30px), width ≈ 73px
33
- * - Positioned at right-2 (8px), top = theme.spacing.s2 (8px) below insets.top
34
- */
35
- const CAPSULE_MENU_HEIGHT = 30
36
- const CAPSULE_MENU_WIDTH = 73
37
- const CAPSULE_MENU_PADDING = 8
38
-
39
- /**
40
- * @param topInsetOffset extra top offset to add when the WebView container does
41
- * NOT already pad by the safe-area inset (i.e. WebView fills edge-to-edge under
42
- * the status bar). Pass `insets.top` in that case, or 0 when the container
43
- * already applies top padding.
44
- */
45
- export function getCapsuleMenuRect(topInsetOffset = 0): CapsuleMenuRect {
46
- const screenWidth = Dimensions.get("window").width
47
- const top = topInsetOffset + CAPSULE_MENU_PADDING
48
- return {
49
- top,
50
- right: CAPSULE_MENU_PADDING,
51
- bottom: top + CAPSULE_MENU_HEIGHT,
52
- left: screenWidth - CAPSULE_MENU_PADDING - CAPSULE_MENU_WIDTH,
53
- width: CAPSULE_MENU_WIDTH,
54
- height: CAPSULE_MENU_HEIGHT,
55
- }
31
+ placement: "corner" | "rail"
56
32
  }
57
33
 
58
34
  export type MiniappColorScheme = "light" | "dark"
@@ -63,37 +39,48 @@ export interface BuildMiniappGlobalsOptions {
63
39
  miniappLocal?: boolean
64
40
  miniappDeveloperMode?: boolean
65
41
  safeAreaInsets: MiniappSafeArea
66
- /**
67
- * True when the WebView container renders edge-to-edge (no top padding for
68
- * the status bar). In that case the capsule menu rect needs to shift down by
69
- * the top inset so its coords match where it's actually drawn on screen.
70
- */
71
- webviewFillsStatusBar?: boolean
42
+ /** Where the host draws its capsule menu over this WebView. */
43
+ capsuleMenu: CapsuleMenuRect
72
44
  /** Current host color scheme. Miniapps may follow this to match the phone. */
73
45
  colorScheme?: MiniappColorScheme
74
46
  }
75
47
 
76
- function safeAreaCssVars(insets: MiniappSafeArea): Record<string, string> {
48
+ // CSS custom properties that mirror the safe-area and capsule menu data, so
49
+ // miniapp CSS (or Tailwind arbitrary values) can read them without touching
50
+ // JS: e.g. `style="margin-top: var(--glassly-capsule-top)"`.
51
+ function layoutCssVars(insets: MiniappSafeArea, capsule: CapsuleMenuRect): Record<string, string> {
77
52
  return {
78
53
  "--glassly-safe-top": `${insets.top}px`,
79
54
  "--glassly-safe-bottom": `${insets.bottom}px`,
80
55
  "--glassly-safe-left": `${insets.left}px`,
81
56
  "--glassly-safe-right": `${insets.right}px`,
57
+ "--glassly-capsule-top": `${capsule.top}px`,
58
+ "--glassly-capsule-bottom": `${capsule.bottom}px`,
59
+ "--glassly-capsule-left": `${capsule.left}px`,
60
+ "--glassly-capsule-right": `${capsule.right}px`,
61
+ "--glassly-capsule-width": `${capsule.width}px`,
62
+ "--glassly-capsule-height": `${capsule.height}px`,
63
+ "--glassly-capsule-center-y": `${capsule.top + capsule.height / 2}px`,
64
+ // Right-side gutter to reserve so content doesn't slide under the
65
+ // capsule: its width + 16px breathing room, none when it sits in the rail.
66
+ "--glassly-capsule-gutter": `${capsule.placement === "rail" ? 0 : capsule.width + 16}px`,
82
67
  }
83
68
  }
84
69
 
85
70
  /**
86
- * JS to inject into a running miniapp when the host's safe area changes (an
87
- * iPhone Duo folding moves its status rail): updates window.Glassly and the
88
- * CSS variables, then fires `glassly:safe-area-change`.
71
+ * JS to inject into a running miniapp when the host's safe area or capsule
72
+ * menu moves (an iPhone Duo folding, or its rail's reserved regions growing):
73
+ * updates window.Glassly and the CSS variables, then fires
74
+ * `glassly:safe-area-change`.
89
75
  */
90
- export function buildSafeAreaUpdateScript(insets: MiniappSafeArea): string {
91
- const sets = Object.entries(safeAreaCssVars(insets))
76
+ export function buildSafeAreaUpdateScript(insets: MiniappSafeArea, capsuleMenu: CapsuleMenuRect): string {
77
+ const sets = Object.entries(layoutCssVars(insets, capsuleMenu))
92
78
  .map(([k, v]) => `document.documentElement.style.setProperty(${JSON.stringify(k)}, ${JSON.stringify(v)});`)
93
79
  .join("\n ")
94
80
  return `
95
81
  window.Glassly = window.Glassly || {};
96
82
  window.Glassly.safeAreaInsets = ${JSON.stringify(insets)};
83
+ window.Glassly.capsuleMenu = ${JSON.stringify(capsuleMenu)};
97
84
  try {
98
85
  ${sets}
99
86
  } catch (e) { /* ignore */ }
@@ -115,37 +102,18 @@ export function buildSafeAreaUpdateScript(insets: MiniappSafeArea): string {
115
102
  * assign to it before the native bridge wires in.
116
103
  */
117
104
  export function buildMiniappGlobalsScript(opts: BuildMiniappGlobalsOptions): string {
118
- const capsuleTopOffset = opts.webviewFillsStatusBar ? opts.safeAreaInsets.top : 0
119
105
  const globals: Record<string, unknown> = {
120
106
  platform: Platform.OS,
121
107
  capabilities: opts.capabilities ?? ["share", "open_url", "copy_clipboard", "download"],
122
108
  safeAreaInsets: opts.safeAreaInsets,
123
- capsuleMenu: getCapsuleMenuRect(capsuleTopOffset),
109
+ capsuleMenu: opts.capsuleMenu,
124
110
  }
125
111
  if (opts.packageName) globals.packageName = opts.packageName
126
112
  if (opts.miniappLocal) globals.miniappLocal = true
127
113
  if (opts.miniappDeveloperMode) globals.miniappDeveloperMode = true
128
114
  if (opts.colorScheme) globals.colorScheme = opts.colorScheme
129
115
 
130
- // CSS custom properties that mirror the capsule menu / safe-area data.
131
- // Miniapp CSS (or Tailwind arbitrary values) can read these without
132
- // touching JS: e.g. `style="margin-top: var(--glassly-capsule-top)"`.
133
- const capsule = globals.capsuleMenu as CapsuleMenuRect
134
- const insets = opts.safeAreaInsets
135
- const capsuleCenter = capsule.top + capsule.height / 2
136
- const cssVars: Record<string, string> = {
137
- ...safeAreaCssVars(insets),
138
- "--glassly-capsule-top": `${capsule.top}px`,
139
- "--glassly-capsule-bottom": `${capsule.bottom}px`,
140
- "--glassly-capsule-left": `${capsule.left}px`,
141
- "--glassly-capsule-right": `${capsule.right}px`,
142
- "--glassly-capsule-width": `${capsule.width}px`,
143
- "--glassly-capsule-height": `${capsule.height}px`,
144
- "--glassly-capsule-center-y": `${capsuleCenter}px`,
145
- // Right-side gutter to reserve so content doesn't slide under the
146
- // capsule: capsule width + 16px breathing room.
147
- "--glassly-capsule-gutter": `${capsule.width + 16}px`,
148
- }
116
+ const cssVars = layoutCssVars(opts.safeAreaInsets, opts.capsuleMenu)
149
117
  const cssVarsBlock = Object.entries(cssVars)
150
118
  .map(([k, v]) => `${k}: ${v};`)
151
119
  .join(" ")