@adminide-stack/yantra-mobile 12.0.51-alpha.1 → 12.0.51-alpha.4

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 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/screens/Characters/index.tsx"],"sourcesContent":["/**\n * Native Characters screen - the host route for the federated characters\n * experience (gallery -> detail -> chat handoff).\n *\n * Same delivery contract as CanvasBoard: the App (MF host) registers the\n * federated component; this screen only reads the registry and wires\n * navigation. Opening it triggers the runtime fetch of\n * `characters_native/Experience` from the character-surface origin - none of\n * that code ships in the app binary.\n */\nimport React, { useCallback } from 'react';\nimport { StyleSheet, Text, View, useColorScheme } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { CommonActions, useNavigation, useRoute } from '@react-navigation/native';\nimport { v4 as uuidv4 } from 'uuid';\nimport { getNativeCharactersExperience } from '../../features/characters/nativeCharactersRegistry';\nimport type { CharacterProfile } from '../../features/characters/characterCore';\n\nexport interface CharactersRouteParams {\n orgName?: string;\n /** Absolute URL of the characters.json manifest; the Apps menu supplies it. */\n manifestUrl?: string;\n}\n\nexport default function CharactersScreen() {\n const navigation = useNavigation();\n const route = useRoute();\n const isDark = useColorScheme() === 'dark';\n const Experience = getNativeCharactersExperience();\n const params = (route.params ?? {}) as CharactersRouteParams;\n\n const handleStartChat = useCallback(\n (prompt: string, character: CharacterProfile) => {\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.Chat',\n params: {\n orgName: params.orgName,\n channelId: uuidv4(),\n initialQuery: prompt,\n initialMode: 'chat',\n headerTitle: character.name,\n },\n }),\n );\n },\n [navigation, params.orgName],\n );\n\n if (!Experience || !params.manifestUrl) {\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]}>\n <View style={styles.center}>\n <Text style={[styles.title, isDark && styles.textLight]}>Characters unavailable</Text>\n <Text style={[styles.detail, isDark && styles.textMuted]}>\n {!Experience\n ? 'This build does not carry the federation host runtime.'\n : 'No characters source was configured for this build.'}\n </Text>\n </View>\n </SafeAreaView>\n );\n }\n\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]} edges={['left', 'right', 'bottom']}>\n <Experience manifestUrl={params.manifestUrl} onStartChat={handleStartChat} />\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1, backgroundColor: '#f8fafc' },\n fillDark: { backgroundColor: '#0f172a' },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },\n title: { fontSize: 17, fontWeight: '600', color: '#0f172a', marginBottom: 6 },\n detail: { fontSize: 13, color: '#64748b', textAlign: 'center' },\n textLight: { color: '#f1f5f9' },\n textMuted: { color: '#94a3b8' },\n});\n"],"names":["uuidv4"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/screens/Characters/index.tsx"],"sourcesContent":["/**\n * Native Characters screen - the host route for the federated character STUDIO.\n *\n * Same delivery contract as CanvasBoard: the App (MF host) registers the\n * federated component; this screen only reads the registry and hands over what\n * the remote cannot know - a surface session token and the character subgraph\n * endpoint for this environment. Opening it triggers the runtime fetch of\n * `characters_native/Characters` from the character surface; none of that code\n * ships in the app binary. The remaining seams (runAction / storage / playAudio)\n * are injected by the App's FederatedCharactersExperience wrapper.\n */\nimport React from 'react';\nimport { StyleSheet, Text, View, useColorScheme } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { getNativeCharactersExperience } from '../../features/characters/nativeCharactersRegistry';\nimport { useCanvasSession } from '../../features/canvas/useCanvasSession';\nimport { config } from '../../config/env-config';\n\nexport interface CharactersRouteParams {\n orgName?: string;\n}\n\nexport default function CharactersScreen() {\n const isDark = useColorScheme() === 'dark';\n const Experience = getNativeCharactersExperience();\n // The shared surface session (token + auto-renewal); the studio's authed\n // subgraph and connector calls ride the same JWT the canvas index uses.\n const { token } = useCanvasSession();\n\n if (!Experience) {\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]}>\n <View style={styles.center}>\n <Text style={[styles.title, isDark && styles.textLight]}>Characters unavailable</Text>\n <Text style={[styles.detail, isDark && styles.textMuted]}>\n This build does not carry the federation host runtime.\n </Text>\n </View>\n </SafeAreaView>\n );\n }\n\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]} edges={['left', 'right', 'bottom']}>\n <Experience token={token} subgraphUrl={config.CHARACTERS_SUBGRAPH_URL} />\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1, backgroundColor: '#f8fafc' },\n fillDark: { backgroundColor: '#0f172a' },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },\n title: { fontSize: 17, fontWeight: '600', color: '#0f172a', marginBottom: 6 },\n detail: { fontSize: 13, color: '#64748b', textAlign: 'center' },\n textLight: { color: '#f1f5f9' },\n textMuted: { color: '#94a3b8' },\n});\n"],"names":[],"mappings":"6XAoBA,SAAwB,gBAAmB,GAAA;AACzC,EAAM,MAAA,MAAA,GAAS,gBAAqB,KAAA,MAAA;AACpC,EAAA,MAAM,aAAa,6BAA8B,EAAA;AAGjD,EAAM,MAAA;AAAA,IACJ;AAAA,MACE,gBAAiB,EAAA;AACrB,EAAA,IAAI,CAAC,UAAY,EAAA;AACf,IAAA,uBAAQ,GAAA,CAAA,YAAA,EAAA,EAAa,KAAO,EAAA,CAAC,OAAO,IAAM,EAAA,MAAA,IAAU,MAAO,CAAA,QAAQ,CACvD,EAAA,QAAA,kBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,KAAA,EAAO,OAAO,MAChB,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,IAAA,EAAA,EAAK,OAAO,CAAC,MAAA,CAAO,OAAO,MAAU,IAAA,MAAA,CAAO,SAAS,CAAA,EAAG,QAAsB,EAAA,wBAAA,EAAA,CAAA;AAAA,sBAC/E,GAAA,CAAC,IAAK,EAAA,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,QAAQ,MAAU,IAAA,MAAA,CAAO,SAAS,CAAA,EAAG,QAE1D,EAAA,wDAAA,EAAA;AAAA,KAAA,EACJ,CACJ,EAAA,CAAA;AAAA;AAEV,EAAO,uBAAA,GAAA,CAAC,gBAAa,KAAO,EAAA,CAAC,OAAO,IAAM,EAAA,MAAA,IAAU,MAAO,CAAA,QAAQ,CAAG,EAAA,KAAA,EAAO,CAAC,MAAQ,EAAA,OAAA,EAAS,QAAQ,CAC7F,EAAA,QAAA,kBAAA,GAAA,CAAC,cAAW,KAAc,EAAA,WAAA,EAAa,MAAO,CAAA,uBAAA,EAAyB,CAC3E,EAAA,CAAA;AACR;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,IAAM,EAAA;AAAA,IACJ,IAAM,EAAA,CAAA;AAAA,IACN,eAAiB,EAAA;AAAA,GACnB;AAAA,EACA,QAAU,EAAA;AAAA,IACR,eAAiB,EAAA;AAAA,GACnB;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,CAAA;AAAA,IACN,UAAY,EAAA,QAAA;AAAA,IACZ,cAAgB,EAAA,QAAA;AAAA,IAChB,OAAS,EAAA;AAAA,GACX;AAAA,EACA,KAAO,EAAA;AAAA,IACL,QAAU,EAAA,EAAA;AAAA,IACV,UAAY,EAAA,KAAA;AAAA,IACZ,KAAO,EAAA,SAAA;AAAA,IACP,YAAc,EAAA;AAAA,GAChB;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,QAAU,EAAA,EAAA;AAAA,IACV,KAAO,EAAA,SAAA;AAAA,IACP,SAAW,EAAA;AAAA,GACb;AAAA,EACA,SAAW,EAAA;AAAA,IACT,KAAO,EAAA;AAAA,GACT;AAAA,EACA,SAAW,EAAA;AAAA,IACT,KAAO,EAAA;AAAA;AAEX,CAAC,CAAA"}
@@ -0,0 +1,41 @@
1
+ import {config}from'../config/env-config.js';const BRAIN_PATH = "/resources/yantra-brain-classify";
2
+ const DEFAULT_TIMEOUT_MS = 15e3;
3
+ function brainEndpoint() {
4
+ const base = (config.CLIENT_URL || "").trim().replace(/\/+$/, "");
5
+ return base ? `${base}${BRAIN_PATH}` : "";
6
+ }
7
+ async function quickReplyViaBrain(text, opts = {}) {
8
+ var _a;
9
+ const trimmed = (text || "").trim();
10
+ const endpoint = brainEndpoint();
11
+ if (!trimmed || !endpoint) return null;
12
+ const controller = new AbortController();
13
+ const timeout = setTimeout(() => controller.abort(), (_a = opts.timeoutMs) != null ? _a : DEFAULT_TIMEOUT_MS);
14
+ if (opts.signal) {
15
+ if (opts.signal.aborted) controller.abort();
16
+ else opts.signal.addEventListener("abort", () => controller.abort(), {
17
+ once: true
18
+ });
19
+ }
20
+ try {
21
+ const res = await fetch(endpoint, {
22
+ method: "POST",
23
+ headers: {
24
+ "Content-Type": "application/json"
25
+ },
26
+ body: JSON.stringify({
27
+ text: trimmed.slice(0, 4e3),
28
+ task: "assist"
29
+ }),
30
+ signal: controller.signal
31
+ });
32
+ if (!res.ok) return null;
33
+ const data = await res.json();
34
+ const answer = typeof (data == null ? void 0 : data.answer) === "string" ? data.answer.trim() : "";
35
+ return answer || null;
36
+ } catch (e) {
37
+ return null;
38
+ } finally {
39
+ clearTimeout(timeout);
40
+ }
41
+ }export{brainEndpoint,quickReplyViaBrain};//# sourceMappingURL=brainQuickReply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brainQuickReply.js","sources":["../../src/services/brainQuickReply.ts"],"sourcesContent":["/**\n * brainQuickReply - a fast first answer from `yantra-brain` while the real agent works.\n *\n * On mobile the first turn of a session is the slow one: the agent session has to\n * bootstrap (sandbox, connectors, memory) before a single delta arrives, and on a\n * cold pod that can run to minutes. The web client only reaches for the brain after\n * the full 300s stall budget expires; mobile did not reach for it at all, so a user\n * saw nothing, then an error. This asks the brain the SAME question in parallel and\n * paints its answer as a provisional reply the moment it lands. When the agent's\n * stream starts, that provisional text is replaced in place - the brain fills the\n * silence, it never wins over the real answer.\n *\n * The brain proxy is the web app's same-origin Remix resource route\n * (`/resources/yantra-brain-classify`, `task: 'assist'`). Mobile has no same\n * origin, so it targets the web app's public URL (CLIENT_URL) explicitly. The\n * route holds the model, prompt and secret server-side - the phone still never\n * sees a provider key.\n *\n * Best-effort and never throws: null on timeout, network error or empty answer.\n */\nimport { config } from '../config/env-config';\n\nconst BRAIN_PATH = '/resources/yantra-brain-classify';\n\n// Server-side upstream cap is ASSIST_TIMEOUT_MS (12s); give the phone headroom\n// past it so a slow-but-successful answer is not aborted at the same instant.\nconst DEFAULT_TIMEOUT_MS = 15000;\n\n/** The web app origin that serves the brain proxy, or '' when unconfigured. */\nexport function brainEndpoint(): string {\n const base = (config.CLIENT_URL || '').trim().replace(/\\/+$/, '');\n return base ? `${base}${BRAIN_PATH}` : '';\n}\n\nexport async function quickReplyViaBrain(\n text: string,\n opts: { timeoutMs?: number; signal?: AbortSignal } = {},\n): Promise<string | null> {\n const trimmed = (text || '').trim();\n const endpoint = brainEndpoint();\n if (!trimmed || !endpoint) return null;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n if (opts.signal) {\n if (opts.signal.aborted) controller.abort();\n else opts.signal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text: trimmed.slice(0, 4000), task: 'assist' }),\n signal: controller.signal,\n });\n if (!res.ok) return null;\n const data = (await res.json()) as { answer?: string | null };\n const answer = typeof data?.answer === 'string' ? data.answer.trim() : '';\n return answer || null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"names":[],"mappings":"6CAqBA,MAAM,UAAa,GAAA,kCAAA;AAInB,MAAM,kBAAqB,GAAA,IAAA;AAGpB,SAAS,aAAwB,GAAA;AACtC,EAAM,MAAA,IAAA,GAAA,CAAQ,OAAO,UAAc,IAAA,EAAA,EAAI,MAAO,CAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAChE,EAAA,OAAO,IAAO,GAAA,CAAA,EAAG,IAAI,CAAA,EAAG,UAAU,CAAK,CAAA,GAAA,EAAA;AACzC;AACA,eAAsB,kBAAmB,CAAA,IAAA,EAAc,IAGnD,GAAA,EAA4B,EAAA;AAnChC,EAAA,IAAA,EAAA;AAoCE,EAAM,MAAA,OAAA,GAAA,CAAW,IAAQ,IAAA,EAAA,EAAI,IAAK,EAAA;AAClC,EAAA,MAAM,WAAW,aAAc,EAAA;AAC/B,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,QAAA,EAAiB,OAAA,IAAA;AAClC,EAAM,MAAA,UAAA,GAAa,IAAI,eAAgB,EAAA;AACvC,EAAM,MAAA,OAAA,GAAU,WAAW,MAAM,UAAA,CAAW,OAAS,EAAA,CAAA,EAAA,GAAA,IAAA,CAAK,SAAL,KAAA,IAAA,GAAA,EAAA,GAAkB,kBAAkB,CAAA;AACzF,EAAA,IAAI,KAAK,MAAQ,EAAA;AACf,IAAA,IAAI,IAAK,CAAA,MAAA,CAAO,OAAS,EAAA,UAAA,CAAW,KAAM,EAAA;AAAA,cAAY,MAAO,CAAA,gBAAA,CAAiB,SAAS,MAAM,UAAA,CAAW,OAAS,EAAA;AAAA,MAC/G,IAAM,EAAA;AAAA,KACP,CAAA;AAAA;AAEH,EAAI,IAAA;AACF,IAAM,MAAA,GAAA,GAAM,MAAM,KAAA,CAAM,QAAU,EAAA;AAAA,MAChC,MAAQ,EAAA,MAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,cAAgB,EAAA;AAAA,OAClB;AAAA,MACA,IAAA,EAAM,KAAK,SAAU,CAAA;AAAA,QACnB,IAAM,EAAA,OAAA,CAAQ,KAAM,CAAA,CAAA,EAAG,GAAI,CAAA;AAAA,QAC3B,IAAM,EAAA;AAAA,OACP,CAAA;AAAA,MACD,QAAQ,UAAW,CAAA;AAAA,KACpB,CAAA;AACD,IAAI,IAAA,CAAC,GAAI,CAAA,EAAA,EAAW,OAAA,IAAA;AACpB,IAAM,MAAA,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAK,EAAA;AAG7B,IAAM,MAAA,MAAA,GAAS,QAAO,IAAM,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,IAAA,CAAA,MAAA,CAAA,KAAW,WAAW,IAAK,CAAA,MAAA,CAAO,MAAS,GAAA,EAAA;AACvE,IAAA,OAAO,MAAU,IAAA,IAAA;AAAA,GACX,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA,GACP,SAAA;AACA,IAAA,YAAA,CAAa,OAAO,CAAA;AAAA;AAExB"}
@@ -0,0 +1,44 @@
1
+ function isEphemeralStreamStatus(text) {
2
+ const t = (text != null ? text : "").trim();
3
+ if (!t) return false;
4
+ return /^(Initializing\.?\.?\.?|Setting up( your (project|workspace))?\.?\.?\.?)$/i.test(t);
5
+ }
6
+ function mergeOptimisticGatewayMessages(optimistic, backend) {
7
+ var _a;
8
+ if (!optimistic.length || !backend.length) return backend;
9
+ const norm = (value) => value.trim();
10
+ const merged = [];
11
+ let cursor = 0;
12
+ for (const message of optimistic) {
13
+ let matchIndex = -1;
14
+ for (let j = cursor; j < backend.length; j++) {
15
+ const candidate = backend[j];
16
+ if (candidate.role === message.role && norm(candidate.content) === norm(message.content)) {
17
+ matchIndex = j;
18
+ break;
19
+ }
20
+ }
21
+ if (matchIndex >= 0) {
22
+ while (cursor <= matchIndex) {
23
+ merged.push(backend[cursor]);
24
+ cursor += 1;
25
+ }
26
+ continue;
27
+ }
28
+ if (message.role === "user") {
29
+ merged.push(message);
30
+ continue;
31
+ }
32
+ if (((_a = backend[cursor]) == null ? void 0 : _a.role) === "assistant") {
33
+ continue;
34
+ }
35
+ if (norm(message.content) && !isEphemeralStreamStatus(message.content)) {
36
+ merged.push(message);
37
+ }
38
+ }
39
+ while (cursor < backend.length) {
40
+ merged.push(backend[cursor]);
41
+ cursor += 1;
42
+ }
43
+ return merged;
44
+ }export{isEphemeralStreamStatus,mergeOptimisticGatewayMessages};//# sourceMappingURL=chatThreadMerge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatThreadMerge.js","sources":["../../src/state/chatThreadMerge.ts"],"sourcesContent":["/**\n * Positional thread merge, mirroring the browser package's\n * `state/chatThreadMerge.ts` (yantra-app PR #750). Kept as a dependency-free leaf\n * so it can be unit-tested without dragging in the Apollo / inversify import chain\n * that `useChatStream` pulls in.\n *\n * Mobile chat routes exclusively through CDeCLI (messenger-gateway) and lets the\n * gateway persist posts (`persistenceMode: 'backend'`). Many gateway lanes -\n * notably cdecli-serve skill turns - NEVER persist the assistant post: no\n * `chatMessageAdded` ever arrives and the reply lives only in the in-memory\n * thread. Before this merge the hydration effect wholesale-replaced the visible\n * thread with the backend snapshot on every refetch, so those replies vanished\n * the moment the next backend snapshot landed (and were gone on reopen).\n *\n * The merge keeps backend rows authoritative wherever they content-match, while\n * preserving optimistic-only rows in place:\n * - a user send the backend has not written yet stays visible (on mobile every\n * send is a gateway send, so there is no separate frontend-save lane that would\n * re-deliver it);\n * - an assistant reply the backend has no row for stays visible, unless the\n * backend's next row is itself an assistant for that turn (prefer the persisted\n * text, e.g. a webbuilder RESULT) or the text is an ephemeral status line.\n *\n * Attachment previews are intentionally NOT handled here - the caller composes\n * this with `preserveAttachmentPreviews`, which owns grafting the in-memory\n * data-URL onto the persisted user row.\n */\n\ntype MergeableMessage = {\n role: string;\n content: string;\n};\n\n/** Short gateway/workflow progress lines that must not stick under a final reply. */\nexport function isEphemeralStreamStatus(text: string | null | undefined): boolean {\n const t = (text ?? '').trim();\n if (!t) return false;\n return /^(Initializing\\.?\\.?\\.?|Setting up( your (project|workspace))?\\.?\\.?\\.?)$/i.test(t);\n}\n\nexport function mergeOptimisticGatewayMessages<T extends MergeableMessage>(optimistic: T[], backend: T[]): T[] {\n if (!optimistic.length || !backend.length) return backend;\n\n const norm = (value: string) => value.trim();\n\n const merged: T[] = [];\n let cursor = 0;\n for (const message of optimistic) {\n let matchIndex = -1;\n for (let j = cursor; j < backend.length; j++) {\n const candidate = backend[j];\n if (candidate.role === message.role && norm(candidate.content) === norm(message.content)) {\n matchIndex = j;\n break;\n }\n }\n if (matchIndex >= 0) {\n // Adopt every backend row up to and including the match so rows persisted\n // by other paths (channel mirror, sibling client) keep their position.\n while (cursor <= matchIndex) {\n merged.push(backend[cursor]);\n cursor += 1;\n }\n continue;\n }\n if (message.role === 'user') {\n // Every mobile send is a gateway send; keep it until the backend writes it.\n merged.push(message);\n continue;\n }\n // Assistant reply with no backend row. If the backend's next row is itself an\n // assistant (this turn's canonical reply, possibly different text), prefer it.\n // Otherwise keep the optimistic reply - dropping it here is what made replies\n // vanish the moment the next message was sent. Ephemeral status lines never\n // earn a permanent bubble.\n if (backend[cursor]?.role === 'assistant') {\n continue;\n }\n if (norm(message.content) && !isEphemeralStreamStatus(message.content)) {\n merged.push(message);\n }\n }\n // Backend rows past the last match (e.g. the assistant post landing mid-merge)\n // still belong at the end.\n while (cursor < backend.length) {\n merged.push(backend[cursor]);\n cursor += 1;\n }\n\n return merged;\n}\n"],"names":[],"mappings":"AAkCO,SAAS,wBAAwB,IAA0C,EAAA;AAChF,EAAM,MAAA,CAAA,GAAA,CAAK,IAAQ,IAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAI,IAAK,EAAA;AAC5B,EAAI,IAAA,CAAC,GAAU,OAAA,KAAA;AACf,EAAO,OAAA,4EAAA,CAA6E,KAAK,CAAC,CAAA;AAC5F;AACgB,SAAA,8BAAA,CAA2D,YAAiB,OAAmB,EAAA;AAvC/G,EAAA,IAAA,EAAA;AAwCE,EAAA,IAAI,CAAC,UAAW,CAAA,MAAA,IAAU,CAAC,OAAA,CAAQ,QAAe,OAAA,OAAA;AAClD,EAAA,MAAM,IAAO,GAAA,CAAC,KAAkB,KAAA,KAAA,CAAM,IAAK,EAAA;AAC3C,EAAA,MAAM,SAAc,EAAC;AACrB,EAAA,IAAI,MAAS,GAAA,CAAA;AACb,EAAA,KAAA,MAAW,WAAW,UAAY,EAAA;AAChC,IAAA,IAAI,UAAa,GAAA,EAAA;AACjB,IAAA,KAAA,IAAS,CAAI,GAAA,MAAA,EAAQ,CAAI,GAAA,OAAA,CAAQ,QAAQ,CAAK,EAAA,EAAA;AAC5C,MAAM,MAAA,SAAA,GAAY,QAAQ,CAAC,CAAA;AAC3B,MAAI,IAAA,SAAA,CAAU,IAAS,KAAA,OAAA,CAAQ,IAAQ,IAAA,IAAA,CAAK,SAAU,CAAA,OAAO,CAAM,KAAA,IAAA,CAAK,OAAQ,CAAA,OAAO,CAAG,EAAA;AACxF,QAAa,UAAA,GAAA,CAAA;AACb,QAAA;AAAA;AACF;AAEF,IAAA,IAAI,cAAc,CAAG,EAAA;AAGnB,MAAA,OAAO,UAAU,UAAY,EAAA;AAC3B,QAAO,MAAA,CAAA,IAAA,CAAK,OAAQ,CAAA,MAAM,CAAC,CAAA;AAC3B,QAAU,MAAA,IAAA,CAAA;AAAA;AAEZ,MAAA;AAAA;AAEF,IAAI,IAAA,OAAA,CAAQ,SAAS,MAAQ,EAAA;AAE3B,MAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AACnB,MAAA;AAAA;AAOF,IAAA,IAAA,CAAA,CAAI,EAAQ,GAAA,OAAA,CAAA,MAAM,CAAd,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAiB,UAAS,WAAa,EAAA;AACzC,MAAA;AAAA;AAEF,IAAI,IAAA,IAAA,CAAK,QAAQ,OAAO,CAAA,IAAK,CAAC,uBAAwB,CAAA,OAAA,CAAQ,OAAO,CAAG,EAAA;AACtE,MAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AAAA;AACrB;AAIF,EAAO,OAAA,MAAA,GAAS,QAAQ,MAAQ,EAAA;AAC9B,IAAO,MAAA,CAAA,IAAA,CAAK,OAAQ,CAAA,MAAM,CAAC,CAAA;AAC3B,IAAU,MAAA,IAAA,CAAA;AAAA;AAEZ,EAAO,OAAA,MAAA;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminide-stack/yantra-mobile",
3
- "version": "12.0.51-alpha.1",
3
+ "version": "12.0.51-alpha.4",
4
4
  "description": "Sample core for higher packages to depend on",
5
5
  "license": "ISC",
6
6
  "author": "CDMBase LLC",
@@ -25,7 +25,7 @@
25
25
  "uuid": "^9.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "common": "12.0.51-alpha.1"
28
+ "common": "12.0.51-alpha.4"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@admin-layout/gluestack-ui-mobile": "*",
@@ -41,7 +41,7 @@
41
41
  "@messenger-box/platform-mobile": "*",
42
42
  "@react-native-async-storage/async-storage": "*",
43
43
  "@react-navigation/native": "*",
44
- "common": "12.0.51-alpha.0",
44
+ "common": "12.0.51-alpha.1",
45
45
  "expo-constants": "*",
46
46
  "expo-secure-store": "*",
47
47
  "lodash": "*",
@@ -57,5 +57,5 @@
57
57
  "typescript": {
58
58
  "definition": "lib/index.d.ts"
59
59
  },
60
- "gitHead": "f68cc7cb9470bb5e3b933bb1e91037e403d5d1a2"
60
+ "gitHead": "a953658078529dc90cb1286415bccb5a81c43313"
61
61
  }