@adminide-stack/yantra-mobile 12.0.104 → 12.0.105-alpha.0

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 (30) hide show
  1. package/lib/bridge/handlers/SurfaceHostBridgeHandler.js +15 -3
  2. package/lib/bridge/handlers/SurfaceHostBridgeHandler.js.map +1 -1
  3. package/lib/bridge/types.js.map +1 -1
  4. package/lib/features/apps/mobileAppCatalog.js +11 -1
  5. package/lib/features/apps/mobileAppCatalog.js.map +1 -1
  6. package/lib/features/fitness-widgets/FitnessDeepLink.js +90 -0
  7. package/lib/features/fitness-widgets/FitnessDeepLink.js.map +1 -0
  8. package/lib/features/fitness-widgets/FitnessWidgetRunner.js +71 -0
  9. package/lib/features/fitness-widgets/FitnessWidgetRunner.js.map +1 -0
  10. package/lib/features/fitness-widgets/fitnessLink.js +38 -0
  11. package/lib/features/fitness-widgets/fitnessLink.js.map +1 -0
  12. package/lib/features/fitness-widgets/fitnessWidgetStore.js +86 -0
  13. package/lib/features/fitness-widgets/fitnessWidgetStore.js.map +1 -0
  14. package/lib/features/fitness-widgets/fitnessWidgetsNative.js +13 -0
  15. package/lib/features/fitness-widgets/fitnessWidgetsNative.js.map +1 -0
  16. package/lib/features/fitness-widgets/hostLiveActivity.js +5 -0
  17. package/lib/features/fitness-widgets/hostLiveActivity.js.map +1 -0
  18. package/lib/features/fitness-widgets/liveActivity.js +76 -0
  19. package/lib/features/fitness-widgets/liveActivity.js.map +1 -0
  20. package/lib/features/fitness-widgets/widgetSnapshot.js +56 -0
  21. package/lib/features/fitness-widgets/widgetSnapshot.js.map +1 -0
  22. package/lib/features/health/fitnessClient.js +15 -0
  23. package/lib/features/health/fitnessClient.js.map +1 -1
  24. package/lib/hooks/useChatApi.js +2 -1
  25. package/lib/hooks/useChatApi.js.map +1 -1
  26. package/lib/index.js +1 -1
  27. package/lib/index.js.map +1 -1
  28. package/lib/screens/WebBuilder/index.js +4 -2
  29. package/lib/screens/WebBuilder/index.js.map +1 -1
  30. package/package.json +4 -4
