@adminide-stack/yantra-mobile 12.0.49 → 12.0.50-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,8 @@ class NativeWebViewBridge {
16
16
  batchDelayMs: 100
17
17
  });
18
18
  __publicField(this, "listeners", /* @__PURE__ */ new Map());
19
+ __publicField(this, "disposed", false);
20
+ __publicField(this, "rafHandles", /* @__PURE__ */ new Set());
19
21
  this.webViewRef = webViewRef;
20
22
  }
21
23
  on(event, listener) {
@@ -113,6 +115,7 @@ class NativeWebViewBridge {
113
115
  this.sendMessage(response);
114
116
  }
115
117
  handleMessage(event) {
118
+ if (this.disposed) return;
116
119
  const rawData = event.nativeEvent.data;
117
120
  const data = typeof rawData === "string" ? rawData : JSON.stringify(rawData);
118
121
  try {
@@ -135,6 +138,9 @@ class NativeWebViewBridge {
135
138
  }
136
139
  }
137
140
  dispose() {
141
+ this.disposed = true;
142
+ this.rafHandles.forEach((handle) => cancelAnimationFrame(handle));
143
+ this.rafHandles.clear();
138
144
  this.flushBatch();
139
145
  this.pendingRequests.forEach((pending) => {
140
146
  clearTimeout(pending.timeout);
@@ -201,18 +207,25 @@ class NativeWebViewBridge {
201
207
  this.emitLocal("bridge:error", message.payload);
202
208
  }
203
209
  sendMessage(message) {
210
+ if (this.disposed) return;
204
211
  if (!this.isReady && message.action !== BRIDGE_READY_ACTION) {
205
212
  this.messageQueue.push(message);
206
213
  return;
207
214
  }
208
215
  this.debugger.logSent(message);
209
216
  const deliver = (serialized) => {
217
+ if (this.disposed) return;
210
218
  const post = () => {
211
219
  var _a;
220
+ if (this.disposed) return;
212
221
  (_a = this.webViewRef.current) == null ? void 0 : _a.postMessage(serialized);
213
222
  };
214
223
  if (Platform.OS === "ios") {
215
- requestAnimationFrame(post);
224
+ const handle = requestAnimationFrame(() => {
225
+ this.rafHandles.delete(handle);
226
+ post();
227
+ });
228
+ this.rafHandles.add(handle);
216
229
  } else {
217
230
  post();
218
231
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NativeWebViewBridge.js","sources":["../../src/bridge/NativeWebViewBridge.ts"],"sourcesContent":["import { Platform } from 'react-native';\nimport type { WebView, WebViewMessageEvent } from 'react-native-webview';\nimport type React from 'react';\nimport { CancellableRequest } from './CancellableRequest';\nimport { ChunkedMessageHandler } from './ChunkedMessageHandler';\nimport { BridgeDebugger } from './BridgeDebugger';\nimport {\n BRIDGE_PROTOCOL_VERSION,\n BRIDGE_READY_ACTION,\n DEFAULT_BRIDGE_TIMEOUT_MS,\n MessageType,\n type BridgeErrorPayload,\n type BridgeMessage,\n type BridgeResponse,\n} from './types';\n\ntype PendingRequest = {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timeout: ReturnType<typeof setTimeout>;\n};\n\ntype BatchConfig = {\n batchSize: number;\n batchDelayMs: number;\n};\n\ntype BridgeListener = (payload: any) => void;\n\n/**\n * Native-side type-safe WebView bridge with READY queue, timeouts,\n * cancellation, chunking, analytics batching, and iOS/Android quirks.\n */\nexport class NativeWebViewBridge {\n private webViewRef: React.RefObject<WebView | null>;\n\n private pendingRequests = new Map<string, PendingRequest>();\n\n private messageQueue: BridgeMessage[] = [];\n\n private isReady = false;\n\n private readonly chunker = new ChunkedMessageHandler();\n\n private readonly debugger = new BridgeDebugger();\n\n private batch: BridgeMessage[] = [];\n\n private batchTimeout?: ReturnType<typeof setTimeout>;\n\n private readonly batchConfig: BatchConfig = { batchSize: 10, batchDelayMs: 100 };\n\n private readonly listeners = new Map<string, Set<BridgeListener>>();\n\n constructor(webViewRef: React.RefObject<WebView | null>) {\n this.webViewRef = webViewRef;\n }\n\n on(event: string, listener: BridgeListener): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off(event: string, listener: BridgeListener): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n private emitLocal(event: string, payload?: unknown): void {\n const set = this.listeners.get(event);\n if (!set) return;\n set.forEach((listener) => {\n try {\n listener(payload);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error(`[Bridge] listener error for ${event}:`, error);\n }\n });\n }\n\n private removeAllListeners(): void {\n this.listeners.clear();\n }\n\n getDebugger(): BridgeDebugger {\n return this.debugger;\n }\n\n request<TRequest, TResponse>(\n action: string,\n payload: TRequest,\n timeoutMs = DEFAULT_BRIDGE_TIMEOUT_MS,\n ): CancellableRequest<TResponse> {\n const id = this.generateId();\n const message: BridgeMessage<TRequest> = {\n id,\n type: MessageType.REQUEST,\n action,\n payload,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const promise = new Promise<TResponse>((resolve, reject) => {\n timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request ${action} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timeout: timeoutId,\n });\n\n this.sendMessage(message);\n });\n\n const cancellable = new CancellableRequest<TResponse>(promise, () => {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.sendEvent('request.cancelled', { requestId: id });\n });\n cancellable.addCleanup(() => clearTimeout(timeoutId));\n\n return cancellable;\n }\n\n /** One-way event toward the WebView (batches analytics.* / metrics.*). */\n sendEvent(action: string, payload?: unknown): void {\n const message: BridgeMessage = {\n id: this.generateId(),\n type: MessageType.EVENT,\n action,\n payload,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n\n if (this.shouldBatch(action)) {\n this.addToBatch(message);\n return;\n }\n\n this.sendMessage(message);\n }\n\n sendResponse(id: string, success: boolean, data?: unknown, error?: BridgeErrorPayload): void {\n const response: BridgeMessage = {\n id,\n type: MessageType.RESPONSE,\n action: 'response',\n payload: {\n id,\n success,\n data,\n error,\n } satisfies BridgeResponse,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n this.sendMessage(response);\n }\n\n handleMessage(event: WebViewMessageEvent): void {\n // Android may send string; iOS sometimes surfaces an object.\n const rawData = event.nativeEvent.data;\n const data = typeof rawData === 'string' ? rawData : JSON.stringify(rawData);\n\n try {\n const parsed: unknown = JSON.parse(data);\n\n if (this.chunker.isChunkEnvelope(parsed)) {\n const complete = this.chunker.handleChunk(parsed);\n if (complete) this.dispatchMessage(complete);\n return;\n }\n\n this.dispatchMessage(parsed as BridgeMessage);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[Bridge] Failed to parse message:', error);\n }\n }\n\n onBridgeReady(): void {\n this.isReady = true;\n while (this.messageQueue.length > 0) {\n const message = this.messageQueue.shift();\n if (message) this.sendMessage(message);\n }\n }\n\n dispose(): void {\n this.flushBatch();\n this.pendingRequests.forEach((pending) => {\n clearTimeout(pending.timeout);\n pending.reject(new Error('Bridge disposed'));\n });\n this.pendingRequests.clear();\n this.messageQueue = [];\n this.isReady = false;\n this.removeAllListeners();\n }\n\n private dispatchMessage(message: BridgeMessage): void {\n this.debugger.logReceived(message);\n\n switch (message.type) {\n case MessageType.RESPONSE:\n this.handleResponse(message);\n break;\n case MessageType.REQUEST:\n this.handleRequest(message);\n break;\n case MessageType.EVENT:\n this.handleEvent(message);\n break;\n case MessageType.ERROR:\n this.handleError(message);\n break;\n default:\n break;\n }\n }\n\n private handleResponse(message: BridgeMessage): void {\n const payload = message.payload as BridgeResponse | undefined;\n const responseId = payload?.id ?? message.id;\n const pending = this.pendingRequests.get(responseId);\n if (!pending) return;\n\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(responseId);\n\n const success = payload?.success ?? true;\n if (success) {\n pending.resolve(payload?.data !== undefined ? payload.data : message.payload);\n } else {\n pending.reject(new Error(payload?.error?.message || 'Unknown error'));\n }\n }\n\n private handleRequest(message: BridgeMessage): void {\n this.emitLocal(`request:${message.action}`, message);\n }\n\n private handleEvent(message: BridgeMessage): void {\n if (message.action === BRIDGE_READY_ACTION) {\n this.onBridgeReady();\n }\n if (message.action === 'batch' && Array.isArray(message.payload)) {\n (message.payload as BridgeMessage[]).forEach((inner) => this.dispatchMessage(inner));\n return;\n }\n this.emitLocal(`event:${message.action}`, message.payload);\n }\n\n private handleError(message: BridgeMessage): void {\n const pending = this.pendingRequests.get(message.id);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(message.id);\n const err = message.payload as BridgeErrorPayload | undefined;\n pending.reject(new Error(err?.message || 'Bridge error'));\n }\n this.emitLocal('bridge:error', message.payload);\n }\n\n private sendMessage(message: BridgeMessage): void {\n if (!this.isReady && message.action !== BRIDGE_READY_ACTION) {\n this.messageQueue.push(message);\n return;\n }\n\n this.debugger.logSent(message);\n\n const deliver = (serialized: string) => {\n const post = () => {\n this.webViewRef.current?.postMessage(serialized);\n };\n // iOS WebView is more reliable when postMessage is deferred one frame.\n if (Platform.OS === 'ios') {\n requestAnimationFrame(post);\n } else {\n post();\n }\n };\n\n this.chunker.sendChunked(message, deliver);\n\n if (__DEV__) {\n // eslint-disable-next-line no-console\n console.log(`[Bridge] Sent: ${message.action}`, message);\n }\n }\n\n private shouldBatch(action: string): boolean {\n return action.startsWith('analytics.') || action.startsWith('metrics.');\n }\n\n private addToBatch(message: BridgeMessage): void {\n this.batch.push(message);\n if (this.batch.length >= this.batchConfig.batchSize) {\n this.flushBatch();\n } else if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => this.flushBatch(), this.batchConfig.batchDelayMs);\n }\n }\n\n private flushBatch(): void {\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n if (this.batch.length === 0) return;\n\n const batchMessage: BridgeMessage = {\n id: this.generateId(),\n type: MessageType.EVENT,\n action: 'batch',\n payload: this.batch,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n this.batch = [];\n this.sendMessage(batchMessage);\n }\n\n private generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NativeWebViewBridge.js","sources":["../../src/bridge/NativeWebViewBridge.ts"],"sourcesContent":["import { Platform } from 'react-native';\nimport type { WebView, WebViewMessageEvent } from 'react-native-webview';\nimport type React from 'react';\nimport { CancellableRequest } from './CancellableRequest';\nimport { ChunkedMessageHandler } from './ChunkedMessageHandler';\nimport { BridgeDebugger } from './BridgeDebugger';\nimport {\n BRIDGE_PROTOCOL_VERSION,\n BRIDGE_READY_ACTION,\n DEFAULT_BRIDGE_TIMEOUT_MS,\n MessageType,\n type BridgeErrorPayload,\n type BridgeMessage,\n type BridgeResponse,\n} from './types';\n\ntype PendingRequest = {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timeout: ReturnType<typeof setTimeout>;\n};\n\ntype BatchConfig = {\n batchSize: number;\n batchDelayMs: number;\n};\n\ntype BridgeListener = (payload: any) => void;\n\n/**\n * Native-side type-safe WebView bridge with READY queue, timeouts,\n * cancellation, chunking, analytics batching, and iOS/Android quirks.\n */\nexport class NativeWebViewBridge {\n private webViewRef: React.RefObject<WebView | null>;\n\n private pendingRequests = new Map<string, PendingRequest>();\n\n private messageQueue: BridgeMessage[] = [];\n\n private isReady = false;\n\n private readonly chunker = new ChunkedMessageHandler();\n\n private readonly debugger = new BridgeDebugger();\n\n private batch: BridgeMessage[] = [];\n\n private batchTimeout?: ReturnType<typeof setTimeout>;\n\n private readonly batchConfig: BatchConfig = { batchSize: 10, batchDelayMs: 100 };\n\n private readonly listeners = new Map<string, Set<BridgeListener>>();\n\n private disposed = false;\n\n private readonly rafHandles = new Set<number>();\n\n constructor(webViewRef: React.RefObject<WebView | null>) {\n this.webViewRef = webViewRef;\n }\n\n on(event: string, listener: BridgeListener): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off(event: string, listener: BridgeListener): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n private emitLocal(event: string, payload?: unknown): void {\n const set = this.listeners.get(event);\n if (!set) return;\n set.forEach((listener) => {\n try {\n listener(payload);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error(`[Bridge] listener error for ${event}:`, error);\n }\n });\n }\n\n private removeAllListeners(): void {\n this.listeners.clear();\n }\n\n getDebugger(): BridgeDebugger {\n return this.debugger;\n }\n\n request<TRequest, TResponse>(\n action: string,\n payload: TRequest,\n timeoutMs = DEFAULT_BRIDGE_TIMEOUT_MS,\n ): CancellableRequest<TResponse> {\n const id = this.generateId();\n const message: BridgeMessage<TRequest> = {\n id,\n type: MessageType.REQUEST,\n action,\n payload,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const promise = new Promise<TResponse>((resolve, reject) => {\n timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request ${action} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timeout: timeoutId,\n });\n\n this.sendMessage(message);\n });\n\n const cancellable = new CancellableRequest<TResponse>(promise, () => {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.sendEvent('request.cancelled', { requestId: id });\n });\n cancellable.addCleanup(() => clearTimeout(timeoutId));\n\n return cancellable;\n }\n\n /** One-way event toward the WebView (batches analytics.* / metrics.*). */\n sendEvent(action: string, payload?: unknown): void {\n const message: BridgeMessage = {\n id: this.generateId(),\n type: MessageType.EVENT,\n action,\n payload,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n\n if (this.shouldBatch(action)) {\n this.addToBatch(message);\n return;\n }\n\n this.sendMessage(message);\n }\n\n sendResponse(id: string, success: boolean, data?: unknown, error?: BridgeErrorPayload): void {\n const response: BridgeMessage = {\n id,\n type: MessageType.RESPONSE,\n action: 'response',\n payload: {\n id,\n success,\n data,\n error,\n } satisfies BridgeResponse,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n this.sendMessage(response);\n }\n\n handleMessage(event: WebViewMessageEvent): void {\n if (this.disposed) return;\n // Android may send string; iOS sometimes surfaces an object.\n const rawData = event.nativeEvent.data;\n const data = typeof rawData === 'string' ? rawData : JSON.stringify(rawData);\n\n try {\n const parsed: unknown = JSON.parse(data);\n\n if (this.chunker.isChunkEnvelope(parsed)) {\n const complete = this.chunker.handleChunk(parsed);\n if (complete) this.dispatchMessage(complete);\n return;\n }\n\n this.dispatchMessage(parsed as BridgeMessage);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[Bridge] Failed to parse message:', error);\n }\n }\n\n onBridgeReady(): void {\n this.isReady = true;\n while (this.messageQueue.length > 0) {\n const message = this.messageQueue.shift();\n if (message) this.sendMessage(message);\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.rafHandles.forEach((handle) => cancelAnimationFrame(handle));\n this.rafHandles.clear();\n this.flushBatch();\n this.pendingRequests.forEach((pending) => {\n clearTimeout(pending.timeout);\n pending.reject(new Error('Bridge disposed'));\n });\n this.pendingRequests.clear();\n this.messageQueue = [];\n this.isReady = false;\n this.removeAllListeners();\n }\n\n private dispatchMessage(message: BridgeMessage): void {\n this.debugger.logReceived(message);\n\n switch (message.type) {\n case MessageType.RESPONSE:\n this.handleResponse(message);\n break;\n case MessageType.REQUEST:\n this.handleRequest(message);\n break;\n case MessageType.EVENT:\n this.handleEvent(message);\n break;\n case MessageType.ERROR:\n this.handleError(message);\n break;\n default:\n break;\n }\n }\n\n private handleResponse(message: BridgeMessage): void {\n const payload = message.payload as BridgeResponse | undefined;\n const responseId = payload?.id ?? message.id;\n const pending = this.pendingRequests.get(responseId);\n if (!pending) return;\n\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(responseId);\n\n const success = payload?.success ?? true;\n if (success) {\n pending.resolve(payload?.data !== undefined ? payload.data : message.payload);\n } else {\n pending.reject(new Error(payload?.error?.message || 'Unknown error'));\n }\n }\n\n private handleRequest(message: BridgeMessage): void {\n this.emitLocal(`request:${message.action}`, message);\n }\n\n private handleEvent(message: BridgeMessage): void {\n if (message.action === BRIDGE_READY_ACTION) {\n this.onBridgeReady();\n }\n if (message.action === 'batch' && Array.isArray(message.payload)) {\n (message.payload as BridgeMessage[]).forEach((inner) => this.dispatchMessage(inner));\n return;\n }\n this.emitLocal(`event:${message.action}`, message.payload);\n }\n\n private handleError(message: BridgeMessage): void {\n const pending = this.pendingRequests.get(message.id);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(message.id);\n const err = message.payload as BridgeErrorPayload | undefined;\n pending.reject(new Error(err?.message || 'Bridge error'));\n }\n this.emitLocal('bridge:error', message.payload);\n }\n\n private sendMessage(message: BridgeMessage): void {\n if (this.disposed) return;\n if (!this.isReady && message.action !== BRIDGE_READY_ACTION) {\n this.messageQueue.push(message);\n return;\n }\n\n this.debugger.logSent(message);\n\n const deliver = (serialized: string) => {\n if (this.disposed) return;\n const post = () => {\n if (this.disposed) return;\n this.webViewRef.current?.postMessage(serialized);\n };\n // iOS WebView is more reliable when postMessage is deferred one frame.\n if (Platform.OS === 'ios') {\n const handle = requestAnimationFrame(() => {\n this.rafHandles.delete(handle);\n post();\n });\n this.rafHandles.add(handle);\n } else {\n post();\n }\n };\n\n this.chunker.sendChunked(message, deliver);\n\n if (__DEV__) {\n // eslint-disable-next-line no-console\n console.log(`[Bridge] Sent: ${message.action}`, message);\n }\n }\n\n private shouldBatch(action: string): boolean {\n return action.startsWith('analytics.') || action.startsWith('metrics.');\n }\n\n private addToBatch(message: BridgeMessage): void {\n this.batch.push(message);\n if (this.batch.length >= this.batchConfig.batchSize) {\n this.flushBatch();\n } else if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => this.flushBatch(), this.batchConfig.batchDelayMs);\n }\n }\n\n private flushBatch(): void {\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n if (this.batch.length === 0) return;\n\n const batchMessage: BridgeMessage = {\n id: this.generateId(),\n type: MessageType.EVENT,\n action: 'batch',\n payload: this.batch,\n timestamp: Date.now(),\n version: BRIDGE_PROTOCOL_VERSION,\n };\n this.batch = [];\n this.sendMessage(batchMessage);\n }\n\n private generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,36 +1,42 @@
1
- import {jsx}from'react/jsx-runtime';import {useEffect}from'react';import {Platform,StyleSheet}from'react-native';import Animated,{useAnimatedKeyboard,useSharedValue,useAnimatedStyle}from'react-native-reanimated';const OPEN_GAP = 8;
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {View,StyleSheet,Platform}from'react-native';import Animated,{useAnimatedKeyboard,useAnimatedStyle,KeyboardState}from'react-native-reanimated';const OPEN_GAP = 8;
2
+ function KeyboardAwareScreen({
3
+ children,
4
+ style
5
+ }) {
6
+ return /* @__PURE__ */ jsx(View, { style: [styles.flex, style], children });
7
+ }
2
8
  function KeyboardComposerDock({
3
9
  children,
4
- closedInset,
5
- keyboardVisible = false
10
+ closedInset
6
11
  }) {
7
12
  const keyboard = useAnimatedKeyboard();
8
13
  const closedPadding = Math.max(closedInset, OPEN_GAP);
9
- const visible = useSharedValue(keyboardVisible);
10
- useEffect(() => {
11
- visible.value = keyboardVisible;
12
- }, [keyboardVisible, visible]);
13
- const dockStyle = useAnimatedStyle(() => {
14
+ const spacerStyle = useAnimatedStyle(() => {
14
15
  if (Platform.OS === "android") {
15
- return {};
16
- }
17
- if (!visible.value) {
18
16
  return {
19
- paddingBottom: closedPadding
17
+ height: 0
20
18
  };
21
19
  }
20
+ const height = keyboard.state.value === KeyboardState.CLOSED ? 0 : keyboard.height.value;
21
+ const lift = height > 0 ? Math.max(0, height - closedPadding + OPEN_GAP) : 0;
22
22
  return {
23
- paddingBottom: Math.max(keyboard.height.value, closedPadding)
23
+ height: lift
24
24
  };
25
25
  });
26
- return /* @__PURE__ */ jsx(Animated.View, { style: [styles.dock, Platform.OS === "android" ? {
27
- paddingBottom: keyboardVisible ? OPEN_GAP : closedPadding
28
- } : null, dockStyle], children });
26
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(View, { style: [styles.dock, {
28
+ paddingBottom: closedPadding
29
+ }], children }),
30
+ Platform.OS === "ios" ? /* @__PURE__ */ jsx(Animated.View, { style: spacerStyle }) : null
31
+ ] });
29
32
  }
30
33
  const styles = StyleSheet.create({
34
+ flex: {
35
+ flex: 1
36
+ },
31
37
  dock: {
32
38
  width: "100%",
33
39
  alignItems: "center",
34
40
  zIndex: 20
35
41
  }
36
- });export{KeyboardComposerDock};//# sourceMappingURL=KeyboardComposerDock.js.map
42
+ });export{KeyboardAwareScreen,KeyboardComposerDock};//# sourceMappingURL=KeyboardComposerDock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"KeyboardComposerDock.js","sources":["../../src/components/KeyboardComposerDock.tsx"],"sourcesContent":["import React, { useEffect } from 'react';\nimport { Platform, StyleSheet } from 'react-native';\nimport Animated, { useAnimatedKeyboard, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';\n\nconst OPEN_GAP = 8;\n\nexport type KeyboardComposerDockProps = {\n children: React.ReactNode;\n /** Home-indicator inset applied when the keyboard is closed. */\n closedInset: number;\n /** Android resize already lifts the window; only used to drop closed inset. */\n keyboardVisible?: boolean;\n};\n\n/**\n * Pins the composer above the keyboard using Reanimated (works in Expo Go).\n * Android `adjustResize` already shrinks the window, so extra iOS padding is skipped there.\n *\n * When JS reports the keyboard closed, ignore a stale animated height so the\n * composer does not float a full bar above the home indicator.\n */\nexport function KeyboardComposerDock({ children, closedInset, keyboardVisible = false }: KeyboardComposerDockProps) {\n const keyboard = useAnimatedKeyboard();\n const closedPadding = Math.max(closedInset, OPEN_GAP);\n const visible = useSharedValue(keyboardVisible);\n useEffect(() => {\n visible.value = keyboardVisible;\n }, [keyboardVisible, visible]);\n const dockStyle = useAnimatedStyle(() => {\n if (Platform.OS === 'android') {\n return {};\n }\n if (!visible.value) {\n return { paddingBottom: closedPadding };\n }\n return {\n paddingBottom: Math.max(keyboard.height.value, closedPadding),\n };\n });\n\n return (\n <Animated.View\n style={[\n styles.dock,\n Platform.OS === 'android' ? { paddingBottom: keyboardVisible ? OPEN_GAP : closedPadding } : null,\n dockStyle,\n ]}\n >\n {children}\n </Animated.View>\n );\n}\n\nconst styles = StyleSheet.create({\n dock: {\n width: '100%',\n alignItems: 'center',\n zIndex: 20,\n },\n});\n"],"names":[],"mappings":"oNAGA,MAAM,QAAW,GAAA,CAAA;AAgBV,SAAS,oBAAqB,CAAA;AAAA,EACnC,QAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAkB,GAAA;AACpB,CAA8B,EAAA;AAC5B,EAAA,MAAM,WAAW,mBAAoB,EAAA;AACrC,EAAA,MAAM,aAAgB,GAAA,IAAA,CAAK,GAAI,CAAA,WAAA,EAAa,QAAQ,CAAA;AACpD,EAAM,MAAA,OAAA,GAAU,eAAe,eAAe,CAAA;AAC9C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAA,CAAQ,KAAQ,GAAA,eAAA;AAAA,GACf,EAAA,CAAC,eAAiB,EAAA,OAAO,CAAC,CAAA;AAC7B,EAAM,MAAA,SAAA,GAAY,iBAAiB,MAAM;AACvC,IAAI,IAAA,QAAA,CAAS,OAAO,SAAW,EAAA;AAC7B,MAAA,OAAO,EAAC;AAAA;AAEV,IAAI,IAAA,CAAC,QAAQ,KAAO,EAAA;AAClB,MAAO,OAAA;AAAA,QACL,aAAe,EAAA;AAAA,OACjB;AAAA;AAEF,IAAO,OAAA;AAAA,MACL,eAAe,IAAK,CAAA,GAAA,CAAI,QAAS,CAAA,MAAA,CAAO,OAAO,aAAa;AAAA,KAC9D;AAAA,GACD,CAAA;AACD,EAAO,uBAAA,GAAA,CAAC,QAAS,CAAA,IAAA,EAAT,EAAc,KAAA,EAAO,CAAC,MAAO,CAAA,IAAA,EAAM,QAAS,CAAA,EAAA,KAAO,SAAY,GAAA;AAAA,IACrE,aAAA,EAAe,kBAAkB,QAAW,GAAA;AAAA,GAC1C,GAAA,IAAA,EAAM,SAAS,CAAA,EACR,QACL,EAAA,CAAA;AACR;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,IAAM,EAAA;AAAA,IACJ,KAAO,EAAA,MAAA;AAAA,IACP,UAAY,EAAA,QAAA;AAAA,IACZ,MAAQ,EAAA;AAAA;AAEZ,CAAC,CAAA"}
1
+ {"version":3,"file":"KeyboardComposerDock.js","sources":["../../src/components/KeyboardComposerDock.tsx"],"sourcesContent":["import React from 'react';\nimport { Platform, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';\nimport Animated, { KeyboardState, useAnimatedKeyboard, useAnimatedStyle } from 'react-native-reanimated';\n\nconst OPEN_GAP = 8;\n\nexport type KeyboardComposerDockProps = {\n children: React.ReactNode;\n /** Home-indicator inset applied when the keyboard is closed. */\n closedInset: number;\n /** Kept for call-site compatibility; lift is not gated on JS keyboard state. */\n keyboardVisible?: boolean;\n};\n\nexport function KeyboardAwareScreen({ children, style }: { children: React.ReactNode; style?: StyleProp<ViewStyle> }) {\n return <View style={[styles.flex, style]}>{children}</View>;\n}\n\n/**\n * Pins the composer above the keyboard in Expo Go and TestFlight.\n *\n * The TextInput lives in a dock with a *static* home-indicator padding. A\n * separate spacer below it tracks `useAnimatedKeyboard()` height, so the\n * field slides as a unit instead of being relaid out every frame (the blink).\n * Android `adjustResize` already shrinks the window, so the spacer is skipped.\n */\nexport function KeyboardComposerDock({ children, closedInset }: KeyboardComposerDockProps) {\n const keyboard = useAnimatedKeyboard();\n const closedPadding = Math.max(closedInset, OPEN_GAP);\n const spacerStyle = useAnimatedStyle(() => {\n if (Platform.OS === 'android') {\n return { height: 0 };\n }\n const height = keyboard.state.value === KeyboardState.CLOSED ? 0 : keyboard.height.value;\n const lift = height > 0 ? Math.max(0, height - closedPadding + OPEN_GAP) : 0;\n return { height: lift };\n });\n\n return (\n <>\n <View style={[styles.dock, { paddingBottom: closedPadding }]}>{children}</View>\n {Platform.OS === 'ios' ? <Animated.View style={spacerStyle} /> : null}\n </>\n );\n}\n\nconst styles = StyleSheet.create({\n flex: {\n flex: 1,\n },\n dock: {\n width: '100%',\n alignItems: 'center',\n zIndex: 20,\n },\n});\n"],"names":[],"mappings":"wMAGA,MAAM,QAAW,GAAA,CAAA;AAQV,SAAS,mBAAoB,CAAA;AAAA,EAClC,QAAA;AAAA,EACA;AACF,CAGG,EAAA;AACD,EAAO,uBAAA,GAAA,CAAC,QAAK,KAAO,EAAA,CAAC,OAAO,IAAM,EAAA,KAAK,GAAI,QAAS,EAAA,CAAA;AACtD;AAUO,SAAS,oBAAqB,CAAA;AAAA,EACnC,QAAA;AAAA,EACA;AACF,CAA8B,EAAA;AAC5B,EAAA,MAAM,WAAW,mBAAoB,EAAA;AACrC,EAAA,MAAM,aAAgB,GAAA,IAAA,CAAK,GAAI,CAAA,WAAA,EAAa,QAAQ,CAAA;AACpD,EAAM,MAAA,WAAA,GAAc,iBAAiB,MAAM;AACzC,IAAI,IAAA,QAAA,CAAS,OAAO,SAAW,EAAA;AAC7B,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA;AAAA,OACV;AAAA;AAEF,IAAM,MAAA,MAAA,GAAS,SAAS,KAAM,CAAA,KAAA,KAAU,cAAc,MAAS,GAAA,CAAA,GAAI,SAAS,MAAO,CAAA,KAAA;AACnF,IAAM,MAAA,IAAA,GAAO,SAAS,CAAI,GAAA,IAAA,CAAK,IAAI,CAAG,EAAA,MAAA,GAAS,aAAgB,GAAA,QAAQ,CAAI,GAAA,CAAA;AAC3E,IAAO,OAAA;AAAA,MACL,MAAQ,EAAA;AAAA,KACV;AAAA,GACD,CAAA;AACD,EAAA,uBACU,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,IAAK,EAAA,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,IAAM,EAAA;AAAA,MACjC,aAAe,EAAA;AAAA,KAChB,GAAI,QAAS,EAAA,CAAA;AAAA,IACL,QAAA,CAAS,OAAO,KAAQ,mBAAA,GAAA,CAAC,SAAS,IAAT,EAAA,EAAc,KAAO,EAAA,WAAA,EAAa,CAAK,GAAA;AAAA,GACrE,EAAA,CAAA;AACR;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,IAAM,EAAA;AAAA,IACJ,IAAM,EAAA;AAAA,GACR;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,KAAO,EAAA,MAAA;AAAA,IACP,UAAY,EAAA,QAAA;AAAA,IACZ,MAAQ,EAAA;AAAA;AAEZ,CAAC,CAAA"}
@@ -54,6 +54,15 @@ function AppsCatalog({
54
54
  channelId,
55
55
  appTitle: app.label
56
56
  };
57
+ if (app.nativeRoute === "characters-native") {
58
+ navigation.dispatch(CommonActions.navigate({
59
+ name: "MainStack.CharactersNative",
60
+ params: __spreadProps(__spreadValues({}, params), {
61
+ manifestUrl: config.CHARACTERS_NATIVE_MANIFEST_URL
62
+ })
63
+ }));
64
+ return;
65
+ }
57
66
  if (app.nativeRoute === "canvas-native") {
58
67
  navigation.dispatch(CommonActions.navigate({
59
68
  name: "MainStack.CanvasNative",
@@ -1 +1 @@
1
- {"version":3,"file":"AppsCatalog.js","sources":["../../../src/features/apps/AppsCatalog.tsx"],"sourcesContent":["/**\n * Full-screen Apps catalog: search + grouped tiles, then navigate into a\n * native surface or a new chat. Shared by the Apps screen (not a popup).\n */\nimport React, { useCallback, useMemo, useState } from 'react';\nimport { Pressable, ScrollView, StyleSheet, Text, TextInput, View, useColorScheme } from 'react-native';\nimport { CommonActions, useNavigation, useRoute } from '@react-navigation/native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { v4 as uuidv4 } from 'uuid';\nimport { config } from '../../config/env-config';\nimport { mobileTokens } from '../../theme/mobileTokens';\nimport { useChatMutations } from '../../hooks/useChatApi';\nimport { resolveSurfaceUrl } from './resolveSurfaceUrl';\nimport { groupMobileApps, MOBILE_APP_CATALOG, type MobileAppItem } from './mobileAppCatalog';\n\nfunction buildChannelTitle(channelId: string): string {\n return `chat-channel-${channelId}`;\n}\n\nexport function AppsCatalog({ orgName: orgNameProp }: { orgName?: string | null }) {\n const navigation = useNavigation<any>();\n const route = useRoute<any>();\n const colorScheme = useColorScheme();\n const isDark = colorScheme === 'dark';\n const { createChannel } = useChatMutations();\n const [query, setQuery] = useState('');\n\n const orgName = useMemo(() => {\n if (typeof orgNameProp === 'string' && orgNameProp.trim()) return orgNameProp.trim();\n const raw = (route?.params?.orgName as string | undefined) ?? '';\n return typeof raw === 'string' && raw.trim() ? raw.trim() : null;\n }, [orgNameProp, route?.params?.orgName]);\n\n const grouped = useMemo(() => groupMobileApps(MOBILE_APP_CATALOG, query), [query]);\n\n const muted = isDark ? '#94a3b8' : mobileTokens.color.textMuted;\n const foreground = isDark ? '#e2e8f0' : mobileTokens.color.text;\n const surface = isDark ? '#111827' : mobileTokens.color.surface;\n const screenBg = isDark ? '#0f172a' : mobileTokens.color.bg;\n const borderColor = isDark ? 'rgba(148,163,184,0.22)' : mobileTokens.color.border;\n const fieldBg = isDark ? '#1e293b' : mobileTokens.color.surfaceMuted;\n const accent = isDark ? '#a5b4fc' : mobileTokens.color.primary;\n const accentSoft = isDark ? 'rgba(99,102,241,0.18)' : mobileTokens.color.primarySoft;\n\n const openApp = useCallback(\n (app: MobileAppItem) => {\n const channelId = uuidv4();\n const params: Record<string, unknown> = {\n orgName: orgName ?? undefined,\n channelId,\n appTitle: app.label,\n };\n\n if (app.nativeRoute === 'canvas-native') {\n // Web canvas opens the boards grid first. Do not mint a chat\n // channelId here — that id is not an index row, and passing it\n // looks like a deep link into a board that does not exist.\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.CanvasNative',\n params: { orgName: orgName ?? undefined, appTitle: app.label },\n }),\n );\n return;\n }\n\n if (app.nativeRoute === 'webbuilder') {\n const surfaceUrl = resolveSurfaceUrl(app, config);\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.WebBuilder',\n params: surfaceUrl ? { ...params, url: surfaceUrl } : params,\n }),\n );\n return;\n }\n\n void createChannel({\n id: channelId,\n title: buildChannelTitle(channelId),\n isShared: false,\n sharedSlug: null,\n isArchived: false,\n isPinned: false,\n model: null,\n systemPrompt: null,\n orgName,\n } as any).catch((err) => {\n console.error('[AppsCatalog] createChannel failed:', err);\n });\n\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.Chat',\n params: {\n ...params,\n initialQuery: app.prompt,\n initialMode: 'chat',\n },\n }),\n );\n },\n [createChannel, navigation, orgName],\n );\n\n return (\n <View style={[styles.root, { backgroundColor: screenBg }]}>\n <View style={[styles.searchWrap, { backgroundColor: fieldBg, borderColor }]}>\n <MaterialCommunityIcons name=\"magnify\" size={18} color={muted} />\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder=\"Search apps\"\n placeholderTextColor={muted}\n autoCorrect={false}\n autoCapitalize=\"none\"\n style={[styles.searchInput, { color: foreground }]}\n />\n </View>\n <ScrollView keyboardShouldPersistTaps=\"handled\" contentContainerStyle={styles.list}>\n {grouped.length === 0 ? (\n <Text style={[styles.empty, { color: muted }]}>No apps match that search.</Text>\n ) : (\n grouped.map((group) => (\n <View key={group.label} style={styles.group}>\n <Text style={[styles.groupLabel, { color: muted }]}>{group.label}</Text>\n <View style={styles.groupGrid}>\n {group.items.map((app) => (\n <Pressable\n key={app.key}\n onPress={() => openApp(app)}\n accessibilityRole=\"button\"\n accessibilityLabel={app.label}\n style={({ pressed }) => [\n styles.appTile,\n {\n backgroundColor: surface,\n borderColor,\n },\n pressed && { backgroundColor: accentSoft },\n ]}\n >\n <View style={[styles.appIcon, { backgroundColor: accentSoft }]}>\n <MaterialCommunityIcons name={app.icon} size={18} color={accent} />\n </View>\n <Text style={[styles.appLabel, { color: foreground }]} numberOfLines={2}>\n {app.label}\n </Text>\n </Pressable>\n ))}\n </View>\n </View>\n ))\n )}\n </ScrollView>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n root: {\n flex: 1,\n },\n searchWrap: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n marginHorizontal: 16,\n marginTop: 12,\n marginBottom: 8,\n borderWidth: 1,\n borderRadius: 12,\n paddingHorizontal: 12,\n minHeight: 42,\n },\n searchInput: {\n flex: 1,\n fontSize: 15,\n paddingVertical: 8,\n },\n list: {\n paddingHorizontal: 16,\n paddingTop: 8,\n paddingBottom: 28,\n gap: 16,\n },\n group: {\n gap: 8,\n },\n groupLabel: {\n fontSize: 11,\n fontWeight: '600',\n letterSpacing: 0.7,\n textTransform: 'uppercase',\n },\n groupGrid: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 8,\n },\n appTile: {\n width: '31%',\n flexGrow: 1,\n minWidth: 96,\n maxWidth: '32.5%',\n borderWidth: 1,\n borderRadius: 14,\n paddingHorizontal: 10,\n paddingVertical: 12,\n gap: 8,\n },\n appIcon: {\n width: 32,\n height: 32,\n borderRadius: 10,\n alignItems: 'center',\n justifyContent: 'center',\n },\n appLabel: {\n fontSize: 13,\n fontWeight: '600',\n lineHeight: 17,\n },\n empty: {\n textAlign: 'center',\n paddingVertical: 28,\n fontSize: 14,\n },\n});\n"],"names":["_a","uuidv4"],"mappings":"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"}
1
+ {"version":3,"file":"AppsCatalog.js","sources":["../../../src/features/apps/AppsCatalog.tsx"],"sourcesContent":["/**\n * Full-screen Apps catalog: search + grouped tiles, then navigate into a\n * native surface or a new chat. Shared by the Apps screen (not a popup).\n */\nimport React, { useCallback, useMemo, useState } from 'react';\nimport { Pressable, ScrollView, StyleSheet, Text, TextInput, View, useColorScheme } from 'react-native';\nimport { CommonActions, useNavigation, useRoute } from '@react-navigation/native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { v4 as uuidv4 } from 'uuid';\nimport { config } from '../../config/env-config';\nimport { mobileTokens } from '../../theme/mobileTokens';\nimport { useChatMutations } from '../../hooks/useChatApi';\nimport { resolveSurfaceUrl } from './resolveSurfaceUrl';\nimport { groupMobileApps, MOBILE_APP_CATALOG, type MobileAppItem } from './mobileAppCatalog';\n\nfunction buildChannelTitle(channelId: string): string {\n return `chat-channel-${channelId}`;\n}\n\nexport function AppsCatalog({ orgName: orgNameProp }: { orgName?: string | null }) {\n const navigation = useNavigation<any>();\n const route = useRoute<any>();\n const colorScheme = useColorScheme();\n const isDark = colorScheme === 'dark';\n const { createChannel } = useChatMutations();\n const [query, setQuery] = useState('');\n\n const orgName = useMemo(() => {\n if (typeof orgNameProp === 'string' && orgNameProp.trim()) return orgNameProp.trim();\n const raw = (route?.params?.orgName as string | undefined) ?? '';\n return typeof raw === 'string' && raw.trim() ? raw.trim() : null;\n }, [orgNameProp, route?.params?.orgName]);\n\n const grouped = useMemo(() => groupMobileApps(MOBILE_APP_CATALOG, query), [query]);\n\n const muted = isDark ? '#94a3b8' : mobileTokens.color.textMuted;\n const foreground = isDark ? '#e2e8f0' : mobileTokens.color.text;\n const surface = isDark ? '#111827' : mobileTokens.color.surface;\n const screenBg = isDark ? '#0f172a' : mobileTokens.color.bg;\n const borderColor = isDark ? 'rgba(148,163,184,0.22)' : mobileTokens.color.border;\n const fieldBg = isDark ? '#1e293b' : mobileTokens.color.surfaceMuted;\n const accent = isDark ? '#a5b4fc' : mobileTokens.color.primary;\n const accentSoft = isDark ? 'rgba(99,102,241,0.18)' : mobileTokens.color.primarySoft;\n\n const openApp = useCallback(\n (app: MobileAppItem) => {\n const channelId = uuidv4();\n const params: Record<string, unknown> = {\n orgName: orgName ?? undefined,\n channelId,\n appTitle: app.label,\n };\n\n if (app.nativeRoute === 'characters-native') {\n // Same wiring as AppsMenu: the tile opens the native character\n // studio, never a chat with the tile's prompt.\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.CharactersNative',\n params: {\n ...params,\n manifestUrl: config.CHARACTERS_NATIVE_MANIFEST_URL,\n },\n }),\n );\n return;\n }\n\n if (app.nativeRoute === 'canvas-native') {\n // Web canvas opens the boards grid first. Do not mint a chat\n // channelId here — that id is not an index row, and passing it\n // looks like a deep link into a board that does not exist.\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.CanvasNative',\n params: { orgName: orgName ?? undefined, appTitle: app.label },\n }),\n );\n return;\n }\n\n if (app.nativeRoute === 'webbuilder') {\n const surfaceUrl = resolveSurfaceUrl(app, config);\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.WebBuilder',\n params: surfaceUrl ? { ...params, url: surfaceUrl } : params,\n }),\n );\n return;\n }\n\n void createChannel({\n id: channelId,\n title: buildChannelTitle(channelId),\n isShared: false,\n sharedSlug: null,\n isArchived: false,\n isPinned: false,\n model: null,\n systemPrompt: null,\n orgName,\n } as any).catch((err) => {\n console.error('[AppsCatalog] createChannel failed:', err);\n });\n\n navigation.dispatch(\n CommonActions.navigate({\n name: 'MainStack.Chat',\n params: {\n ...params,\n initialQuery: app.prompt,\n initialMode: 'chat',\n },\n }),\n );\n },\n [createChannel, navigation, orgName],\n );\n\n return (\n <View style={[styles.root, { backgroundColor: screenBg }]}>\n <View style={[styles.searchWrap, { backgroundColor: fieldBg, borderColor }]}>\n <MaterialCommunityIcons name=\"magnify\" size={18} color={muted} />\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder=\"Search apps\"\n placeholderTextColor={muted}\n autoCorrect={false}\n autoCapitalize=\"none\"\n style={[styles.searchInput, { color: foreground }]}\n />\n </View>\n <ScrollView keyboardShouldPersistTaps=\"handled\" contentContainerStyle={styles.list}>\n {grouped.length === 0 ? (\n <Text style={[styles.empty, { color: muted }]}>No apps match that search.</Text>\n ) : (\n grouped.map((group) => (\n <View key={group.label} style={styles.group}>\n <Text style={[styles.groupLabel, { color: muted }]}>{group.label}</Text>\n <View style={styles.groupGrid}>\n {group.items.map((app) => (\n <Pressable\n key={app.key}\n onPress={() => openApp(app)}\n accessibilityRole=\"button\"\n accessibilityLabel={app.label}\n style={({ pressed }) => [\n styles.appTile,\n {\n backgroundColor: surface,\n borderColor,\n },\n pressed && { backgroundColor: accentSoft },\n ]}\n >\n <View style={[styles.appIcon, { backgroundColor: accentSoft }]}>\n <MaterialCommunityIcons name={app.icon} size={18} color={accent} />\n </View>\n <Text style={[styles.appLabel, { color: foreground }]} numberOfLines={2}>\n {app.label}\n </Text>\n </Pressable>\n ))}\n </View>\n </View>\n ))\n )}\n </ScrollView>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n root: {\n flex: 1,\n },\n searchWrap: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n marginHorizontal: 16,\n marginTop: 12,\n marginBottom: 8,\n borderWidth: 1,\n borderRadius: 12,\n paddingHorizontal: 12,\n minHeight: 42,\n },\n searchInput: {\n flex: 1,\n fontSize: 15,\n paddingVertical: 8,\n },\n list: {\n paddingHorizontal: 16,\n paddingTop: 8,\n paddingBottom: 28,\n gap: 16,\n },\n group: {\n gap: 8,\n },\n groupLabel: {\n fontSize: 11,\n fontWeight: '600',\n letterSpacing: 0.7,\n textTransform: 'uppercase',\n },\n groupGrid: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 8,\n },\n appTile: {\n width: '31%',\n flexGrow: 1,\n minWidth: 96,\n maxWidth: '32.5%',\n borderWidth: 1,\n borderRadius: 14,\n paddingHorizontal: 10,\n paddingVertical: 12,\n gap: 8,\n },\n appIcon: {\n width: 32,\n height: 32,\n borderRadius: 10,\n alignItems: 'center',\n justifyContent: 'center',\n },\n appLabel: {\n fontSize: 13,\n fontWeight: '600',\n lineHeight: 17,\n },\n empty: {\n textAlign: 'center',\n paddingVertical: 28,\n fontSize: 14,\n },\n});\n"],"names":["_a","uuidv4"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsx,jsxs}from'react/jsx-runtime';import {useRef,useMemo,useCallback,useEffect,memo}from'react';import {FlatList,StyleSheet,View,Text}from'react-native';import Markdown from'react-native-markdown-display';import {shouldRenderUserTranscriptTurn,displayUserMessageText}from'../attachments/displayUserMessageText.js';import {MessageAttachmentPreviews}from'../attachments/MessageAttachmentPreviews.js';var __defProp = Object.defineProperty;
1
+ import {jsx,jsxs}from'react/jsx-runtime';import {useRef,useMemo,useCallback,useEffect,memo}from'react';import {Keyboard,FlatList,StyleSheet,View,Text}from'react-native';import Markdown from'react-native-markdown-display';import {shouldRenderUserTranscriptTurn,displayUserMessageText}from'../attachments/displayUserMessageText.js';import {MessageAttachmentPreviews}from'../attachments/MessageAttachmentPreviews.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -23,8 +23,7 @@ function ChatTranscript({
23
23
  streamingContent,
24
24
  renderMessageActions,
25
25
  listContentStyle,
26
- isDark = false,
27
- keyboardVisible = false
26
+ isDark = false
28
27
  }) {
29
28
  const listRef = useRef(null);
30
29
  const stickToBottomRef = useRef(true);
@@ -56,9 +55,12 @@ function ChatTranscript({
56
55
  return () => clearTimeout(t);
57
56
  }, [rows.length, streamingContent == null ? void 0 : streamingContent.length, scrollToLatest]);
58
57
  useEffect(() => {
59
- if (!stickToBottomRef.current) return;
60
- scrollToLatest(false);
61
- }, [keyboardVisible, scrollToLatest]);
58
+ const show = Keyboard.addListener("keyboardDidShow", () => {
59
+ if (!stickToBottomRef.current) return;
60
+ scrollToLatest(true);
61
+ });
62
+ return () => show.remove();
63
+ }, [scrollToLatest]);
62
64
  const handleScroll = useCallback((e) => {
63
65
  const {
64
66
  contentOffset,
@@ -79,7 +81,7 @@ function ChatTranscript({
79
81
  const renderItem = useCallback(({
80
82
  item
81
83
  }) => /* @__PURE__ */ jsx(TranscriptRowView, { item, isDark, renderMessageActions: item.streaming ? void 0 : renderMessageActions }), [isDark, renderMessageActions]);
82
- return /* @__PURE__ */ jsx(FlatList, { ref: listRef, data: rows, keyExtractor: (item) => item.id, renderItem, extraData: `${isDark}:${streamingContent != null ? streamingContent : ""}`, onScroll: handleScroll, onContentSizeChange: handleContentSizeChange, onLayout: () => {
84
+ return /* @__PURE__ */ jsx(FlatList, { ref: listRef, data: rows, keyExtractor: (item) => item.id, renderItem, extraData: streamingContent, initialNumToRender: 12, maxToRenderPerBatch: 8, updateCellsBatchingPeriod: 50, windowSize: 7, onScroll: handleScroll, onContentSizeChange: handleContentSizeChange, onLayout: () => {
83
85
  if (stickToBottomRef.current) scrollToLatest(false);
84
86
  }, scrollEventThrottle: 16, keyboardShouldPersistTaps: "always", keyboardDismissMode: "none", automaticallyAdjustKeyboardInsets: false, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", contentContainerStyle: [styles.listContent, listContentStyle], style: styles.list });
85
87
  }
@@ -102,7 +104,9 @@ const TranscriptRowView = memo(function TranscriptRowView2({
102
104
  const codeBg = isDark ? "#1e293b" : "#f3f4f6";
103
105
  return /* @__PURE__ */ jsxs(View, { style: styles.assistantCol, children: [
104
106
  attachments.length > 0 ? /* @__PURE__ */ jsx(MessageAttachmentPreviews, { attachments, align: "start", isDark }) : null,
105
- text ? /* @__PURE__ */ jsx(Markdown, { style: {
107
+ text ? item.streaming ? /* @__PURE__ */ jsx(Text, { style: [styles.assistantStreamText, {
108
+ color: markdownColor
109
+ }], children: text }) : /* @__PURE__ */ jsx(Markdown, { style: {
106
110
  body: {
107
111
  color: markdownColor,
108
112
  fontSize: 15,
@@ -199,5 +203,9 @@ const styles = StyleSheet.create({
199
203
  alignItems: "flex-start",
200
204
  marginBottom: 8,
201
205
  paddingHorizontal: 4
206
+ },
207
+ assistantStreamText: {
208
+ fontSize: 15,
209
+ lineHeight: 22
202
210
  }
203
211
  });export{ChatTranscript};//# sourceMappingURL=ChatTranscript.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatTranscript.js","sources":["../../../src/features/chat/ChatTranscript.tsx"],"sourcesContent":["/**\n * Chat thread list that can show attachment previews.\n *\n * `@messenger-box/platform-mobile` PlanModeView maps each row to GiftedChat\n * `{ text }` only, so an image send becomes the gateway caption\n * \"Attached: IMG_0005.jpg\" with no thumbnail. This list keeps streaming\n * markdown for the assistant and draws Manus-style previews on the user side.\n *\n * Rows stay oldest → newest (same as web). A previous `inverted` FlatList\n * reversed that order on device. The composer dock already lifts with the\n * keyboard; this list just fills the leftover space and pins to the latest\n * turn.\n */\nimport React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';\nimport { FlatList, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\nimport { displayUserMessageText, shouldRenderUserTranscriptTurn } from '../attachments/displayUserMessageText';\nimport { MessageAttachmentPreviews } from '../attachments/MessageAttachmentPreviews';\n\nexport type TranscriptMessage = {\n id: string;\n role: string;\n content: string;\n attachments?: MessageAttachment[];\n};\n\ntype TranscriptRow = TranscriptMessage & { streaming?: boolean };\n\nconst USER_BUBBLE_BG = '#2563eb';\n\nexport function ChatTranscript({\n messages,\n streamingContent,\n renderMessageActions,\n listContentStyle,\n isDark = false,\n keyboardVisible = false,\n}: {\n messages: TranscriptMessage[];\n streamingContent?: string;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n listContentStyle?: StyleProp<ViewStyle>;\n isDark?: boolean;\n keyboardVisible?: boolean;\n}) {\n const listRef = useRef<FlatList<TranscriptRow>>(null);\n const stickToBottomRef = useRef(true);\n const isProgrammaticScrollRef = useRef(false);\n\n const historyRows = useMemo<TranscriptRow[]>(\n () =>\n messages\n .filter((msg) => msg.role !== 'user' || shouldRenderUserTranscriptTurn(msg.content, msg.attachments))\n .map((msg) => ({ ...msg, streaming: false })),\n [messages],\n );\n\n const rows = useMemo<TranscriptRow[]>(() => {\n const stream = streamingContent?.trim();\n if (!stream) return historyRows;\n return [\n ...historyRows,\n {\n id: '__streaming__',\n role: 'assistant',\n content: stream,\n streaming: true,\n },\n ];\n }, [historyRows, streamingContent]);\n\n const scrollToLatest = useCallback(\n (animated: boolean) => {\n if (rows.length === 0) return;\n isProgrammaticScrollRef.current = true;\n listRef.current?.scrollToEnd({ animated });\n },\n [rows.length],\n );\n\n useEffect(() => {\n if (rows.length === 0 || !stickToBottomRef.current) return undefined;\n const t = setTimeout(() => scrollToLatest(false), 0);\n return () => clearTimeout(t);\n }, [rows.length, streamingContent?.length, scrollToLatest]);\n\n useEffect(() => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(false);\n }, [keyboardVisible, scrollToLatest]);\n\n const handleScroll = useCallback(\n (e: {\n nativeEvent: {\n contentOffset: { y: number };\n contentSize: { height: number };\n layoutMeasurement: { height: number };\n };\n }) => {\n const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;\n const nearLatest = contentOffset.y + layoutMeasurement.height >= contentSize.height - 80;\n if (!isProgrammaticScrollRef.current) {\n stickToBottomRef.current = nearLatest;\n } else if (nearLatest) {\n isProgrammaticScrollRef.current = false;\n }\n },\n [],\n );\n\n const handleContentSizeChange = useCallback(() => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(false);\n }, [scrollToLatest]);\n\n const renderItem = useCallback(\n ({ item }: { item: TranscriptRow }) => (\n <TranscriptRowView\n item={item}\n isDark={isDark}\n renderMessageActions={item.streaming ? undefined : renderMessageActions}\n />\n ),\n [isDark, renderMessageActions],\n );\n\n return (\n <FlatList\n ref={listRef}\n data={rows}\n keyExtractor={(item) => item.id}\n renderItem={renderItem}\n extraData={`${isDark}:${streamingContent ?? ''}`}\n onScroll={handleScroll}\n onContentSizeChange={handleContentSizeChange}\n onLayout={() => {\n if (stickToBottomRef.current) scrollToLatest(false);\n }}\n scrollEventThrottle={16}\n keyboardShouldPersistTaps=\"always\"\n keyboardDismissMode=\"none\"\n automaticallyAdjustKeyboardInsets={false}\n automaticallyAdjustContentInsets={false}\n contentInsetAdjustmentBehavior=\"never\"\n contentContainerStyle={[styles.listContent, listContentStyle]}\n style={styles.list}\n />\n );\n}\n\nconst TranscriptRowView = memo(function TranscriptRowView({\n item,\n isDark,\n renderMessageActions,\n}: {\n item: TranscriptRow;\n isDark: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n const isUser = item.role === 'user';\n const attachments = item.attachments ?? [];\n const text = isUser ? displayUserMessageText(item.content, attachments) : item.content;\n\n if (isUser) {\n return (\n <View style={styles.userCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"end\" isDark={isDark} />\n ) : null}\n {text ? (\n <View style={styles.userBubble}>\n <Text style={styles.userText}>{text}</Text>\n </View>\n ) : null}\n </View>\n );\n }\n\n const markdownColor = isDark ? '#e2e8f0' : '#111827';\n const codeBg = isDark ? '#1e293b' : '#f3f4f6';\n\n return (\n <View style={styles.assistantCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"start\" isDark={isDark} />\n ) : null}\n {text ? (\n <Markdown\n style={{\n body: { color: markdownColor, fontSize: 15, lineHeight: 22 },\n heading1: {\n fontSize: 18,\n fontWeight: '700',\n marginTop: 12,\n marginBottom: 4,\n color: markdownColor,\n },\n heading2: {\n fontSize: 16,\n fontWeight: '700',\n marginTop: 10,\n marginBottom: 4,\n color: markdownColor,\n },\n heading3: {\n fontSize: 15,\n fontWeight: '600',\n marginTop: 8,\n marginBottom: 4,\n color: markdownColor,\n },\n strong: { fontWeight: '700' },\n paragraph: { marginTop: 4, marginBottom: 4 },\n list_item: { marginVertical: 2 },\n bullet_list: { marginVertical: 4 },\n ordered_list: { marginVertical: 4 },\n code_inline: {\n backgroundColor: codeBg,\n paddingHorizontal: 4,\n borderRadius: 4,\n fontSize: 14,\n color: markdownColor,\n },\n code_block: {\n backgroundColor: codeBg,\n padding: 12,\n borderRadius: 8,\n marginVertical: 8,\n fontSize: 14,\n color: markdownColor,\n },\n }}\n >\n {text}\n </Markdown>\n ) : null}\n {renderMessageActions\n ? renderMessageActions({ id: item.id, role: 'assistant', content: item.content })\n : null}\n </View>\n );\n});\n\nconst styles = StyleSheet.create({\n list: {\n flex: 1,\n },\n listContent: {\n paddingTop: 8,\n paddingBottom: 8,\n paddingHorizontal: 8,\n },\n userCol: {\n width: '100%',\n alignItems: 'flex-end',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n userBubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n padding: 12,\n maxWidth: '92%',\n },\n userText: {\n color: '#ffffff',\n fontSize: 15,\n lineHeight: 22,\n },\n assistantCol: {\n width: '100%',\n alignItems: 'flex-start',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n});\n"],"names":["TranscriptRowView"],"mappings":"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"}
1
+ {"version":3,"file":"ChatTranscript.js","sources":["../../../src/features/chat/ChatTranscript.tsx"],"sourcesContent":["/**\n * Chat thread list that can show attachment previews.\n *\n * `@messenger-box/platform-mobile` PlanModeView maps each row to GiftedChat\n * `{ text }` only, so an image send becomes the gateway caption\n * \"Attached: IMG_0005.jpg\" with no thumbnail. This list keeps streaming\n * markdown for the assistant and draws Manus-style previews on the user side.\n *\n * Rows stay oldest → newest (same as web). A previous `inverted` FlatList\n * reversed that order on device. The composer dock already lifts with the\n * keyboard; this list just fills the leftover space and pins to the latest\n * turn.\n */\nimport React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';\nimport { FlatList, Keyboard, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\nimport { displayUserMessageText, shouldRenderUserTranscriptTurn } from '../attachments/displayUserMessageText';\nimport { MessageAttachmentPreviews } from '../attachments/MessageAttachmentPreviews';\n\nexport type TranscriptMessage = {\n id: string;\n role: string;\n content: string;\n attachments?: MessageAttachment[];\n};\n\ntype TranscriptRow = TranscriptMessage & { streaming?: boolean };\n\nconst USER_BUBBLE_BG = '#2563eb';\n\nexport function ChatTranscript({\n messages,\n streamingContent,\n renderMessageActions,\n listContentStyle,\n isDark = false,\n}: {\n messages: TranscriptMessage[];\n streamingContent?: string;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n listContentStyle?: StyleProp<ViewStyle>;\n isDark?: boolean;\n}) {\n const listRef = useRef<FlatList<TranscriptRow>>(null);\n const stickToBottomRef = useRef(true);\n const isProgrammaticScrollRef = useRef(false);\n\n const historyRows = useMemo<TranscriptRow[]>(\n () =>\n messages\n .filter((msg) => msg.role !== 'user' || shouldRenderUserTranscriptTurn(msg.content, msg.attachments))\n .map((msg) => ({ ...msg, streaming: false })),\n [messages],\n );\n\n const rows = useMemo<TranscriptRow[]>(() => {\n const stream = streamingContent?.trim();\n if (!stream) return historyRows;\n return [\n ...historyRows,\n {\n id: '__streaming__',\n role: 'assistant',\n content: stream,\n streaming: true,\n },\n ];\n }, [historyRows, streamingContent]);\n\n const scrollToLatest = useCallback(\n (animated: boolean) => {\n if (rows.length === 0) return;\n isProgrammaticScrollRef.current = true;\n listRef.current?.scrollToEnd({ animated });\n },\n [rows.length],\n );\n\n useEffect(() => {\n if (rows.length === 0 || !stickToBottomRef.current) return undefined;\n const t = setTimeout(() => scrollToLatest(false), 0);\n return () => clearTimeout(t);\n }, [rows.length, streamingContent?.length, scrollToLatest]);\n\n useEffect(() => {\n const show = Keyboard.addListener('keyboardDidShow', () => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(true);\n });\n return () => show.remove();\n }, [scrollToLatest]);\n\n const handleScroll = useCallback(\n (e: {\n nativeEvent: {\n contentOffset: { y: number };\n contentSize: { height: number };\n layoutMeasurement: { height: number };\n };\n }) => {\n const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;\n const nearLatest = contentOffset.y + layoutMeasurement.height >= contentSize.height - 80;\n if (!isProgrammaticScrollRef.current) {\n stickToBottomRef.current = nearLatest;\n } else if (nearLatest) {\n isProgrammaticScrollRef.current = false;\n }\n },\n [],\n );\n\n const handleContentSizeChange = useCallback(() => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(false);\n }, [scrollToLatest]);\n\n const renderItem = useCallback(\n ({ item }: { item: TranscriptRow }) => (\n <TranscriptRowView\n item={item}\n isDark={isDark}\n renderMessageActions={item.streaming ? undefined : renderMessageActions}\n />\n ),\n [isDark, renderMessageActions],\n );\n\n return (\n <FlatList\n ref={listRef}\n data={rows}\n keyExtractor={(item) => item.id}\n renderItem={renderItem}\n extraData={streamingContent}\n initialNumToRender={12}\n maxToRenderPerBatch={8}\n updateCellsBatchingPeriod={50}\n windowSize={7}\n onScroll={handleScroll}\n onContentSizeChange={handleContentSizeChange}\n onLayout={() => {\n if (stickToBottomRef.current) scrollToLatest(false);\n }}\n scrollEventThrottle={16}\n keyboardShouldPersistTaps=\"always\"\n keyboardDismissMode=\"none\"\n automaticallyAdjustKeyboardInsets={false}\n automaticallyAdjustContentInsets={false}\n contentInsetAdjustmentBehavior=\"never\"\n contentContainerStyle={[styles.listContent, listContentStyle]}\n style={styles.list}\n />\n );\n}\n\nconst TranscriptRowView = memo(function TranscriptRowView({\n item,\n isDark,\n renderMessageActions,\n}: {\n item: TranscriptRow;\n isDark: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n const isUser = item.role === 'user';\n const attachments = item.attachments ?? [];\n const text = isUser ? displayUserMessageText(item.content, attachments) : item.content;\n\n if (isUser) {\n return (\n <View style={styles.userCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"end\" isDark={isDark} />\n ) : null}\n {text ? (\n <View style={styles.userBubble}>\n <Text style={styles.userText}>{text}</Text>\n </View>\n ) : null}\n </View>\n );\n }\n\n const markdownColor = isDark ? '#e2e8f0' : '#111827';\n const codeBg = isDark ? '#1e293b' : '#f3f4f6';\n\n return (\n <View style={styles.assistantCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"start\" isDark={isDark} />\n ) : null}\n {text ? (\n item.streaming ? (\n <Text style={[styles.assistantStreamText, { color: markdownColor }]}>{text}</Text>\n ) : (\n <Markdown\n style={{\n body: { color: markdownColor, fontSize: 15, lineHeight: 22 },\n heading1: {\n fontSize: 18,\n fontWeight: '700',\n marginTop: 12,\n marginBottom: 4,\n color: markdownColor,\n },\n heading2: {\n fontSize: 16,\n fontWeight: '700',\n marginTop: 10,\n marginBottom: 4,\n color: markdownColor,\n },\n heading3: {\n fontSize: 15,\n fontWeight: '600',\n marginTop: 8,\n marginBottom: 4,\n color: markdownColor,\n },\n strong: { fontWeight: '700' },\n paragraph: { marginTop: 4, marginBottom: 4 },\n list_item: { marginVertical: 2 },\n bullet_list: { marginVertical: 4 },\n ordered_list: { marginVertical: 4 },\n code_inline: {\n backgroundColor: codeBg,\n paddingHorizontal: 4,\n borderRadius: 4,\n fontSize: 14,\n color: markdownColor,\n },\n code_block: {\n backgroundColor: codeBg,\n padding: 12,\n borderRadius: 8,\n marginVertical: 8,\n fontSize: 14,\n color: markdownColor,\n },\n }}\n >\n {text}\n </Markdown>\n )\n ) : null}\n {renderMessageActions\n ? renderMessageActions({ id: item.id, role: 'assistant', content: item.content })\n : null}\n </View>\n );\n});\n\nconst styles = StyleSheet.create({\n list: {\n flex: 1,\n },\n listContent: {\n paddingTop: 8,\n paddingBottom: 8,\n paddingHorizontal: 8,\n },\n userCol: {\n width: '100%',\n alignItems: 'flex-end',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n userBubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n padding: 12,\n maxWidth: '92%',\n },\n userText: {\n color: '#ffffff',\n fontSize: 15,\n lineHeight: 22,\n },\n assistantCol: {\n width: '100%',\n alignItems: 'flex-start',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n assistantStreamText: {\n fontSize: 15,\n lineHeight: 22,\n },\n});\n"],"names":["TranscriptRowView"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skillTemplates.js","sources":["../../../src/features/skills/skillTemplates.ts"],"sourcesContent":["/**\n * Skill templates - the mobile twin of the browser's\n * `services/skillTemplates.ts`. The SkillTemplate shape is the CONTRACT\n * (HeaderSkillsButton hands one to cdecli connect + agentSessionManager) and\n * must not drift from the web's.\n *\n * Mobile v1 ships the built-in templates only: the web additionally merges\n * marketplace `aiTemplates` (uiLayout settings) and user-custom templates\n * from localStorage - both need a mobile owner (settings loader / storage)\n * and arrive with the header sheet's next iteration.\n */\n\nexport interface SkillTemplate {\n id: string;\n name: string;\n description: string;\n icon: string;\n /** SOUL.md content - agent persona/instructions */\n soul: string;\n /** Recommended model (e.g. 'anthropic/claude-opus-4-6') */\n model?: string;\n /** Skills that should be enabled for this template */\n requiredSkills?: string[];\n /** Category for UI grouping */\n category: 'coding' | 'research' | 'automation' | 'creative' | 'data' | 'general';\n /** Tags for search/filtering */\n tags: string[];\n /**\n * Shipped with the app (not backed by a marketplace form_table row) - the\n * template's `id` is a connector slug, so form_table lookups must be\n * skipped.\n */\n builtin?: boolean;\n}\n\nexport const BUILTIN_SKILL_TEMPLATES: SkillTemplate[] = [\n {\n id: 'yantra-github-search',\n name: 'GitHub Research',\n description:\n 'Search and analyze GitHub repositories. Ask a question and get an AI-powered summary of the most relevant open-source projects.',\n icon: '🔍',\n soul: 'You are a GitHub research assistant powered by the yantra-github-search connector. When the user provides a topic or question, search GitHub for relevant repositories, analyze the top results, and return a concise summary with key findings and citations.',\n model: undefined,\n requiredSkills: ['yantra-github-search'],\n category: 'research',\n tags: ['github', 'search', 'repositories', 'open-source', 'analysis'],\n builtin: true,\n },\n];\n"],"names":[],"mappings":"AAkCO,MAAM,0BAA2C,CAAC;AAAA,EACvD,EAAI,EAAA,sBAAA;AAAA,EACJ,IAAM,EAAA,iBAAA;AAAA,EACN,WAAa,EAAA,iIAAA;AAAA,EACb,IAAM,EAAA,WAAA;AAAA,EACN,IAAM,EAAA,gQAAA;AAAA,EACN,KAAO,EAAA,MAAA;AAAA,EACP,cAAA,EAAgB,CAAC,sBAAsB,CAAA;AAAA,EACvC,QAAU,EAAA,UAAA;AAAA,EACV,MAAM,CAAC,QAAA,EAAU,QAAU,EAAA,cAAA,EAAgB,eAAe,UAAU,CAAA;AAAA,EACpE,OAAS,EAAA;AACX,CAAC"}
1
+ {"version":3,"file":"skillTemplates.js","sources":["../../../src/features/skills/skillTemplates.ts"],"sourcesContent":["/**\n * Skill templates - the mobile twin of the browser's\n * `services/skillTemplates.ts`. The SkillTemplate shape is the CONTRACT\n * (HeaderSkillsButton hands one to cdecli connect + agentSessionManager) and\n * must not drift from the web's.\n *\n * Mobile ships the built-in templates plus the browser's pure helpers\n * (parseFormToSkillTemplate/searchTemplates) so useSkillTemplates can merge\n * marketplace `aiTemplates` the same way the web does. Custom templates have\n * no on-device store yet: loadCustomTemplates answers [] until a mobile\n * storage owner exists (web persists them to localStorage).\n */\n\nexport interface SkillTemplate {\n id: string;\n name: string;\n description: string;\n icon: string;\n /** SOUL.md content - agent persona/instructions */\n soul: string;\n /** Recommended model (e.g. 'anthropic/claude-opus-4-6') */\n model?: string;\n /** Skills that should be enabled for this template */\n requiredSkills?: string[];\n /** Category for UI grouping */\n category: 'coding' | 'research' | 'automation' | 'creative' | 'data' | 'general';\n /** Tags for search/filtering */\n tags: string[];\n /**\n * Shipped with the app (not backed by a marketplace form_table row) - the\n * template's `id` is a connector slug, so form_table lookups must be\n * skipped.\n */\n builtin?: boolean;\n}\n\nexport const BUILTIN_SKILL_TEMPLATES: SkillTemplate[] = [\n {\n id: 'yantra-github-search',\n name: 'GitHub Research',\n description:\n 'Search and analyze GitHub repositories. Ask a question and get an AI-powered summary of the most relevant open-source projects.',\n icon: '🔍',\n soul: 'You are a GitHub research assistant powered by the yantra-github-search connector. When the user provides a topic or question, search GitHub for relevant repositories, analyze the top results, and return a concise summary with key findings and citations.',\n model: undefined,\n requiredSkills: ['yantra-github-search'],\n category: 'research',\n tags: ['github', 'search', 'repositories', 'open-source', 'analysis'],\n builtin: true,\n },\n];\n\n/** User-custom templates: no on-device store yet (web uses localStorage). */\nexport function loadCustomTemplates(): SkillTemplate[] {\n return [];\n}\n\n/** Parse a form entry from aiTemplates into a SkillTemplate (browser twin). */\nexport function parseFormToSkillTemplate(\n formKey: string,\n form: Record<string, unknown>,\n parentGroup: Record<string, unknown> = {},\n): SkillTemplate | null {\n if (!form.title && !form.formTemplateId) return null;\n\n const rawTags = form.tags as Record<string, unknown> | undefined;\n const tags: string[] = rawTags ? Object.values(rawTags).filter((v): v is string => typeof v === 'string') : [];\n\n const rawSkills = form.requiredSkills as Record<string, unknown> | undefined;\n const requiredSkills: string[] = rawSkills\n ? Object.values(rawSkills).filter((v): v is string => typeof v === 'string')\n : [];\n\n const validCategories = new Set(['coding', 'research', 'automation', 'creative', 'data', 'general']);\n const rawCategory = (form.category as string) || '';\n const category: SkillTemplate['category'] = validCategories.has(rawCategory)\n ? (rawCategory as SkillTemplate['category'])\n : 'general';\n\n return {\n id: (form.formTemplateId as string) || formKey,\n name: (form.title as string) || formKey,\n description: (form.description as string) || (parentGroup.description as string) || '',\n icon: (form.icon as string) || (parentGroup.formIcon as string) || '🌟',\n soul: (form.soul as string) || '',\n model: (form.model as string) || undefined,\n requiredSkills: requiredSkills.length > 0 ? requiredSkills : undefined,\n category,\n tags,\n };\n}\n\n/** Search templates by name, description, or tags (browser twin). */\nexport function searchTemplates(templates: SkillTemplate[], query: string): SkillTemplate[] {\n const q = query.toLowerCase();\n return templates.filter(\n (t) =>\n t.name.toLowerCase().includes(q) ||\n t.description.toLowerCase().includes(q) ||\n t.tags.some((tag) => tag.includes(q)),\n );\n}\n"],"names":[],"mappings":"AAmCO,MAAM,0BAA2C,CAAC;AAAA,EACvD,EAAI,EAAA,sBAAA;AAAA,EACJ,IAAM,EAAA,iBAAA;AAAA,EACN,WAAa,EAAA,iIAAA;AAAA,EACb,IAAM,EAAA,WAAA;AAAA,EACN,IAAM,EAAA,gQAAA;AAAA,EACN,KAAO,EAAA,MAAA;AAAA,EACP,cAAA,EAAgB,CAAC,sBAAsB,CAAA;AAAA,EACvC,QAAU,EAAA,UAAA;AAAA,EACV,MAAM,CAAC,QAAA,EAAU,QAAU,EAAA,cAAA,EAAgB,eAAe,UAAU,CAAA;AAAA,EACpE,OAAS,EAAA;AACX,CAAC"}
@@ -1,4 +1,4 @@
1
- import {useState,useRef,useCallback,useMemo,useEffect}from'react';import {useCreateAccountChatFileUploadLinksMutation,useCreateAccountChatFileDownloadLinksMutation}from'common/graphql';import {useChatMutations,useChatMessages}from'./useChatApi.js';import {useCdecliChannel,isNoActiveSessionError}from'./useCdecliChannel.js';import {usePrerequisiteIds}from'./usePrerequisiteIds.js';import {buildGatewayMediaPreferUrls}from'../features/attachments/buildGatewayMedia.js';import {preserveAttachmentPreviews}from'../features/attachments/preserveAttachmentPreviews.js';var __defProp = Object.defineProperty;
1
+ import {useState,useRef,useCallback,useEffect,useMemo}from'react';import {useCreateAccountChatFileUploadLinksMutation,useCreateAccountChatFileDownloadLinksMutation}from'common/graphql';import {useChatMutations,useChatMessages}from'./useChatApi.js';import {useCdecliChannel,isNoActiveSessionError}from'./useCdecliChannel.js';import {usePrerequisiteIds}from'./usePrerequisiteIds.js';import {buildGatewayMediaPreferUrls}from'../features/attachments/buildGatewayMedia.js';import {preserveAttachmentPreviews}from'../features/attachments/preserveAttachmentPreviews.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -45,6 +45,23 @@ function useChatStream(sessionId, routing = {
45
45
  const cdecliRoundHandledRef = useRef(false);
46
46
  const routingRef = useRef(routing);
47
47
  routingRef.current = routing;
48
+ const pendingStreamRef = useRef("");
49
+ const streamFlushTimerRef = useRef(null);
50
+ const clearPendingStream = useCallback(() => {
51
+ if (streamFlushTimerRef.current != null) {
52
+ clearTimeout(streamFlushTimerRef.current);
53
+ streamFlushTimerRef.current = null;
54
+ }
55
+ pendingStreamRef.current = "";
56
+ }, []);
57
+ const flushPendingStream = useCallback(() => {
58
+ streamFlushTimerRef.current = null;
59
+ const pending = pendingStreamRef.current;
60
+ if (!pending) return;
61
+ pendingStreamRef.current = "";
62
+ setResponse((r) => r + pending);
63
+ }, []);
64
+ useEffect(() => () => clearPendingStream(), [clearPendingStream]);
48
65
  const {
49
66
  saveMessages
50
67
  } = useChatMutations();
@@ -61,6 +78,7 @@ function useChatStream(sessionId, routing = {
61
78
  const lastHydratedSessionIdRef = useRef(null);
62
79
  const streamChannelId = sessionId || cdecliStreamOverrideId;
63
80
  const finalizeCdecliRound = useCallback((assistantContent) => {
81
+ clearPendingStream();
64
82
  saveSessionIdRef.current = null;
65
83
  userMsgForSaveRef.current = null;
66
84
  sessionLiveRef.current = false;
@@ -74,17 +92,21 @@ function useChatStream(sessionId, routing = {
74
92
  content: assistantContent
75
93
  }]);
76
94
  }
77
- }, []);
95
+ }, [clearPendingStream]);
78
96
  const cdecliCallbacks = useMemo(() => ({
79
97
  onChunk: (text) => {
80
- setResponse((r) => r + text);
98
+ pendingStreamRef.current += text;
81
99
  if (!sessionLiveRef.current) {
82
100
  sessionLiveRef.current = true;
83
101
  void flushPendingSteersRef.current();
84
102
  }
103
+ if (streamFlushTimerRef.current == null) {
104
+ streamFlushTimerRef.current = setTimeout(flushPendingStream, 50);
105
+ }
85
106
  },
86
107
  onComplete: (text) => {
87
108
  var _a;
109
+ clearPendingStream();
88
110
  if (cdecliRoundHandledRef.current) {
89
111
  return;
90
112
  }
@@ -154,7 +176,7 @@ function useChatStream(sessionId, routing = {
154
176
  setError(err || "CDeCLI stream error.");
155
177
  finalizeCdecliRound();
156
178
  }
157
- }), [saveMessages, accountUserId, finalizeCdecliRound]);
179
+ }), [saveMessages, accountUserId, finalizeCdecliRound, flushPendingStream, clearPendingStream]);
158
180
  const {
159
181
  sendMessage: sendCdecliMessage,
160
182
  steer: steerCdecliMessage,
@@ -318,6 +340,7 @@ function useChatStream(sessionId, routing = {
318
340
  sendMessageRef.current = sendMessage;
319
341
  const cancel = useCallback(() => {
320
342
  var _a;
343
+ clearPendingStream();
321
344
  pendingSteersRef.current = [];
322
345
  sessionLiveRef.current = false;
323
346
  const sid = (_a = saveSessionIdRef.current) != null ? _a : sessionId;
@@ -326,12 +349,13 @@ function useChatStream(sessionId, routing = {
326
349
  cdecliRoundHandledRef.current = true;
327
350
  finalizeCdecliRound();
328
351
  }
329
- }, [cancelCdecliTurn, finalizeCdecliRound, sessionId]);
352
+ }, [cancelCdecliTurn, finalizeCdecliRound, sessionId, clearPendingStream]);
330
353
  const clearMessages = useCallback(() => {
354
+ clearPendingStream();
331
355
  setMessages([]);
332
356
  setResponse("");
333
357
  setError(null);
334
- }, []);
358
+ }, [clearPendingStream]);
335
359
  const messagesLoading = !!sessionId && backendMessagesLoading && !backendMessagesLoaded;
336
360
  return {
337
361
  messages,