@ateam-ai-mobile/ui 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/AdasChatScreen.tsx","../src/chat/MessageBubble.tsx","../src/chat/JobProgressBar.tsx","../src/chat/UIActionsRow.tsx","../src/providers.ts","../src/chat/TableCardBlock.tsx","../src/stores/lightboxStore.ts","../src/chat/TypingIndicator.tsx","../src/chat/ChatInput.tsx","../src/Lightbox.tsx","../src/plugin/Plugin.tsx","../src/plugin/EmbeddedPluginWebView.tsx","../src/plugin/usePluginBridge.ts","../src/plugin/PluginSurface.tsx","../src/widgets/WidgetWebView.tsx"],"sourcesContent":["/**\n * AdasChatScreen — the drop-in ADAS chat experience.\n *\n * Push it as a screen or mount it full-flex anywhere: message history with\n * live agent progress, day separators, UI-action chips, table cards,\n * attachment previews, pull-to-refresh, and the composer. Built ONLY on the\n * public @ateam-ai-mobile/core + @ateam-ai-mobile/plugin-runtime APIs.\n *\n * The host must call initAdas() first, and typically:\n * setUIActionNavigator(route => router.push(...)) // chips → plugin screen\n * setupExpoProviders() // Expo hosts\n *\n * Also mount <SurfaceDispatcher /> once at the app root so engine-summoned\n * plugin surfaces (drawers/fullscreens) can appear over the chat.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n FlatList,\n KeyboardAvoidingView,\n Platform,\n RefreshControl,\n StyleSheet,\n View,\n} from 'react-native';\nimport { ada, useChatStore, pluginCommandEmitter } from '@ateam-ai-mobile/core';\nimport type { Message } from '@ateam-ai-mobile/core';\nimport MessageBubble, { DaySeparator } from './chat/MessageBubble';\nimport TypingIndicator from './chat/TypingIndicator';\nimport ChatInput from './chat/ChatInput';\nimport Lightbox from './Lightbox';\n\nexport interface AdasChatScreenProps {\n /** Called when the user taps the call button; omit to hide it. */\n onCallStart?: () => void;\n /** Composer placeholder. */\n placeholder?: string;\n /** Extra element rendered above the composer (e.g. suggestion chips). */\n footerAccessory?: React.ReactNode;\n /** Mount the fullscreen image viewer here. Default true — set false if\n * the host already mounts <Lightbox /> at its root. */\n withLightbox?: boolean;\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\n}\n\nexport default function AdasChatScreen({\n onCallStart,\n placeholder,\n footerAccessory,\n withLightbox = true,\n}: AdasChatScreenProps) {\n const messages = useChatStore(s => s.messages);\n const loading = useChatStore(s => s.loading);\n const conversationLoaded = useChatStore(s => s.conversationLoaded);\n const loadConversation = useChatStore(s => s.loadConversation);\n const sendMessage = useChatStore(s => s.sendMessage);\n\n const listRef = useRef<FlatList<Message>>(null);\n const [refreshing, setRefreshing] = useState(false);\n const [prefill, setPrefill] = useState('');\n\n // Initial history load.\n useEffect(() => {\n if (!conversationLoaded) loadConversation();\n }, [conversationLoaded, loadConversation]);\n\n // bridge.prepareMessage(text) → pre-fill the composer without sending.\n useEffect(() => {\n const unsub = pluginCommandEmitter.on('prepare-chat-message', ({ text }: { text?: string }) => {\n if (text) setPrefill(text);\n });\n return unsub;\n }, []);\n\n // Inverted list — newest at the bottom without scroll management. On WEB\n // `inverted` is a scaleY(-1) transform that react-native-web applies to the\n // container but not reliably to items — content renders upside-down. Use a\n // normal list there and scroll to the end instead.\n const useInverted = Platform.OS !== 'web';\n const listData = useMemo(\n () => (useInverted ? [...messages].reverse() : messages),\n [messages, useInverted],\n );\n\n const scrollToLatest = useCallback((animated: boolean) => {\n if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });\n else listRef.current?.scrollToEnd({ animated });\n }, [useInverted]);\n\n const handleSend = useCallback((text: string) => {\n setPrefill('');\n sendMessage(text);\n scrollToLatest(true);\n }, [sendMessage, scrollToLatest]);\n\n const handleRefresh = useCallback(async () => {\n setRefreshing(true);\n try { await loadConversation(); } catch {}\n setRefreshing(false);\n }, [loadConversation]);\n\n const renderItem = useCallback(({ item, index }: { item: Message; index: number }) => {\n // The \"previous\" (older) message is the NEXT array item when inverted,\n // the previous one otherwise.\n const older = useInverted ? listData[index + 1] : listData[index - 1];\n const showDaySep = !older || !isSameDay(older.timestamp, item.timestamp);\n return (\n <View>\n {showDaySep && <DaySeparator date={item.timestamp} />}\n <MessageBubble message={item} />\n </View>\n );\n }, [listData, useInverted]);\n\n const typingIndicator =\n loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress\n ? <TypingIndicator />\n : null;\n\n return (\n <KeyboardAvoidingView\n style={styles.root}\n behavior={Platform.OS === 'ios' ? 'padding' : undefined}\n >\n <FlatList\n ref={listRef}\n style={styles.list}\n data={listData}\n inverted={useInverted}\n keyExtractor={m => m.id}\n renderItem={renderItem}\n // In an inverted list the header renders at the visual BOTTOM — where\n // the typing indicator belongs; on web (non-inverted) it's the footer.\n // Suppressed when the newest bubble already shows its progress pill.\n ListHeaderComponent={useInverted ? typingIndicator : null}\n ListFooterComponent={useInverted ? null : typingIndicator}\n onContentSizeChange={useInverted ? undefined : () => scrollToLatest(false)}\n refreshControl={\n <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={ada.orange} />\n }\n keyboardShouldPersistTaps=\"handled\"\n contentContainerStyle={styles.listContent}\n />\n {footerAccessory}\n <ChatInput\n onSend={handleSend}\n onCallStart={onCallStart}\n loading={loading}\n prefill={prefill}\n placeholder={placeholder}\n />\n {withLightbox && <Lightbox />}\n </KeyboardAvoidingView>\n );\n}\n\nconst styles = StyleSheet.create({\n root: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n list: {\n flex: 1,\n },\n listContent: {\n paddingVertical: 12,\n },\n});\n","import { useState, useCallback } from 'react';\nimport { View, Text, Pressable, StyleSheet, Image, Platform } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport type { Message, ToolCall } from '@ateam-ai-mobile/core';\nimport { TABLE_BLOCK_OPEN, TABLE_BLOCK_CLOSE, getConfig, ada, borderRadius, fontSize } from '@ateam-ai-mobile/core';\nimport { AdaOrb, getHaptics } from '@ateam-ai-mobile/plugin-runtime';\nimport JobProgressBar from './JobProgressBar';\nimport UIActionsRow from './UIActionsRow';\nimport TableCardBlock from './TableCardBlock';\nimport { useLightboxStore } from '../stores/lightboxStore';\nimport { getClipboard } from '../providers';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\ninterface Props {\n message: Message;\n}\n\nfunction formatTime(date: Date): string {\n const h = date.getHours();\n const m = date.getMinutes().toString().padStart(2, '0');\n return `${h}:${m}`;\n}\n\nexport function DaySeparator({ date }: { date: Date }) {\n const today = new Date();\n const yesterday = new Date(today);\n yesterday.setDate(yesterday.getDate() - 1);\n\n let label: string;\n if (isSameDay(date, today)) {\n label = 'Today';\n } else if (isSameDay(date, yesterday)) {\n label = 'Yesterday';\n } else {\n label = date.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' });\n }\n\n return (\n <View style={separatorStyles.container}>\n <View style={separatorStyles.pill}>\n <Text style={separatorStyles.text}>{label}</Text>\n </View>\n </View>\n );\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\n}\n\nconst separatorStyles = StyleSheet.create({\n container: {\n alignItems: 'center',\n marginVertical: 14,\n },\n pill: {\n backgroundColor: ada.paperSoft,\n paddingHorizontal: 14,\n paddingVertical: 5,\n borderRadius: 999,\n },\n text: {\n fontSize: 12,\n fontWeight: '600',\n color: ada.inkMuted,\n letterSpacing: 0.4,\n fontFamily: fontFam,\n },\n});\n\nexport default function MessageBubble({ message }: Props) {\n const isUser = message.role === 'user';\n const isSystem = message.role === 'system';\n const isNotification = !isUser && message.kind === 'notification';\n const [copied, setCopied] = useState('');\n\n const showCopied = useCallback((label: string) => {\n setCopied(label);\n getHaptics()?.notification('success');\n setTimeout(() => setCopied(''), 1500);\n }, []);\n\n const copyText = useCallback(async (text: string) => {\n const clipboard = getClipboard();\n if (!clipboard) return; // no provider — silently skip (no false \"Copied!\")\n await clipboard.setString(text);\n showCopied('Copied!');\n }, [showCopied]);\n\n const handleLongPress = useCallback(() => {\n copyText(message.content);\n }, [message.content, copyText]);\n\n return (\n <View style={[styles.row, isUser && styles.rowUser]}>\n {!isUser && !isNotification && (\n <View style={styles.avatarOrb}>\n <AdaOrb\n size={28}\n state={message.jobProgress?.status === 'running' ? 'thinking' : 'idle'}\n />\n </View>\n )}\n {isNotification && <View style={styles.notificationLeftStripe} />}\n <Pressable\n accessibilityRole=\"text\"\n accessibilityLabel={`${isUser ? 'You' : isNotification ? 'Notification' : 'Ada'}: ${message.content}`}\n accessibilityHint=\"Long-press to copy\"\n onLongPress={handleLongPress}\n style={[\n styles.bubble,\n isUser && styles.bubbleUser,\n isSystem && styles.bubbleSystem,\n isNotification && styles.bubbleNotification,\n ]}\n >\n {isNotification && (\n <View style={styles.notificationHeader}>\n <Text style={styles.notificationHeaderIcon}>🔔</Text>\n <Text style={styles.notificationHeaderText}>Notification</Text>\n </View>\n )}\n {/* Attachment thumbnails */}\n {message.attachments && message.attachments.length > 0 && (\n <View style={styles.attachments}>\n {message.attachments.map((att, i) => (\n att.previewUri ? (\n <Pressable\n key={i}\n accessibilityRole=\"imagebutton\"\n accessibilityLabel={`Photo${att.name ? `: ${att.name}` : ''}. Tap to enlarge.`}\n onPress={() => useLightboxStore.getState().show(att.previewUri!)}\n >\n <Image\n source={{\n uri: att.previewUri,\n ...(att.previewUri.startsWith('http') ? {\n headers: { Authorization: `Bearer ${getConfig().tokenProvider.getToken()}` },\n } : {}),\n }}\n style={styles.attachmentImage}\n resizeMode=\"cover\"\n onError={(e) => console.warn('[Image] Failed to load:', att.previewUri?.slice(0, 100), e.nativeEvent.error)}\n />\n </Pressable>\n ) : (\n <View key={i} style={styles.attachmentFile}>\n <Text style={styles.attachmentFileName} numberOfLines={1}>{att.name}</Text>\n </View>\n )\n ))}\n </View>\n )}\n {isUser ? (\n // Hide the \"See attachment\" placeholder when the bubble already shows the photo.\n message.content && !(message.content === 'See attachment' && message.attachments?.length) ? (\n <Text selectable style={styles.userText}>{message.content}</Text>\n ) : null\n ) : (\n renderAssistantContent(message.content, copyText)\n )}\n {!!copied && (\n <View style={styles.copiedBadge}>\n <Text style={styles.copiedText}>{copied}</Text>\n </View>\n )}\n {message.toolCalls && message.toolCalls.length > 0 && (\n <ToolCallsBadge calls={message.toolCalls} />\n )}\n {message.jobProgress && (\n <JobProgressBar progress={message.jobProgress} />\n )}\n {!isUser && message.actions && message.actions.length > 0 && (\n <UIActionsRow actions={message.actions} />\n )}\n <Text style={[styles.timeText, isUser && styles.timeTextUser]}>\n {formatTime(message.timestamp)}\n </Text>\n </Pressable>\n {isUser && <View style={styles.avatarSpacer} />}\n </View>\n );\n}\n\n/**\n * Split assistant content on table-block sentinels and render each segment:\n * markdown text → <Markdown>, table payload → <TableCardBlock>. Falls back\n * to plain markdown when no sentinels are present (the common case).\n */\nfunction renderAssistantContent(content: string, copyText: (s: string) => void) {\n if (!content) return null;\n if (!content.includes(TABLE_BLOCK_OPEN)) {\n return <MarkdownBubble content={content} copyText={copyText} />;\n }\n\n const segments: Array<{ kind: 'md' | 'table'; content: string }> = [];\n let cursor = 0;\n while (cursor < content.length) {\n const open = content.indexOf(TABLE_BLOCK_OPEN, cursor);\n if (open === -1) {\n segments.push({ kind: 'md', content: content.slice(cursor) });\n break;\n }\n if (open > cursor) {\n segments.push({ kind: 'md', content: content.slice(cursor, open) });\n }\n const close = content.indexOf(TABLE_BLOCK_CLOSE, open + TABLE_BLOCK_OPEN.length);\n if (close === -1) {\n // Unterminated marker — render the rest as markdown so we don't lose data.\n segments.push({ kind: 'md', content: content.slice(open) });\n break;\n }\n const payload = content.slice(open + TABLE_BLOCK_OPEN.length, close);\n segments.push({ kind: 'table', content: payload });\n cursor = close + TABLE_BLOCK_CLOSE.length;\n }\n\n return (\n <>\n {segments.map((seg, i) => {\n if (seg.kind === 'md') {\n const trimmed = seg.content.trim();\n if (!trimmed) return null;\n return <MarkdownBubble key={i} content={seg.content} copyText={copyText} />;\n }\n try {\n const data = JSON.parse(decodeURIComponent(seg.content));\n return <TableCardBlock key={i} columns={data.columns} rows={data.rows} />;\n } catch {\n return null;\n }\n })}\n </>\n );\n}\n\nfunction MarkdownBubble({ content, copyText }: { content: string; copyText: (s: string) => void }) {\n return (\n <Markdown\n style={markdownStyles}\n rules={{\n code_inline: (node) => (\n <Text\n key={node.key}\n style={markdownStyles.code_inline}\n onPress={() => copyText(node.content)}\n >\n {node.content}\n </Text>\n ),\n fence: (node) => (\n <Pressable\n key={node.key}\n onPress={() => copyText(node.content)}\n style={markdownStyles.fence}\n >\n <Text style={markdownStyles.fenceText}>{node.content}</Text>\n </Pressable>\n ),\n }}\n >\n {content}\n </Markdown>\n );\n}\n\nfunction ToolCallsBadge({ calls }: { calls: ToolCall[] }) {\n const [expanded, setExpanded] = useState(false);\n const label = calls\n .filter(c => c.name === 'send_message_to_skill')\n .map(c => (c.args as any).skill_name || c.skill)\n .filter(Boolean)\n .join(', ') || 'Tool call';\n\n return (\n <View style={styles.toolCalls}>\n <Pressable style={styles.toolToggle} onPress={() => setExpanded(!expanded)}>\n <Text style={styles.toolLabel}>{label}</Text>\n <Text style={styles.toolChevron}>{expanded ? '▲' : '▼'}</Text>\n </Pressable>\n {expanded && calls.map((tc, i) => (\n <View key={i} style={styles.toolItem}>\n <Text style={styles.toolName}>{tc.name}</Text>\n {tc.skill && <Text style={styles.toolSkill}>{tc.skill}</Text>}\n </View>\n ))}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n row: {\n flexDirection: 'row',\n alignItems: 'flex-end',\n marginBottom: 12,\n paddingHorizontal: 12,\n gap: 8,\n },\n rowUser: {\n justifyContent: 'flex-end',\n },\n avatarOrb: {\n width: 28,\n height: 28,\n justifyContent: 'center',\n alignItems: 'center',\n },\n avatarSpacer: {\n width: 28,\n height: 28,\n },\n bubble: {\n maxWidth: '78%',\n backgroundColor: ada.botBubble,\n borderWidth: 1,\n borderColor: ada.botBubbleBorder,\n borderRadius: borderRadius.xl,\n paddingHorizontal: 16,\n paddingVertical: 12,\n },\n bubbleUser: {\n backgroundColor: ada.userBubble,\n borderColor: 'transparent',\n },\n bubbleSystem: {\n backgroundColor: ada.orangeSoft,\n borderColor: ada.orange,\n maxWidth: '90%',\n },\n bubbleNotification: {\n backgroundColor: ada.orangeSoft,\n borderColor: 'rgba(255, 122, 40, 0.35)',\n borderRadius: borderRadius.lg,\n maxWidth: '90%',\n },\n notificationLeftStripe: {\n width: 3,\n alignSelf: 'stretch',\n backgroundColor: ada.orange,\n borderRadius: 2,\n marginRight: 4,\n },\n notificationHeader: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 6,\n marginBottom: 6,\n },\n notificationHeaderIcon: {\n fontSize: 12,\n },\n notificationHeaderText: {\n color: ada.orangeDeep,\n fontSize: 11,\n fontWeight: '700',\n letterSpacing: 0.6,\n textTransform: 'uppercase',\n fontFamily: fontFam,\n },\n userText: {\n color: ada.userBubbleText,\n fontSize: fontSize.md,\n lineHeight: 22,\n fontFamily: fontFam,\n },\n timeText: {\n fontSize: 11,\n color: ada.inkMuted,\n alignSelf: 'flex-end',\n marginTop: 4,\n fontFamily: fontFam,\n },\n timeTextUser: {\n color: 'rgba(250,246,238,0.55)',\n },\n attachments: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 6,\n marginBottom: 8,\n },\n attachmentImage: {\n width: 120,\n height: 90,\n borderRadius: 8,\n },\n attachmentFile: {\n backgroundColor: 'rgba(255,255,255,0.15)',\n borderRadius: 8,\n paddingHorizontal: 10,\n paddingVertical: 6,\n },\n attachmentFileName: {\n color: 'rgba(255,255,255,0.8)',\n fontSize: 11,\n maxWidth: 100,\n },\n copiedBadge: {\n position: 'absolute',\n top: 6,\n right: 6,\n backgroundColor: '#5A8A5C',\n paddingHorizontal: 8,\n paddingVertical: 2,\n borderRadius: borderRadius.sm,\n },\n copiedText: {\n color: '#fff',\n fontSize: fontSize.xs,\n fontWeight: '600',\n },\n toolCalls: {\n marginTop: 8,\n borderTopWidth: 1,\n borderTopColor: ada.border,\n paddingTop: 6,\n },\n toolToggle: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 6,\n },\n toolLabel: {\n color: ada.inkSoft,\n fontSize: fontSize.xs,\n fontWeight: '500',\n },\n toolChevron: {\n color: ada.inkMuted,\n fontSize: 8,\n },\n toolItem: {\n marginTop: 4,\n paddingLeft: 8,\n },\n toolName: {\n color: ada.inkSoft,\n fontSize: fontSize.xs,\n fontFamily: 'monospace',\n },\n toolSkill: {\n color: ada.orangeDeep,\n fontSize: fontSize.xs,\n },\n});\n\nconst markdownStyles = StyleSheet.create({\n body: {\n color: ada.botBubbleText,\n fontSize: fontSize.md,\n lineHeight: 22,\n fontFamily: fontFam,\n },\n paragraph: {\n marginTop: 0,\n marginBottom: 4,\n },\n strong: {\n fontWeight: '700',\n color: ada.botBubbleText,\n },\n em: {\n fontStyle: 'italic',\n },\n code_inline: {\n backgroundColor: ada.paperDeep,\n color: ada.orangeDeep,\n paddingHorizontal: 5,\n paddingVertical: 1,\n borderRadius: 4,\n fontFamily: 'monospace',\n fontSize: fontSize.sm,\n },\n fence: {\n backgroundColor: ada.paperDeep,\n padding: 10,\n borderRadius: 10,\n },\n fenceText: {\n fontFamily: 'monospace',\n fontSize: fontSize.sm,\n color: ada.ink,\n },\n link: {\n color: ada.orangeDeep,\n },\n list_item: {\n color: ada.botBubbleText,\n },\n bullet_list: {\n marginVertical: 4,\n },\n});\n","import { useEffect, useRef, useState } from 'react';\nimport { View, Text, Animated, StyleSheet } from 'react-native';\nimport type { JobProgress } from '@ateam-ai-mobile/core';\nimport { ada, fontSize, borderRadius } from '@ateam-ai-mobile/core';\n\n// Semantic warm-palette colors for terminal states (kept local — Ada palette\n// is mostly orange/ink, but progress states need a small warm green/red/amber\n// vocabulary to stay readable).\nconst WARM_GREEN = '#5A8A5C';\nconst WARM_GREEN_SOFT = 'rgba(90, 138, 92, 0.14)';\nconst WARM_RED = '#C15545';\nconst WARM_RED_SOFT = 'rgba(193, 85, 69, 0.14)';\nconst WARM_AMBER = '#D49A4E';\nconst WARM_AMBER_SOFT = 'rgba(212, 154, 78, 0.16)';\n\ninterface Props {\n progress: JobProgress;\n}\n\nexport default function JobProgressBar({ progress }: Props) {\n const pulseAnim = useRef(new Animated.Value(0.5)).current;\n const [elapsed, setElapsed] = useState(0);\n\n const isRunning = progress.status === 'running';\n const isDone = progress.status === 'done';\n const isError = progress.status === 'error';\n const isPaused = progress.status === 'paused';\n\n // Pulse animation while running\n useEffect(() => {\n if (!isRunning) {\n pulseAnim.setValue(1);\n return;\n }\n const anim = Animated.loop(\n Animated.sequence([\n Animated.timing(pulseAnim, { toValue: 1, duration: 800, useNativeDriver: true }),\n Animated.timing(pulseAnim, { toValue: 0.5, duration: 800, useNativeDriver: true }),\n ]),\n );\n anim.start();\n return () => anim.stop();\n }, [isRunning, pulseAnim]);\n\n // Elapsed timer — ticks every second while running, clears on terminal state\n useEffect(() => {\n if (!isRunning || !progress.chainStartedAt) {\n setElapsed(0);\n return;\n }\n const start = progress.chainStartedAt;\n setElapsed(Math.floor((Date.now() - start) / 1000));\n const id = setInterval(() => {\n setElapsed(Math.floor((Date.now() - start) / 1000));\n }, 1000);\n return () => clearInterval(id);\n }, [isRunning, progress.chainStartedAt]);\n\n const statusColor = isDone\n ? WARM_GREEN\n : isError\n ? WARM_RED\n : isPaused\n ? WARM_AMBER\n : ada.orange;\n\n const statusBg = isDone\n ? WARM_GREEN_SOFT\n : isError\n ? WARM_RED_SOFT\n : isPaused\n ? WARM_AMBER_SOFT\n : ada.orangeSoft;\n\n // Terminal states — simple one-liner\n if (isDone || isError) {\n return (\n <View style={styles.container}>\n <View style={styles.row}>\n <View style={[styles.dot, { backgroundColor: statusColor }]} />\n <Text style={[styles.label, { color: statusColor }]}>\n {isDone ? 'Completed' : 'Failed'}\n </Text>\n </View>\n </View>\n );\n }\n\n if (isPaused) {\n return (\n <View style={styles.container}>\n <View style={styles.row}>\n <View style={[styles.dot, { backgroundColor: statusColor }]} />\n <Text style={[styles.label, { color: statusColor }]}>\n Waiting for your reply\n </Text>\n </View>\n </View>\n );\n }\n\n // Running — voice-agent-style compact line:\n // ● messaging-agent → whatsapp.send · iter 3 · 0:42\n const elapsedStr = progress.chainStartedAt\n ? `${Math.floor(elapsed / 60)}:${String(elapsed % 60).padStart(2, '0')}`\n : null;\n\n const metaParts = [\n progress.iteration != null && progress.iteration > 0 ? `iter ${progress.iteration}` : null,\n elapsedStr,\n ].filter(Boolean) as string[];\n\n const lp = progress.liveProgress;\n\n return (\n <View style={styles.container}>\n <View style={styles.row}>\n <Animated.View\n style={[styles.dot, { backgroundColor: ada.orange, opacity: pulseAnim }]}\n />\n <Text style={styles.skill} numberOfLines={1}>\n {progress.skillSlug || 'Processing...'}\n </Text>\n {!!progress.currentTool && (\n <Text style={styles.tool} numberOfLines={1}>\n {` → ${progress.currentTool}`}\n </Text>\n )}\n {metaParts.length > 0 && (\n <Text style={styles.meta} numberOfLines={1}>\n {` · ${metaParts.join(' · ')}`}\n </Text>\n )}\n </View>\n {/* Live progress from adas_emit_progress() — shown during fat tool execution */}\n {!!lp?.message && (\n <View style={styles.liveProgressRow}>\n {lp.step != null && lp.total != null && (\n <Text style={styles.liveProgressStep}>[{lp.step}/{lp.total}]</Text>\n )}\n <Text style={styles.liveProgressMsg} numberOfLines={1}>{lp.message}</Text>\n </View>\n )}\n <View style={[styles.progressTrack, { backgroundColor: statusBg }]}>\n <Animated.View\n style={[styles.progressFill, { backgroundColor: ada.orange, opacity: pulseAnim }]}\n />\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n marginTop: 8,\n paddingTop: 8,\n borderTopWidth: 1,\n borderTopColor: ada.border,\n },\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n flexWrap: 'nowrap',\n overflow: 'hidden',\n },\n dot: {\n width: 7,\n height: 7,\n borderRadius: 4,\n marginRight: 6,\n flexShrink: 0,\n },\n label: {\n fontSize: fontSize.xs,\n fontWeight: '600',\n },\n // Running state text segments\n skill: {\n fontSize: fontSize.xs,\n fontWeight: '600',\n color: ada.orange,\n flexShrink: 1,\n },\n tool: {\n fontSize: fontSize.xs,\n color: ada.inkMuted,\n flexShrink: 2,\n },\n meta: {\n fontSize: fontSize.xs,\n color: ada.inkMuted,\n flexShrink: 0,\n },\n liveProgressRow: {\n flexDirection: 'row',\n alignItems: 'center',\n marginTop: 4,\n gap: 4,\n },\n liveProgressStep: {\n fontSize: fontSize.xs,\n fontWeight: '600',\n color: ada.inkMuted,\n },\n liveProgressMsg: {\n fontSize: fontSize.xs,\n color: ada.orange,\n flex: 1,\n },\n progressTrack: {\n height: 3,\n borderRadius: 2,\n marginTop: 6,\n overflow: 'hidden',\n },\n progressFill: {\n height: '100%',\n width: '60%',\n borderRadius: 2,\n },\n});\n","// Renders the row of UI Action chips beneath an assistant bubble.\n// Payload comes from `message.actions[]` produced by the polisher and\n// validated by Core's `cp.uiActions_api`.\n//\n// Navigation is host-owned: chips targeting a registered surface mount it\n// directly via the surface store; anything else goes through the navigator\n// registered with setUIActionNavigator() (no router imports here).\n\nimport { View, Text, Pressable, StyleSheet, Platform } from 'react-native';\nimport { ada, fontSize } from '@ateam-ai-mobile/core';\nimport type { UIAction } from '@ateam-ai-mobile/core';\nimport { useSurfaceStore, getHaptics } from '@ateam-ai-mobile/plugin-runtime';\nimport { getUIActionNavigator } from '../providers';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\nconst ICON_MAP: Record<string, string> = {\n timer: '⏱',\n memory: '🧠',\n calendar: '📅',\n home: '🏠',\n bell: '🔔',\n star: '⭐',\n bolt: '⚡',\n};\n\ninterface Props {\n actions: UIAction[];\n}\n\nexport default function UIActionsRow({ actions }: Props) {\n if (!Array.isArray(actions) || actions.length === 0) return null;\n\n const handlePress = (a: UIAction) => {\n getHaptics()?.selection();\n // Validator returns pluginId as the full id (\"mcp:connector:plugin\" for\n // solution plugins, \"platform:<name>\" for host platform screens). Fall\n // back to target (short id) only for debugging.\n const id = a.pluginId || a.target;\n if (!id) return;\n\n // Compute focus once; both branches use it.\n const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;\n\n // Host-rendered platform surfaces live in the surface store, not in the\n // dynamic plugin registry. Mount the registered drawer/fullscreen\n // directly — skip navigation (which would try getPlugin() and fail).\n const surface = useSurfaceStore.getState().manifests[id];\n if (surface) {\n useSurfaceStore.getState().show(id, focus ? { focus } : undefined);\n return;\n }\n\n // Pass payload fields as params so the plugin can focus the relevant\n // entity on load (e.g. ?focus=rem_abc).\n const params: Record<string, string> = {};\n if (focus) params.focus = String(focus);\n if (a.intent === 'suggested_action' && a.payload?.tool) {\n params.tool = String(a.payload.tool);\n if (a.payload?.args) {\n try { params.args = JSON.stringify(a.payload.args); } catch {}\n }\n }\n if (a.intent === 'quick_toggle' && a.payload?.verb) {\n params.verb = String(a.payload.verb);\n }\n\n const navigate = getUIActionNavigator();\n if (!navigate) {\n console.warn('[UIActionsRow] no navigator registered — call setUIActionNavigator() in the host to handle plugin chips');\n return;\n }\n navigate({ pluginId: id, params });\n };\n\n return (\n <View style={styles.row}>\n {actions.map((a, i) => {\n const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;\n return (\n <Pressable\n key={`${a.target || 'a'}-${i}`}\n accessibilityRole=\"button\"\n accessibilityLabel={a.description || a.label}\n onPress={() => handlePress(a)}\n style={({ pressed }) => [styles.chip, pressed && styles.chipPressed]}\n >\n {icon ? <Text style={styles.icon}>{icon}</Text> : null}\n <Text style={styles.label} numberOfLines={1}>{a.label}</Text>\n </Pressable>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n row: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 6,\n marginTop: 10,\n },\n chip: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 6,\n paddingHorizontal: 12,\n paddingVertical: 6,\n backgroundColor: ada.orangeSoft,\n borderColor: ada.orange,\n borderWidth: 1,\n borderRadius: 14,\n },\n chipPressed: {\n backgroundColor: ada.orange,\n },\n icon: {\n fontSize: 12,\n lineHeight: 14,\n },\n label: {\n color: ada.orangeDeep,\n fontSize: fontSize.xs,\n fontWeight: '600',\n fontFamily: fontFam,\n },\n});\n","/**\n * Host integration points for @ateam-ai-mobile/ui — same DI pattern as\n * @ateam-ai-mobile/plugin-runtime's host.ts. Everything has a safe default; the host\n * (or @ateam-ai-mobile/expo) wires real implementations at startup.\n */\n\n// ── Clipboard ──────────────────────────────────────────────────────────────\n\nexport interface ClipboardProvider {\n setString(text: string): Promise<void>;\n}\n\nlet _clipboard: ClipboardProvider | null = null;\n\n/** Register a clipboard implementation (e.g. expo-clipboard via @ateam-ai-mobile/expo). */\nexport function setClipboardProvider(clipboard: ClipboardProvider | null): void {\n _clipboard = clipboard;\n}\n\nexport function getClipboard(): ClipboardProvider | null {\n return _clipboard;\n}\n\n// ── UI-action navigation ───────────────────────────────────────────────────\n// Chips with a plugin target need to open the host's plugin screen. The SDK\n// never imports a router — the HOST owns navigation and registers a handler\n// (e.g. Expo Router: router.push({ pathname: '/plugin/[id]', params })).\n\nexport interface UIActionRoute {\n /** Full plugin id, e.g. \"mcp:connector:plugin\" or \"platform:<name>\". */\n pluginId: string;\n /** Query params the plugin reads on load (focus, tool, args, verb). */\n params: Record<string, string>;\n}\n\ntype UIActionNavigator = (route: UIActionRoute) => void;\n\nlet _navigator: UIActionNavigator | null = null;\n\nexport function setUIActionNavigator(navigate: UIActionNavigator | null): void {\n _navigator = navigate;\n}\n\nexport function getUIActionNavigator(): UIActionNavigator | null {\n return _navigator;\n}\n","import { View, Text, StyleSheet, Platform } from 'react-native';\nimport { ada, borderRadius, fontSize, spacing } from '@ateam-ai-mobile/core';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\ninterface Props {\n columns: string[];\n rows: any[][];\n}\n\n/**\n * Renders tabular data as a vertical stack of cards — one card per row.\n * The first column becomes the card title; remaining columns become\n * key:value rows inside the card. Built specifically for narrow phone\n * screens where horizontal markdown tables become unreadable.\n */\nexport default function TableCardBlock({ columns, rows }: Props) {\n if (!columns?.length || !rows?.length) return null;\n\n const [titleCol, ...detailCols] = columns;\n\n return (\n <View style={styles.wrap}>\n {rows.map((row, i) => {\n const cells = Array.isArray(row) ? row : [row];\n const title = cells[0];\n return (\n <View key={i} style={styles.card}>\n <Text style={styles.title}>{String(title ?? '')}</Text>\n {detailCols.length > 0 && (\n <View style={styles.details}>\n {detailCols.map((col, ci) => {\n const value = cells[ci + 1];\n if (value == null || value === '') return null;\n return (\n <View key={ci} style={styles.row}>\n <Text style={styles.label}>{String(col)}</Text>\n <Text style={styles.value}>{String(value)}</Text>\n </View>\n );\n })}\n </View>\n )}\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n wrap: {\n marginVertical: 6,\n gap: 8,\n },\n card: {\n backgroundColor: ada.paper,\n borderRadius: borderRadius.md,\n borderWidth: 1,\n borderColor: ada.border,\n padding: spacing.md,\n },\n title: {\n color: ada.ink,\n fontSize: fontSize.md,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.2,\n marginBottom: 6,\n },\n details: {\n gap: 4,\n },\n row: {\n flexDirection: 'row',\n alignItems: 'flex-start',\n gap: 8,\n },\n label: {\n color: ada.inkMuted,\n fontSize: fontSize.xs,\n fontFamily: fontFam,\n fontWeight: '500',\n width: 84,\n textTransform: 'uppercase',\n letterSpacing: 0.6,\n paddingTop: 2,\n },\n value: {\n flex: 1,\n color: ada.inkSoft,\n fontSize: fontSize.sm,\n fontFamily: fontFam,\n lineHeight: 18,\n },\n});\n","/**\n * Lightbox store — any component can open a fullscreen image viewer\n * by calling `useLightboxStore.getState().show(uri)`. The lightbox is\n * mounted once at the app root.\n */\nimport { create } from 'zustand';\n\ninterface LightboxState {\n uri: string | null;\n show: (uri: string) => void;\n hide: () => void;\n}\n\nexport const useLightboxStore = create<LightboxState>((set) => ({\n uri: null,\n show: (uri) => set({ uri }),\n hide: () => set({ uri: null }),\n}));\n","import { View, StyleSheet } from 'react-native';\nimport { AdaOrb } from '@ateam-ai-mobile/plugin-runtime';\n\nexport default function TypingIndicator() {\n return (\n <View style={styles.row}>\n <AdaOrb size={32} state=\"thinking\" />\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n marginBottom: 12,\n paddingHorizontal: 16,\n paddingVertical: 4,\n },\n});\n","import { useState, useRef, useCallback, useEffect } from 'react';\nimport {\n View,\n TextInput,\n Pressable,\n StyleSheet,\n Platform,\n} from 'react-native';\nimport Svg, { Path, Line, Polygon } from 'react-native-svg';\nimport { colors, borderRadius } from '@ateam-ai-mobile/core';\nimport { getHaptics } from '@ateam-ai-mobile/plugin-runtime';\n\ninterface Props {\n onSend: (text: string) => void;\n onCallStart?: () => void;\n loading: boolean;\n /** Pre-fill the composer (e.g. from bridge.prepareMessage). The text is\n * placed in the input and focused — not sent. */\n prefill?: string;\n placeholder?: string;\n}\n\nexport default function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }: Props) {\n const [text, setText] = useState('');\n const [height, setHeight] = useState(40);\n const inputRef = useRef<TextInput>(null);\n\n useEffect(() => {\n if (prefill) {\n setText(prefill);\n inputRef.current?.focus();\n }\n }, [prefill]);\n\n // NOTE: no `loading` guard and no editable={!loading} — sending WHILE a\n // chain runs is a feature (backend Chain Additions router decides\n // attach/cancel/new), and the input must never be disabled mid-run.\n const handleSend = useCallback(() => {\n const trimmed = text.trim();\n if (!trimmed) return;\n getHaptics()?.impact('light');\n onSend(trimmed);\n setText('');\n setHeight(40);\n }, [text, loading, onSend]);\n\n const canSend = text.trim().length > 0;\n\n return (\n <View style={styles.dock}>\n <View style={styles.inputRow}>\n {onCallStart && (\n <Pressable style={styles.callBtn} onPress={onCallStart}>\n <Svg width={20} height={20} viewBox=\"0 0 24 24\" fill=\"none\" stroke={colors.light.accent} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <Path d=\"M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z\" />\n </Svg>\n </Pressable>\n )}\n <TextInput\n ref={inputRef}\n style={[styles.input, { height: Math.min(Math.max(40, height), 120) }]}\n value={text}\n onChangeText={setText}\n onContentSizeChange={e => setHeight(e.nativeEvent.contentSize.height)}\n placeholder={placeholder || 'Message A-Team...'}\n placeholderTextColor={colors.light.textMuted}\n multiline\n returnKeyType=\"default\"\n blurOnSubmit={false}\n onSubmitEditing={() => {\n if (Platform.OS === 'web') handleSend();\n }}\n />\n <Pressable\n style={[styles.sendBtn, canSend && styles.sendBtnActive]}\n onPress={handleSend}\n disabled={!canSend}\n >\n <Svg width={18} height={18} viewBox=\"0 0 24 24\" fill=\"none\" stroke={canSend ? '#fff' : colors.light.textMuted} strokeWidth={2}>\n <Line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\" />\n <Polygon points=\"22 2 15 22 11 13 2 9 22 2\" />\n </Svg>\n </Pressable>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n dock: {\n paddingHorizontal: 12,\n paddingVertical: 8,\n backgroundColor: colors.light.bgPrimary,\n borderTopWidth: 0.5,\n borderTopColor: colors.light.border,\n },\n inputRow: {\n flexDirection: 'row',\n alignItems: 'flex-end',\n backgroundColor: colors.light.bgInput,\n borderRadius: borderRadius.xl,\n borderWidth: 1,\n borderColor: colors.light.border,\n paddingHorizontal: 4,\n paddingVertical: 4,\n gap: 4,\n },\n callBtn: {\n width: 36,\n height: 36,\n borderRadius: 18,\n justifyContent: 'center',\n alignItems: 'center',\n },\n input: {\n flex: 1,\n fontSize: 16,\n color: colors.light.textPrimary,\n paddingHorizontal: 8,\n paddingVertical: 8,\n maxHeight: 120,\n },\n sendBtn: {\n width: 36,\n height: 36,\n borderRadius: 18,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: colors.light.bgTertiary,\n },\n sendBtnActive: {\n backgroundColor: colors.light.accent,\n },\n});\n","/**\n * Fullscreen image viewer — mounted once inside AdasChatScreen (or at the\n * host root). Any component opens it via useLightboxStore.getState().show(uri).\n */\nimport { Modal, View, Image, Pressable, Text, StyleSheet } from 'react-native';\nimport { getConfig } from '@ateam-ai-mobile/core';\nimport { useLightboxStore } from './stores/lightboxStore';\n\nexport default function Lightbox() {\n const uri = useLightboxStore(s => s.uri);\n const hide = useLightboxStore(s => s.hide);\n\n if (!uri) return null;\n\n return (\n <Modal visible transparent animationType=\"fade\" onRequestClose={hide}>\n <Pressable style={styles.backdrop} onPress={hide}>\n <Image\n source={{\n uri,\n ...(uri.startsWith('http') ? {\n headers: { Authorization: `Bearer ${getConfig().tokenProvider.getToken()}` },\n } : {}),\n }}\n style={styles.image}\n resizeMode=\"contain\"\n />\n <View style={styles.closeHint}>\n <Text style={styles.closeHintText}>Tap to close</Text>\n </View>\n </Pressable>\n </Modal>\n );\n}\n\nconst styles = StyleSheet.create({\n backdrop: {\n flex: 1,\n backgroundColor: 'rgba(0,0,0,0.92)',\n justifyContent: 'center',\n },\n image: {\n width: '100%',\n height: '85%',\n },\n closeHint: {\n position: 'absolute',\n bottom: 40,\n alignSelf: 'center',\n },\n closeHintText: {\n color: 'rgba(255,255,255,0.6)',\n fontSize: 13,\n },\n});\n","/**\n * <Plugin /> — unified plugin renderer.\n *\n * Auto-detects native vs webview, provides bridge/theme/context, wraps in an\n * error boundary.\n *\n * Usage:\n * <Plugin id=\"mcp:connector:plugin-name\" manifest={manifest} />\n */\nimport React, { useMemo } from 'react';\nimport { View, Text, Pressable, ActivityIndicator, StyleSheet } from 'react-native';\nimport { getTenant, getUser, colors } from '@ateam-ai-mobile/core';\nimport {\n usePlugin,\n useNativeBridge,\n usePluginTheme,\n getPluginNative,\n type PluginContext,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport EmbeddedPluginWebView from './EmbeddedPluginWebView';\n\n// ── Error Boundary ──────────────────────────────────────────\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\nclass PluginErrorBoundary extends React.Component<\n { pluginId: string; children: React.ReactNode },\n ErrorBoundaryState\n> {\n state: ErrorBoundaryState = { hasError: false, error: null };\n\n static getDerivedStateFromError(error: Error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);\n }\n\n render() {\n if (this.state.hasError) {\n return (\n <View style={styles.error}>\n <Text style={styles.errorTitle}>Plugin Error</Text>\n <Text style={styles.errorMessage}>\n {this.state.error?.message || 'An unexpected error occurred'}\n </Text>\n <Pressable\n style={styles.retryBtn}\n onPress={() => this.setState({ hasError: false, error: null })}\n >\n <Text style={styles.retryText}>Retry</Text>\n </Pressable>\n </View>\n );\n }\n return this.props.children;\n }\n}\n\n// ── Plugin Component ─────────────────────────────────────────\n\ninterface PluginRendererProps {\n /** Full plugin ID, e.g. \"mcp:connector-id:plugin-name\" */\n id: string;\n /** Plugin manifest (from API) */\n manifest: any;\n /** Shared command buffer from parent — captures commands before plugin mounts */\n commandBufferRef?: React.MutableRefObject<any[]>;\n /** Engine-supplied props from sys.focusUiPlugin(id, props) — forwarded to\n * the plugin Component as `PluginProps.props`. */\n props?: any;\n}\n\nexport default function Plugin({ id, manifest, commandBufferRef, props: engineProps }: PluginRendererProps) {\n // Load the plugin component (handles cache, download, eval)\n const { plugin, loading, error, bundleMissing } = usePlugin(id, manifest);\n\n // Extract connectorId from MCP plugin ID: mcp:<connector>:<plugin>\n const connectorId = id.startsWith('mcp:') ? id.split(':')[1] : manifest._connectorId;\n\n // Wire up bridge, capabilities, theme\n const bridge = useNativeBridge(id, connectorId, commandBufferRef);\n const native = getPluginNative();\n const theme = usePluginTheme();\n\n // Build context from the host-owned identity\n const user = getUser();\n const context = useMemo<PluginContext>(() => ({\n tenant: getTenant(),\n pluginId: id,\n connectorId,\n user: user ? { id: user.actorId || '', name: user.name || '', roles: [] } : undefined,\n }), [id, connectorId, user?.actorId, user?.name]);\n\n // Fallback: connector declared an RN component but didn't ship the bundle.\n // When the manifest also has an iframeUrl (adaptive mode), render the\n // WebView instead of an error screen. Must run AFTER all hook calls above\n // to keep hook order stable across renders.\n const iframeUrl = manifest?.render?.iframeUrl;\n if (bundleMissing && iframeUrl) {\n return <EmbeddedPluginWebView pluginId={id} manifest={manifest} commandBufferRef={commandBufferRef} />;\n }\n\n if (loading) {\n return (\n <View style={styles.center}>\n <ActivityIndicator size=\"small\" color={colors.dark.accent} />\n <Text style={styles.loadingText}>Loading plugin...</Text>\n </View>\n );\n }\n\n if (error || !plugin) {\n return (\n <View style={styles.center}>\n <Text style={styles.errorMessage}>{error || 'Plugin not found'}</Text>\n </View>\n );\n }\n\n const { Component } = plugin;\n return (\n <PluginErrorBoundary pluginId={id}>\n <Component bridge={bridge} native={native} theme={theme} context={context} props={engineProps} />\n </PluginErrorBoundary>\n );\n}\n\n// ── Styles ───────────────────────────────────────────────────\n\nconst styles = StyleSheet.create({\n center: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: colors.dark.bgPrimary,\n paddingHorizontal: 32,\n gap: 12,\n },\n loadingText: {\n color: colors.dark.textSecondary,\n fontSize: 14,\n },\n error: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: colors.dark.bgPrimary,\n paddingHorizontal: 32,\n gap: 8,\n },\n errorTitle: {\n color: colors.dark.red,\n fontSize: 16,\n fontWeight: '600',\n },\n errorMessage: {\n color: colors.dark.textSecondary,\n fontSize: 14,\n textAlign: 'center',\n },\n retryBtn: {\n marginTop: 12,\n paddingHorizontal: 20,\n paddingVertical: 10,\n backgroundColor: colors.dark.accentSoft,\n borderRadius: 8,\n },\n retryText: {\n color: colors.dark.accent,\n fontSize: 14,\n fontWeight: '600',\n },\n});\n","import { useState, useMemo, useCallback } from 'react';\nimport { View, Text, ActivityIndicator, StyleSheet, Platform } from 'react-native';\nimport { WebView } from 'react-native-webview';\nimport { getConfig, getCoreApiUrl, getTenant, getToken, ada } from '@ateam-ai-mobile/core';\nimport { resolvePluginUrl, type PlatformPluginManifest as PluginManifest } from '@ateam-ai-mobile/plugin-runtime';\nimport { usePluginBridge, buildPluginBridgeJS } from './usePluginBridge';\n\ninterface Props {\n pluginId: string;\n manifest: PluginManifest;\n commandBufferRef?: React.MutableRefObject<any[]>;\n}\n\n/**\n * WebView renderer for iframe-mode plugins. The injected bridge JS makes\n * existing web plugins work unmodified; the origin guards below make sure\n * the JWT seeded into localStorage can never leave the trusted hosts.\n */\nexport default function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }: Props) {\n const [error, setError] = useState<string | null>(null);\n\n const token = getToken();\n const tenant = getTenant();\n\n // _connectorId from the manifest (set by cp.getContextPlugin) — same as\n // what the web PluginHost sends in init.\n const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);\n\n const url = resolvePluginUrl(manifest, tenant || undefined);\n const bridgeJS = buildPluginBridgeJS(token || '', tenant || '');\n\n // Hosts the plugin may navigate to — derived from the SDK config so the\n // whitelist always matches the backend the host pointed us at.\n const allowedHosts = useMemo(() => {\n const hosts = new Set<string>();\n const urls = [getCoreApiUrl()];\n const voice = getConfig().voiceUrl;\n if (voice) urls.push(voice);\n for (const u of urls) {\n try { hosts.add(new URL(u).hostname); } catch { /* ignore bad URL */ }\n }\n return hosts;\n }, []);\n\n const isAllowedPluginUrl = useCallback((rawUrl: string | undefined | null): boolean => {\n if (!rawUrl) return false;\n // about:blank, data:, file: — never allow off-origin navigation\n let parsed: URL;\n try { parsed = new URL(rawUrl); } catch { return false; }\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;\n if (parsed.protocol === 'http:') return false; // belt-and-suspenders with mixedContentMode=never\n return allowedHosts.has(parsed.hostname);\n }, [allowedHosts]);\n\n const originWhitelist = useMemo(\n () => Array.from(allowedHosts).map(h => `https://${h}`),\n [allowedHosts],\n );\n\n if (error) {\n return (\n <View style={styles.center}>\n <Text style={styles.errorText}>{error}</Text>\n </View>\n );\n }\n\n return (\n <View style={styles.container}>\n <WebView\n ref={webViewRef}\n source={{ uri: url }}\n style={styles.webview}\n javaScriptEnabled\n domStorageEnabled\n allowsInlineMediaPlayback\n mediaPlaybackRequiresUserAction={false}\n // SECURITY:\n // mixedContentMode=never → block any plain HTTP load inside the WebView.\n // originWhitelist → only the ADAS-owned hosts.\n // onShouldStartLoadWithRequest → reject navigation off allowlisted hosts.\n // setSupportMultipleWindows / javaScriptCanOpenWindowsAutomatically\n // → prevent window.open() from spawning a WebView that inherits\n // the bridge but skips the origin guard.\n // allowsBackForwardNavigationGestures=false\n // → a user gesture must not be a way out of the sandbox.\n // The bridge writes the JWT into the WebView's localStorage; these\n // guards make sure it can never leave the trusted origins.\n mixedContentMode=\"never\"\n originWhitelist={originWhitelist}\n allowsBackForwardNavigationGestures={false}\n setSupportMultipleWindows={false}\n javaScriptCanOpenWindowsAutomatically={false}\n onShouldStartLoadWithRequest={(req) => {\n if (!isAllowedPluginUrl(req.url)) {\n console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);\n return false;\n }\n return true;\n }}\n injectedJavaScriptBeforeContentLoaded={bridgeJS}\n onMessage={handleMessage}\n onLoad={() => {\n // Send init with retries to beat the race condition\n setTimeout(sendInit, 100);\n setTimeout(sendInit, 500);\n setTimeout(sendInit, 1200);\n }}\n onError={(syntheticEvent) => {\n const { nativeEvent } = syntheticEvent;\n console.warn('[EmbeddedPluginWebView] Error:', nativeEvent.description);\n setError(`WebView error: ${nativeEvent.description}`);\n }}\n onHttpError={(syntheticEvent) => {\n const { nativeEvent } = syntheticEvent;\n console.warn('[EmbeddedPluginWebView] HTTP error:', nativeEvent.statusCode, nativeEvent.url);\n if (nativeEvent.statusCode >= 400) {\n setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);\n }\n }}\n startInLoadingState\n renderLoading={() => (\n <View style={styles.webviewLoading}>\n <ActivityIndicator size=\"small\" color={ada.orange} />\n <Text style={styles.loadingText}>Loading plugin...</Text>\n </View>\n )}\n {...(Platform.OS === 'ios' ? { bounces: false } : {})}\n />\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n center: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: ada.paper,\n paddingHorizontal: 32,\n },\n errorText: {\n color: '#C15545',\n fontSize: 16,\n textAlign: 'center',\n },\n webview: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n webviewLoading: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: ada.paper,\n gap: 12,\n },\n loadingText: {\n color: ada.inkSoft,\n fontSize: 14,\n },\n});\n","import { useCallback, useEffect, useRef } from 'react';\nimport {\n getTenant,\n connectorCall,\n mcpFetch,\n mcpToolCall,\n postCommandResult,\n pluginCommandEmitter,\n} from '@ateam-ai-mobile/core';\n\n/**\n * PostMessage bridge between the React Native host and a plugin WebView.\n * Implements the same protocol as PluginHost.jsx in the web frontend.\n *\n * Handles THREE communication paths:\n * 1. fetch(\"/mcp\") — intercepted in bridge JS, proxied through postMessage\n * 2. postMessage mcp-call — direct postMessage from plugin SDK\n * 3. plugin commands — routed from the job stream to the WebView\n */\nexport function usePluginBridge(\n pluginId: string,\n connectorId?: string,\n commandBufferRef?: React.MutableRefObject<any[]>,\n wsRef?: { current: WebSocket | null },\n) {\n // Loosely typed on purpose: react-native-webview's generic class type\n // resolves JSX props to `never` under React 19 typings when used as a\n // useRef parameter. Runtime shape is the WebView instance (postMessage).\n const webViewRef = useRef<any>(null);\n\n // Send init message to plugin (mirrors web PluginHost sendInit)\n const sendInit = useCallback(() => {\n const msg = JSON.stringify({\n source: 'adas-host',\n pluginId,\n message: {\n type: 'init',\n payload: {\n tenant: getTenant(),\n connectorId: connectorId || null,\n mcpEndpoint: null,\n slug: null,\n skillSlug: null,\n },\n },\n });\n webViewRef.current?.postMessage(msg);\n }, [pluginId, connectorId]);\n\n // Send result back to plugin WebView\n const sendToPlugin = useCallback(\n (type: string, payload: any) => {\n const msg = JSON.stringify({\n source: 'adas-host',\n message: { type, payload },\n });\n webViewRef.current?.postMessage(msg);\n },\n [],\n );\n\n // Helper to dispatch a command to the WebView plugin\n const dispatchToWebView = useCallback((cmd: any) => {\n if (cmd.command === '__focus') return; // Focus-only, handled by the host screen\n const msg = JSON.stringify({\n source: 'adas-host',\n pluginId,\n message: {\n type: 'plugin.command',\n payload: {\n command: cmd.command,\n args: cmd.args,\n correlationId: cmd.correlationId,\n },\n },\n });\n webViewRef.current?.postMessage(msg);\n }, [pluginId]);\n\n // ── Listen for live plugin commands from the job stream ──\n useEffect(() => {\n const unsub = pluginCommandEmitter.on('plugin-command', (cmd: any) => {\n if (cmd.pluginId !== pluginId) return;\n dispatchToWebView(cmd);\n });\n return unsub;\n }, [pluginId, dispatchToWebView]);\n\n // ── Flush buffered commands when the WebView loads ──\n useEffect(() => {\n if (!commandBufferRef) return;\n // Flush after a delay to let the iframe initialize\n const timer = setTimeout(() => {\n const buf = commandBufferRef.current;\n const remaining: any[] = [];\n for (const cmd of buf) {\n if (cmd.pluginId === pluginId) {\n dispatchToWebView(cmd);\n } else {\n remaining.push(cmd);\n }\n }\n commandBufferRef.current = remaining;\n }, 1000);\n return () => clearTimeout(timer);\n }, [pluginId, commandBufferRef, dispatchToWebView]);\n\n // Handle messages from plugin WebView\n const handleMessage = useCallback(\n async (event: { nativeEvent: { data: string } }) => {\n let data: any;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch {\n return;\n }\n\n if (data.source !== 'adas-plugin') return;\n\n const action = data.message?.action || data.action;\n const payload = data.message?.payload || data.payload;\n const type = data.message?.type || data.type;\n\n // -----------------------------------------------------------------\n // 1. Intercepted fetch(\"/mcp\") — proxy to Core backend\n // -----------------------------------------------------------------\n if (action === 'mcp-fetch' && payload?.requestId) {\n const { requestId, body } = payload;\n try {\n const result = await mcpFetch(body);\n sendToPlugin('mcp-fetch-result', { requestId, result, error: null });\n } catch (err: any) {\n console.warn('[PluginBridge] mcp-fetch ERROR:', err.message);\n sendToPlugin('mcp-fetch-result', { requestId, result: null, error: err.message });\n }\n return;\n }\n\n // -----------------------------------------------------------------\n // 2. PostMessage mcp-call — plugin SDK's callTool()\n // a) toolName present (e.g. \"cp.fe_api\") → JSON-RPC to /mcp\n // b) direct connectorId/tool → /api/connectors/:id/call\n // -----------------------------------------------------------------\n if (action === 'mcp-call' && payload?.requestId) {\n const { requestId, toolName, args: callArgs } = payload;\n\n // Path A: JSON-RPC tool call (cp.fe_api, cp.admin_api, etc.)\n if (toolName) {\n try {\n const rpcResponse = await mcpToolCall(toolName, callArgs || {});\n // Unwrap the JSON-RPC envelope — plugin expects { content: [...] }\n const result = rpcResponse?.result ?? rpcResponse;\n sendToPlugin('mcp-result', { requestId, result, error: null });\n } catch (err: any) {\n sendToPlugin('mcp-result', { requestId, result: null, error: err.message });\n }\n return;\n }\n\n // Path B: direct connector call (connectorId + tool). The plugin may\n // send connectorId/tool at payload level OR nested in args.\n const cId = payload.connectorId || callArgs?.connectorId || callArgs?.params?.connectorId;\n const tool = payload.tool || callArgs?.tool || callArgs?.params?.tool;\n const toolArgs = payload.args || callArgs?.args || callArgs?.params?.args || {};\n\n if (!cId || !tool) {\n console.error(`[PluginBridge] mcp-call missing params — connectorId: ${cId}, tool: ${tool}`);\n sendToPlugin('mcp-result', { requestId, result: null, error: `Missing connectorId or tool in mcp-call. Got connectorId=${cId}, tool=${tool}` });\n return;\n }\n\n try {\n const data = await connectorCall(cId, tool, toolArgs);\n if (data.ok) {\n sendToPlugin('mcp-result', { requestId, result: data.result, error: null });\n } else {\n sendToPlugin('mcp-result', { requestId, result: null, error: data.error || 'MCP call failed' });\n }\n } catch (err: any) {\n sendToPlugin('mcp-result', { requestId, result: null, error: err.message });\n }\n return;\n }\n\n // -----------------------------------------------------------------\n // 3. Plugin self-close — return to chat\n // -----------------------------------------------------------------\n if (action === 'close') {\n pluginCommandEmitter.emit('plugin-close', { pluginId });\n return;\n }\n\n // -----------------------------------------------------------------\n // 4. Plugin command result — WebSocket (preferred) or HTTP POST\n // -----------------------------------------------------------------\n if (type === 'plugin.command.result' && payload?.correlationId) {\n const ws = wsRef?.current;\n if (ws && ws.readyState === WebSocket.OPEN) {\n try {\n ws.send(JSON.stringify({\n type: 'plugin-command-result',\n correlationId: payload.correlationId,\n result: payload.result,\n error: payload.error,\n }));\n } catch (err: any) {\n console.warn('[PluginBridge] WS send failed, falling back to POST:', err.message);\n await postCommandResult(payload.correlationId, payload.result, payload.error).catch(() => {});\n }\n } else {\n try {\n await postCommandResult(payload.correlationId, payload.result, payload.error);\n } catch (err: any) {\n console.warn('[PluginBridge] Failed to send command result:', err.message);\n }\n }\n return;\n }\n },\n [sendToPlugin],\n );\n\n return {\n webViewRef,\n sendInit,\n handleMessage,\n };\n}\n\n/**\n * Build injected JavaScript that:\n * 1. Seeds localStorage with JWT + tenant (plugins read from localStorage)\n * 2. Intercepts fetch(\"/mcp\") and proxies through the postMessage bridge\n * 3. Bridges iframe postMessage API to React Native WebView\n *\n * This makes existing web plugins work on mobile WITHOUT modification.\n */\nexport function buildPluginBridgeJS(jwt: string, tenant: string): string {\n return `\n(function() {\n function _dbg(msg) {\n console.log('[PluginBridge] ' + msg);\n }\n\n // 1. Seed localStorage. JWT is required by web-shared plugin code\n // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown\n // lives in EmbeddedPluginWebView (originWhitelist +\n // onShouldStartLoadWithRequest) — not here. Never log the token.\n try {\n localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});\n localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});\n _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});\n } catch(e) { _dbg('localStorage ERROR: ' + e.message); }\n\n // 2. Intercept ALL fetch calls — proxy /mcp through the RN bridge\n var _origFetch = window.fetch.bind(window);\n var _mcpReqId = 0;\n var _pendingMcp = {};\n\n window.fetch = function(url, opts) {\n var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';\n var method = (opts && opts.method) || 'GET';\n\n _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));\n\n // Intercept /mcp calls\n if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {\n _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');\n return new Promise(function(resolve, reject) {\n var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();\n _pendingMcp[requestId] = { resolve: resolve, reject: reject };\n window.ReactNativeWebView.postMessage(JSON.stringify({\n source: 'adas-plugin',\n action: 'mcp-fetch',\n payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }\n }));\n setTimeout(function() {\n if (_pendingMcp[requestId]) {\n delete _pendingMcp[requestId];\n _dbg('TIMEOUT mcp-fetch ' + requestId);\n reject(new Error('MCP fetch timeout (30s)'));\n }\n }, 30000);\n });\n }\n\n // Inject Authorization header ONLY for same-origin requests. A relative\n // path resolves against window.location.origin; an absolute URL must\n // literally match. This stops a plugin from leaking the JWT to a foreign\n // host via fetch('https://attacker.com/x') or via <base href>.\n var sameOrigin = false;\n try {\n var resolved = new URL(urlStr, window.location.origin);\n sameOrigin = (resolved.origin === window.location.origin);\n } catch (_) { sameOrigin = false; }\n if (sameOrigin) {\n opts = Object.assign({}, opts || {});\n var h = opts.headers;\n if (h instanceof Headers) {\n if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }\n } else {\n h = Object.assign({}, h || {});\n if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }\n opts.headers = h;\n }\n }\n\n return _origFetch(url, opts).then(function(res) {\n _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));\n return res;\n }).catch(function(err) {\n _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));\n throw err;\n });\n };\n\n // Handle results from RN host\n function _handleHostMessage(d) {\n if (!d || d.source !== 'adas-host') return;\n var msg = d.message || {};\n\n // mcp-fetch-result: response to intercepted fetch(\"/mcp\")\n if (msg.type === 'mcp-fetch-result' && msg.payload) {\n var p = msg.payload;\n var pending = _pendingMcp[p.requestId];\n if (pending) {\n delete _pendingMcp[p.requestId];\n if (p.error) {\n pending.reject(new Error(p.error));\n } else {\n pending.resolve(new Response(JSON.stringify(p.result), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n }));\n }\n }\n }\n }\n\n // 3. Override window.parent.postMessage (plugins use this in iframes)\n window.parent = window.parent || {};\n window.parent.postMessage = function(data, origin) {\n if (data && data.source === 'adas-plugin') {\n window.ReactNativeWebView.postMessage(JSON.stringify(data));\n }\n };\n\n // 4. Also intercept direct window.postMessage\n var origPost = window.postMessage.bind(window);\n window.postMessage = function(data, origin) {\n if (data && data.source === 'adas-plugin') {\n window.ReactNativeWebView.postMessage(JSON.stringify(data));\n }\n origPost(data, origin);\n };\n\n // 5. Forward messages from RN host -> plugin\n // iOS: window 'message' event, Android: document 'message' event\n window.addEventListener('message', function(e) {\n try {\n var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;\n if (d && d.source === 'adas-host') {\n _handleHostMessage(d);\n if (typeof e.data === 'string') {\n window.dispatchEvent(new MessageEvent('message', { data: d }));\n }\n }\n } catch(_) {}\n });\n\n document.addEventListener('message', function(e) {\n try {\n var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;\n _handleHostMessage(d);\n window.dispatchEvent(new MessageEvent('message', { data: d }));\n } catch(_) {}\n });\n\n _dbg('Bridge JS loaded. Waiting for plugin...');\n true;\n})();\n`;\n}\n","/**\n * PluginSurface — universal mount that wraps any platform-declared plugin\n * (iframe or React Native) inside the right Ada-themed surface container.\n *\n * Mounted when an `engine` or `user` event activates a platform plugin that\n * declared a `surface` field in its manifest. Picks SurfaceDrawer or\n * SurfaceFullscreen based on `surface.type`, fetches the full PluginManifest\n * lazily, and renders <Plugin /> inside the body.\n */\nimport { useEffect, useState } from 'react';\nimport { View, ActivityIndicator } from 'react-native';\nimport { ada } from '@ateam-ai-mobile/core';\nimport {\n getPlugin,\n SurfaceDrawer,\n SurfaceFullscreen,\n type PluginSurfaceMeta,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport type { PlatformPluginManifest as PluginManifest } from '@ateam-ai-mobile/plugin-runtime';\nimport Plugin from './Plugin';\n\ninterface Props {\n pluginId: string;\n surface: PluginSurfaceMeta;\n dismiss: () => void;\n /** Engine-supplied props forwarded from sys.focusUiPlugin(id, props). */\n props?: any;\n}\n\nexport default function PluginSurface({ pluginId, surface, dismiss, props }: Props) {\n const [manifest, setManifest] = useState<PluginManifest | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n getPlugin(pluginId)\n .then(m => { if (!cancelled) m ? setManifest(m) : setError('Plugin not found'); })\n .catch(e => { if (!cancelled) setError(e?.message || 'Failed to load'); });\n return () => { cancelled = true; };\n }, [pluginId]);\n\n const body = manifest ? (\n <Plugin id={pluginId} manifest={manifest as any} props={props} />\n ) : (\n <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 }}>\n {error ? null : <ActivityIndicator color={ada.orange} />}\n </View>\n );\n\n if (surface.type === 'fullscreen') {\n return (\n <SurfaceFullscreen\n visible={true}\n onClose={dismiss}\n status={surface.title}\n showWordmark\n closeIcon=\"x\"\n closeLabel=\"Close\"\n >\n {body}\n </SurfaceFullscreen>\n );\n }\n\n // Default: drawer\n return (\n <SurfaceDrawer\n visible={true}\n onClose={dismiss}\n title={surface.title || pluginId}\n subtitle={surface.subtitle}\n privacy={!!surface.privacy}\n >\n {body}\n </SurfaceDrawer>\n );\n}\n","// src/components/widgets/WidgetWebView.tsx\n//\n// Fullscreen modal that renders an ACS widget bundle in a WebView.\n//\n// The bundleUrl on each /surface item is a signed URL minted by the backend\n// proxy (apps/backend/routes/acsProxy.js → signItemBundleUrl). The URL\n// self-authenticates — the bundle endpoint verifies sig/exp/tenant/actor on\n// the server side — so the WebView doesn't need to pass auth headers.\n//\n// Relative URLs (e.g. `/api/acs/artifacts/.../bundle/1?sig=…`) are prepended\n// with the current core URL from configStore.\n//\n// Strict CSP is set on the response by the bundle endpoint. We additionally\n// disable JS injection paths we don't need (no onMessage handlers wired,\n// no injectedJavaScriptBeforeContentLoaded).\n\nimport { useMemo, useState } from 'react';\nimport {\n Modal,\n View,\n Text,\n Pressable,\n StyleSheet,\n ActivityIndicator,\n Platform,\n} from 'react-native';\nimport { WebView } from 'react-native-webview';\nimport { ada } from '@ateam-ai-mobile/core';\nimport { getCoreApiUrl } from '@ateam-ai-mobile/core';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\ninterface Props {\n visible: boolean;\n /** Display title shown in the top bar. */\n title?: string;\n /** Signed bundle URL from a /surface item. Relative or absolute. */\n bundleUrl?: string;\n onClose: () => void;\n}\n\nexport default function WidgetWebView({ visible, title, bundleUrl, onClose }: Props) {\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n // Absolute URL — prepend the resolved core if backend returned a relative path.\n const uri = useMemo(() => {\n if (!bundleUrl) return null;\n if (/^https?:\\/\\//i.test(bundleUrl)) return bundleUrl;\n return `${getCoreApiUrl()}${bundleUrl}`;\n }, [bundleUrl]);\n\n return (\n <Modal\n visible={visible}\n animationType=\"slide\"\n presentationStyle=\"pageSheet\"\n onRequestClose={onClose}\n >\n <View style={styles.root}>\n <View style={styles.bar}>\n <Pressable onPress={onClose} hitSlop={12} style={styles.closeBtn}>\n <Text style={styles.closeIcon}>{'✕'}</Text>\n </Pressable>\n <Text style={styles.title} numberOfLines={1}>{title || 'Widget'}</Text>\n {/* Spacer matches closeBtn width so the title stays centered */}\n <View style={styles.closeBtn} />\n </View>\n {!uri ? (\n <View style={styles.errorWrap}>\n <Text style={styles.errorText}>No bundle available for this widget.</Text>\n </View>\n ) : error ? (\n <View style={styles.errorWrap}>\n <Text style={styles.errorText}>Couldn't load widget</Text>\n <Text style={styles.errorSub}>{error}</Text>\n </View>\n ) : (\n <>\n <WebView\n source={{ uri }}\n style={styles.web}\n onLoadStart={() => { setLoading(true); setError(null); }}\n onLoadEnd={() => setLoading(false)}\n onError={(e) => {\n setLoading(false);\n setError(e.nativeEvent.description || 'Unknown error');\n }}\n onHttpError={(e) => {\n setLoading(false);\n setError(`HTTP ${e.nativeEvent.statusCode}`);\n }}\n // The bundle is the only thing this WebView should render.\n // Block navigation away from it (prevents accidental browse\n // out of the widget sandbox into arbitrary URLs).\n originWhitelist={['https://*']}\n setSupportMultipleWindows={false}\n />\n {loading && (\n <View pointerEvents=\"none\" style={styles.spinner}>\n <ActivityIndicator color={ada.orange} />\n </View>\n )}\n </>\n )}\n </View>\n </Modal>\n );\n}\n\nconst styles = StyleSheet.create({\n root: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n bar: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 14,\n paddingVertical: 12,\n borderBottomWidth: 1,\n borderBottomColor: ada.border,\n backgroundColor: ada.paperSoft,\n },\n closeBtn: {\n width: 32,\n height: 32,\n alignItems: 'center',\n justifyContent: 'center',\n },\n closeIcon: {\n fontSize: 18,\n color: ada.ink,\n fontWeight: '500',\n },\n title: {\n flex: 1,\n textAlign: 'center',\n fontSize: 16,\n fontWeight: '600',\n color: ada.ink,\n fontFamily: fontFam,\n letterSpacing: -0.2,\n },\n web: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n spinner: {\n position: 'absolute',\n top: 80,\n left: 0,\n right: 0,\n alignItems: 'center',\n },\n errorWrap: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: 24,\n },\n errorText: {\n fontSize: 15,\n color: ada.ink,\n fontWeight: '600',\n marginBottom: 6,\n },\n errorSub: {\n fontSize: 13,\n color: ada.inkMuted,\n textAlign: 'center',\n },\n});\n"],"mappings":";AAeA,SAAS,eAAAA,cAAa,aAAAC,YAAW,SAAS,UAAAC,SAAQ,YAAAC,iBAAgB;AAClE;AAAA,EACE;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA,QAAAC;AAAA,OACK;AACP,SAAS,OAAAC,MAAK,cAAc,4BAA4B;;;ACxBxD,SAAS,YAAAC,WAAU,mBAAmB;AACtC,SAAS,QAAAC,OAAM,QAAAC,OAAM,aAAAC,YAAW,cAAAC,aAAY,OAAO,YAAAC,iBAAgB;AACnE,OAAO,cAAc;AAErB,SAAS,kBAAkB,mBAAmB,WAAW,OAAAC,MAAK,gBAAAC,eAAc,YAAAC,iBAAgB;AAC5F,SAAS,QAAQ,cAAAC,mBAAkB;;;ACLnC,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,MAAM,MAAM,UAAU,kBAAkB;AAEjD,SAAS,KAAK,gBAA8B;AA2EpC,SACE,KADF;AAtER,IAAM,aAAa;AACnB,IAAM,kBAAkB;AACxB,IAAM,WAAW;AACjB,IAAM,gBAAgB;AACtB,IAAM,aAAa;AACnB,IAAM,kBAAkB;AAMT,SAAR,eAAgC,EAAE,SAAS,GAAU;AAC1D,QAAM,YAAY,OAAO,IAAI,SAAS,MAAM,GAAG,CAAC,EAAE;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC;AAExC,QAAM,YAAY,SAAS,WAAW;AACtC,QAAM,SAAS,SAAS,WAAW;AACnC,QAAM,UAAU,SAAS,WAAW;AACpC,QAAM,WAAW,SAAS,WAAW;AAGrC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW;AACd,gBAAU,SAAS,CAAC;AACpB;AAAA,IACF;AACA,UAAM,OAAO,SAAS;AAAA,MACpB,SAAS,SAAS;AAAA,QAChB,SAAS,OAAO,WAAW,EAAE,SAAS,GAAG,UAAU,KAAK,iBAAiB,KAAK,CAAC;AAAA,QAC/E,SAAS,OAAO,WAAW,EAAE,SAAS,KAAK,UAAU,KAAK,iBAAiB,KAAK,CAAC;AAAA,MACnF,CAAC;AAAA,IACH;AACA,SAAK,MAAM;AACX,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB,GAAG,CAAC,WAAW,SAAS,CAAC;AAGzB,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,CAAC,SAAS,gBAAgB;AAC1C,iBAAW,CAAC;AACZ;AAAA,IACF;AACA,UAAM,QAAQ,SAAS;AACvB,eAAW,KAAK,OAAO,KAAK,IAAI,IAAI,SAAS,GAAI,CAAC;AAClD,UAAM,KAAK,YAAY,MAAM;AAC3B,iBAAW,KAAK,OAAO,KAAK,IAAI,IAAI,SAAS,GAAI,CAAC;AAAA,IACpD,GAAG,GAAI;AACP,WAAO,MAAM,cAAc,EAAE;AAAA,EAC/B,GAAG,CAAC,WAAW,SAAS,cAAc,CAAC;AAEvC,QAAM,cAAc,SAChB,aACA,UACE,WACA,WACE,aACA,IAAI;AAEZ,QAAM,WAAW,SACb,kBACA,UACE,gBACA,WACE,kBACA,IAAI;AAGZ,MAAI,UAAU,SAAS;AACrB,WACE,oBAAC,QAAK,OAAO,OAAO,WAClB,+BAAC,QAAK,OAAO,OAAO,KAClB;AAAA,0BAAC,QAAK,OAAO,CAAC,OAAO,KAAK,EAAE,iBAAiB,YAAY,CAAC,GAAG;AAAA,MAC7D,oBAAC,QAAK,OAAO,CAAC,OAAO,OAAO,EAAE,OAAO,YAAY,CAAC,GAC/C,mBAAS,cAAc,UAC1B;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,MAAI,UAAU;AACZ,WACE,oBAAC,QAAK,OAAO,OAAO,WAClB,+BAAC,QAAK,OAAO,OAAO,KAClB;AAAA,0BAAC,QAAK,OAAO,CAAC,OAAO,KAAK,EAAE,iBAAiB,YAAY,CAAC,GAAG;AAAA,MAC7D,oBAAC,QAAK,OAAO,CAAC,OAAO,OAAO,EAAE,OAAO,YAAY,CAAC,GAAG,oCAErD;AAAA,OACF,GACF;AAAA,EAEJ;AAIA,QAAM,aAAa,SAAS,iBACxB,GAAG,KAAK,MAAM,UAAU,EAAE,CAAC,IAAI,OAAO,UAAU,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,KACpE;AAEJ,QAAM,YAAY;AAAA,IAChB,SAAS,aAAa,QAAQ,SAAS,YAAY,IAAI,QAAQ,SAAS,SAAS,KAAK;AAAA,IACtF;AAAA,EACF,EAAE,OAAO,OAAO;AAEhB,QAAM,KAAK,SAAS;AAEpB,SACE,qBAAC,QAAK,OAAO,OAAO,WAClB;AAAA,yBAAC,QAAK,OAAO,OAAO,KAClB;AAAA;AAAA,QAAC,SAAS;AAAA,QAAT;AAAA,UACC,OAAO,CAAC,OAAO,KAAK,EAAE,iBAAiB,IAAI,QAAQ,SAAS,UAAU,CAAC;AAAA;AAAA,MACzE;AAAA,MACA,oBAAC,QAAK,OAAO,OAAO,OAAO,eAAe,GACvC,mBAAS,aAAa,iBACzB;AAAA,MACC,CAAC,CAAC,SAAS,eACV,oBAAC,QAAK,OAAO,OAAO,MAAM,eAAe,GACtC,qBAAM,SAAS,WAAW,IAC7B;AAAA,MAED,UAAU,SAAS,KAClB,oBAAC,QAAK,OAAO,OAAO,MAAM,eAAe,GACtC,qBAAQ,UAAU,KAAK,UAAO,CAAC,IAClC;AAAA,OAEJ;AAAA,IAEC,CAAC,CAAC,IAAI,WACL,qBAAC,QAAK,OAAO,OAAO,iBACjB;AAAA,SAAG,QAAQ,QAAQ,GAAG,SAAS,QAC9B,qBAAC,QAAK,OAAO,OAAO,kBAAkB;AAAA;AAAA,QAAE,GAAG;AAAA,QAAK;AAAA,QAAE,GAAG;AAAA,QAAM;AAAA,SAAC;AAAA,MAE9D,oBAAC,QAAK,OAAO,OAAO,iBAAiB,eAAe,GAAI,aAAG,SAAQ;AAAA,OACrE;AAAA,IAEF,oBAAC,QAAK,OAAO,CAAC,OAAO,eAAe,EAAE,iBAAiB,SAAS,CAAC,GAC/D;AAAA,MAAC,SAAS;AAAA,MAAT;AAAA,QACC,OAAO,CAAC,OAAO,cAAc,EAAE,iBAAiB,IAAI,QAAQ,SAAS,UAAU,CAAC;AAAA;AAAA,IAClF,GACF;AAAA,KACF;AAEJ;AAEA,IAAM,SAAS,WAAW,OAAO;AAAA,EAC/B,WAAW;AAAA,IACT,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,gBAAgB,IAAI;AAAA,EACtB;AAAA,EACA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA,KAAK;AAAA,IACH,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,aAAa;AAAA,IACb,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,UAAU,SAAS;AAAA,IACnB,YAAY;AAAA,EACd;AAAA;AAAA,EAEA,OAAO;AAAA,IACL,UAAU,SAAS;AAAA,IACnB,YAAY;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,MAAM;AAAA,IACJ,UAAU,SAAS;AAAA,IACnB,OAAO,IAAI;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,MAAM;AAAA,IACJ,UAAU,SAAS;AAAA,IACnB,OAAO,IAAI;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,iBAAiB;AAAA,IACf,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,KAAK;AAAA,EACP;AAAA,EACA,kBAAkB;AAAA,IAChB,UAAU,SAAS;AAAA,IACnB,YAAY;AAAA,IACZ,OAAO,IAAI;AAAA,EACb;AAAA,EACA,iBAAiB;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,OAAO,IAAI;AAAA,IACX,MAAM;AAAA,EACR;AAAA,EACA,eAAe;AAAA,IACb,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,WAAW;AAAA,IACX,UAAU;AAAA,EACZ;AAAA,EACA,cAAc;AAAA,IACZ,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,EAChB;AACF,CAAC;;;ACpND,SAAS,QAAAC,OAAM,QAAAC,OAAM,WAAW,cAAAC,aAAY,gBAAgB;AAC5D,SAAS,OAAAC,MAAK,YAAAC,iBAAgB;AAE9B,SAAS,iBAAiB,kBAAkB;;;ACC5C,IAAI,aAAuC;AAGpC,SAAS,qBAAqB,WAA2C;AAC9E,eAAa;AACf;AAEO,SAAS,eAAyC;AACvD,SAAO;AACT;AAgBA,IAAI,aAAuC;AAEpC,SAAS,qBAAqB,UAA0C;AAC7E,eAAa;AACf;AAEO,SAAS,uBAAiD;AAC/D,SAAO;AACT;;;ADmCU,SAOU,OAAAC,MAPV,QAAAC,aAAA;AAlEV,IAAM,UAAU,SAAS,OAAO,QAAQ,mBAAmB;AAE3D,IAAM,WAAmC;AAAA,EACvC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAMe,SAAR,aAA8B,EAAE,QAAQ,GAAU;AACvD,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,EAAG,QAAO;AAE5D,QAAM,cAAc,CAAC,MAAgB;AACnC,eAAW,GAAG,UAAU;AAIxB,UAAM,KAAK,EAAE,YAAY,EAAE;AAC3B,QAAI,CAAC,GAAI;AAGT,UAAM,QAAQ,EAAE,SAAS,MAAM,EAAE,SAAS,aAAa,EAAE,SAAS;AAKlE,UAAM,UAAU,gBAAgB,SAAS,EAAE,UAAU,EAAE;AACvD,QAAI,SAAS;AACX,sBAAgB,SAAS,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,IAAI,MAAS;AACjE;AAAA,IACF;AAIA,UAAM,SAAiC,CAAC;AACxC,QAAI,MAAO,QAAO,QAAQ,OAAO,KAAK;AACtC,QAAI,EAAE,WAAW,sBAAsB,EAAE,SAAS,MAAM;AACtD,aAAO,OAAO,OAAO,EAAE,QAAQ,IAAI;AACnC,UAAI,EAAE,SAAS,MAAM;AACnB,YAAI;AAAE,iBAAO,OAAO,KAAK,UAAU,EAAE,QAAQ,IAAI;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MAC/D;AAAA,IACF;AACA,QAAI,EAAE,WAAW,kBAAkB,EAAE,SAAS,MAAM;AAClD,aAAO,OAAO,OAAO,EAAE,QAAQ,IAAI;AAAA,IACrC;AAEA,UAAM,WAAW,qBAAqB;AACtC,QAAI,CAAC,UAAU;AACb,cAAQ,KAAK,8GAAyG;AACtH;AAAA,IACF;AACA,aAAS,EAAE,UAAU,IAAI,OAAO,CAAC;AAAA,EACnC;AAEA,SACE,gBAAAD,KAACE,OAAA,EAAK,OAAOC,QAAO,KACjB,kBAAQ,IAAI,CAAC,GAAG,MAAM;AACrB,UAAM,OAAO,EAAE,OAAO,SAAS,EAAE,KAAK,YAAY,CAAC,IAAI;AACvD,WACE,gBAAAF;AAAA,MAAC;AAAA;AAAA,QAEC,mBAAkB;AAAA,QAClB,oBAAoB,EAAE,eAAe,EAAE;AAAA,QACvC,SAAS,MAAM,YAAY,CAAC;AAAA,QAC5B,OAAO,CAAC,EAAE,QAAQ,MAAM,CAACE,QAAO,MAAM,WAAWA,QAAO,WAAW;AAAA,QAElE;AAAA,iBAAO,gBAAAH,KAACI,OAAA,EAAK,OAAOD,QAAO,MAAO,gBAAK,IAAU;AAAA,UAClD,gBAAAH,KAACI,OAAA,EAAK,OAAOD,QAAO,OAAO,eAAe,GAAI,YAAE,OAAM;AAAA;AAAA;AAAA,MAPjD,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;AAAA,IAQ9B;AAAA,EAEJ,CAAC,GACH;AAEJ;AAEA,IAAMA,UAASE,YAAW,OAAO;AAAA,EAC/B,KAAK;AAAA,IACH,eAAe;AAAA,IACf,UAAU;AAAA,IACV,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AAAA,EACA,MAAM;AAAA,IACJ,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,iBAAiBC,KAAI;AAAA,IACrB,aAAaA,KAAI;AAAA,IACjB,aAAa;AAAA,IACb,cAAc;AAAA,EAChB;AAAA,EACA,aAAa;AAAA,IACX,iBAAiBA,KAAI;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,IACJ,UAAU;AAAA,IACV,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,OAAOA,KAAI;AAAA,IACX,UAAUC,UAAS;AAAA,IACnB,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AACF,CAAC;;;AE/HD,SAAS,QAAAC,OAAM,QAAAC,OAAM,cAAAC,aAAY,YAAAC,iBAAgB;AACjD,SAAS,OAAAC,MAAK,gBAAAC,eAAc,YAAAC,WAAU,eAAe;AA2BzC,gBAAAC,MAOQ,QAAAC,aAPR;AAzBZ,IAAMC,WAAUN,UAAS,OAAO,QAAQ,mBAAmB;AAa5C,SAAR,eAAgC,EAAE,SAAS,KAAK,GAAU;AAC/D,MAAI,CAAC,SAAS,UAAU,CAAC,MAAM,OAAQ,QAAO;AAE9C,QAAM,CAAC,UAAU,GAAG,UAAU,IAAI;AAElC,SACE,gBAAAI,KAACP,OAAA,EAAK,OAAOU,QAAO,MACjB,eAAK,IAAI,CAAC,KAAK,MAAM;AACpB,UAAM,QAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAC7C,UAAM,QAAQ,MAAM,CAAC;AACrB,WACE,gBAAAF,MAACR,OAAA,EAAa,OAAOU,QAAO,MAC1B;AAAA,sBAAAH,KAACN,OAAA,EAAK,OAAOS,QAAO,OAAQ,iBAAO,SAAS,EAAE,GAAE;AAAA,MAC/C,WAAW,SAAS,KACnB,gBAAAH,KAACP,OAAA,EAAK,OAAOU,QAAO,SACjB,qBAAW,IAAI,CAAC,KAAK,OAAO;AAC3B,cAAM,QAAQ,MAAM,KAAK,CAAC;AAC1B,YAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,eACE,gBAAAF,MAACR,OAAA,EAAc,OAAOU,QAAO,KAC3B;AAAA,0BAAAH,KAACN,OAAA,EAAK,OAAOS,QAAO,OAAQ,iBAAO,GAAG,GAAE;AAAA,UACxC,gBAAAH,KAACN,OAAA,EAAK,OAAOS,QAAO,OAAQ,iBAAO,KAAK,GAAE;AAAA,aAFjC,EAGX;AAAA,MAEJ,CAAC,GACH;AAAA,SAdO,CAgBX;AAAA,EAEJ,CAAC,GACH;AAEJ;AAEA,IAAMA,UAASR,YAAW,OAAO;AAAA,EAC/B,MAAM;AAAA,IACJ,gBAAgB;AAAA,IAChB,KAAK;AAAA,EACP;AAAA,EACA,MAAM;AAAA,IACJ,iBAAiBE,KAAI;AAAA,IACrB,cAAcC,cAAa;AAAA,IAC3B,aAAa;AAAA,IACb,aAAaD,KAAI;AAAA,IACjB,SAAS,QAAQ;AAAA,EACnB;AAAA,EACA,OAAO;AAAA,IACL,OAAOA,KAAI;AAAA,IACX,UAAUE,UAAS;AAAA,IACnB,YAAY;AAAA,IACZ,YAAYG;AAAA,IACZ,eAAe;AAAA,IACf,cAAc;AAAA,EAChB;AAAA,EACA,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,OAAOL,KAAI;AAAA,IACX,UAAUE,UAAS;AAAA,IACnB,YAAYG;AAAA,IACZ,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,eAAe;AAAA,IACf,eAAe;AAAA,IACf,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOL,KAAI;AAAA,IACX,UAAUE,UAAS;AAAA,IACnB,YAAYG;AAAA,IACZ,YAAY;AAAA,EACd;AACF,CAAC;;;AC1FD,SAAS,cAAc;AAQhB,IAAM,mBAAmB,OAAsB,CAAC,SAAS;AAAA,EAC9D,KAAK;AAAA,EACL,MAAM,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,EAC1B,MAAM,MAAM,IAAI,EAAE,KAAK,KAAK,CAAC;AAC/B,EAAE;;;ALwBM,SAkLJ,UAlLI,OAAAE,MA6EE,QAAAC,aA7EF;AA7BR,IAAMC,WAAUC,UAAS,OAAO,QAAQ,mBAAmB;AAM3D,SAAS,WAAW,MAAoB;AACtC,QAAM,IAAI,KAAK,SAAS;AACxB,QAAM,IAAI,KAAK,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACtD,SAAO,GAAG,CAAC,IAAI,CAAC;AAClB;AAEO,SAAS,aAAa,EAAE,KAAK,GAAmB;AACrD,QAAM,QAAQ,oBAAI,KAAK;AACvB,QAAM,YAAY,IAAI,KAAK,KAAK;AAChC,YAAU,QAAQ,UAAU,QAAQ,IAAI,CAAC;AAEzC,MAAI;AACJ,MAAI,UAAU,MAAM,KAAK,GAAG;AAC1B,YAAQ;AAAA,EACV,WAAW,UAAU,MAAM,SAAS,GAAG;AACrC,YAAQ;AAAA,EACV,OAAO;AACL,YAAQ,KAAK,mBAAmB,QAAW,EAAE,SAAS,QAAQ,OAAO,SAAS,KAAK,UAAU,CAAC;AAAA,EAChG;AAEA,SACE,gBAAAH,KAACI,OAAA,EAAK,OAAO,gBAAgB,WAC3B,0BAAAJ,KAACI,OAAA,EAAK,OAAO,gBAAgB,MAC3B,0BAAAJ,KAACK,OAAA,EAAK,OAAO,gBAAgB,MAAO,iBAAM,GAC5C,GACF;AAEJ;AAEA,SAAS,UAAU,GAAS,GAAkB;AAC5C,SAAO,EAAE,YAAY,MAAM,EAAE,YAAY,KAAK,EAAE,SAAS,MAAM,EAAE,SAAS,KAAK,EAAE,QAAQ,MAAM,EAAE,QAAQ;AAC3G;AAEA,IAAM,kBAAkBC,YAAW,OAAO;AAAA,EACxC,WAAW;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM;AAAA,IACJ,iBAAiBC,KAAI;AAAA,IACrB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,cAAc;AAAA,EAChB;AAAA,EACA,MAAM;AAAA,IACJ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAOA,KAAI;AAAA,IACX,eAAe;AAAA,IACf,YAAYL;AAAA,EACd;AACF,CAAC;AAEc,SAAR,cAA+B,EAAE,QAAQ,GAAU;AACxD,QAAM,SAAS,QAAQ,SAAS;AAChC,QAAM,WAAW,QAAQ,SAAS;AAClC,QAAM,iBAAiB,CAAC,UAAU,QAAQ,SAAS;AACnD,QAAM,CAAC,QAAQ,SAAS,IAAIM,UAAS,EAAE;AAEvC,QAAM,aAAa,YAAY,CAAC,UAAkB;AAChD,cAAU,KAAK;AACf,IAAAC,YAAW,GAAG,aAAa,SAAS;AACpC,eAAW,MAAM,UAAU,EAAE,GAAG,IAAI;AAAA,EACtC,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,OAAO,SAAiB;AACnD,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAChB,UAAM,UAAU,UAAU,IAAI;AAC9B,eAAW,SAAS;AAAA,EACtB,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,kBAAkB,YAAY,MAAM;AACxC,aAAS,QAAQ,OAAO;AAAA,EAC1B,GAAG,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAE9B,SACE,gBAAAR,MAACG,OAAA,EAAK,OAAO,CAACM,QAAO,KAAK,UAAUA,QAAO,OAAO,GAC/C;AAAA,KAAC,UAAU,CAAC,kBACX,gBAAAV,KAACI,OAAA,EAAK,OAAOM,QAAO,WAClB,0BAAAV;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAO,QAAQ,aAAa,WAAW,YAAY,aAAa;AAAA;AAAA,IAClE,GACF;AAAA,IAED,kBAAkB,gBAAAA,KAACI,OAAA,EAAK,OAAOM,QAAO,wBAAwB;AAAA,IAC/D,gBAAAT;AAAA,MAACU;AAAA,MAAA;AAAA,QACC,mBAAkB;AAAA,QAClB,oBAAoB,GAAG,SAAS,QAAQ,iBAAiB,iBAAiB,KAAK,KAAK,QAAQ,OAAO;AAAA,QACnG,mBAAkB;AAAA,QAClB,aAAa;AAAA,QACb,OAAO;AAAA,UACLD,QAAO;AAAA,UACP,UAAUA,QAAO;AAAA,UACjB,YAAYA,QAAO;AAAA,UACnB,kBAAkBA,QAAO;AAAA,QAC3B;AAAA,QAEC;AAAA,4BACC,gBAAAT,MAACG,OAAA,EAAK,OAAOM,QAAO,oBAClB;AAAA,4BAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,wBAAwB,uBAAE;AAAA,YAC9C,gBAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,wBAAwB,0BAAY;AAAA,aAC1D;AAAA,UAGD,QAAQ,eAAe,QAAQ,YAAY,SAAS,KACnD,gBAAAV,KAACI,OAAA,EAAK,OAAOM,QAAO,aACjB,kBAAQ,YAAY,IAAI,CAAC,KAAK,MAC7B,IAAI,aACF,gBAAAV;AAAA,YAACW;AAAA,YAAA;AAAA,cAEC,mBAAkB;AAAA,cAClB,oBAAoB,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,EAAE;AAAA,cAC3D,SAAS,MAAM,iBAAiB,SAAS,EAAE,KAAK,IAAI,UAAW;AAAA,cAE/D,0BAAAX;AAAA,gBAAC;AAAA;AAAA,kBACC,QAAQ;AAAA,oBACN,KAAK,IAAI;AAAA,oBACT,GAAI,IAAI,WAAW,WAAW,MAAM,IAAI;AAAA,sBACtC,SAAS,EAAE,eAAe,UAAU,UAAU,EAAE,cAAc,SAAS,CAAC,GAAG;AAAA,oBAC7E,IAAI,CAAC;AAAA,kBACP;AAAA,kBACA,OAAOU,QAAO;AAAA,kBACd,YAAW;AAAA,kBACX,SAAS,CAAC,MAAM,QAAQ,KAAK,2BAA2B,IAAI,YAAY,MAAM,GAAG,GAAG,GAAG,EAAE,YAAY,KAAK;AAAA;AAAA,cAC5G;AAAA;AAAA,YAfK;AAAA,UAgBP,IAEA,gBAAAV,KAACI,OAAA,EAAa,OAAOM,QAAO,gBAC1B,0BAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,oBAAoB,eAAe,GAAI,cAAI,MAAK,KAD3D,CAEX,CAEH,GACH;AAAA,UAED;AAAA;AAAA,YAEC,QAAQ,WAAW,EAAE,QAAQ,YAAY,oBAAoB,QAAQ,aAAa,UAChF,gBAAAV,KAACK,OAAA,EAAK,YAAU,MAAC,OAAOK,QAAO,UAAW,kBAAQ,SAAQ,IACxD;AAAA,cAEJ,uBAAuB,QAAQ,SAAS,QAAQ;AAAA,UAEjD,CAAC,CAAC,UACD,gBAAAV,KAACI,OAAA,EAAK,OAAOM,QAAO,aAClB,0BAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,YAAa,kBAAO,GAC1C;AAAA,UAED,QAAQ,aAAa,QAAQ,UAAU,SAAS,KAC/C,gBAAAV,KAAC,kBAAe,OAAO,QAAQ,WAAW;AAAA,UAE3C,QAAQ,eACP,gBAAAA,KAAC,kBAAe,UAAU,QAAQ,aAAa;AAAA,UAEhD,CAAC,UAAU,QAAQ,WAAW,QAAQ,QAAQ,SAAS,KACtD,gBAAAA,KAAC,gBAAa,SAAS,QAAQ,SAAS;AAAA,UAE1C,gBAAAA,KAACK,OAAA,EAAK,OAAO,CAACK,QAAO,UAAU,UAAUA,QAAO,YAAY,GACzD,qBAAW,QAAQ,SAAS,GAC/B;AAAA;AAAA;AAAA,IACF;AAAA,IACC,UAAU,gBAAAV,KAACI,OAAA,EAAK,OAAOM,QAAO,cAAc;AAAA,KAC/C;AAEJ;AAOA,SAAS,uBAAuB,SAAiB,UAA+B;AAC9E,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,CAAC,QAAQ,SAAS,gBAAgB,GAAG;AACvC,WAAO,gBAAAV,KAAC,kBAAe,SAAkB,UAAoB;AAAA,EAC/D;AAEA,QAAM,WAA6D,CAAC;AACpE,MAAI,SAAS;AACb,SAAO,SAAS,QAAQ,QAAQ;AAC9B,UAAM,OAAO,QAAQ,QAAQ,kBAAkB,MAAM;AACrD,QAAI,SAAS,IAAI;AACf,eAAS,KAAK,EAAE,MAAM,MAAM,SAAS,QAAQ,MAAM,MAAM,EAAE,CAAC;AAC5D;AAAA,IACF;AACA,QAAI,OAAO,QAAQ;AACjB,eAAS,KAAK,EAAE,MAAM,MAAM,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAE,CAAC;AAAA,IACpE;AACA,UAAM,QAAQ,QAAQ,QAAQ,mBAAmB,OAAO,iBAAiB,MAAM;AAC/E,QAAI,UAAU,IAAI;AAEhB,eAAS,KAAK,EAAE,MAAM,MAAM,SAAS,QAAQ,MAAM,IAAI,EAAE,CAAC;AAC1D;AAAA,IACF;AACA,UAAM,UAAU,QAAQ,MAAM,OAAO,iBAAiB,QAAQ,KAAK;AACnE,aAAS,KAAK,EAAE,MAAM,SAAS,SAAS,QAAQ,CAAC;AACjD,aAAS,QAAQ,kBAAkB;AAAA,EACrC;AAEA,SACE,gBAAAA,KAAA,YACG,mBAAS,IAAI,CAAC,KAAK,MAAM;AACxB,QAAI,IAAI,SAAS,MAAM;AACrB,YAAM,UAAU,IAAI,QAAQ,KAAK;AACjC,UAAI,CAAC,QAAS,QAAO;AACrB,aAAO,gBAAAA,KAAC,kBAAuB,SAAS,IAAI,SAAS,YAAzB,CAA6C;AAAA,IAC3E;AACA,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,mBAAmB,IAAI,OAAO,CAAC;AACvD,aAAO,gBAAAA,KAAC,kBAAuB,SAAS,KAAK,SAAS,MAAM,KAAK,QAArC,CAA2C;AAAA,IACzE,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,GACH;AAEJ;AAEA,SAAS,eAAe,EAAE,SAAS,SAAS,GAAuD;AACjG,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP,OAAO;AAAA,QACL,aAAa,CAAC,SACZ,gBAAAA;AAAA,UAACK;AAAA,UAAA;AAAA,YAEC,OAAO,eAAe;AAAA,YACtB,SAAS,MAAM,SAAS,KAAK,OAAO;AAAA,YAEnC,eAAK;AAAA;AAAA,UAJD,KAAK;AAAA,QAKZ;AAAA,QAEF,OAAO,CAAC,SACN,gBAAAL;AAAA,UAACW;AAAA,UAAA;AAAA,YAEC,SAAS,MAAM,SAAS,KAAK,OAAO;AAAA,YACpC,OAAO,eAAe;AAAA,YAEtB,0BAAAX,KAACK,OAAA,EAAK,OAAO,eAAe,WAAY,eAAK,SAAQ;AAAA;AAAA,UAJhD,KAAK;AAAA,QAKZ;AAAA,MAEJ;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEA,SAAS,eAAe,EAAE,MAAM,GAA0B;AACxD,QAAM,CAAC,UAAU,WAAW,IAAIG,UAAS,KAAK;AAC9C,QAAM,QAAQ,MACX,OAAO,OAAK,EAAE,SAAS,uBAAuB,EAC9C,IAAI,OAAM,EAAE,KAAa,cAAc,EAAE,KAAK,EAC9C,OAAO,OAAO,EACd,KAAK,IAAI,KAAK;AAEjB,SACE,gBAAAP,MAACG,OAAA,EAAK,OAAOM,QAAO,WAClB;AAAA,oBAAAT,MAACU,YAAA,EAAU,OAAOD,QAAO,YAAY,SAAS,MAAM,YAAY,CAAC,QAAQ,GACvE;AAAA,sBAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,WAAY,iBAAM;AAAA,MACtC,gBAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,aAAc,qBAAW,WAAM,UAAI;AAAA,OACzD;AAAA,IACC,YAAY,MAAM,IAAI,CAAC,IAAI,MAC1B,gBAAAT,MAACG,OAAA,EAAa,OAAOM,QAAO,UAC1B;AAAA,sBAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,UAAW,aAAG,MAAK;AAAA,MACtC,GAAG,SAAS,gBAAAV,KAACK,OAAA,EAAK,OAAOK,QAAO,WAAY,aAAG,OAAM;AAAA,SAF7C,CAGX,CACD;AAAA,KACH;AAEJ;AAEA,IAAMA,UAASJ,YAAW,OAAO;AAAA,EAC/B,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,KAAK;AAAA,EACP;AAAA,EACA,SAAS;AAAA,IACP,gBAAgB;AAAA,EAClB;AAAA,EACA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAAA,EACA,cAAc;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,iBAAiBC,KAAI;AAAA,IACrB,aAAa;AAAA,IACb,aAAaA,KAAI;AAAA,IACjB,cAAcK,cAAa;AAAA,IAC3B,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,EACnB;AAAA,EACA,YAAY;AAAA,IACV,iBAAiBL,KAAI;AAAA,IACrB,aAAa;AAAA,EACf;AAAA,EACA,cAAc;AAAA,IACZ,iBAAiBA,KAAI;AAAA,IACrB,aAAaA,KAAI;AAAA,IACjB,UAAU;AAAA,EACZ;AAAA,EACA,oBAAoB;AAAA,IAClB,iBAAiBA,KAAI;AAAA,IACrB,aAAa;AAAA,IACb,cAAcK,cAAa;AAAA,IAC3B,UAAU;AAAA,EACZ;AAAA,EACA,wBAAwB;AAAA,IACtB,OAAO;AAAA,IACP,WAAW;AAAA,IACX,iBAAiBL,KAAI;AAAA,IACrB,cAAc;AAAA,IACd,aAAa;AAAA,EACf;AAAA,EACA,oBAAoB;AAAA,IAClB,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,cAAc;AAAA,EAChB;AAAA,EACA,wBAAwB;AAAA,IACtB,UAAU;AAAA,EACZ;AAAA,EACA,wBAAwB;AAAA,IACtB,OAAOA,KAAI;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,YAAYL;AAAA,EACd;AAAA,EACA,UAAU;AAAA,IACR,OAAOK,KAAI;AAAA,IACX,UAAUM,UAAS;AAAA,IACnB,YAAY;AAAA,IACZ,YAAYX;AAAA,EACd;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,OAAOK,KAAI;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,YAAYL;AAAA,EACd;AAAA,EACA,cAAc;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,aAAa;AAAA,IACX,eAAe;AAAA,IACf,UAAU;AAAA,IACV,KAAK;AAAA,IACL,cAAc;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,IACf,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,IACd,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,EACnB;AAAA,EACA,oBAAoB;AAAA,IAClB,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA,aAAa;AAAA,IACX,UAAU;AAAA,IACV,KAAK;AAAA,IACL,OAAO;AAAA,IACP,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,cAAcU,cAAa;AAAA,EAC7B;AAAA,EACA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,UAAUC,UAAS;AAAA,IACnB,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgBN,KAAI;AAAA,IACpB,YAAY;AAAA,EACd;AAAA,EACA,YAAY;AAAA,IACV,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,EACP;AAAA,EACA,WAAW;AAAA,IACT,OAAOA,KAAI;AAAA,IACX,UAAUM,UAAS;AAAA,IACnB,YAAY;AAAA,EACd;AAAA,EACA,aAAa;AAAA,IACX,OAAON,KAAI;AAAA,IACX,UAAU;AAAA,EACZ;AAAA,EACA,UAAU;AAAA,IACR,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA,UAAU;AAAA,IACR,OAAOA,KAAI;AAAA,IACX,UAAUM,UAAS;AAAA,IACnB,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,OAAON,KAAI;AAAA,IACX,UAAUM,UAAS;AAAA,EACrB;AACF,CAAC;AAED,IAAM,iBAAiBP,YAAW,OAAO;AAAA,EACvC,MAAM;AAAA,IACJ,OAAOC,KAAI;AAAA,IACX,UAAUM,UAAS;AAAA,IACnB,YAAY;AAAA,IACZ,YAAYX;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,cAAc;AAAA,EAChB;AAAA,EACA,QAAQ;AAAA,IACN,YAAY;AAAA,IACZ,OAAOK,KAAI;AAAA,EACb;AAAA,EACA,IAAI;AAAA,IACF,WAAW;AAAA,EACb;AAAA,EACA,aAAa;AAAA,IACX,iBAAiBA,KAAI;AAAA,IACrB,OAAOA,KAAI;AAAA,IACX,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,UAAUM,UAAS;AAAA,EACrB;AAAA,EACA,OAAO;AAAA,IACL,iBAAiBN,KAAI;AAAA,IACrB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA,WAAW;AAAA,IACT,YAAY;AAAA,IACZ,UAAUM,UAAS;AAAA,IACnB,OAAON,KAAI;AAAA,EACb;AAAA,EACA,MAAM;AAAA,IACJ,OAAOA,KAAI;AAAA,EACb;AAAA,EACA,WAAW;AAAA,IACT,OAAOA,KAAI;AAAA,EACb;AAAA,EACA,aAAa;AAAA,IACX,gBAAgB;AAAA,EAClB;AACF,CAAC;;;AM7eD,SAAS,QAAAO,OAAM,cAAAC,mBAAkB;AACjC,SAAS,UAAAC,eAAc;AAKjB,gBAAAC,YAAA;AAHS,SAAR,kBAAmC;AACxC,SACE,gBAAAA,KAACH,OAAA,EAAK,OAAOI,QAAO,KAClB,0BAAAD,KAACD,SAAA,EAAO,MAAM,IAAI,OAAM,YAAW,GACrC;AAEJ;AAEA,IAAME,UAASH,YAAW,OAAO;AAAA,EAC/B,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,EACnB;AACF,CAAC;;;ACnBD,SAAS,YAAAI,WAAU,UAAAC,SAAQ,eAAAC,cAAa,aAAAC,kBAAiB;AACzD;AAAA,EACE,QAAAC;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,EACA,cAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AACP,OAAO,OAAO,MAAM,MAAM,eAAe;AACzC,SAAS,QAAQ,gBAAAC,qBAAoB;AACrC,SAAS,cAAAC,mBAAkB;AA4Cb,gBAAAC,MAwBJ,QAAAC,aAxBI;AAhCC,SAAR,UAA2B,EAAE,QAAQ,aAAa,SAAS,SAAS,YAAY,GAAU;AAC/F,QAAM,CAAC,MAAM,OAAO,IAAIX,UAAS,EAAE;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,EAAE;AACvC,QAAM,WAAWC,QAAkB,IAAI;AAEvC,EAAAE,WAAU,MAAM;AACd,QAAI,SAAS;AACX,cAAQ,OAAO;AACf,eAAS,SAAS,MAAM;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAKZ,QAAM,aAAaD,aAAY,MAAM;AACnC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AACd,IAAAO,YAAW,GAAG,OAAO,OAAO;AAC5B,WAAO,OAAO;AACd,YAAQ,EAAE;AACV,cAAU,EAAE;AAAA,EACd,GAAG,CAAC,MAAM,SAAS,MAAM,CAAC;AAE1B,QAAM,UAAU,KAAK,KAAK,EAAE,SAAS;AAErC,SACE,gBAAAC,KAACN,OAAA,EAAK,OAAOQ,QAAO,MAClB,0BAAAD,MAACP,OAAA,EAAK,OAAOQ,QAAO,UACjB;AAAA,mBACC,gBAAAF,KAACL,YAAA,EAAU,OAAOO,QAAO,SAAS,SAAS,aACzC,0BAAAF,KAAC,OAAI,OAAO,IAAI,QAAQ,IAAI,SAAQ,aAAY,MAAK,QAAO,QAAQ,OAAO,MAAM,QAAQ,aAAa,GAAG,eAAc,SAAQ,gBAAe,SAC5I,0BAAAA,KAAC,QAAK,GAAE,8QAA6Q,GACvR,GACF;AAAA,IAEF,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,OAAO,CAACE,QAAO,OAAO,EAAE,QAAQ,KAAK,IAAI,KAAK,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;AAAA,QACrE,OAAO;AAAA,QACP,cAAc;AAAA,QACd,qBAAqB,OAAK,UAAU,EAAE,YAAY,YAAY,MAAM;AAAA,QACpE,aAAa,eAAe;AAAA,QAC5B,sBAAsB,OAAO,MAAM;AAAA,QACnC,WAAS;AAAA,QACT,eAAc;AAAA,QACd,cAAc;AAAA,QACd,iBAAiB,MAAM;AACrB,cAAIL,UAAS,OAAO,MAAO,YAAW;AAAA,QACxC;AAAA;AAAA,IACF;AAAA,IACA,gBAAAG;AAAA,MAACL;AAAA,MAAA;AAAA,QACC,OAAO,CAACO,QAAO,SAAS,WAAWA,QAAO,aAAa;AAAA,QACvD,SAAS;AAAA,QACT,UAAU,CAAC;AAAA,QAEX,0BAAAD,MAAC,OAAI,OAAO,IAAI,QAAQ,IAAI,SAAQ,aAAY,MAAK,QAAO,QAAQ,UAAU,SAAS,OAAO,MAAM,WAAW,aAAa,GAC1H;AAAA,0BAAAD,KAAC,QAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,UACrC,gBAAAA,KAAC,WAAQ,QAAO,6BAA4B;AAAA,WAC9C;AAAA;AAAA,IACF;AAAA,KACF,GACF;AAEJ;AAEA,IAAME,UAASN,YAAW,OAAO;AAAA,EAC/B,MAAM;AAAA,IACJ,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,iBAAiB,OAAO,MAAM;AAAA,IAC9B,gBAAgB;AAAA,IAChB,gBAAgB,OAAO,MAAM;AAAA,EAC/B;AAAA,EACA,UAAU;AAAA,IACR,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,iBAAiB,OAAO,MAAM;AAAA,IAC9B,cAAcE,cAAa;AAAA,IAC3B,aAAa;AAAA,IACb,aAAa,OAAO,MAAM;AAAA,IAC1B,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,KAAK;AAAA,EACP;AAAA,EACA,SAAS;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO,OAAO,MAAM;AAAA,IACpB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,WAAW;AAAA,EACb;AAAA,EACA,SAAS;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,iBAAiB,OAAO,MAAM;AAAA,EAChC;AAAA,EACA,eAAe;AAAA,IACb,iBAAiB,OAAO,MAAM;AAAA,EAChC;AACF,CAAC;;;ACjID,SAAS,OAAO,QAAAK,OAAM,SAAAC,QAAO,aAAAC,YAAW,QAAAC,OAAM,cAAAC,mBAAkB;AAChE,SAAS,aAAAC,kBAAiB;AAWpB,SACE,OAAAC,MADF,QAAAC,aAAA;AARS,SAAR,WAA4B;AACjC,QAAM,MAAM,iBAAiB,OAAK,EAAE,GAAG;AACvC,QAAM,OAAO,iBAAiB,OAAK,EAAE,IAAI;AAEzC,MAAI,CAAC,IAAK,QAAO;AAEjB,SACE,gBAAAD,KAAC,SAAM,SAAO,MAAC,aAAW,MAAC,eAAc,QAAO,gBAAgB,MAC9D,0BAAAC,MAACC,YAAA,EAAU,OAAOC,QAAO,UAAU,SAAS,MAC1C;AAAA,oBAAAH;AAAA,MAACI;AAAA,MAAA;AAAA,QACC,QAAQ;AAAA,UACN;AAAA,UACA,GAAI,IAAI,WAAW,MAAM,IAAI;AAAA,YAC3B,SAAS,EAAE,eAAe,UAAUC,WAAU,EAAE,cAAc,SAAS,CAAC,GAAG;AAAA,UAC7E,IAAI,CAAC;AAAA,QACP;AAAA,QACA,OAAOF,QAAO;AAAA,QACd,YAAW;AAAA;AAAA,IACb;AAAA,IACA,gBAAAH,KAACM,OAAA,EAAK,OAAOH,QAAO,WAClB,0BAAAH,KAACO,OAAA,EAAK,OAAOJ,QAAO,eAAe,0BAAY,GACjD;AAAA,KACF,GACF;AAEJ;AAEA,IAAMA,UAASK,YAAW,OAAO;AAAA,EAC/B,UAAU;AAAA,IACR,MAAM;AAAA,IACN,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,EAClB;AAAA,EACA,OAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,EACb;AAAA,EACA,eAAe;AAAA,IACb,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AACF,CAAC;;;ATuDK,SACiB,OAAAC,MADjB,QAAAC,aAAA;AAlEN,SAASC,WAAU,GAAS,GAAkB;AAC5C,SAAO,EAAE,YAAY,MAAM,EAAE,YAAY,KAAK,EAAE,SAAS,MAAM,EAAE,SAAS,KAAK,EAAE,QAAQ,MAAM,EAAE,QAAQ;AAC3G;AAEe,SAAR,eAAgC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAAwB;AACtB,QAAM,WAAW,aAAa,OAAK,EAAE,QAAQ;AAC7C,QAAM,UAAU,aAAa,OAAK,EAAE,OAAO;AAC3C,QAAM,qBAAqB,aAAa,OAAK,EAAE,kBAAkB;AACjE,QAAM,mBAAmB,aAAa,OAAK,EAAE,gBAAgB;AAC7D,QAAM,cAAc,aAAa,OAAK,EAAE,WAAW;AAEnD,QAAM,UAAUC,QAA0B,IAAI;AAC9C,QAAM,CAAC,YAAY,aAAa,IAAIC,UAAS,KAAK;AAClD,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,EAAE;AAGzC,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,mBAAoB,kBAAiB;AAAA,EAC5C,GAAG,CAAC,oBAAoB,gBAAgB,CAAC;AAGzC,EAAAA,WAAU,MAAM;AACd,UAAM,QAAQ,qBAAqB,GAAG,wBAAwB,CAAC,EAAE,KAAK,MAAyB;AAC7F,UAAI,KAAM,YAAW,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAML,QAAM,cAAcC,UAAS,OAAO;AACpC,QAAM,WAAW;AAAA,IACf,MAAO,cAAc,CAAC,GAAG,QAAQ,EAAE,QAAQ,IAAI;AAAA,IAC/C,CAAC,UAAU,WAAW;AAAA,EACxB;AAEA,QAAM,iBAAiBC,aAAY,CAAC,aAAsB;AACxD,QAAI,YAAa,SAAQ,SAAS,eAAe,EAAE,QAAQ,GAAG,SAAS,CAAC;AAAA,QACnE,SAAQ,SAAS,YAAY,EAAE,SAAS,CAAC;AAAA,EAChD,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,aAAaA,aAAY,CAAC,SAAiB;AAC/C,eAAW,EAAE;AACb,gBAAY,IAAI;AAChB,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,aAAa,cAAc,CAAC;AAEhC,QAAM,gBAAgBA,aAAY,YAAY;AAC5C,kBAAc,IAAI;AAClB,QAAI;AAAE,YAAM,iBAAiB;AAAA,IAAG,QAAQ;AAAA,IAAC;AACzC,kBAAc,KAAK;AAAA,EACrB,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,aAAaA,aAAY,CAAC,EAAE,MAAM,MAAM,MAAwC;AAGpF,UAAM,QAAQ,cAAc,SAAS,QAAQ,CAAC,IAAI,SAAS,QAAQ,CAAC;AACpE,UAAM,aAAa,CAAC,SAAS,CAACL,WAAU,MAAM,WAAW,KAAK,SAAS;AACvE,WACE,gBAAAD,MAACO,OAAA,EACE;AAAA,oBAAc,gBAAAR,KAAC,gBAAa,MAAM,KAAK,WAAW;AAAA,MACnD,gBAAAA,KAAC,iBAAc,SAAS,MAAM;AAAA,OAChC;AAAA,EAEJ,GAAG,CAAC,UAAU,WAAW,CAAC;AAE1B,QAAM,kBACJ,WAAW,EAAE,cAAc,SAAS,CAAC,IAAI,SAAS,SAAS,SAAS,CAAC,IAAI,cACrE,gBAAAA,KAAC,mBAAgB,IACjB;AAEN,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,OAAOQ,QAAO;AAAA,MACd,UAAUH,UAAS,OAAO,QAAQ,YAAY;AAAA,MAE9C;AAAA,wBAAAN;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,OAAOS,QAAO;AAAA,YACd,MAAM;AAAA,YACN,UAAU;AAAA,YACV,cAAc,OAAK,EAAE;AAAA,YACrB;AAAA,YAIA,qBAAqB,cAAc,kBAAkB;AAAA,YACrD,qBAAqB,cAAc,OAAO;AAAA,YAC1C,qBAAqB,cAAc,SAAY,MAAM,eAAe,KAAK;AAAA,YACzE,gBACE,gBAAAT,KAAC,kBAAe,YAAwB,WAAW,eAAe,WAAWU,KAAI,QAAQ;AAAA,YAE3F,2BAA0B;AAAA,YAC1B,uBAAuBD,QAAO;AAAA;AAAA,QAChC;AAAA,QACC;AAAA,QACD,gBAAAT;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QACC,gBAAgB,gBAAAA,KAAC,YAAS;AAAA;AAAA;AAAA,EAC7B;AAEJ;AAEA,IAAMS,UAASE,YAAW,OAAO;AAAA,EAC/B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,iBAAiBD,KAAI;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,aAAa;AAAA,IACX,iBAAiB;AAAA,EACnB;AACF,CAAC;;;AUhKD,OAAO,SAAS,WAAAE,gBAAe;AAC/B,SAAS,QAAAC,QAAM,QAAAC,OAAM,aAAAC,YAAW,qBAAAC,oBAAmB,cAAAC,oBAAkB;AACrE,SAAS,aAAAC,YAAW,SAAS,UAAAC,eAAc;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;AClBP,SAAS,YAAAC,WAAU,WAAAC,UAAS,eAAAC,oBAAmB;AAC/C,SAAS,QAAAC,OAAM,QAAAC,OAAM,mBAAmB,cAAAC,aAAY,YAAAC,iBAAgB;AACpE,SAAS,eAAe;AACxB,SAAS,aAAAC,YAAW,eAAe,aAAAC,YAAW,UAAU,OAAAC,YAAW;AACnE,SAAS,wBAAuE;;;ACJhF,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,eAAc;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAAC;AAAA,OACK;AAWA,SAAS,gBACd,UACA,aACA,kBACA,OACA;AAIA,QAAM,aAAaD,QAAY,IAAI;AAGnC,QAAM,WAAWF,aAAY,MAAM;AACjC,UAAM,MAAM,KAAK,UAAU;AAAA,MACzB,QAAQ;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,UACP,QAAQ,UAAU;AAAA,UAClB,aAAa,eAAe;AAAA,UAC5B,aAAa;AAAA,UACb,MAAM;AAAA,UACN,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF,CAAC;AACD,eAAW,SAAS,YAAY,GAAG;AAAA,EACrC,GAAG,CAAC,UAAU,WAAW,CAAC;AAG1B,QAAM,eAAeA;AAAA,IACnB,CAAC,MAAc,YAAiB;AAC9B,YAAM,MAAM,KAAK,UAAU;AAAA,QACzB,QAAQ;AAAA,QACR,SAAS,EAAE,MAAM,QAAQ;AAAA,MAC3B,CAAC;AACD,iBAAW,SAAS,YAAY,GAAG;AAAA,IACrC;AAAA,IACA,CAAC;AAAA,EACH;AAGA,QAAM,oBAAoBA,aAAY,CAAC,QAAa;AAClD,QAAI,IAAI,YAAY,UAAW;AAC/B,UAAM,MAAM,KAAK,UAAU;AAAA,MACzB,QAAQ;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,UACP,SAAS,IAAI;AAAA,UACb,MAAM,IAAI;AAAA,UACV,eAAe,IAAI;AAAA,QACrB;AAAA,MACF;AAAA,IACF,CAAC;AACD,eAAW,SAAS,YAAY,GAAG;AAAA,EACrC,GAAG,CAAC,QAAQ,CAAC;AAGb,EAAAC,WAAU,MAAM;AACd,UAAM,QAAQE,sBAAqB,GAAG,kBAAkB,CAAC,QAAa;AACpE,UAAI,IAAI,aAAa,SAAU;AAC/B,wBAAkB,GAAG;AAAA,IACvB,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,iBAAiB,CAAC;AAGhC,EAAAF,WAAU,MAAM;AACd,QAAI,CAAC,iBAAkB;AAEvB,UAAM,QAAQ,WAAW,MAAM;AAC7B,YAAM,MAAM,iBAAiB;AAC7B,YAAM,YAAmB,CAAC;AAC1B,iBAAW,OAAO,KAAK;AACrB,YAAI,IAAI,aAAa,UAAU;AAC7B,4BAAkB,GAAG;AAAA,QACvB,OAAO;AACL,oBAAU,KAAK,GAAG;AAAA,QACpB;AAAA,MACF;AACA,uBAAiB,UAAU;AAAA,IAC7B,GAAG,GAAI;AACP,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,UAAU,kBAAkB,iBAAiB,CAAC;AAGlD,QAAM,gBAAgBD;AAAA,IACpB,OAAO,UAA6C;AAClD,UAAI;AACJ,UAAI;AACF,eAAO,KAAK,MAAM,MAAM,YAAY,IAAI;AAAA,MAC1C,QAAQ;AACN;AAAA,MACF;AAEA,UAAI,KAAK,WAAW,cAAe;AAEnC,YAAM,SAAS,KAAK,SAAS,UAAU,KAAK;AAC5C,YAAM,UAAU,KAAK,SAAS,WAAW,KAAK;AAC9C,YAAM,OAAO,KAAK,SAAS,QAAQ,KAAK;AAKxC,UAAI,WAAW,eAAe,SAAS,WAAW;AAChD,cAAM,EAAE,WAAW,KAAK,IAAI;AAC5B,YAAI;AACF,gBAAM,SAAS,MAAM,SAAS,IAAI;AAClC,uBAAa,oBAAoB,EAAE,WAAW,QAAQ,OAAO,KAAK,CAAC;AAAA,QACrE,SAAS,KAAU;AACjB,kBAAQ,KAAK,mCAAmC,IAAI,OAAO;AAC3D,uBAAa,oBAAoB,EAAE,WAAW,QAAQ,MAAM,OAAO,IAAI,QAAQ,CAAC;AAAA,QAClF;AACA;AAAA,MACF;AAOA,UAAI,WAAW,cAAc,SAAS,WAAW;AAC/C,cAAM,EAAE,WAAW,UAAU,MAAM,SAAS,IAAI;AAGhD,YAAI,UAAU;AACZ,cAAI;AACF,kBAAM,cAAc,MAAM,YAAY,UAAU,YAAY,CAAC,CAAC;AAE9D,kBAAM,SAAS,aAAa,UAAU;AACtC,yBAAa,cAAc,EAAE,WAAW,QAAQ,OAAO,KAAK,CAAC;AAAA,UAC/D,SAAS,KAAU;AACjB,yBAAa,cAAc,EAAE,WAAW,QAAQ,MAAM,OAAO,IAAI,QAAQ,CAAC;AAAA,UAC5E;AACA;AAAA,QACF;AAIA,cAAM,MAAM,QAAQ,eAAe,UAAU,eAAe,UAAU,QAAQ;AAC9E,cAAM,OAAO,QAAQ,QAAQ,UAAU,QAAQ,UAAU,QAAQ;AACjE,cAAM,WAAW,QAAQ,QAAQ,UAAU,QAAQ,UAAU,QAAQ,QAAQ,CAAC;AAE9E,YAAI,CAAC,OAAO,CAAC,MAAM;AACjB,kBAAQ,MAAM,8DAAyD,GAAG,WAAW,IAAI,EAAE;AAC3F,uBAAa,cAAc,EAAE,WAAW,QAAQ,MAAM,OAAO,4DAA4D,GAAG,UAAU,IAAI,GAAG,CAAC;AAC9I;AAAA,QACF;AAEA,YAAI;AACF,gBAAMI,QAAO,MAAM,cAAc,KAAK,MAAM,QAAQ;AACpD,cAAIA,MAAK,IAAI;AACX,yBAAa,cAAc,EAAE,WAAW,QAAQA,MAAK,QAAQ,OAAO,KAAK,CAAC;AAAA,UAC5E,OAAO;AACL,yBAAa,cAAc,EAAE,WAAW,QAAQ,MAAM,OAAOA,MAAK,SAAS,kBAAkB,CAAC;AAAA,UAChG;AAAA,QACF,SAAS,KAAU;AACjB,uBAAa,cAAc,EAAE,WAAW,QAAQ,MAAM,OAAO,IAAI,QAAQ,CAAC;AAAA,QAC5E;AACA;AAAA,MACF;AAKA,UAAI,WAAW,SAAS;AACtB,QAAAD,sBAAqB,KAAK,gBAAgB,EAAE,SAAS,CAAC;AACtD;AAAA,MACF;AAKA,UAAI,SAAS,2BAA2B,SAAS,eAAe;AAC9D,cAAM,KAAK,OAAO;AAClB,YAAI,MAAM,GAAG,eAAe,UAAU,MAAM;AAC1C,cAAI;AACF,eAAG,KAAK,KAAK,UAAU;AAAA,cACrB,MAAM;AAAA,cACN,eAAe,QAAQ;AAAA,cACvB,QAAQ,QAAQ;AAAA,cAChB,OAAO,QAAQ;AAAA,YACjB,CAAC,CAAC;AAAA,UACJ,SAAS,KAAU;AACjB,oBAAQ,KAAK,wDAAwD,IAAI,OAAO;AAChF,kBAAM,kBAAkB,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UAC9F;AAAA,QACF,OAAO;AACL,cAAI;AACF,kBAAM,kBAAkB,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,KAAK;AAAA,UAC9E,SAAS,KAAU;AACjB,oBAAQ,KAAK,iDAAiD,IAAI,OAAO;AAAA,UAC3E;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAUO,SAAS,oBAAoB,KAAa,QAAwB;AACvE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAW8B,KAAK,UAAU,GAAG,CAAC;AAAA,0CAChB,KAAK,UAAU,MAAM,CAAC;AAAA,0CACtB,KAAK,UAAU,GAAG,CAAC,+CAA+C,KAAK,UAAU,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmIlI;;;ADhUQ,gBAAAE,MA4DE,QAAAC,aA5DF;AA5CO,SAAR,sBAAuC,EAAE,UAAU,UAAU,iBAAiB,GAAU;AAC7F,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAAwB,IAAI;AAEtD,QAAM,QAAQ,SAAS;AACvB,QAAM,SAASC,WAAU;AAIzB,QAAM,EAAE,YAAY,UAAU,cAAc,IAAI,gBAAgB,UAAU,SAAS,cAAc,gBAAgB;AAEjH,QAAM,MAAM,iBAAiB,UAAU,UAAU,MAAS;AAC1D,QAAM,WAAW,oBAAoB,SAAS,IAAI,UAAU,EAAE;AAI9D,QAAM,eAAeC,SAAQ,MAAM;AACjC,UAAM,QAAQ,oBAAI,IAAY;AAC9B,UAAM,OAAO,CAAC,cAAc,CAAC;AAC7B,UAAM,QAAQC,WAAU,EAAE;AAC1B,QAAI,MAAO,MAAK,KAAK,KAAK;AAC1B,eAAW,KAAK,MAAM;AACpB,UAAI;AAAE,cAAM,IAAI,IAAI,IAAI,CAAC,EAAE,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAuB;AAAA,IACvE;AACA,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,QAAM,qBAAqBC,aAAY,CAAC,WAA+C;AACrF,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI;AACJ,QAAI;AAAE,eAAS,IAAI,IAAI,MAAM;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAO;AACxD,QAAI,OAAO,aAAa,YAAY,OAAO,aAAa,QAAS,QAAO;AACxE,QAAI,OAAO,aAAa,QAAS,QAAO;AACxC,WAAO,aAAa,IAAI,OAAO,QAAQ;AAAA,EACzC,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,kBAAkBF;AAAA,IACtB,MAAM,MAAM,KAAK,YAAY,EAAE,IAAI,OAAK,WAAW,CAAC,EAAE;AAAA,IACtD,CAAC,YAAY;AAAA,EACf;AAEA,MAAI,OAAO;AACT,WACE,gBAAAJ,KAACO,OAAA,EAAK,OAAOC,QAAO,QAClB,0BAAAR,KAACS,OAAA,EAAK,OAAOD,QAAO,WAAY,iBAAM,GACxC;AAAA,EAEJ;AAEA,SACE,gBAAAR,KAACO,OAAA,EAAK,OAAOC,QAAO,WAClB,0BAAAR;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,QAAQ,EAAE,KAAK,IAAI;AAAA,MACnB,OAAOQ,QAAO;AAAA,MACd,mBAAiB;AAAA,MACjB,mBAAiB;AAAA,MACjB,2BAAyB;AAAA,MACzB,iCAAiC;AAAA,MAYjC,kBAAiB;AAAA,MACjB;AAAA,MACA,qCAAqC;AAAA,MACrC,2BAA2B;AAAA,MAC3B,uCAAuC;AAAA,MACvC,8BAA8B,CAAC,QAAQ;AACrC,YAAI,CAAC,mBAAmB,IAAI,GAAG,GAAG;AAChC,kBAAQ,KAAK,6DAA6D,IAAI,GAAG,EAAE;AACnF,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT;AAAA,MACA,uCAAuC;AAAA,MACvC,WAAW;AAAA,MACX,QAAQ,MAAM;AAEZ,mBAAW,UAAU,GAAG;AACxB,mBAAW,UAAU,GAAG;AACxB,mBAAW,UAAU,IAAI;AAAA,MAC3B;AAAA,MACA,SAAS,CAAC,mBAAmB;AAC3B,cAAM,EAAE,YAAY,IAAI;AACxB,gBAAQ,KAAK,kCAAkC,YAAY,WAAW;AACtE,iBAAS,kBAAkB,YAAY,WAAW,EAAE;AAAA,MACtD;AAAA,MACA,aAAa,CAAC,mBAAmB;AAC/B,cAAM,EAAE,YAAY,IAAI;AACxB,gBAAQ,KAAK,uCAAuC,YAAY,YAAY,YAAY,GAAG;AAC3F,YAAI,YAAY,cAAc,KAAK;AACjC,mBAAS,QAAQ,YAAY,UAAU,yBAAyB;AAAA,QAClE;AAAA,MACF;AAAA,MACA,qBAAmB;AAAA,MACnB,eAAe,MACb,gBAAAP,MAACM,OAAA,EAAK,OAAOC,QAAO,gBAClB;AAAA,wBAAAR,KAAC,qBAAkB,MAAK,SAAQ,OAAOU,KAAI,QAAQ;AAAA,QACnD,gBAAAV,KAACS,OAAA,EAAK,OAAOD,QAAO,aAAa,+BAAiB;AAAA,SACpD;AAAA,MAED,GAAIG,UAAS,OAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,CAAC;AAAA;AAAA,EACrD,GACF;AAEJ;AAEA,IAAMH,UAASI,YAAW,OAAO;AAAA,EAC/B,WAAW;AAAA,IACT,MAAM;AAAA,IACN,iBAAiBF,KAAI;AAAA,EACvB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,iBAAiBA,KAAI;AAAA,IACrB,mBAAmB;AAAA,EACrB;AAAA,EACA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,UAAU;AAAA,IACV,WAAW;AAAA,EACb;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,iBAAiBA,KAAI;AAAA,EACvB;AAAA,EACA,gBAAgB;AAAA,IACd,UAAU;AAAA,IACV,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,iBAAiBA,KAAI;AAAA,IACrB,KAAK;AAAA,EACP;AAAA,EACA,aAAa;AAAA,IACX,OAAOA,KAAI;AAAA,IACX,UAAU;AAAA,EACZ;AACF,CAAC;;;AD5HO,SACE,OAAAG,OADF,QAAAC,aAAA;AAjBR,IAAM,sBAAN,cAAkC,MAAM,UAGtC;AAAA,EAHF;AAAA;AAIE,iBAA4B,EAAE,UAAU,OAAO,OAAO,KAAK;AAAA;AAAA,EAE3D,OAAO,yBAAyB,OAAc;AAC5C,WAAO,EAAE,UAAU,MAAM,MAAM;AAAA,EACjC;AAAA,EAEA,kBAAkB,OAAc,MAAuB;AACrD,YAAQ,MAAM,WAAW,KAAK,MAAM,QAAQ,YAAY,OAAO,KAAK,cAAc;AAAA,EACpF;AAAA,EAEA,SAAS;AACP,QAAI,KAAK,MAAM,UAAU;AACvB,aACE,gBAAAA,MAACC,QAAA,EAAK,OAAOC,SAAO,OAClB;AAAA,wBAAAH,MAACI,OAAA,EAAK,OAAOD,SAAO,YAAY,0BAAY;AAAA,QAC5C,gBAAAH,MAACI,OAAA,EAAK,OAAOD,SAAO,cACjB,eAAK,MAAM,OAAO,WAAW,gCAChC;AAAA,QACA,gBAAAH;AAAA,UAACK;AAAA,UAAA;AAAA,YACC,OAAOF,SAAO;AAAA,YACd,SAAS,MAAM,KAAK,SAAS,EAAE,UAAU,OAAO,OAAO,KAAK,CAAC;AAAA,YAE7D,0BAAAH,MAACI,OAAA,EAAK,OAAOD,SAAO,WAAW,mBAAK;AAAA;AAAA,QACtC;AAAA,SACF;AAAA,IAEJ;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAgBe,SAAR,OAAwB,EAAE,IAAI,UAAU,kBAAkB,OAAO,YAAY,GAAwB;AAE1G,QAAM,EAAE,QAAQ,SAAS,OAAO,cAAc,IAAI,UAAU,IAAI,QAAQ;AAGxE,QAAM,cAAc,GAAG,WAAW,MAAM,IAAI,GAAG,MAAM,GAAG,EAAE,CAAC,IAAI,SAAS;AAGxE,QAAM,SAAS,gBAAgB,IAAI,aAAa,gBAAgB;AAChE,QAAM,SAAS,gBAAgB;AAC/B,QAAM,QAAQ,eAAe;AAG7B,QAAM,OAAO,QAAQ;AACrB,QAAM,UAAUG,SAAuB,OAAO;AAAA,IAC5C,QAAQC,WAAU;AAAA,IAClB,UAAU;AAAA,IACV;AAAA,IACA,MAAM,OAAO,EAAE,IAAI,KAAK,WAAW,IAAI,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,IAAI;AAAA,EAC9E,IAAI,CAAC,IAAI,aAAa,MAAM,SAAS,MAAM,IAAI,CAAC;AAMhD,QAAM,YAAY,UAAU,QAAQ;AACpC,MAAI,iBAAiB,WAAW;AAC9B,WAAO,gBAAAP,MAAC,yBAAsB,UAAU,IAAI,UAAoB,kBAAoC;AAAA,EACtG;AAEA,MAAI,SAAS;AACX,WACE,gBAAAC,MAACC,QAAA,EAAK,OAAOC,SAAO,QAClB;AAAA,sBAAAH,MAACQ,oBAAA,EAAkB,MAAK,SAAQ,OAAOC,QAAO,KAAK,QAAQ;AAAA,MAC3D,gBAAAT,MAACI,OAAA,EAAK,OAAOD,SAAO,aAAa,+BAAiB;AAAA,OACpD;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,QAAQ;AACpB,WACE,gBAAAH,MAACE,QAAA,EAAK,OAAOC,SAAO,QAClB,0BAAAH,MAACI,OAAA,EAAK,OAAOD,SAAO,cAAe,mBAAS,oBAAmB,GACjE;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI;AACtB,SACE,gBAAAH,MAAC,uBAAoB,UAAU,IAC7B,0BAAAA,MAAC,aAAU,QAAgB,QAAgB,OAAc,SAAkB,OAAO,aAAa,GACjG;AAEJ;AAIA,IAAMG,WAASO,aAAW,OAAO;AAAA,EAC/B,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,iBAAiBD,QAAO,KAAK;AAAA,IAC7B,mBAAmB;AAAA,IACnB,KAAK;AAAA,EACP;AAAA,EACA,aAAa;AAAA,IACX,OAAOA,QAAO,KAAK;AAAA,IACnB,UAAU;AAAA,EACZ;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,iBAAiBA,QAAO,KAAK;AAAA,IAC7B,mBAAmB;AAAA,IACnB,KAAK;AAAA,EACP;AAAA,EACA,YAAY;AAAA,IACV,OAAOA,QAAO,KAAK;AAAA,IACnB,UAAU;AAAA,IACV,YAAY;AAAA,EACd;AAAA,EACA,cAAc;AAAA,IACZ,OAAOA,QAAO,KAAK;AAAA,IACnB,UAAU;AAAA,IACV,WAAW;AAAA,EACb;AAAA,EACA,UAAU;AAAA,IACR,WAAW;AAAA,IACX,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,iBAAiBA,QAAO,KAAK;AAAA,IAC7B,cAAc;AAAA,EAChB;AAAA,EACA,WAAW;AAAA,IACT,OAAOA,QAAO,KAAK;AAAA,IACnB,UAAU;AAAA,IACV,YAAY;AAAA,EACd;AACF,CAAC;;;AGxKD,SAAS,aAAAE,YAAW,YAAAC,iBAAgB;AACpC,SAAS,QAAAC,QAAM,qBAAAC,0BAAyB;AACxC,SAAS,OAAAC,YAAW;AACpB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAyBH,gBAAAC,aAAA;AAbW,SAAR,cAA+B,EAAE,UAAU,SAAS,SAAS,MAAM,GAAU;AAClF,QAAM,CAAC,UAAU,WAAW,IAAIC,UAAgC,IAAI;AACpE,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAwB,IAAI;AAEtD,EAAAC,WAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,QAAQ,EACf,KAAK,OAAK;AAAE,UAAI,CAAC,UAAW,KAAI,YAAY,CAAC,IAAI,SAAS,kBAAkB;AAAA,IAAG,CAAC,EAChF,MAAM,OAAK;AAAE,UAAI,CAAC,UAAW,UAAS,GAAG,WAAW,gBAAgB;AAAA,IAAG,CAAC;AAC3E,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAM;AAAA,EACnC,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,OAAO,WACX,gBAAAF,MAAC,UAAO,IAAI,UAAU,UAA2B,OAAc,IAE/D,gBAAAA,MAACG,QAAA,EAAK,OAAO,EAAE,MAAM,GAAG,gBAAgB,UAAU,YAAY,UAAU,SAAS,GAAG,GACjF,kBAAQ,OAAO,gBAAAH,MAACI,oBAAA,EAAkB,OAAOC,KAAI,QAAQ,GACxD;AAGF,MAAI,QAAQ,SAAS,cAAc;AACjC,WACE,gBAAAL;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,SAAS;AAAA,QACT,QAAQ,QAAQ;AAAA,QAChB,cAAY;AAAA,QACZ,WAAU;AAAA,QACV,YAAW;AAAA,QAEV;AAAA;AAAA,IACH;AAAA,EAEJ;AAGA,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,QAAQ,SAAS;AAAA,MACxB,UAAU,QAAQ;AAAA,MAClB,SAAS,CAAC,CAAC,QAAQ;AAAA,MAElB;AAAA;AAAA,EACH;AAEJ;;;AC5DA,SAAS,WAAAM,UAAS,YAAAC,iBAAgB;AAClC;AAAA,EACE,SAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,aAAAC;AAAA,EACA,cAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AACP,SAAS,WAAAC,gBAAe;AACxB,SAAS,OAAAC,YAAW;AACpB,SAAS,iBAAAC,sBAAqB;AAgCtB,SAkBE,YAAAC,WAhBE,OAAAC,OAFJ,QAAAC,cAAA;AA9BR,IAAMC,WAAUP,UAAS,OAAO,QAAQ,mBAAmB;AAW5C,SAAR,cAA+B,EAAE,SAAS,OAAO,WAAW,QAAQ,GAAU;AACnF,QAAM,CAAC,SAAS,UAAU,IAAIP,UAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAwB,IAAI;AAGtD,QAAM,MAAMD,SAAQ,MAAM;AACxB,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI,gBAAgB,KAAK,SAAS,EAAG,QAAO;AAC5C,WAAO,GAAGW,eAAc,CAAC,GAAG,SAAS;AAAA,EACvC,GAAG,CAAC,SAAS,CAAC;AAEd,SACE,gBAAAE;AAAA,IAACX;AAAA,IAAA;AAAA,MACC;AAAA,MACA,eAAc;AAAA,MACd,mBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAEhB,0BAAAY,OAACX,QAAA,EAAK,OAAOa,SAAO,MAClB;AAAA,wBAAAF,OAACX,QAAA,EAAK,OAAOa,SAAO,KAClB;AAAA,0BAAAH,MAACR,YAAA,EAAU,SAAS,SAAS,SAAS,IAAI,OAAOW,SAAO,UACtD,0BAAAH,MAACT,OAAA,EAAK,OAAOY,SAAO,WAAY,oBAAI,GACtC;AAAA,UACA,gBAAAH,MAACT,OAAA,EAAK,OAAOY,SAAO,OAAO,eAAe,GAAI,mBAAS,UAAS;AAAA,UAEhE,gBAAAH,MAACV,QAAA,EAAK,OAAOa,SAAO,UAAU;AAAA,WAChC;AAAA,QACC,CAAC,MACA,gBAAAH,MAACV,QAAA,EAAK,OAAOa,SAAO,WAClB,0BAAAH,MAACT,OAAA,EAAK,OAAOY,SAAO,WAAW,kDAAoC,GACrE,IACE,QACF,gBAAAF,OAACX,QAAA,EAAK,OAAOa,SAAO,WAClB;AAAA,0BAAAH,MAACT,OAAA,EAAK,OAAOY,SAAO,WAAW,kCAAoB;AAAA,UACnD,gBAAAH,MAACT,OAAA,EAAK,OAAOY,SAAO,UAAW,iBAAM;AAAA,WACvC,IAEA,gBAAAF,OAAAF,WAAA,EACE;AAAA,0BAAAC;AAAA,YAACJ;AAAA,YAAA;AAAA,cACC,QAAQ,EAAE,IAAI;AAAA,cACd,OAAOO,SAAO;AAAA,cACd,aAAa,MAAM;AAAE,2BAAW,IAAI;AAAG,yBAAS,IAAI;AAAA,cAAG;AAAA,cACvD,WAAW,MAAM,WAAW,KAAK;AAAA,cACjC,SAAS,CAAC,MAAM;AACd,2BAAW,KAAK;AAChB,yBAAS,EAAE,YAAY,eAAe,eAAe;AAAA,cACvD;AAAA,cACA,aAAa,CAAC,MAAM;AAClB,2BAAW,KAAK;AAChB,yBAAS,QAAQ,EAAE,YAAY,UAAU,EAAE;AAAA,cAC7C;AAAA,cAIA,iBAAiB,CAAC,WAAW;AAAA,cAC7B,2BAA2B;AAAA;AAAA,UAC7B;AAAA,UACC,WACC,gBAAAH,MAACV,QAAA,EAAK,eAAc,QAAO,OAAOa,SAAO,SACvC,0BAAAH,MAACN,oBAAA,EAAkB,OAAOG,KAAI,QAAQ,GACxC;AAAA,WAEJ;AAAA,SAEJ;AAAA;AAAA,EACF;AAEJ;AAEA,IAAMM,WAASV,aAAW,OAAO;AAAA,EAC/B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,iBAAiBI,KAAI;AAAA,EACvB;AAAA,EACA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,mBAAmBA,KAAI;AAAA,IACvB,iBAAiBA,KAAI;AAAA,EACvB;AAAA,EACA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AAAA,EACA,WAAW;AAAA,IACT,UAAU;AAAA,IACV,OAAOA,KAAI;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAOA,KAAI;AAAA,IACX,YAAYK;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,iBAAiBL,KAAI;AAAA,EACvB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,EACrB;AAAA,EACA,WAAW;AAAA,IACT,UAAU;AAAA,IACV,OAAOA,KAAI;AAAA,IACX,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,OAAOA,KAAI;AAAA,IACX,WAAW;AAAA,EACb;AACF,CAAC;","names":["useCallback","useEffect","useRef","useState","Platform","StyleSheet","View","ada","useState","View","Text","Pressable","StyleSheet","Platform","ada","borderRadius","fontSize","getHaptics","View","Text","StyleSheet","ada","fontSize","jsx","jsxs","View","styles","Text","StyleSheet","ada","fontSize","View","Text","StyleSheet","Platform","ada","borderRadius","fontSize","jsx","jsxs","fontFam","styles","jsx","jsxs","fontFam","Platform","View","Text","StyleSheet","ada","useState","getHaptics","styles","Pressable","borderRadius","fontSize","View","StyleSheet","AdaOrb","jsx","styles","useState","useRef","useCallback","useEffect","View","Pressable","StyleSheet","Platform","borderRadius","getHaptics","jsx","jsxs","styles","View","Image","Pressable","Text","StyleSheet","getConfig","jsx","jsxs","Pressable","styles","Image","getConfig","View","Text","StyleSheet","jsx","jsxs","isSameDay","useRef","useState","useEffect","Platform","useCallback","View","styles","ada","StyleSheet","useMemo","View","Text","Pressable","ActivityIndicator","StyleSheet","getTenant","colors","useState","useMemo","useCallback","View","Text","StyleSheet","Platform","getConfig","getTenant","ada","useCallback","useEffect","useRef","pluginCommandEmitter","data","jsx","jsxs","useState","getTenant","useMemo","getConfig","useCallback","View","styles","Text","ada","Platform","StyleSheet","jsx","jsxs","View","styles","Text","Pressable","useMemo","getTenant","ActivityIndicator","colors","StyleSheet","useEffect","useState","View","ActivityIndicator","ada","jsx","useState","useEffect","View","ActivityIndicator","ada","useMemo","useState","Modal","View","Text","Pressable","StyleSheet","ActivityIndicator","Platform","WebView","ada","getCoreApiUrl","Fragment","jsx","jsxs","fontFam","styles"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@ateam-ai-mobile/ui",
3
+ "version": "0.1.0",
4
+ "description": "Prebuilt ADAS chat experience for React Native \u2014 full-screen chat, chips, widget views. Built on @ateam-ai-mobile/core + @ateam-ai-mobile/plugin-runtime.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "tsup"
10
+ },
11
+ "dependencies": {
12
+ "@ateam-ai-mobile/core": "0.1.0",
13
+ "@ateam-ai-mobile/plugin-runtime": "0.1.0",
14
+ "zustand": "^5.0.0"
15
+ },
16
+ "peerDependencies": {
17
+ "react": ">=18",
18
+ "react-native": "*",
19
+ "react-native-markdown-display": "*",
20
+ "react-native-reanimated": "*",
21
+ "react-native-svg": "*",
22
+ "react-native-webview": "*"
23
+ },
24
+ "module": "dist/index.mjs",
25
+ "source": "src/index.ts",
26
+ "react-native": "src/index.ts",
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * AdasChatScreen — the drop-in ADAS chat experience.
3
+ *
4
+ * Push it as a screen or mount it full-flex anywhere: message history with
5
+ * live agent progress, day separators, UI-action chips, table cards,
6
+ * attachment previews, pull-to-refresh, and the composer. Built ONLY on the
7
+ * public @ateam-ai-mobile/core + @ateam-ai-mobile/plugin-runtime APIs.
8
+ *
9
+ * The host must call initAdas() first, and typically:
10
+ * setUIActionNavigator(route => router.push(...)) // chips → plugin screen
11
+ * setupExpoProviders() // Expo hosts
12
+ *
13
+ * Also mount <SurfaceDispatcher /> once at the app root so engine-summoned
14
+ * plugin surfaces (drawers/fullscreens) can appear over the chat.
15
+ */
16
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
+ import {
18
+ FlatList,
19
+ KeyboardAvoidingView,
20
+ Platform,
21
+ RefreshControl,
22
+ StyleSheet,
23
+ View,
24
+ } from 'react-native';
25
+ import { ada, useChatStore, pluginCommandEmitter } from '@ateam-ai-mobile/core';
26
+ import type { Message } from '@ateam-ai-mobile/core';
27
+ import MessageBubble, { DaySeparator } from './chat/MessageBubble';
28
+ import TypingIndicator from './chat/TypingIndicator';
29
+ import ChatInput from './chat/ChatInput';
30
+ import Lightbox from './Lightbox';
31
+
32
+ export interface AdasChatScreenProps {
33
+ /** Called when the user taps the call button; omit to hide it. */
34
+ onCallStart?: () => void;
35
+ /** Composer placeholder. */
36
+ placeholder?: string;
37
+ /** Extra element rendered above the composer (e.g. suggestion chips). */
38
+ footerAccessory?: React.ReactNode;
39
+ /** Mount the fullscreen image viewer here. Default true — set false if
40
+ * the host already mounts <Lightbox /> at its root. */
41
+ withLightbox?: boolean;
42
+ }
43
+
44
+ function isSameDay(a: Date, b: Date): boolean {
45
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
46
+ }
47
+
48
+ export default function AdasChatScreen({
49
+ onCallStart,
50
+ placeholder,
51
+ footerAccessory,
52
+ withLightbox = true,
53
+ }: AdasChatScreenProps) {
54
+ const messages = useChatStore(s => s.messages);
55
+ const loading = useChatStore(s => s.loading);
56
+ const conversationLoaded = useChatStore(s => s.conversationLoaded);
57
+ const loadConversation = useChatStore(s => s.loadConversation);
58
+ const sendMessage = useChatStore(s => s.sendMessage);
59
+
60
+ const listRef = useRef<FlatList<Message>>(null);
61
+ const [refreshing, setRefreshing] = useState(false);
62
+ const [prefill, setPrefill] = useState('');
63
+
64
+ // Initial history load.
65
+ useEffect(() => {
66
+ if (!conversationLoaded) loadConversation();
67
+ }, [conversationLoaded, loadConversation]);
68
+
69
+ // bridge.prepareMessage(text) → pre-fill the composer without sending.
70
+ useEffect(() => {
71
+ const unsub = pluginCommandEmitter.on('prepare-chat-message', ({ text }: { text?: string }) => {
72
+ if (text) setPrefill(text);
73
+ });
74
+ return unsub;
75
+ }, []);
76
+
77
+ // Inverted list — newest at the bottom without scroll management. On WEB
78
+ // `inverted` is a scaleY(-1) transform that react-native-web applies to the
79
+ // container but not reliably to items — content renders upside-down. Use a
80
+ // normal list there and scroll to the end instead.
81
+ const useInverted = Platform.OS !== 'web';
82
+ const listData = useMemo(
83
+ () => (useInverted ? [...messages].reverse() : messages),
84
+ [messages, useInverted],
85
+ );
86
+
87
+ const scrollToLatest = useCallback((animated: boolean) => {
88
+ if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
89
+ else listRef.current?.scrollToEnd({ animated });
90
+ }, [useInverted]);
91
+
92
+ const handleSend = useCallback((text: string) => {
93
+ setPrefill('');
94
+ sendMessage(text);
95
+ scrollToLatest(true);
96
+ }, [sendMessage, scrollToLatest]);
97
+
98
+ const handleRefresh = useCallback(async () => {
99
+ setRefreshing(true);
100
+ try { await loadConversation(); } catch {}
101
+ setRefreshing(false);
102
+ }, [loadConversation]);
103
+
104
+ const renderItem = useCallback(({ item, index }: { item: Message; index: number }) => {
105
+ // The "previous" (older) message is the NEXT array item when inverted,
106
+ // the previous one otherwise.
107
+ const older = useInverted ? listData[index + 1] : listData[index - 1];
108
+ const showDaySep = !older || !isSameDay(older.timestamp, item.timestamp);
109
+ return (
110
+ <View>
111
+ {showDaySep && <DaySeparator date={item.timestamp} />}
112
+ <MessageBubble message={item} />
113
+ </View>
114
+ );
115
+ }, [listData, useInverted]);
116
+
117
+ const typingIndicator =
118
+ loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress
119
+ ? <TypingIndicator />
120
+ : null;
121
+
122
+ return (
123
+ <KeyboardAvoidingView
124
+ style={styles.root}
125
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
126
+ >
127
+ <FlatList
128
+ ref={listRef}
129
+ style={styles.list}
130
+ data={listData}
131
+ inverted={useInverted}
132
+ keyExtractor={m => m.id}
133
+ renderItem={renderItem}
134
+ // In an inverted list the header renders at the visual BOTTOM — where
135
+ // the typing indicator belongs; on web (non-inverted) it's the footer.
136
+ // Suppressed when the newest bubble already shows its progress pill.
137
+ ListHeaderComponent={useInverted ? typingIndicator : null}
138
+ ListFooterComponent={useInverted ? null : typingIndicator}
139
+ onContentSizeChange={useInverted ? undefined : () => scrollToLatest(false)}
140
+ refreshControl={
141
+ <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={ada.orange} />
142
+ }
143
+ keyboardShouldPersistTaps="handled"
144
+ contentContainerStyle={styles.listContent}
145
+ />
146
+ {footerAccessory}
147
+ <ChatInput
148
+ onSend={handleSend}
149
+ onCallStart={onCallStart}
150
+ loading={loading}
151
+ prefill={prefill}
152
+ placeholder={placeholder}
153
+ />
154
+ {withLightbox && <Lightbox />}
155
+ </KeyboardAvoidingView>
156
+ );
157
+ }
158
+
159
+ const styles = StyleSheet.create({
160
+ root: {
161
+ flex: 1,
162
+ backgroundColor: ada.paper,
163
+ },
164
+ list: {
165
+ flex: 1,
166
+ },
167
+ listContent: {
168
+ paddingVertical: 12,
169
+ },
170
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Fullscreen image viewer — mounted once inside AdasChatScreen (or at the
3
+ * host root). Any component opens it via useLightboxStore.getState().show(uri).
4
+ */
5
+ import { Modal, View, Image, Pressable, Text, StyleSheet } from 'react-native';
6
+ import { getConfig } from '@ateam-ai-mobile/core';
7
+ import { useLightboxStore } from './stores/lightboxStore';
8
+
9
+ export default function Lightbox() {
10
+ const uri = useLightboxStore(s => s.uri);
11
+ const hide = useLightboxStore(s => s.hide);
12
+
13
+ if (!uri) return null;
14
+
15
+ return (
16
+ <Modal visible transparent animationType="fade" onRequestClose={hide}>
17
+ <Pressable style={styles.backdrop} onPress={hide}>
18
+ <Image
19
+ source={{
20
+ uri,
21
+ ...(uri.startsWith('http') ? {
22
+ headers: { Authorization: `Bearer ${getConfig().tokenProvider.getToken()}` },
23
+ } : {}),
24
+ }}
25
+ style={styles.image}
26
+ resizeMode="contain"
27
+ />
28
+ <View style={styles.closeHint}>
29
+ <Text style={styles.closeHintText}>Tap to close</Text>
30
+ </View>
31
+ </Pressable>
32
+ </Modal>
33
+ );
34
+ }
35
+
36
+ const styles = StyleSheet.create({
37
+ backdrop: {
38
+ flex: 1,
39
+ backgroundColor: 'rgba(0,0,0,0.92)',
40
+ justifyContent: 'center',
41
+ },
42
+ image: {
43
+ width: '100%',
44
+ height: '85%',
45
+ },
46
+ closeHint: {
47
+ position: 'absolute',
48
+ bottom: 40,
49
+ alignSelf: 'center',
50
+ },
51
+ closeHintText: {
52
+ color: 'rgba(255,255,255,0.6)',
53
+ fontSize: 13,
54
+ },
55
+ });