@@ -1,4 +1,10 @@
1
- const SURFACE_HOST_ACTIONS = ["host.capabilities", "chat.open", "link.open", "health.status", "health.connect", "health.open", "devices.open"];
1
+ const SURFACE_HOST_ACTIONS = ["host.capabilities", "chat.open", "link.open", "health.status", "health.connect", "health.open", "devices.open", "liveActivity.start", "liveActivity.update", "liveActivity.end"];
2
+ function offered(action, deps) {
3
+ var _a;
4
+ if (action.startsWith("health.") || action.startsWith("devices.")) return Boolean(deps.health);
5
+ if (action.startsWith("liveActivity.")) return Boolean((_a = deps.liveActivity) == null ? void 0 : _a.supported());
6
+ return true;
7
+ }
2
8
  class SurfaceHostBridgeHandler {
3
9
  constructor(bridge, deps) {
4
10
  this.bridge = bridge;
@@ -17,8 +23,7 @@ class SurfaceHostBridgeHandler {
17
23
  } = this;
18
24
  this.bridge.on("request:host.capabilities", (message) => this.reply(message, () => ({
19
25
  platform: deps.platform,
20
- // Devices pair into the same store the health screen uses: no health, no devices either.
21
- actions: SURFACE_HOST_ACTIONS.filter((a) => deps.health || !a.startsWith("health.") && !a.startsWith("devices."))
26
+ actions: SURFACE_HOST_ACTIONS.filter((a) => offered(a, deps))
22
27
  }), "HOST_ERROR"));
23
28
  this.bridge.on("request:chat.open", (message) => this.reply(message, async () => {
24
29
  var _a;
@@ -65,5 +70,12 @@ class SurfaceHostBridgeHandler {
65
70
  if (!deps.health) throw new Error("Health data is not available in this version of the app.");
66
71
  return deps.health.connect();
67
72
  }, "HEALTH_ERROR"));
73
+ const liveActivity = () => {
74
+ if (!deps.liveActivity) throw new Error("Live Activities are not available in this version of the app.");
75
+ return deps.liveActivity;
76
+ };
77
+ this.bridge.on("request:liveActivity.start", (message) => this.reply(message, () => liveActivity().start(message.payload), "LIVE_ACTIVITY_ERROR"));
78
+ this.bridge.on("request:liveActivity.update", (message) => this.reply(message, () => liveActivity().update(message.payload), "LIVE_ACTIVITY_ERROR"));
79
+ this.bridge.on("request:liveActivity.end", (message) => this.reply(message, () => liveActivity().end(), "LIVE_ACTIVITY_ERROR"));
68
80
  }
69
81
  }export{SURFACE_HOST_ACTIONS,SurfaceHostBridgeHandler};//# sourceMappingURL=SurfaceHostBridgeHandler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SurfaceHostBridgeHandler.js","sources":["../../../src/bridge/handlers/SurfaceHostBridgeHandler.ts"],"sourcesContent":["/**\n * What a web surface may ask the app for, beyond auth and raw navigation:\n * open the native chat, connect Apple Health / Health Connect, pair a\n * Bluetooth health device, open a link the right way. Each is a hand-off to something the app already does well and a\n * WebView cannot - the person is signed in natively, and HealthKit lives only\n * on the phone.\n *\n * `host.capabilities` comes first: a surface asks once (short timeout) and\n * only shows native buttons this build answers. An older app that predates an\n * action simply never answers it, and the surface keeps its web behaviour.\n */\n\nimport type { NativeWebViewBridge } from '../NativeWebViewBridge';\nimport type { BridgeMessage } from '../types';\nimport type { SurfaceNavigation } from '../surfaceLinkRouting';\n\nexport interface HostHealthStatus {\n /** available | not-installed | needs-update | unsupported | unknown */\n status: string;\n /** Plain words when not available. */\n reason?: string;\n connected: boolean;\n /** apple-health | health-connect */\n source?: string;\n /** \"Apple Health\" or \"Health Connect\", for the button. */\n storeName: string;\n lastSyncAt?: string;\n lastError?: string;\n busy?: string;\n}\n\nexport interface SurfaceHostDeps {\n platform: string;\n /** Start a new native chat; `message` is sent as the first turn. */\n openChat: (message?: string) => void | Promise<void>;\n /** The app's Health screen (connect, sync, disconnect). */\n openHealth: () => void;\n /**\n * The app's Devices screen, straight to \"Add a device\". Resolves when the\n * person comes back to the surface, so it can reload what was measured.\n */\n openDevices: () => Promise<void>;\n /** Route a link off the surface (OS app or in-app browser). */\n openLink: (nav: Exclude<SurfaceNavigation, { kind: 'stay' } | { kind: 'chat' }>) => void | Promise<void>;\n /** Null when this build has no health support at all. */\n health: {\n status: () => Promise<HostHealthStatus>;\n connect: () => Promise<HostHealthStatus>;\n } | null;\n}\n\nexport const SURFACE_HOST_ACTIONS = [\n 'host.capabilities',\n 'chat.open',\n 'link.open',\n 'health.status',\n 'health.connect',\n 'health.open',\n 'devices.open',\n] as const;\n\nexport class SurfaceHostBridgeHandler {\n constructor(\n private readonly bridge: NativeWebViewBridge,\n private readonly deps: SurfaceHostDeps,\n ) {\n this.setupHandlers();\n }\n\n private reply(message: BridgeMessage, run: () => unknown | Promise<unknown>, code: string): void {\n Promise.resolve()\n .then(run)\n .then((data) => this.bridge.sendResponse(message.id, true, data))\n .catch((error: unknown) =>\n this.bridge.sendResponse(message.id, false, null, {\n code,\n message: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n\n private setupHandlers(): void {\n const { deps } = this;\n\n this.bridge.on('request:host.capabilities', (message: BridgeMessage) =>\n this.reply(\n message,\n () => ({\n platform: deps.platform,\n // Devices pair into the same store the health screen uses: no health, no devices either.\n actions: SURFACE_HOST_ACTIONS.filter(\n (a) => deps.health || (!a.startsWith('health.') && !a.startsWith('devices.')),\n ),\n }),\n 'HOST_ERROR',\n ),\n );\n\n this.bridge.on('request:chat.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n const payload = (message.payload ?? {}) as { message?: unknown };\n const text = typeof payload.message === 'string' ? payload.message.trim().slice(0, 2000) : '';\n await deps.openChat(text || undefined);\n return { opened: true };\n },\n 'CHAT_ERROR',\n ),\n );\n\n this.bridge.on('request:link.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n const url = String((message.payload as { url?: unknown } | undefined)?.url ?? '').trim();\n if (!url) throw new Error('url is required');\n const external = !/^https?:/i.test(url);\n await deps.openLink(external ? { kind: 'external', url } : { kind: 'browser', url });\n return { opened: true };\n },\n 'LINK_ERROR',\n ),\n );\n\n this.bridge.on('request:health.open', (message: BridgeMessage) =>\n this.reply(\n message,\n () => {\n deps.openHealth();\n return { opened: true };\n },\n 'HEALTH_ERROR',\n ),\n );\n\n this.bridge.on('request:devices.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n await deps.openDevices();\n return { returned: true };\n },\n 'DEVICES_ERROR',\n ),\n );\n\n this.bridge.on('request:health.status', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n if (!deps.health) throw new Error('Health data is not available in this version of the app.');\n return deps.health.status();\n },\n 'HEALTH_ERROR',\n ),\n );\n\n this.bridge.on('request:health.connect', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n if (!deps.health) throw new Error('Health data is not available in this version of the app.');\n return deps.health.connect();\n },\n 'HEALTH_ERROR',\n ),\n );\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SurfaceHostBridgeHandler.js","sources":["../../../src/bridge/handlers/SurfaceHostBridgeHandler.ts"],"sourcesContent":["/**\n * What a web surface may ask the app for, beyond auth and raw navigation:\n * open the native chat, connect Apple Health / Health Connect, pair a\n * Bluetooth health device, open a link the right way, show a workout as a\n * Live Activity. Each is a hand-off to something the app already does well and a\n * WebView cannot - the person is signed in natively, and HealthKit lives only\n * on the phone.\n *\n * `host.capabilities` comes first: a surface asks once (short timeout) and\n * only shows native buttons this build answers. An older app that predates an\n * action simply never answers it, and the surface keeps its web behaviour.\n */\n\nimport type { NativeWebViewBridge } from '../NativeWebViewBridge';\nimport type { BridgeMessage } from '../types';\nimport type { SurfaceNavigation } from '../surfaceLinkRouting';\nimport type { HostLiveActivity } from '../../features/fitness-widgets/liveActivity';\n\nexport interface HostHealthStatus {\n /** available | not-installed | needs-update | unsupported | unknown */\n status: string;\n /** Plain words when not available. */\n reason?: string;\n connected: boolean;\n /** apple-health | health-connect */\n source?: string;\n /** \"Apple Health\" or \"Health Connect\", for the button. */\n storeName: string;\n lastSyncAt?: string;\n lastError?: string;\n busy?: string;\n}\n\nexport interface SurfaceHostDeps {\n platform: string;\n /** Start a new native chat; `message` is sent as the first turn. */\n openChat: (message?: string) => void | Promise<void>;\n /** The app's Health screen (connect, sync, disconnect). */\n openHealth: () => void;\n /**\n * The app's Devices screen, straight to \"Add a device\". Resolves when the\n * person comes back to the surface, so it can reload what was measured.\n */\n openDevices: () => Promise<void>;\n /** Route a link off the surface (OS app or in-app browser). */\n openLink: (nav: Exclude<SurfaceNavigation, { kind: 'stay' } | { kind: 'chat' }>) => void | Promise<void>;\n /** Null when this build has no health support at all. */\n health: {\n status: () => Promise<HostHealthStatus>;\n connect: () => Promise<HostHealthStatus>;\n } | null;\n /**\n * The workout Live Activity (iOS). Null, or `supported()` false (before iOS 16.2,\n * or Live Activities turned off for Yantra), and the actions are not offered.\n */\n liveActivity?: HostLiveActivity | null;\n}\n\nexport const SURFACE_HOST_ACTIONS = [\n 'host.capabilities',\n 'chat.open',\n 'link.open',\n 'health.status',\n 'health.connect',\n 'health.open',\n 'devices.open',\n 'liveActivity.start',\n 'liveActivity.update',\n 'liveActivity.end',\n] as const;\n\nfunction offered(action: string, deps: SurfaceHostDeps): boolean {\n // Devices pair into the same store the health screen uses: no health, no devices either.\n if (action.startsWith('health.') || action.startsWith('devices.')) return Boolean(deps.health);\n // Asked every time: Live Activities can be turned off in Settings while the app runs.\n if (action.startsWith('liveActivity.')) return Boolean(deps.liveActivity?.supported());\n return true;\n}\n\nexport class SurfaceHostBridgeHandler {\n constructor(\n private readonly bridge: NativeWebViewBridge,\n private readonly deps: SurfaceHostDeps,\n ) {\n this.setupHandlers();\n }\n\n private reply(message: BridgeMessage, run: () => unknown | Promise<unknown>, code: string): void {\n Promise.resolve()\n .then(run)\n .then((data) => this.bridge.sendResponse(message.id, true, data))\n .catch((error: unknown) =>\n this.bridge.sendResponse(message.id, false, null, {\n code,\n message: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n\n private setupHandlers(): void {\n const { deps } = this;\n\n this.bridge.on('request:host.capabilities', (message: BridgeMessage) =>\n this.reply(\n message,\n () => ({\n platform: deps.platform,\n actions: SURFACE_HOST_ACTIONS.filter((a) => offered(a, deps)),\n }),\n 'HOST_ERROR',\n ),\n );\n\n this.bridge.on('request:chat.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n const payload = (message.payload ?? {}) as { message?: unknown };\n const text = typeof payload.message === 'string' ? payload.message.trim().slice(0, 2000) : '';\n await deps.openChat(text || undefined);\n return { opened: true };\n },\n 'CHAT_ERROR',\n ),\n );\n\n this.bridge.on('request:link.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n const url = String((message.payload as { url?: unknown } | undefined)?.url ?? '').trim();\n if (!url) throw new Error('url is required');\n const external = !/^https?:/i.test(url);\n await deps.openLink(external ? { kind: 'external', url } : { kind: 'browser', url });\n return { opened: true };\n },\n 'LINK_ERROR',\n ),\n );\n\n this.bridge.on('request:health.open', (message: BridgeMessage) =>\n this.reply(\n message,\n () => {\n deps.openHealth();\n return { opened: true };\n },\n 'HEALTH_ERROR',\n ),\n );\n\n this.bridge.on('request:devices.open', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n await deps.openDevices();\n return { returned: true };\n },\n 'DEVICES_ERROR',\n ),\n );\n\n this.bridge.on('request:health.status', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n if (!deps.health) throw new Error('Health data is not available in this version of the app.');\n return deps.health.status();\n },\n 'HEALTH_ERROR',\n ),\n );\n\n this.bridge.on('request:health.connect', (message: BridgeMessage) =>\n this.reply(\n message,\n async () => {\n if (!deps.health) throw new Error('Health data is not available in this version of the app.');\n return deps.health.connect();\n },\n 'HEALTH_ERROR',\n ),\n );\n\n const liveActivity = () => {\n if (!deps.liveActivity) throw new Error('Live Activities are not available in this version of the app.');\n return deps.liveActivity;\n };\n\n // Payload for start and update: { name, startedAt, setsDone, setsTotal, exercise, restEndsAt? }\n // (features/fitness-widgets/liveActivity.ts). start replaces any running workout activity.\n this.bridge.on('request:liveActivity.start', (message: BridgeMessage) =>\n this.reply(message, () => liveActivity().start(message.payload), 'LIVE_ACTIVITY_ERROR'),\n );\n\n this.bridge.on('request:liveActivity.update', (message: BridgeMessage) =>\n this.reply(message, () => liveActivity().update(message.payload), 'LIVE_ACTIVITY_ERROR'),\n );\n\n this.bridge.on('request:liveActivity.end', (message: BridgeMessage) =>\n this.reply(message, () => liveActivity().end(), 'LIVE_ACTIVITY_ERROR'),\n );\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../src/bridge/types.ts"],"sourcesContent":["/** Shared WebView ↔ native bridge protocol (sph.sh Parts 1–2). */\n\nexport enum MessageType {\n REQUEST = 'REQUEST',\n RESPONSE = 'RESPONSE',\n EVENT = 'EVENT',\n ERROR = 'ERROR',\n CHUNK = 'CHUNK',\n}\n\nexport interface BridgeMessage<T = unknown> {\n id: string;\n type: MessageType;\n action: string;\n payload: T;\n timestamp: number;\n version: string;\n}\n\nexport interface BridgeErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nexport interface BridgeResponse<T = unknown> {\n id: string;\n success: boolean;\n data?: T;\n error?: BridgeErrorPayload;\n}\n\n/** Chunk envelope for large postMessage payloads. */\nexport interface ChunkEnvelope {\n type: MessageType.CHUNK;\n chunkId: string;\n chunkIndex: number;\n totalChunks: number;\n data: string;\n}\n\nexport const BRIDGE_PROTOCOL_VERSION = '1.0';\nexport const BRIDGE_READY_ACTION = 'BRIDGE_READY';\nexport const DEFAULT_BRIDGE_TIMEOUT_MS = 10_000;\nexport const DEFAULT_CHUNK_SIZE = 50_000;\n\n/**\n * Typed action contract used by both native and web sides.\n * Camera / biometrics deferred until a concrete WebBuilder need exists.\n */\nexport interface BridgeAPI {\n 'auth.getToken': {\n request: void;\n response: { token: string; expiresAt: number };\n };\n 'auth.refreshToken': {\n request: { currentToken?: string };\n response: { token: string; expiresAt: number };\n };\n 'navigation.navigate': {\n request: { screen: string; params?: Record<string, unknown> };\n response: void;\n };\n 'navigation.goBack': {\n request: void;\n response: boolean;\n };\n 'host.capabilities': {\n request: void;\n response: { platform: string; actions: string[] };\n };\n 'chat.open': {\n request: { message?: string };\n response: { opened: boolean };\n };\n 'link.open': {\n request: { url: string };\n response: { opened: boolean };\n };\n 'health.status': {\n request: void;\n response: import('./handlers/SurfaceHostBridgeHandler').HostHealthStatus;\n };\n 'health.connect': {\n request: void;\n response: import('./handlers/SurfaceHostBridgeHandler').HostHealthStatus;\n };\n 'health.open': {\n request: void;\n response: { opened: boolean };\n };\n 'analytics.track': {\n request: {\n event: string;\n properties?: Record<string, unknown>;\n };\n response: void;\n };\n}\n\nexport type BridgeAction = keyof BridgeAPI;\n"],"names":["MessageType"],"mappings":"AAEY,IAAA,WAAA,qBAAAA,YAAL,KAAA;AACL,EAAAA,aAAA,SAAU,CAAA,GAAA,SAAA;AACV,EAAAA,aAAA,UAAW,CAAA,GAAA,UAAA;AACX,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AACR,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AACR,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AALE,EAAAA,OAAAA,YAAAA;AAAA,CAAA,EAAA,WAAA,IAAA,EAAA;AAmCL,MAAM,uBAA0B,GAAA;AAChC,MAAM,mBAAsB,GAAA;AAC5B,MAAM,yBAA4B,GAAA;AAClC,MAAM,kBAAqB,GAAA"}
1
+ {"version":3,"file":"types.js","sources":["../../src/bridge/types.ts"],"sourcesContent":["/** Shared WebView ↔ native bridge protocol (sph.sh Parts 1–2). */\n\nexport enum MessageType {\n REQUEST = 'REQUEST',\n RESPONSE = 'RESPONSE',\n EVENT = 'EVENT',\n ERROR = 'ERROR',\n CHUNK = 'CHUNK',\n}\n\nexport interface BridgeMessage<T = unknown> {\n id: string;\n type: MessageType;\n action: string;\n payload: T;\n timestamp: number;\n version: string;\n}\n\nexport interface BridgeErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nexport interface BridgeResponse<T = unknown> {\n id: string;\n success: boolean;\n data?: T;\n error?: BridgeErrorPayload;\n}\n\n/** Chunk envelope for large postMessage payloads. */\nexport interface ChunkEnvelope {\n type: MessageType.CHUNK;\n chunkId: string;\n chunkIndex: number;\n totalChunks: number;\n data: string;\n}\n\nexport const BRIDGE_PROTOCOL_VERSION = '1.0';\nexport const BRIDGE_READY_ACTION = 'BRIDGE_READY';\nexport const DEFAULT_BRIDGE_TIMEOUT_MS = 10_000;\nexport const DEFAULT_CHUNK_SIZE = 50_000;\n\n/**\n * Typed action contract used by both native and web sides.\n * Camera / biometrics deferred until a concrete WebBuilder need exists.\n */\nexport interface BridgeAPI {\n 'auth.getToken': {\n request: void;\n response: { token: string; expiresAt: number };\n };\n 'auth.refreshToken': {\n request: { currentToken?: string };\n response: { token: string; expiresAt: number };\n };\n 'navigation.navigate': {\n request: { screen: string; params?: Record<string, unknown> };\n response: void;\n };\n 'navigation.goBack': {\n request: void;\n response: boolean;\n };\n 'host.capabilities': {\n request: void;\n response: { platform: string; actions: string[] };\n };\n 'chat.open': {\n request: { message?: string };\n response: { opened: boolean };\n };\n 'link.open': {\n request: { url: string };\n response: { opened: boolean };\n };\n 'health.status': {\n request: void;\n response: import('./handlers/SurfaceHostBridgeHandler').HostHealthStatus;\n };\n 'health.connect': {\n request: void;\n response: import('./handlers/SurfaceHostBridgeHandler').HostHealthStatus;\n };\n 'health.open': {\n request: void;\n response: { opened: boolean };\n };\n /** iOS 16.2+ only (offered in host.capabilities only then). Replaces any running workout activity. */\n 'liveActivity.start': {\n request: import('../features/fitness-widgets/liveActivity').LiveActivityPayload;\n response: { started: true; id: string };\n };\n /** False when no workout activity is running (ended, or dismissed on the lock screen). */\n 'liveActivity.update': {\n request: import('../features/fitness-widgets/liveActivity').LiveActivityPayload;\n response: { updated: boolean };\n };\n 'liveActivity.end': {\n request: Record<string, never>;\n response: { ended: number };\n };\n 'analytics.track': {\n request: {\n event: string;\n properties?: Record<string, unknown>;\n };\n response: void;\n };\n}\n\nexport type BridgeAction = keyof BridgeAPI;\n"],"names":["MessageType"],"mappings":"AAEY,IAAA,WAAA,qBAAAA,YAAL,KAAA;AACL,EAAAA,aAAA,SAAU,CAAA,GAAA,SAAA;AACV,EAAAA,aAAA,UAAW,CAAA,GAAA,UAAA;AACX,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AACR,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AACR,EAAAA,aAAA,OAAQ,CAAA,GAAA,OAAA;AALE,EAAAA,OAAAA,YAAAA;AAAA,CAAA,EAAA,WAAA,IAAA,EAAA;AAmCL,MAAM,uBAA0B,GAAA;AAChC,MAAM,mBAAsB,GAAA;AAC5B,MAAM,yBAA4B,GAAA;AAClC,MAAM,kBAAqB,GAAA"}
@@ -80,7 +80,8 @@ const MOBILE_APP_CATALOG = [
80
80
  nativeRoute: "characters-native",
81
81
  surfaceSlug: "character"
82
82
  },
83
- // Word, Excel, PowerPoint and PDF files, each in its own browser editor (yantra-office).
83
+ // Word, Excel, PowerPoint and PDF files and pictures, each in its own browser editor
84
+ // (yantra-office).
84
85
  {
85
86
  key: "word",
86
87
  label: "Documents",
@@ -117,6 +118,15 @@ const MOBILE_APP_CATALOG = [
117
118
  nativeRoute: "webbuilder",
118
119
  surfaceSlug: "pdf"
119
120
  },
121
+ {
122
+ key: "photo",
123
+ label: "Photos",
124
+ icon: "image-edit-outline",
125
+ group: "Office",
126
+ prompt: "Make an Instagram post from a template with our spring sale headline",
127
+ nativeRoute: "webbuilder",
128
+ surfaceSlug: "photo"
129
+ },
120
130
  {
121
131
  key: "chat-native",
122
132
  label: "Chats",
@@ -1 +1 @@
1
- {"version":3,"file":"mobileAppCatalog.js","sources":["../../../src/features/apps/mobileAppCatalog.ts"],"sourcesContent":["import type { ComponentProps } from 'react';\nimport type { MaterialCommunityIcons } from '@expo/vector-icons';\n\ntype IconName = ComponentProps<typeof MaterialCommunityIcons>['name'];\n\nexport type AppGroupId = 'Create' | 'Office' | 'Automate' | 'Sell' | 'Money' | 'Team' | 'More';\n\nexport type MobileAppItem = {\n key: string;\n label: string;\n icon: IconName;\n group: AppGroupId;\n /** Prompt sent as the first chat turn when the app has no native screen. */\n prompt: string;\n /**\n * Which surface host opens this app, if it has one. `webbuilder` is the\n * historical name of the shared WebView surface shell (`screens/WebBuilder`),\n * which is not web-builder-specific: it takes a `url` route param, mints the\n * system token, injects the bridge + cdecli token, stamps `embed=mobile` and\n * owns the timeout/retry UI. Anything with a deployed surface SPA can use it.\n */\n nativeRoute?:\n | 'webbuilder'\n | 'canvas-native'\n | 'characters-native'\n | 'chat-native'\n | 'billing-native'\n | 'shopping-native'\n | 'travel-native'\n | 'job-search-native'\n | 'resume-native'\n | 'inbox-native'\n | 'study-native'\n | 'local-services-native'\n | 'movies-native'\n | 'expenses-native';\n /**\n * Deployed surface SPA for this app, as the `<slug>-surface` name. Resolved\n * against SURFACE_URL_TEMPLATE so one env var covers every surface and stays\n * per-environment, instead of 20+ near-identical URLs in eas.json.\n *\n * The slug is the SURFACE's name, which is not always the tile key\n * (issue-ticket -> issues, shop -> shopping, notebook -> notebooklm).\n */\n surfaceSlug?: string;\n /**\n * Explicit env override, for the surfaces people actually run locally\n * (design 5174, canvas 5175, webbuilder). Wins over `surfaceSlug`.\n */\n surfaceUrlKey?: 'WEB_BUILDER_URL' | 'CANVAS_SURFACE_URL' | 'DESIGN_SURFACE_URL';\n /**\n * Key of the web tile this native tile replaces in every launcher list.\n * The web entry stays in the catalog (deep links, tests, the WebView\n * lane) but users see one tile per app, the native one, under the\n * shared name.\n */\n supersedes?: string;\n /**\n * Account chrome, not a product app. Stays in the catalog so drawer /\n * deep-link routing still knows the native host, but never appears as\n * a tile in Apps or the Apps menu.\n */\n hideInLauncher?: boolean;\n};\n\n/**\n * Mobile Apps launcher. Labels, groups, and prompts stay in lockstep with the\n * browser ChatTopBar Apps menu (`packages-modules/account/browser`).\n */\nexport const MOBILE_APP_CATALOG: readonly MobileAppItem[] = [\n {\n key: 'design',\n label: 'Design',\n icon: 'palette-outline',\n group: 'Create',\n prompt: 'Design a 10-slide pitch deck for a coffee subscription startup',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'DESIGN_SURFACE_URL',\n surfaceSlug: 'design',\n },\n {\n key: 'canvas',\n label: 'Canvas',\n icon: 'view-grid-outline',\n group: 'Create',\n prompt: 'Open a canvas and help me lay out the assets for this project.',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'CANVAS_SURFACE_URL',\n surfaceSlug: 'canvas',\n },\n {\n key: 'canvas-native',\n label: 'Canvas',\n supersedes: 'canvas',\n icon: 'vector-square',\n group: 'Create',\n prompt: 'Open a canvas and help me lay out the assets for this project.',\n // Renders through Module Federation: the viewer downloads from the\n // canvas-surface origin on open. First native surface; the classic\n // Canvas tile keeps the full web editor in the WebView host.\n nativeRoute: 'canvas-native',\n },\n {\n key: 'webbuilder',\n label: 'Web Builder',\n icon: 'application-outline',\n group: 'Create',\n prompt: 'Build a landing page for my bakery with a menu and a contact form',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'WEB_BUILDER_URL',\n surfaceSlug: 'webbuilder',\n },\n {\n key: 'paper2poster',\n label: 'Paper 2 Poster',\n icon: 'presentation',\n group: 'Create',\n prompt: 'Turn this research paper into a conference poster I can edit.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'paper2poster',\n },\n {\n key: 'codezen',\n label: 'Codezen',\n icon: 'code-braces',\n group: 'Create',\n prompt: 'Clone my repository and fix the failing test',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'codezen',\n },\n {\n key: 'character',\n label: 'Characters',\n icon: 'drama-masks',\n group: 'Create',\n prompt: 'Create a stoic philosophy mentor character I can chat with',\n // Native federated experience (gallery -> detail -> chat); the web\n // surface remains reachable through the surface lane if ever needed.\n nativeRoute: 'characters-native',\n surfaceSlug: 'character',\n },\n {\n key: 'characters-native',\n label: 'Characters',\n supersedes: 'character',\n icon: 'account-star',\n group: 'Create',\n prompt: 'Show me characters I can chat with.',\n nativeRoute: 'characters-native',\n surfaceSlug: 'character',\n },\n // Word, Excel, PowerPoint and PDF files, each in its own browser editor (yantra-office).\n {\n key: 'word',\n label: 'Documents',\n icon: 'file-document-outline',\n group: 'Office',\n prompt: 'Write a one-page project proposal as a Word document',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'word',\n },\n {\n key: 'excel',\n label: 'Spreadsheets',\n icon: 'file-table-outline',\n group: 'Office',\n prompt: 'Make a monthly budget spreadsheet with totals and a chart',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'excel',\n },\n {\n key: 'presentation',\n label: 'Presentations',\n icon: 'presentation',\n group: 'Office',\n prompt: 'Turn my notes into a six-slide project update deck',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'presentation',\n },\n {\n key: 'pdf',\n label: 'PDFs',\n icon: 'file-pdf-box',\n group: 'Office',\n prompt: 'Make a one-page PDF checklist for moving house',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'pdf',\n },\n {\n key: 'chat-native',\n label: 'Chats',\n icon: 'message-text-outline',\n group: 'Team',\n prompt: 'Show me my conversations.',\n // Person-to-person messaging, federated like canvas: the whole UI\n // downloads from the chat-native surface on open. Not the AI chat -\n // that is the Home tab.\n nativeRoute: 'chat-native',\n },\n {\n key: 'workflow',\n label: 'Workflow',\n icon: 'sitemap',\n group: 'Automate',\n prompt: 'Build a workflow that runs every morning and sends me a summary of what changed.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'workflow',\n },\n {\n key: 'computer',\n label: 'Computer',\n icon: 'monitor',\n group: 'Automate',\n prompt: 'Open a browser and compare the top 5 CRM tools with screenshots',\n },\n {\n key: 'crm',\n label: 'CRM',\n icon: 'account-group-outline',\n group: 'Sell',\n prompt: 'Look at my pipeline and tell me which deals are going cold and what I should send them.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'crm',\n },\n {\n key: 'salesiq',\n label: 'SalesIQ',\n icon: 'radar',\n group: 'Sell',\n prompt: 'Show me who is on my website right now',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'salesiq',\n },\n {\n key: 'inbox',\n label: 'Inbox',\n icon: 'inbox-outline',\n group: 'Sell',\n prompt: 'Go through my inbox, tell me what actually needs a reply, and draft those replies.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'inbox',\n },\n {\n key: 'inbox-native',\n label: 'Inbox',\n supersedes: 'inbox',\n icon: 'inbox-outline',\n group: 'Sell',\n prompt: 'Go through my inbox, tell me what actually needs a reply, and draft those replies.',\n // Native mail client over the same yantra-mailer store as the web Inbox.\n nativeRoute: 'inbox-native',\n },\n {\n key: 'mailcampaign',\n label: 'Mail Campaigns',\n icon: 'bullhorn-outline',\n group: 'Sell',\n prompt: 'Draft an email campaign announcing what we shipped this month.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'mailcampaign',\n },\n {\n key: 'smscampaign',\n label: 'SMS Campaigns',\n icon: 'message-text-outline',\n group: 'Sell',\n prompt: \"Text my customers a short reminder about this week's offer.\",\n nativeRoute: 'webbuilder',\n surfaceSlug: 'smscampaign',\n },\n {\n key: 'social-post',\n label: 'Social Posts',\n icon: 'send-outline',\n group: 'Sell',\n prompt: 'Draft a launch post for X and LinkedIn and schedule it for Tuesday morning.',\n nativeRoute: 'webbuilder',\n // The deployed surface is `posts-surface`, not `social-post-surface`.\n surfaceSlug: 'posts',\n },\n {\n key: 'expenses',\n label: 'Expenses',\n icon: 'receipt',\n group: 'Money',\n prompt: 'Summarise my spend this month and flag anything unusual.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'expenses',\n },\n {\n key: 'expenses-native',\n label: 'Expenses',\n supersedes: 'expenses',\n icon: 'receipt',\n group: 'Money',\n prompt: 'Summarise my spend this month and flag anything unusual.',\n // Native expenses over the same expenses subgraph as the web app: scan receipts,\n // approvals, budgets, recurring payments, accounts.\n nativeRoute: 'expenses-native',\n },\n {\n key: 'cards-wallet',\n label: 'Cards & Wallet',\n icon: 'credit-card-outline',\n group: 'Money',\n prompt: 'Issue a $200 virtual card for monthly ad spend',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'cards-wallet',\n },\n {\n key: 'billing',\n label: 'Billing',\n icon: 'currency-usd',\n group: 'Money',\n prompt: 'Create an invoice for a $2,400 consulting project',\n nativeRoute: 'billing-native',\n surfaceSlug: 'billing',\n // Plan / subscription for this account — already in the drawer.\n hideInLauncher: true,\n },\n {\n key: 'kpi-motivation',\n label: 'KPI Motivation',\n icon: 'trophy-outline',\n group: 'Team',\n prompt: 'Set up a weekly sales KPI scoreboard for my team',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'kpi-motivation',\n },\n {\n key: 'issue-ticket',\n label: 'Issue Ticket',\n icon: 'bug-outline',\n group: 'Team',\n prompt: 'Create a bug board and triage my open issues',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'issues',\n },\n {\n key: 'notebook',\n label: 'Study',\n icon: 'school-outline',\n group: 'Team',\n prompt: 'Make a study guide from my lecture notes',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'notebooklm',\n },\n {\n key: 'study-native',\n label: 'Study',\n supersedes: 'notebook',\n icon: 'school-outline',\n group: 'Team',\n prompt: 'Make a study guide from my lecture notes',\n // Native study notebooks over the same yantra-notebook store as the web Study.\n nativeRoute: 'study-native',\n },\n {\n key: 'resume',\n label: 'Resume',\n icon: 'file-account-outline',\n group: 'Team',\n prompt: 'Draft a resume for a senior product designer with 8 years of experience',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'resume',\n },\n {\n key: 'resume-native',\n label: 'Resume',\n supersedes: 'resume',\n icon: 'file-account-outline',\n group: 'Team',\n prompt: 'Draft a resume for a senior product designer with 8 years of experience',\n nativeRoute: 'resume-native',\n },\n {\n key: 'job-search',\n label: 'Job Search',\n icon: 'briefcase-outline',\n group: 'Team',\n prompt: 'Find remote senior backend engineer roles and tailor my resume for one',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'job-search',\n },\n {\n key: 'job-search-native',\n label: 'Job Search',\n supersedes: 'job-search',\n icon: 'briefcase-search-outline',\n group: 'Team',\n prompt: 'Find remote senior backend engineer roles and tailor my resume for one',\n nativeRoute: 'job-search-native',\n },\n {\n key: 'shop',\n label: 'Shop',\n icon: 'cart-outline',\n group: 'More',\n prompt: 'Compare the best options for what I need and show me the tradeoffs.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'shopping',\n },\n {\n key: 'shopping-native',\n label: 'Shop',\n supersedes: 'shop',\n icon: 'storefront-outline',\n group: 'More',\n prompt: 'Compare the best options for what I need and show me the tradeoffs.',\n // Federated storefront: search + cart download from shopping_native/Shop.\n // The classic Shop tile keeps the web surface in the WebView host.\n nativeRoute: 'shopping-native',\n },\n {\n key: 'travel-native',\n label: 'Travel',\n supersedes: 'travel',\n icon: 'airplane-takeoff',\n group: 'More',\n prompt: 'Plan a 4-day trip for me: flights, somewhere to stay, and what to do each day.',\n nativeRoute: 'travel-native',\n },\n {\n key: 'travel',\n label: 'Travel',\n icon: 'airplane',\n group: 'More',\n prompt: 'Plan a 4-day trip for me: flights, somewhere to stay, and what to do each day.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'travel',\n },\n {\n key: 'social-events',\n label: 'yantra.live',\n icon: 'ticket-outline',\n group: 'More',\n prompt: 'What concerts are happening nearby this weekend?',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'social-events',\n },\n {\n key: 'movies',\n label: 'Movies',\n icon: 'popcorn',\n group: 'More',\n prompt: 'What movies are playing near me tonight?',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'movies',\n },\n {\n key: 'movies-native',\n label: 'Movies',\n supersedes: 'movies',\n icon: 'popcorn',\n group: 'More',\n prompt: 'What movies are playing near me tonight?',\n // Native cinema app over the yantra-movies connector: discover, trailers, showtimes, watchlist.\n nativeRoute: 'movies-native',\n },\n {\n key: 'local-services',\n label: 'Local Services',\n icon: 'wrench',\n group: 'More',\n prompt: 'Find an electrician near me',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'local-services',\n },\n {\n key: 'local-services-native',\n label: 'Local Services',\n supersedes: 'local-services',\n icon: 'wrench',\n group: 'More',\n prompt: 'Find an electrician near me',\n // Native search, details and saved pros over the same yantra-local-services\n // connector as the web Local Services surface.\n nativeRoute: 'local-services-native',\n },\n {\n key: 'fitness',\n label: 'Fitness',\n icon: 'dumbbell',\n group: 'More',\n prompt: 'Build me a workout plan for this week',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'fitness',\n },\n];\n\nexport const APP_GROUP_ORDER: readonly AppGroupId[] = ['Create', 'Office', 'Automate', 'Sell', 'Money', 'Team', 'More'];\n\nexport type AppsTab = 'all' | 'favorites';\n\n/**\n * Poster colors per group - the tile artwork. Deliberately the same in light\n * and dark themes: artwork is content, the card chrome is what adapts.\n */\nexport const APP_GROUP_ART: Record<AppGroupId, { base: string; glow: string }> = {\n Create: { base: '#4f46e5', glow: '#c4b5fd' },\n Office: { base: '#334155', glow: '#cbd5e1' },\n Automate: { base: '#0f766e', glow: '#99f6e4' },\n Sell: { base: '#c2410c', glow: '#fed7aa' },\n Money: { base: '#15803d', glow: '#bbf7d0' },\n Team: { base: '#0369a1', glow: '#bae6fd' },\n More: { base: '#be185d', glow: '#fbcfe8' },\n};\n\nexport function matchesAppQuery(label: string, query: string): boolean {\n const q = query.toLowerCase().replace(/[^a-z0-9]/g, '');\n if (!q) return true;\n return label\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n .includes(q);\n}\n\n/**\n * Favorites keyed by a web tile that a native tile now replaces, carried over to\n * the native tile, so pinning \"Inbox\" before it went native still pins it after.\n */\nexport function carryOverSupersededFavorites(\n favorites: ReadonlySet<string>,\n items: readonly MobileAppItem[] = MOBILE_APP_CATALOG,\n): Set<string> {\n const replacement = new Map<string, string>();\n for (const item of items) if (item.supersedes) replacement.set(item.supersedes, item.key);\n const out = new Set<string>();\n for (const key of favorites) out.add(replacement.get(key) ?? key);\n return out;\n}\n\n/** One tile per app: a web tile disappears when its native replacement is in the list. */\nexport function visibleMobileApps(items: readonly MobileAppItem[]): MobileAppItem[] {\n const replaced = new Set(items.map((item) => item.supersedes).filter(Boolean));\n return items.filter((item) => !item.hideInLauncher && !replaced.has(item.key));\n}\n\nexport function groupMobileApps(items: readonly MobileAppItem[], query: string) {\n const hits = visibleMobileApps(items).filter((item) => matchesAppQuery(item.label, query));\n return APP_GROUP_ORDER.map((label) => ({\n label,\n items: hits.filter((item) => item.group === label),\n })).filter((group) => group.items.length > 0);\n}\n\n/**\n * One flat grid (Manus/game-launcher style) instead of grouped sections: rows\n * stay dense and the group reads from each card's caption. Catalog order is\n * kept inside a group; groups follow APP_GROUP_ORDER.\n */\nexport function filterMobileApps(\n items: readonly MobileAppItem[],\n query: string,\n tab: AppsTab,\n favorites: ReadonlySet<string>,\n): MobileAppItem[] {\n const rank = new Map(APP_GROUP_ORDER.map((group, index) => [group, index]));\n return visibleMobileApps(items)\n .filter((item) => matchesAppQuery(item.label, query))\n .filter((item) => tab !== 'favorites' || favorites.has(item.key))\n .sort((a, b) => (rank.get(a.group) ?? APP_GROUP_ORDER.length) - (rank.get(b.group) ?? APP_GROUP_ORDER.length));\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mobileAppCatalog.js","sources":["../../../src/features/apps/mobileAppCatalog.ts"],"sourcesContent":["import type { ComponentProps } from 'react';\nimport type { MaterialCommunityIcons } from '@expo/vector-icons';\n\ntype IconName = ComponentProps<typeof MaterialCommunityIcons>['name'];\n\nexport type AppGroupId = 'Create' | 'Office' | 'Automate' | 'Sell' | 'Money' | 'Team' | 'More';\n\nexport type MobileAppItem = {\n key: string;\n label: string;\n icon: IconName;\n group: AppGroupId;\n /** Prompt sent as the first chat turn when the app has no native screen. */\n prompt: string;\n /**\n * Which surface host opens this app, if it has one. `webbuilder` is the\n * historical name of the shared WebView surface shell (`screens/WebBuilder`),\n * which is not web-builder-specific: it takes a `url` route param, mints the\n * system token, injects the bridge + cdecli token, stamps `embed=mobile` and\n * owns the timeout/retry UI. Anything with a deployed surface SPA can use it.\n */\n nativeRoute?:\n | 'webbuilder'\n | 'canvas-native'\n | 'characters-native'\n | 'chat-native'\n | 'billing-native'\n | 'shopping-native'\n | 'travel-native'\n | 'job-search-native'\n | 'resume-native'\n | 'inbox-native'\n | 'study-native'\n | 'local-services-native'\n | 'movies-native'\n | 'expenses-native';\n /**\n * Deployed surface SPA for this app, as the `<slug>-surface` name. Resolved\n * against SURFACE_URL_TEMPLATE so one env var covers every surface and stays\n * per-environment, instead of 20+ near-identical URLs in eas.json.\n *\n * The slug is the SURFACE's name, which is not always the tile key\n * (issue-ticket -> issues, shop -> shopping, notebook -> notebooklm).\n */\n surfaceSlug?: string;\n /**\n * Explicit env override, for the surfaces people actually run locally\n * (design 5174, canvas 5175, webbuilder). Wins over `surfaceSlug`.\n */\n surfaceUrlKey?: 'WEB_BUILDER_URL' | 'CANVAS_SURFACE_URL' | 'DESIGN_SURFACE_URL';\n /**\n * Key of the web tile this native tile replaces in every launcher list.\n * The web entry stays in the catalog (deep links, tests, the WebView\n * lane) but users see one tile per app, the native one, under the\n * shared name.\n */\n supersedes?: string;\n /**\n * Account chrome, not a product app. Stays in the catalog so drawer /\n * deep-link routing still knows the native host, but never appears as\n * a tile in Apps or the Apps menu.\n */\n hideInLauncher?: boolean;\n};\n\n/**\n * Mobile Apps launcher. Labels, groups, and prompts stay in lockstep with the\n * browser ChatTopBar Apps menu (`packages-modules/account/browser`).\n */\nexport const MOBILE_APP_CATALOG: readonly MobileAppItem[] = [\n {\n key: 'design',\n label: 'Design',\n icon: 'palette-outline',\n group: 'Create',\n prompt: 'Design a 10-slide pitch deck for a coffee subscription startup',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'DESIGN_SURFACE_URL',\n surfaceSlug: 'design',\n },\n {\n key: 'canvas',\n label: 'Canvas',\n icon: 'view-grid-outline',\n group: 'Create',\n prompt: 'Open a canvas and help me lay out the assets for this project.',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'CANVAS_SURFACE_URL',\n surfaceSlug: 'canvas',\n },\n {\n key: 'canvas-native',\n label: 'Canvas',\n supersedes: 'canvas',\n icon: 'vector-square',\n group: 'Create',\n prompt: 'Open a canvas and help me lay out the assets for this project.',\n // Renders through Module Federation: the viewer downloads from the\n // canvas-surface origin on open. First native surface; the classic\n // Canvas tile keeps the full web editor in the WebView host.\n nativeRoute: 'canvas-native',\n },\n {\n key: 'webbuilder',\n label: 'Web Builder',\n icon: 'application-outline',\n group: 'Create',\n prompt: 'Build a landing page for my bakery with a menu and a contact form',\n nativeRoute: 'webbuilder',\n surfaceUrlKey: 'WEB_BUILDER_URL',\n surfaceSlug: 'webbuilder',\n },\n {\n key: 'paper2poster',\n label: 'Paper 2 Poster',\n icon: 'presentation',\n group: 'Create',\n prompt: 'Turn this research paper into a conference poster I can edit.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'paper2poster',\n },\n {\n key: 'codezen',\n label: 'Codezen',\n icon: 'code-braces',\n group: 'Create',\n prompt: 'Clone my repository and fix the failing test',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'codezen',\n },\n {\n key: 'character',\n label: 'Characters',\n icon: 'drama-masks',\n group: 'Create',\n prompt: 'Create a stoic philosophy mentor character I can chat with',\n // Native federated experience (gallery -> detail -> chat); the web\n // surface remains reachable through the surface lane if ever needed.\n nativeRoute: 'characters-native',\n surfaceSlug: 'character',\n },\n {\n key: 'characters-native',\n label: 'Characters',\n supersedes: 'character',\n icon: 'account-star',\n group: 'Create',\n prompt: 'Show me characters I can chat with.',\n nativeRoute: 'characters-native',\n surfaceSlug: 'character',\n },\n // Word, Excel, PowerPoint and PDF files and pictures, each in its own browser editor\n // (yantra-office).\n {\n key: 'word',\n label: 'Documents',\n icon: 'file-document-outline',\n group: 'Office',\n prompt: 'Write a one-page project proposal as a Word document',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'word',\n },\n {\n key: 'excel',\n label: 'Spreadsheets',\n icon: 'file-table-outline',\n group: 'Office',\n prompt: 'Make a monthly budget spreadsheet with totals and a chart',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'excel',\n },\n {\n key: 'presentation',\n label: 'Presentations',\n icon: 'presentation',\n group: 'Office',\n prompt: 'Turn my notes into a six-slide project update deck',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'presentation',\n },\n {\n key: 'pdf',\n label: 'PDFs',\n icon: 'file-pdf-box',\n group: 'Office',\n prompt: 'Make a one-page PDF checklist for moving house',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'pdf',\n },\n {\n key: 'photo',\n label: 'Photos',\n icon: 'image-edit-outline',\n group: 'Office',\n prompt: 'Make an Instagram post from a template with our spring sale headline',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'photo',\n },\n {\n key: 'chat-native',\n label: 'Chats',\n icon: 'message-text-outline',\n group: 'Team',\n prompt: 'Show me my conversations.',\n // Person-to-person messaging, federated like canvas: the whole UI\n // downloads from the chat-native surface on open. Not the AI chat -\n // that is the Home tab.\n nativeRoute: 'chat-native',\n },\n {\n key: 'workflow',\n label: 'Workflow',\n icon: 'sitemap',\n group: 'Automate',\n prompt: 'Build a workflow that runs every morning and sends me a summary of what changed.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'workflow',\n },\n {\n key: 'computer',\n label: 'Computer',\n icon: 'monitor',\n group: 'Automate',\n prompt: 'Open a browser and compare the top 5 CRM tools with screenshots',\n },\n {\n key: 'crm',\n label: 'CRM',\n icon: 'account-group-outline',\n group: 'Sell',\n prompt: 'Look at my pipeline and tell me which deals are going cold and what I should send them.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'crm',\n },\n {\n key: 'salesiq',\n label: 'SalesIQ',\n icon: 'radar',\n group: 'Sell',\n prompt: 'Show me who is on my website right now',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'salesiq',\n },\n {\n key: 'inbox',\n label: 'Inbox',\n icon: 'inbox-outline',\n group: 'Sell',\n prompt: 'Go through my inbox, tell me what actually needs a reply, and draft those replies.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'inbox',\n },\n {\n key: 'inbox-native',\n label: 'Inbox',\n supersedes: 'inbox',\n icon: 'inbox-outline',\n group: 'Sell',\n prompt: 'Go through my inbox, tell me what actually needs a reply, and draft those replies.',\n // Native mail client over the same yantra-mailer store as the web Inbox.\n nativeRoute: 'inbox-native',\n },\n {\n key: 'mailcampaign',\n label: 'Mail Campaigns',\n icon: 'bullhorn-outline',\n group: 'Sell',\n prompt: 'Draft an email campaign announcing what we shipped this month.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'mailcampaign',\n },\n {\n key: 'smscampaign',\n label: 'SMS Campaigns',\n icon: 'message-text-outline',\n group: 'Sell',\n prompt: \"Text my customers a short reminder about this week's offer.\",\n nativeRoute: 'webbuilder',\n surfaceSlug: 'smscampaign',\n },\n {\n key: 'social-post',\n label: 'Social Posts',\n icon: 'send-outline',\n group: 'Sell',\n prompt: 'Draft a launch post for X and LinkedIn and schedule it for Tuesday morning.',\n nativeRoute: 'webbuilder',\n // The deployed surface is `posts-surface`, not `social-post-surface`.\n surfaceSlug: 'posts',\n },\n {\n key: 'expenses',\n label: 'Expenses',\n icon: 'receipt',\n group: 'Money',\n prompt: 'Summarise my spend this month and flag anything unusual.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'expenses',\n },\n {\n key: 'expenses-native',\n label: 'Expenses',\n supersedes: 'expenses',\n icon: 'receipt',\n group: 'Money',\n prompt: 'Summarise my spend this month and flag anything unusual.',\n // Native expenses over the same expenses subgraph as the web app: scan receipts,\n // approvals, budgets, recurring payments, accounts.\n nativeRoute: 'expenses-native',\n },\n {\n key: 'cards-wallet',\n label: 'Cards & Wallet',\n icon: 'credit-card-outline',\n group: 'Money',\n prompt: 'Issue a $200 virtual card for monthly ad spend',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'cards-wallet',\n },\n {\n key: 'billing',\n label: 'Billing',\n icon: 'currency-usd',\n group: 'Money',\n prompt: 'Create an invoice for a $2,400 consulting project',\n nativeRoute: 'billing-native',\n surfaceSlug: 'billing',\n // Plan / subscription for this account — already in the drawer.\n hideInLauncher: true,\n },\n {\n key: 'kpi-motivation',\n label: 'KPI Motivation',\n icon: 'trophy-outline',\n group: 'Team',\n prompt: 'Set up a weekly sales KPI scoreboard for my team',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'kpi-motivation',\n },\n {\n key: 'issue-ticket',\n label: 'Issue Ticket',\n icon: 'bug-outline',\n group: 'Team',\n prompt: 'Create a bug board and triage my open issues',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'issues',\n },\n {\n key: 'notebook',\n label: 'Study',\n icon: 'school-outline',\n group: 'Team',\n prompt: 'Make a study guide from my lecture notes',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'notebooklm',\n },\n {\n key: 'study-native',\n label: 'Study',\n supersedes: 'notebook',\n icon: 'school-outline',\n group: 'Team',\n prompt: 'Make a study guide from my lecture notes',\n // Native study notebooks over the same yantra-notebook store as the web Study.\n nativeRoute: 'study-native',\n },\n {\n key: 'resume',\n label: 'Resume',\n icon: 'file-account-outline',\n group: 'Team',\n prompt: 'Draft a resume for a senior product designer with 8 years of experience',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'resume',\n },\n {\n key: 'resume-native',\n label: 'Resume',\n supersedes: 'resume',\n icon: 'file-account-outline',\n group: 'Team',\n prompt: 'Draft a resume for a senior product designer with 8 years of experience',\n nativeRoute: 'resume-native',\n },\n {\n key: 'job-search',\n label: 'Job Search',\n icon: 'briefcase-outline',\n group: 'Team',\n prompt: 'Find remote senior backend engineer roles and tailor my resume for one',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'job-search',\n },\n {\n key: 'job-search-native',\n label: 'Job Search',\n supersedes: 'job-search',\n icon: 'briefcase-search-outline',\n group: 'Team',\n prompt: 'Find remote senior backend engineer roles and tailor my resume for one',\n nativeRoute: 'job-search-native',\n },\n {\n key: 'shop',\n label: 'Shop',\n icon: 'cart-outline',\n group: 'More',\n prompt: 'Compare the best options for what I need and show me the tradeoffs.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'shopping',\n },\n {\n key: 'shopping-native',\n label: 'Shop',\n supersedes: 'shop',\n icon: 'storefront-outline',\n group: 'More',\n prompt: 'Compare the best options for what I need and show me the tradeoffs.',\n // Federated storefront: search + cart download from shopping_native/Shop.\n // The classic Shop tile keeps the web surface in the WebView host.\n nativeRoute: 'shopping-native',\n },\n {\n key: 'travel-native',\n label: 'Travel',\n supersedes: 'travel',\n icon: 'airplane-takeoff',\n group: 'More',\n prompt: 'Plan a 4-day trip for me: flights, somewhere to stay, and what to do each day.',\n nativeRoute: 'travel-native',\n },\n {\n key: 'travel',\n label: 'Travel',\n icon: 'airplane',\n group: 'More',\n prompt: 'Plan a 4-day trip for me: flights, somewhere to stay, and what to do each day.',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'travel',\n },\n {\n key: 'social-events',\n label: 'yantra.live',\n icon: 'ticket-outline',\n group: 'More',\n prompt: 'What concerts are happening nearby this weekend?',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'social-events',\n },\n {\n key: 'movies',\n label: 'Movies',\n icon: 'popcorn',\n group: 'More',\n prompt: 'What movies are playing near me tonight?',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'movies',\n },\n {\n key: 'movies-native',\n label: 'Movies',\n supersedes: 'movies',\n icon: 'popcorn',\n group: 'More',\n prompt: 'What movies are playing near me tonight?',\n // Native cinema app over the yantra-movies connector: discover, trailers, showtimes, watchlist.\n nativeRoute: 'movies-native',\n },\n {\n key: 'local-services',\n label: 'Local Services',\n icon: 'wrench',\n group: 'More',\n prompt: 'Find an electrician near me',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'local-services',\n },\n {\n key: 'local-services-native',\n label: 'Local Services',\n supersedes: 'local-services',\n icon: 'wrench',\n group: 'More',\n prompt: 'Find an electrician near me',\n // Native search, details and saved pros over the same yantra-local-services\n // connector as the web Local Services surface.\n nativeRoute: 'local-services-native',\n },\n {\n key: 'fitness',\n label: 'Fitness',\n icon: 'dumbbell',\n group: 'More',\n prompt: 'Build me a workout plan for this week',\n nativeRoute: 'webbuilder',\n surfaceSlug: 'fitness',\n },\n];\n\nexport const APP_GROUP_ORDER: readonly AppGroupId[] = ['Create', 'Office', 'Automate', 'Sell', 'Money', 'Team', 'More'];\n\nexport type AppsTab = 'all' | 'favorites';\n\n/**\n * Poster colors per group - the tile artwork. Deliberately the same in light\n * and dark themes: artwork is content, the card chrome is what adapts.\n */\nexport const APP_GROUP_ART: Record<AppGroupId, { base: string; glow: string }> = {\n Create: { base: '#4f46e5', glow: '#c4b5fd' },\n Office: { base: '#334155', glow: '#cbd5e1' },\n Automate: { base: '#0f766e', glow: '#99f6e4' },\n Sell: { base: '#c2410c', glow: '#fed7aa' },\n Money: { base: '#15803d', glow: '#bbf7d0' },\n Team: { base: '#0369a1', glow: '#bae6fd' },\n More: { base: '#be185d', glow: '#fbcfe8' },\n};\n\nexport function matchesAppQuery(label: string, query: string): boolean {\n const q = query.toLowerCase().replace(/[^a-z0-9]/g, '');\n if (!q) return true;\n return label\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n .includes(q);\n}\n\n/**\n * Favorites keyed by a web tile that a native tile now replaces, carried over to\n * the native tile, so pinning \"Inbox\" before it went native still pins it after.\n */\nexport function carryOverSupersededFavorites(\n favorites: ReadonlySet<string>,\n items: readonly MobileAppItem[] = MOBILE_APP_CATALOG,\n): Set<string> {\n const replacement = new Map<string, string>();\n for (const item of items) if (item.supersedes) replacement.set(item.supersedes, item.key);\n const out = new Set<string>();\n for (const key of favorites) out.add(replacement.get(key) ?? key);\n return out;\n}\n\n/** One tile per app: a web tile disappears when its native replacement is in the list. */\nexport function visibleMobileApps(items: readonly MobileAppItem[]): MobileAppItem[] {\n const replaced = new Set(items.map((item) => item.supersedes).filter(Boolean));\n return items.filter((item) => !item.hideInLauncher && !replaced.has(item.key));\n}\n\nexport function groupMobileApps(items: readonly MobileAppItem[], query: string) {\n const hits = visibleMobileApps(items).filter((item) => matchesAppQuery(item.label, query));\n return APP_GROUP_ORDER.map((label) => ({\n label,\n items: hits.filter((item) => item.group === label),\n })).filter((group) => group.items.length > 0);\n}\n\n/**\n * One flat grid (Manus/game-launcher style) instead of grouped sections: rows\n * stay dense and the group reads from each card's caption. Catalog order is\n * kept inside a group; groups follow APP_GROUP_ORDER.\n */\nexport function filterMobileApps(\n items: readonly MobileAppItem[],\n query: string,\n tab: AppsTab,\n favorites: ReadonlySet<string>,\n): MobileAppItem[] {\n const rank = new Map(APP_GROUP_ORDER.map((group, index) => [group, index]));\n return visibleMobileApps(items)\n .filter((item) => matchesAppQuery(item.label, query))\n .filter((item) => tab !== 'favorites' || favorites.has(item.key))\n .sort((a, b) => (rank.get(a.group) ?? APP_GROUP_ORDER.length) - (rank.get(b.group) ?? APP_GROUP_ORDER.length));\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import {navigationRef}from'@common-stack/client-react';import {isAuthenticatedSelector}from'@adminide-stack/user-auth0-client';import {CommonActions}from'@react-navigation/native';import {useState,useEffect}from'react';import {Linking}from'react-native';import {useSelector}from'react-redux';import {config}from'../../config/env-config.js';import {MOBILE_APP_CATALOG}from'../apps/mobileAppCatalog.js';import {resolveSurfaceUrl}from'../apps/resolveSurfaceUrl.js';import {parseFitnessLink,fitnessSurfaceUrl}from'./fitnessLink.js';var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ const WEB_BUILDER_ROUTE = "MainStack.WebBuilder";
21
+ const PENDING_TTL_MS = 6e4;
22
+ const RETRY_MS = 400;
23
+ const MAX_NAVIGATES = 10;
24
+ function fitnessBaseUrl() {
25
+ const app = MOBILE_APP_CATALOG.find((item) => item.key === "fitness");
26
+ return app ? resolveSurfaceUrl(app, config) : void 0;
27
+ }
28
+ function FitnessDeepLink() {
29
+ const isAuthenticated = useSelector(isAuthenticatedSelector);
30
+ const [link, setLink] = useState(null);
31
+ useEffect(() => {
32
+ let active = true;
33
+ const adopt = (url) => {
34
+ const next = parseFitnessLink(url);
35
+ if (next && active) setLink(__spreadProps(__spreadValues({}, next), {
36
+ at: Date.now()
37
+ }));
38
+ };
39
+ void Linking.getInitialURL().then(adopt).catch(() => void 0);
40
+ const sub = Linking.addEventListener("url", ({
41
+ url
42
+ }) => adopt(url));
43
+ return () => {
44
+ active = false;
45
+ sub.remove();
46
+ };
47
+ }, []);
48
+ useEffect(() => {
49
+ if (!link || !isAuthenticated) return void 0;
50
+ const base = fitnessBaseUrl();
51
+ if (!base) {
52
+ setLink(null);
53
+ return void 0;
54
+ }
55
+ let cancelled = false;
56
+ let navigates = 0;
57
+ let timer;
58
+ const tryOpen = () => {
59
+ if (cancelled) return;
60
+ if (Date.now() - link.at > PENDING_TTL_MS || navigates >= MAX_NAVIGATES) {
61
+ setLink(null);
62
+ return;
63
+ }
64
+ if (!navigationRef.isReady()) {
65
+ timer = setTimeout(tryOpen, RETRY_MS);
66
+ return;
67
+ }
68
+ navigates += 1;
69
+ navigationRef.dispatch(CommonActions.navigate({
70
+ name: WEB_BUILDER_ROUTE,
71
+ params: {
72
+ url: fitnessSurfaceUrl(base, link.view),
73
+ appTitle: "Fitness"
74
+ }
75
+ }));
76
+ timer = setTimeout(() => {
77
+ var _a;
78
+ if (cancelled) return;
79
+ if (((_a = navigationRef.getCurrentRoute()) == null ? void 0 : _a.name) === WEB_BUILDER_ROUTE) setLink(null);
80
+ else tryOpen();
81
+ }, RETRY_MS);
82
+ };
83
+ timer = setTimeout(tryOpen, 0);
84
+ return () => {
85
+ cancelled = true;
86
+ if (timer) clearTimeout(timer);
87
+ };
88
+ }, [link, isAuthenticated]);
89
+ return null;
90
+ }export{FitnessDeepLink};//# sourceMappingURL=FitnessDeepLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FitnessDeepLink.js","sources":["../../../src/features/fitness-widgets/FitnessDeepLink.tsx"],"sourcesContent":["/**\n * Opens the Fitness surface when a Fitness widget or the workout Live\n * Activity is tapped (`yantraapp://fitness?view=today`). Mounted once at the\n * app root, next to the invite links: the tap can cold-start the app, before\n * the navigator or the session is ready, so the link waits for both - and then\n * checks it really landed, because right after sign-in resolves the navigator\n * still drops a navigate (seen on the simulator: the first one left the app on\n * Home, the retry 400 ms later opened Fitness).\n *\n * Signed out, the link is dropped: the widgets show nothing then (signing out\n * clears their snapshot), and Fitness needs an account.\n */\nimport { navigationRef } from '@common-stack/client-react';\nimport { isAuthenticatedSelector } from '@adminide-stack/user-auth0-client';\nimport { CommonActions } from '@react-navigation/native';\nimport { useEffect, useState } from 'react';\nimport { Linking } from 'react-native';\nimport { useSelector } from 'react-redux';\nimport { config } from '../../config/env-config';\nimport { MOBILE_APP_CATALOG } from '../apps/mobileAppCatalog';\nimport { resolveSurfaceUrl } from '../apps/resolveSurfaceUrl';\nimport { fitnessSurfaceUrl, parseFitnessLink, type FitnessLink } from './fitnessLink';\n\nconst WEB_BUILDER_ROUTE = 'MainStack.WebBuilder';\n/** A link the session never became ready for is not opened later by surprise. */\nconst PENDING_TTL_MS = 60_000;\n/** How long to wait before checking the navigator again, and how many navigates to try. */\nconst RETRY_MS = 400;\nconst MAX_NAVIGATES = 10;\n\nfunction fitnessBaseUrl(): string | undefined {\n const app = MOBILE_APP_CATALOG.find((item) => item.key === 'fitness');\n return app ? resolveSurfaceUrl(app, config) : undefined;\n}\n\nexport function FitnessDeepLink(): null {\n const isAuthenticated = useSelector(isAuthenticatedSelector);\n const [link, setLink] = useState<(FitnessLink & { at: number }) | null>(null);\n\n useEffect(() => {\n let active = true;\n const adopt = (url: string | null) => {\n const next = parseFitnessLink(url);\n if (next && active) setLink({ ...next, at: Date.now() });\n };\n void Linking.getInitialURL()\n .then(adopt)\n .catch(() => undefined);\n const sub = Linking.addEventListener('url', ({ url }) => adopt(url));\n return () => {\n active = false;\n sub.remove();\n };\n }, []);\n\n useEffect(() => {\n if (!link || !isAuthenticated) return undefined;\n const base = fitnessBaseUrl();\n if (!base) {\n setLink(null);\n return undefined;\n }\n let cancelled = false;\n let navigates = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n // The navigator mounts after auth resolves; wait for it rather than dropping the tap.\n const tryOpen = () => {\n if (cancelled) return;\n if (Date.now() - link.at > PENDING_TTL_MS || navigates >= MAX_NAVIGATES) {\n setLink(null);\n return;\n }\n if (!navigationRef.isReady()) {\n timer = setTimeout(tryOpen, RETRY_MS);\n return;\n }\n navigates += 1;\n navigationRef.dispatch(\n CommonActions.navigate({\n name: WEB_BUILDER_ROUTE,\n params: { url: fitnessSurfaceUrl(base, link.view), appTitle: 'Fitness' },\n }),\n );\n // On a cold start the signed-in navigator is still settling for a moment after auth\n // resolves, and a navigate dispatched then is silently dropped: check it landed.\n timer = setTimeout(() => {\n if (cancelled) return;\n if (navigationRef.getCurrentRoute()?.name === WEB_BUILDER_ROUTE) setLink(null);\n else tryOpen();\n }, RETRY_MS);\n };\n timer = setTimeout(tryOpen, 0);\n return () => {\n cancelled = true;\n if (timer) clearTimeout(timer);\n };\n }, [link, isAuthenticated]);\n\n return null;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import {jsx}from'react/jsx-runtime';import {isAuthenticatedSelector}from'@adminide-stack/user-auth0-client';import {useState,useRef,useEffect}from'react';import {AppState}from'react-native';import {useSelector}from'react-redux';import {config}from'../../config/env-config.js';import {useCanvasSession}from'../canvas/useCanvasSession.js';import {useHealthSync}from'../health/healthSync.js';import {fitnessWidgetsNative}from'./fitnessWidgetsNative.js';import {FitnessWidgetStore}from'./fitnessWidgetStore.js';const fitnessWidgetStore = new FitnessWidgetStore({
2
+ writer: fitnessWidgetsNative()
3
+ });
4
+ const UNSEEN = Symbol("unseen");
5
+ function ConnectedFitnessWidgets() {
6
+ var _a;
7
+ const {
8
+ token
9
+ } = useCanvasSession();
10
+ const url = config.FITNESS_BACKEND_GRAPHQL_URL;
11
+ const health = useHealthSync();
12
+ const seenSync = useRef(UNSEEN);
13
+ useEffect(() => {
14
+ fitnessWidgetStore.setSession(token && url ? {
15
+ token,
16
+ url
17
+ } : null);
18
+ if (token && url) void fitnessWidgetStore.refreshIfDue();
19
+ }, [token, url]);
20
+ useEffect(() => () => fitnessWidgetStore.setSession(null), []);
21
+ useEffect(() => {
22
+ const sub = AppState.addEventListener("change", (next) => {
23
+ if (next === "active") void fitnessWidgetStore.refreshIfDue();
24
+ });
25
+ return () => sub.remove();
26
+ }, []);
27
+ const lastSyncAt = (_a = health.sync.lastSyncAt) != null ? _a : null;
28
+ useEffect(() => {
29
+ if (!health.loaded) return;
30
+ if (seenSync.current === UNSEEN) {
31
+ seenSync.current = lastSyncAt;
32
+ return;
33
+ }
34
+ if (lastSyncAt && lastSyncAt !== seenSync.current) {
35
+ seenSync.current = lastSyncAt;
36
+ void fitnessWidgetStore.refresh();
37
+ }
38
+ }, [health.loaded, lastSyncAt]);
39
+ return null;
40
+ }
41
+ function FitnessWidgetRunner() {
42
+ const native = fitnessWidgetsNative();
43
+ const isAuthenticated = useSelector(isAuthenticatedSelector);
44
+ const [hasWidgets, setHasWidgets] = useState(false);
45
+ const wasAuthenticated = useRef(false);
46
+ useEffect(() => {
47
+ if (wasAuthenticated.current && !isAuthenticated) fitnessWidgetStore.clear();
48
+ wasAuthenticated.current = Boolean(isAuthenticated);
49
+ }, [isAuthenticated]);
50
+ useEffect(() => {
51
+ if (!native || !isAuthenticated) return void 0;
52
+ let active = true;
53
+ const check = () => {
54
+ native.installedWidgetCount().then((count) => {
55
+ if (active) setHasWidgets(count > 0);
56
+ }).catch(() => {
57
+ if (active) setHasWidgets(true);
58
+ });
59
+ };
60
+ check();
61
+ const sub = AppState.addEventListener("change", (next) => {
62
+ if (next === "active") check();
63
+ });
64
+ return () => {
65
+ active = false;
66
+ sub.remove();
67
+ };
68
+ }, [native, isAuthenticated]);
69
+ if (!native || !isAuthenticated || !hasWidgets) return null;
70
+ return /* @__PURE__ */ jsx(ConnectedFitnessWidgets, {});
71
+ }export{FitnessWidgetRunner,fitnessWidgetStore};//# sourceMappingURL=FitnessWidgetRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FitnessWidgetRunner.js","sources":["../../../src/features/fitness-widgets/FitnessWidgetRunner.tsx"],"sourcesContent":["/**\n * Mounted once at the app root (portable-devices/mobile App.tsx). Keeps the\n * iOS Fitness widgets' snapshot fresh while someone is signed in AND has a\n * Yantra widget on the home or lock screen; without a widget it never mints a\n * session or calls the backend. Renders nothing.\n *\n * - On every return to the foreground: refresh, at most every 15 minutes.\n * - When a health sync finishes: refresh now (new data just went up).\n * - Signing out clears the snapshot, so the widgets stop showing the numbers.\n */\nimport { isAuthenticatedSelector } from '@adminide-stack/user-auth0-client';\nimport { useEffect, useRef, useState } from 'react';\nimport { AppState } from 'react-native';\nimport { useSelector } from 'react-redux';\nimport { config } from '../../config/env-config';\nimport { useCanvasSession } from '../canvas/useCanvasSession';\nimport { useHealthSync } from '../health/healthSync';\nimport { fitnessWidgetsNative } from './fitnessWidgetsNative';\nimport { FitnessWidgetStore } from './fitnessWidgetStore';\n\nexport const fitnessWidgetStore = new FitnessWidgetStore({ writer: fitnessWidgetsNative() });\n\n/** No baseline yet: the first loaded `lastSyncAt` is the old sync, not a new one. */\nconst UNSEEN = Symbol('unseen');\n\nfunction ConnectedFitnessWidgets() {\n // The surface session health sync and the canvas use, renewed before it expires.\n const { token } = useCanvasSession();\n const url = config.FITNESS_BACKEND_GRAPHQL_URL;\n const health = useHealthSync();\n const seenSync = useRef<string | null | typeof UNSEEN>(UNSEEN);\n\n useEffect(() => {\n fitnessWidgetStore.setSession(token && url ? { token, url } : null);\n if (token && url) void fitnessWidgetStore.refreshIfDue();\n }, [token, url]);\n\n useEffect(() => () => fitnessWidgetStore.setSession(null), []);\n\n useEffect(() => {\n const sub = AppState.addEventListener('change', (next) => {\n if (next === 'active') void fitnessWidgetStore.refreshIfDue();\n });\n return () => sub.remove();\n }, []);\n\n const lastSyncAt = health.sync.lastSyncAt ?? null;\n useEffect(() => {\n if (!health.loaded) return;\n if (seenSync.current === UNSEEN) {\n seenSync.current = lastSyncAt;\n return;\n }\n if (lastSyncAt && lastSyncAt !== seenSync.current) {\n seenSync.current = lastSyncAt;\n void fitnessWidgetStore.refresh();\n }\n }, [health.loaded, lastSyncAt]);\n\n return null;\n}\n\nexport function FitnessWidgetRunner() {\n const native = fitnessWidgetsNative();\n const isAuthenticated = useSelector(isAuthenticatedSelector);\n const [hasWidgets, setHasWidgets] = useState(false);\n const wasAuthenticated = useRef(false);\n\n useEffect(() => {\n if (wasAuthenticated.current && !isAuthenticated) fitnessWidgetStore.clear();\n wasAuthenticated.current = Boolean(isAuthenticated);\n }, [isAuthenticated]);\n\n // Widgets come and go while the app is in the background: ask again on every return.\n useEffect(() => {\n if (!native || !isAuthenticated) return undefined;\n let active = true;\n const check = () => {\n native\n .installedWidgetCount()\n .then((count) => {\n if (active) setHasWidgets(count > 0);\n })\n .catch(() => {\n // Unknown is treated as \"there may be one\": a fetch is cheaper than a blank widget.\n if (active) setHasWidgets(true);\n });\n };\n check();\n const sub = AppState.addEventListener('change', (next) => {\n if (next === 'active') check();\n });\n return () => {\n active = false;\n sub.remove();\n };\n }, [native, isAuthenticated]);\n\n if (!native || !isAuthenticated || !hasWidgets) return null;\n return <ConnectedFitnessWidgets />;\n}\n"],"names":[],"mappings":"2fAmBa,MAAA,kBAAA,GAAqB,IAAI,kBAAmB,CAAA;AAAA,EACvD,QAAQ,oBAAqB;AAC/B,CAAC;AAGD,MAAM,MAAA,GAAS,OAAO,QAAQ,CAAA;AAC9B,SAAS,uBAA0B,GAAA;AAzBnC,EAAA,IAAA,EAAA;AA2BE,EAAM,MAAA;AAAA,IACJ;AAAA,MACE,gBAAiB,EAAA;AACrB,EAAA,MAAM,MAAM,MAAO,CAAA,2BAAA;AACnB,EAAA,MAAM,SAAS,aAAc,EAAA;AAC7B,EAAM,MAAA,QAAA,GAAW,OAAsC,MAAM,CAAA;AAC7D,EAAA,SAAA,CAAU,MAAM;AACd,IAAmB,kBAAA,CAAA,UAAA,CAAW,SAAS,GAAM,GAAA;AAAA,MAC3C,KAAA;AAAA,MACA;AAAA,QACE,IAAI,CAAA;AACR,IAAA,IAAI,KAAS,IAAA,GAAA,EAAU,KAAA,kBAAA,CAAmB,YAAa,EAAA;AAAA,GACtD,EAAA,CAAC,KAAO,EAAA,GAAG,CAAC,CAAA;AACf,EAAA,SAAA,CAAU,MAAM,MAAM,kBAAA,CAAmB,WAAW,IAAI,CAAA,EAAG,EAAE,CAAA;AAC7D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAM,GAAA,QAAA,CAAS,gBAAiB,CAAA,QAAA,EAAU,CAAQ,IAAA,KAAA;AACtD,MAAA,IAAI,IAAS,KAAA,QAAA,EAAe,KAAA,kBAAA,CAAmB,YAAa,EAAA;AAAA,KAC7D,CAAA;AACD,IAAO,OAAA,MAAM,IAAI,MAAO,EAAA;AAAA,GAC1B,EAAG,EAAE,CAAA;AACL,EAAA,MAAM,UAAa,GAAA,CAAA,EAAA,GAAA,MAAA,CAAO,IAAK,CAAA,UAAA,KAAZ,IAA0B,GAAA,EAAA,GAAA,IAAA;AAC7C,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,CAAC,OAAO,MAAQ,EAAA;AACpB,IAAI,IAAA,QAAA,CAAS,YAAY,MAAQ,EAAA;AAC/B,MAAA,QAAA,CAAS,OAAU,GAAA,UAAA;AACnB,MAAA;AAAA;AAEF,IAAI,IAAA,UAAA,IAAc,UAAe,KAAA,QAAA,CAAS,OAAS,EAAA;AACjD,MAAA,QAAA,CAAS,OAAU,GAAA,UAAA;AACnB,MAAA,KAAK,mBAAmB,OAAQ,EAAA;AAAA;AAClC,GACC,EAAA,CAAC,MAAO,CAAA,MAAA,EAAQ,UAAU,CAAC,CAAA;AAC9B,EAAO,OAAA,IAAA;AACT;AACO,SAAS,mBAAsB,GAAA;AACpC,EAAA,MAAM,SAAS,oBAAqB,EAAA;AACpC,EAAM,MAAA,eAAA,GAAkB,YAAY,uBAAuB,CAAA;AAC3D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAClD,EAAM,MAAA,gBAAA,GAAmB,OAAO,KAAK,CAAA;AACrC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,gBAAiB,CAAA,OAAA,IAAW,CAAC,eAAA,qBAAoC,KAAM,EAAA;AAC3E,IAAiB,gBAAA,CAAA,OAAA,GAAU,QAAQ,eAAe,CAAA;AAAA,GACpD,EAAG,CAAC,eAAe,CAAC,CAAA;AAGpB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,eAAA,EAAwB,OAAA,MAAA;AACxC,IAAA,IAAI,MAAS,GAAA,IAAA;AACb,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAO,MAAA,CAAA,oBAAA,EAAuB,CAAA,IAAA,CAAK,CAAS,KAAA,KAAA;AAC1C,QAAI,IAAA,MAAA,EAAsB,aAAA,CAAA,KAAA,GAAQ,CAAC,CAAA;AAAA,OACpC,CAAE,CAAA,KAAA,CAAM,MAAM;AAEb,QAAI,IAAA,MAAA,gBAAsB,IAAI,CAAA;AAAA,OAC/B,CAAA;AAAA,KACH;AACA,IAAM,KAAA,EAAA;AACN,IAAA,MAAM,GAAM,GAAA,QAAA,CAAS,gBAAiB,CAAA,QAAA,EAAU,CAAQ,IAAA,KAAA;AACtD,MAAI,IAAA,IAAA,KAAS,UAAgB,KAAA,EAAA;AAAA,KAC9B,CAAA;AACD,IAAA,OAAO,MAAM;AACX,MAAS,MAAA,GAAA,KAAA;AACT,MAAA,GAAA,CAAI,MAAO,EAAA;AAAA,KACb;AAAA,GACC,EAAA,CAAC,MAAQ,EAAA,eAAe,CAAC,CAAA;AAC5B,EAAA,IAAI,CAAC,MAAU,IAAA,CAAC,eAAmB,IAAA,CAAC,YAAmB,OAAA,IAAA;AACvD,EAAA,2BAAQ,uBAAwB,EAAA,EAAA,CAAA;AAClC"}
@@ -0,0 +1,38 @@
1
+ const FITNESS_LINK_SCHEMES = ["yantraapp", "com.cdebase.yantra"];
2
+ const DEFAULT_VIEW = "today";
3
+ const VIEW_NAME = /^[a-z][a-z-]{0,39}$/;
4
+ function queryValue(url, key) {
5
+ const withoutFragment = url.split("#")[0];
6
+ const at = withoutFragment.indexOf("?");
7
+ if (at === -1) return null;
8
+ for (const pair of withoutFragment.slice(at + 1).split("&")) {
9
+ const eq = pair.indexOf("=");
10
+ try {
11
+ if (eq > 0 && decodeURIComponent(pair.slice(0, eq)) === key) {
12
+ return decodeURIComponent(pair.slice(eq + 1)).trim();
13
+ }
14
+ } catch (e) {
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function parseFitnessLink(url) {
20
+ var _a;
21
+ if (typeof url !== "string") return null;
22
+ const match = url.trim().match(/^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)/i);
23
+ if (!match) return null;
24
+ const scheme = match[1].toLowerCase();
25
+ if (!FITNESS_LINK_SCHEMES.includes(scheme)) return null;
26
+ if (match[2].toLowerCase() !== "fitness") return null;
27
+ const view = ((_a = queryValue(url, "view")) != null ? _a : "").toLowerCase();
28
+ return {
29
+ view: VIEW_NAME.test(view) ? view : DEFAULT_VIEW
30
+ };
31
+ }
32
+ function fitnessSurfaceUrl(surfaceUrl, view) {
33
+ const [beforeHash, hash] = surfaceUrl.split("#");
34
+ const [path, query = ""] = beforeHash.split("?");
35
+ const kept = query.split("&").filter((pair) => pair && !/^view=/i.test(pair));
36
+ kept.push(`view=${encodeURIComponent(view)}`);
37
+ return `${path}?${kept.join("&")}${hash !== void 0 ? `#${hash}` : ""}`;
38
+ }export{FITNESS_LINK_SCHEMES,fitnessSurfaceUrl,parseFitnessLink};//# sourceMappingURL=fitnessLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fitnessLink.js","sources":["../../../src/features/fitness-widgets/fitnessLink.ts"],"sourcesContent":["/**\n * `yantraapp://fitness?view=<screen>`: what the Fitness widgets and the\n * workout Live Activity open. The app's own scheme (also `com.cdebase.yantra://`),\n * no web host - a widget tap goes straight to the app.\n *\n * `view` is handed to the Fitness surface as its own `?view=` (the surface\n * checks it against the screens it can land on); anything that is not a\n * plain screen name becomes `today`.\n *\n * Hand-parsed like the chat invite links: React Native's URL has no\n * `searchParams` without a polyfill and throws on custom schemes.\n */\n\nexport const FITNESS_LINK_SCHEMES = ['yantraapp', 'com.cdebase.yantra'] as const;\n\nconst DEFAULT_VIEW = 'today';\nconst VIEW_NAME = /^[a-z][a-z-]{0,39}$/;\n\nexport interface FitnessLink {\n view: string;\n}\n\nfunction queryValue(url: string, key: string): string | null {\n const withoutFragment = url.split('#')[0];\n const at = withoutFragment.indexOf('?');\n if (at === -1) return null;\n for (const pair of withoutFragment.slice(at + 1).split('&')) {\n const eq = pair.indexOf('=');\n try {\n if (eq > 0 && decodeURIComponent(pair.slice(0, eq)) === key) {\n return decodeURIComponent(pair.slice(eq + 1)).trim();\n }\n } catch {\n // A malformed escape in one pair must not discard the others.\n }\n }\n return null;\n}\n\nexport function parseFitnessLink(url: string | null | undefined): FitnessLink | null {\n if (typeof url !== 'string') return null;\n const match = url.trim().match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]*)/i);\n if (!match) return null;\n const scheme = match[1].toLowerCase();\n if (!(FITNESS_LINK_SCHEMES as readonly string[]).includes(scheme)) return null;\n if (match[2].toLowerCase() !== 'fitness') return null;\n const view = (queryValue(url, 'view') ?? '').toLowerCase();\n return { view: VIEW_NAME.test(view) ? view : DEFAULT_VIEW };\n}\n\n/** The Fitness surface URL with `view` set, keeping whatever query it already has. */\nexport function fitnessSurfaceUrl(surfaceUrl: string, view: string): string {\n const [beforeHash, hash] = surfaceUrl.split('#');\n const [path, query = ''] = beforeHash.split('?');\n const kept = query.split('&').filter((pair) => pair && !/^view=/i.test(pair));\n kept.push(`view=${encodeURIComponent(view)}`);\n return `${path}?${kept.join('&')}${hash !== undefined ? `#${hash}` : ''}`;\n}\n"],"names":[],"mappings":"AAaa,MAAA,oBAAA,GAAuB,CAAC,WAAA,EAAa,oBAAoB;AACtE,MAAM,YAAe,GAAA,OAAA;AACrB,MAAM,SAAY,GAAA,qBAAA;AAIlB,SAAS,UAAA,CAAW,KAAa,GAA4B,EAAA;AAC3D,EAAA,MAAM,eAAkB,GAAA,GAAA,CAAI,KAAM,CAAA,GAAG,EAAE,CAAC,CAAA;AACxC,EAAM,MAAA,EAAA,GAAK,eAAgB,CAAA,OAAA,CAAQ,GAAG,CAAA;AACtC,EAAI,IAAA,EAAA,KAAO,IAAW,OAAA,IAAA;AACtB,EAAW,KAAA,MAAA,IAAA,IAAQ,gBAAgB,KAAM,CAAA,EAAA,GAAK,CAAC,CAAE,CAAA,KAAA,CAAM,GAAG,CAAG,EAAA;AAC3D,IAAM,MAAA,EAAA,GAAK,IAAK,CAAA,OAAA,CAAQ,GAAG,CAAA;AAC3B,IAAI,IAAA;AACF,MAAI,IAAA,EAAA,GAAK,KAAK,kBAAmB,CAAA,IAAA,CAAK,MAAM,CAAG,EAAA,EAAE,CAAC,CAAA,KAAM,GAAK,EAAA;AAC3D,QAAA,OAAO,mBAAmB,IAAK,CAAA,KAAA,CAAM,KAAK,CAAC,CAAC,EAAE,IAAK,EAAA;AAAA;AACrD,KACM,CAAA,OAAA,CAAA,EAAA;AAAA;AAER;AAEF,EAAO,OAAA,IAAA;AACT;AACO,SAAS,iBAAiB,GAAoD,EAAA;AAnCrF,EAAA,IAAA,EAAA;AAoCE,EAAI,IAAA,OAAO,GAAQ,KAAA,QAAA,EAAiB,OAAA,IAAA;AACpC,EAAA,MAAM,KAAQ,GAAA,GAAA,CAAI,IAAK,EAAA,CAAE,MAAM,qCAAqC,CAAA;AACpE,EAAI,IAAA,CAAC,OAAc,OAAA,IAAA;AACnB,EAAA,MAAM,MAAS,GAAA,KAAA,CAAM,CAAC,CAAA,CAAE,WAAY,EAAA;AACpC,EAAA,IAAI,CAAE,oBAAA,CAA2C,QAAS,CAAA,MAAM,GAAU,OAAA,IAAA;AAC1E,EAAA,IAAI,MAAM,CAAC,CAAA,CAAE,WAAY,EAAA,KAAM,WAAkB,OAAA,IAAA;AACjD,EAAA,MAAM,SAAQ,EAAW,GAAA,UAAA,CAAA,GAAA,EAAK,MAAM,CAAtB,KAAA,IAAA,GAAA,EAAA,GAA2B,IAAI,WAAY,EAAA;AACzD,EAAO,OAAA;AAAA,IACL,IAAM,EAAA,SAAA,CAAU,IAAK,CAAA,IAAI,IAAI,IAAO,GAAA;AAAA,GACtC;AACF;AAGgB,SAAA,iBAAA,CAAkB,YAAoB,IAAsB,EAAA;AAC1E,EAAA,MAAM,CAAC,UAAY,EAAA,IAAI,CAAI,GAAA,UAAA,CAAW,MAAM,GAAG,CAAA;AAC/C,EAAA,MAAM,CAAC,IAAM,EAAA,KAAA,GAAQ,EAAE,CAAI,GAAA,UAAA,CAAW,MAAM,GAAG,CAAA;AAC/C,EAAA,MAAM,IAAO,GAAA,KAAA,CAAM,KAAM,CAAA,GAAG,CAAE,CAAA,MAAA,CAAO,CAAQ,IAAA,KAAA,IAAA,IAAQ,CAAC,SAAA,CAAU,IAAK,CAAA,IAAI,CAAC,CAAA;AAC1E,EAAA,IAAA,CAAK,IAAK,CAAA,CAAA,KAAA,EAAQ,kBAAmB,CAAA,IAAI,CAAC,CAAE,CAAA,CAAA;AAC5C,EAAA,OAAO,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA,IAAA,CAAK,IAAK,CAAA,GAAG,CAAC,CAAA,EAAG,IAAS,KAAA,MAAA,GAAY,CAAI,CAAA,EAAA,IAAI,KAAK,EAAE,CAAA,CAAA;AACzE"}