@adminide-stack/yantra-mobile 12.0.127 → 12.0.128-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/lib/components/NavigationHeader/NavigationHeader.js +5 -5
  2. package/lib/components/NavigationHeader/NavigationHeader.js.map +1 -1
  3. package/lib/compute.js +7 -3
  4. package/lib/compute.js.map +1 -1
  5. package/lib/contexts/ChatLiveStreamContext.js +2 -1
  6. package/lib/contexts/ChatLiveStreamContext.js.map +1 -1
  7. package/lib/features/apps/nativeAppNavigation.js +1 -27
  8. package/lib/features/apps/nativeAppNavigation.js.map +1 -1
  9. package/lib/features/attachments/composerAttachmentHandoff.js +4 -1
  10. package/lib/features/attachments/composerAttachmentHandoff.js.map +1 -1
  11. package/lib/features/chat/ChatTranscript.js +3 -3
  12. package/lib/features/chat/ChatTranscript.js.map +1 -1
  13. package/lib/features/shopping/chat/openShopNative.js +1 -27
  14. package/lib/features/shopping/chat/openShopNative.js.map +1 -1
  15. package/lib/hooks/useCdecliAutoConnect.js +29 -4
  16. package/lib/hooks/useCdecliAutoConnect.js.map +1 -1
  17. package/lib/hooks/useCdecliChannel.js +7 -3
  18. package/lib/hooks/useCdecliChannel.js.map +1 -1
  19. package/lib/hooks/useChatStream.js +56 -11
  20. package/lib/hooks/useChatStream.js.map +1 -1
  21. package/lib/routes.json +4 -2
  22. package/lib/screens/Characters/index.js +9 -18
  23. package/lib/screens/Characters/index.js.map +1 -1
  24. package/lib/screens/Chat/index.js +111 -62
  25. package/lib/screens/Chat/index.js.map +1 -1
  26. package/lib/screens/Home/HomeScreen.js +9 -39
  27. package/lib/screens/Home/HomeScreen.js.map +1 -1
  28. package/lib/state/chatThreadMerge.js +1 -1
  29. package/lib/state/chatThreadMerge.js.map +1 -1
  30. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {useMemo,useLayoutEffect,useCallback,useState,useRef,useEffect}from'react';import {useWindowDimensions,Keyboard,StyleSheet,View,Pressable}from'react-native';import {getDefaultLeftItems,Box,Text,InputToolBar}from'@admin-layout/gluestack-ui-mobile';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {MessageActionBar}from'../../features/chat/MessageActionBar.js';import {ChatTranscript}from'../../features/chat/ChatTranscript.js';import {ChatLinkOpenerProvider}from'../../features/links/ChatLinkOpenerProvider.js';import {AskUserCard}from'../../features/chat/AskUserCard.js';import {pendingAskUser}from'../../features/chat/askUser.js';import {PendingConnectorConnectCard}from'../../features/chat/ConnectorConnectPrompt.js';import {QueuedFollowUps}from'../../features/chat/QueuedFollowUps.js';import {displayConnectorConnectMessages,displayConnectorConnectText}from'common';import {splitPreResponse}from'../../features/chat/preResponse.js';import {useCdecliConnection}from'../../contexts/CdecliConnectionContext.js';import {useChatLiveStream}from'../../contexts/ChatLiveStreamContext.js';import {KeyboardAwareScreen,KeyboardComposerDock}from'../../components/KeyboardComposerDock.js';import {YantraBrandLoader,YANTRA_LOADER_SIZE_COMPACT}from'../../components/YantraBrandLoader.js';import ThinkingIndicator from'../../components/ThinkingIndicator.js';import {mobileTokens}from'../../theme/mobileTokens.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import DeepSearchModal from'../Home/components/DeepSearchModal.js';import {normalizeSummaryText,extractDeepSearchSources}from'../Home/deepSearchUtils.js';import {AudioRecorderPanel}from'../../features/audio-input/AudioRecorderPanel.js';import {NativeDictationBar}from'../../features/audio-input/NativeDictationBar.js';import {isNativeDictationAvailable}from'../../features/audio-input/nativeDictation.js';import {useImageAttachments}from'../../features/attachments/useImageAttachments.js';import {ComposerAttachmentBar}from'../../features/attachments/ComposerAttachmentBar.js';import {useComposerAttachMenu}from'../../features/attachments/ComposerAttachMenu.js';import {hasComposerAttachments,takeComposerAttachments}from'../../features/attachments/composerAttachmentHandoff.js';import {MicErrorBoundary}from'../../features/audio-input/MicErrorBoundary.js';import {requestMicPermission}from'../../features/audio-input/useAudioPermission.js';import {shouldRedirectToShop}from'../../features/shopping/chat/homeShoppingIntent.js';import {replaceWithShopNative,shopHandoffParams,pushShopNative}from'../../features/shopping/chat/openShopNative.js';import {resolveNativeHandoff}from'../../features/apps/nativeAppHandoff.js';import {replaceWithNativeApp,nativeHandoffParams,pushNativeApp}from'../../features/apps/nativeAppNavigation.js';var __defProp = Object.defineProperty;
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {useMemo,useLayoutEffect,useState,useCallback,useRef,useEffect}from'react';import {useWindowDimensions,Keyboard,StyleSheet,View,Pressable}from'react-native';import {getDefaultLeftItems,Box,Text,InputToolBar}from'@admin-layout/gluestack-ui-mobile';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {MessageActionBar}from'../../features/chat/MessageActionBar.js';import {ChatTranscript}from'../../features/chat/ChatTranscript.js';import {ChatLinkOpenerProvider}from'../../features/links/ChatLinkOpenerProvider.js';import {AskUserCard}from'../../features/chat/AskUserCard.js';import {pendingAskUser}from'../../features/chat/askUser.js';import {PendingConnectorConnectCard}from'../../features/chat/ConnectorConnectPrompt.js';import {QueuedFollowUps}from'../../features/chat/QueuedFollowUps.js';import {displayConnectorConnectMessages,displayConnectorConnectText}from'common';import {splitPreResponse}from'../../features/chat/preResponse.js';import {useCdecliConnection}from'../../contexts/CdecliConnectionContext.js';import {useChatLiveStream}from'../../contexts/ChatLiveStreamContext.js';import {KeyboardAwareScreen,KeyboardComposerDock}from'../../components/KeyboardComposerDock.js';import {YantraBrandLoader,YANTRA_LOADER_SIZE_COMPACT}from'../../components/YantraBrandLoader.js';import ThinkingIndicator from'../../components/ThinkingIndicator.js';import {mobileTokens}from'../../theme/mobileTokens.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import DeepSearchModal from'../Home/components/DeepSearchModal.js';import {normalizeSummaryText,extractDeepSearchSources}from'../Home/deepSearchUtils.js';import {AudioRecorderPanel}from'../../features/audio-input/AudioRecorderPanel.js';import {NativeDictationBar}from'../../features/audio-input/NativeDictationBar.js';import {isNativeDictationAvailable}from'../../features/audio-input/nativeDictation.js';import {useImageAttachments}from'../../features/attachments/useImageAttachments.js';import {ComposerAttachmentBar}from'../../features/attachments/ComposerAttachmentBar.js';import {useComposerAttachMenu}from'../../features/attachments/ComposerAttachMenu.js';import {peekComposerAttachments,hasComposerAttachments,takeComposerAttachments}from'../../features/attachments/composerAttachmentHandoff.js';import {MicErrorBoundary}from'../../features/audio-input/MicErrorBoundary.js';import {requestMicPermission}from'../../features/audio-input/useAudioPermission.js';import {shouldRedirectToShop}from'../../features/shopping/chat/homeShoppingIntent.js';import {replaceWithShopNative,shopHandoffParams,pushShopNative}from'../../features/shopping/chat/openShopNative.js';import {resolveNativeHandoff}from'../../features/apps/nativeAppHandoff.js';import {replaceWithNativeApp,nativeHandoffParams,pushNativeApp}from'../../features/apps/nativeAppNavigation.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -26,6 +26,25 @@ function modeOf(isDeepSearchMode, isBuildMode) {
26
26
  if (isBuildMode) return "build";
27
27
  return "chat";
28
28
  }
29
+ const FIRST_TURN = "first";
30
+ function normalizeRouteAttachments(attachments) {
31
+ return attachments == null ? void 0 : attachments.map((a, index) => {
32
+ var _a, _b, _c, _d;
33
+ return __spreadValues(__spreadValues(__spreadValues(__spreadValues({
34
+ id: (_a = a.id) != null ? _a : `att-${index}`,
35
+ name: (_b = a.name) != null ? _b : `attachment-${index + 1}`,
36
+ type: a.type === "screenshot" ? "screenshot" : "file"
37
+ }, a.mimeType ? {
38
+ mimeType: a.mimeType
39
+ } : {}), a.size ? {
40
+ size: a.size
41
+ } : {}), ((_c = a.url) != null ? _c : a.uri) ? {
42
+ url: (_d = a.url) != null ? _d : a.uri
43
+ } : {}), a.dataUrl ? {
44
+ dataUrl: a.dataUrl
45
+ } : {});
46
+ });
47
+ }
29
48
  const PLACEHOLDER = {
30
49
  "deep-search": "Research anything...",
31
50
  build: "Describe an app to build...",
@@ -67,6 +86,19 @@ function ChatScreen() {
67
86
  setActiveChannelId(channelId);
68
87
  }, [channelId, setActiveChannelId]);
69
88
  const chat = useChatLiveStream();
89
+ const [routingTurns, setRoutingTurns] = useState(() => {
90
+ var _a2, _b;
91
+ return initialQuery || (initialAttachments == null ? void 0 : initialAttachments.length) || ((_a2 = peekComposerAttachments(channelId)) == null ? void 0 : _a2.length) ? [{
92
+ id: FIRST_TURN,
93
+ text: initialQuery != null ? initialQuery : "",
94
+ attachments: (_b = peekComposerAttachments(channelId)) != null ? _b : normalizeRouteAttachments(initialAttachments),
95
+ placement: "thread"
96
+ }] : [];
97
+ });
98
+ const releaseRoutingTurn = useCallback((id) => {
99
+ setRoutingTurns((prev) => prev.filter((turn) => turn.id !== id));
100
+ }, []);
101
+ const routingSeqRef = useRef(0);
70
102
  const transcriptRows = useMemo(() => {
71
103
  var _a2;
72
104
  const waiting = chat.queuedFollowUps.map((queued) => queued.text.trim());
@@ -90,15 +122,23 @@ function ChatScreen() {
90
122
  // Turns typed while the gateway was down. They live in the outbox, not in
91
123
  // the thread, so they survive a backend hydration and an app restart - and
92
124
  // they are always the newest rows, so they belong at the end.
93
- ...chat.queuedMessages.map((queued) => ({
125
+ ...chat.queuedMessages.map((queued) => __spreadValues({
94
126
  id: `queued-${queued.id}`,
95
127
  role: "user",
96
128
  content: queued.text,
97
129
  attachments: queued.attachments,
98
130
  queued: true
131
+ }, cdecli.channelConnected ? {
132
+ queuedNote: chat.outboxRetrying ? "Not sent yet \xB7 trying again" : "Not sent \xB7 tap Send now"
133
+ } : {})),
134
+ ...routingTurns.filter((turn) => turn.placement === "thread").map((turn) => ({
135
+ id: `routing-${turn.id}`,
136
+ role: "user",
137
+ content: turn.text,
138
+ attachments: turn.attachments
99
139
  }))
100
140
  ];
101
- }, [chat.messages, chat.queuedMessages, chat.queuedFollowUps]);
141
+ }, [chat.messages, chat.queuedMessages, chat.queuedFollowUps, chat.outboxRetrying, cdecli.channelConnected, routingTurns]);
102
142
  const {
103
143
  messages,
104
144
  response,
@@ -108,6 +148,7 @@ function ChatScreen() {
108
148
  isStreaming,
109
149
  messagesLoading,
110
150
  queuedMessages,
151
+ outboxRetrying,
111
152
  flushQueuedMessages,
112
153
  discardQueuedMessages,
113
154
  queuedFollowUps,
@@ -153,42 +194,31 @@ function ChatScreen() {
153
194
  const [showNativeDictation, setShowNativeDictation] = useState(false);
154
195
  const [voiceError, setVoiceError] = useState(null);
155
196
  const autoFiredRef = useRef(false);
197
+ const firstTurnRef = useRef(null);
198
+ const [firstTurnRouted, setFirstTurnRouted] = useState(false);
156
199
  useEffect(() => {
157
200
  if (autoFiredRef.current) return;
158
201
  if (!channelId) return;
159
- if (cdecli.status !== "connected" && cdecli.status !== "error") return;
160
202
  const stashed = hasComposerAttachments(channelId) ? takeComposerAttachments(channelId) : void 0;
161
- const attachments = (stashed == null ? void 0 : stashed.length) ? stashed : initialAttachments == null ? void 0 : initialAttachments.map((a, index) => {
162
- var _a2, _b, _c, _d;
163
- return __spreadValues(__spreadValues(__spreadValues(__spreadValues({
164
- id: (_a2 = a.id) != null ? _a2 : `att-${index}`,
165
- name: (_b = a.name) != null ? _b : `attachment-${index + 1}`,
166
- type: a.type === "screenshot" ? "screenshot" : "file"
167
- }, a.mimeType ? {
168
- mimeType: a.mimeType
169
- } : {}), a.size ? {
170
- size: a.size
171
- } : {}), ((_c = a.url) != null ? _c : a.uri) ? {
172
- url: (_d = a.url) != null ? _d : a.uri
173
- } : {}), a.dataUrl ? {
174
- dataUrl: a.dataUrl
175
- } : {});
176
- });
203
+ const attachments = (stashed == null ? void 0 : stashed.length) ? stashed : normalizeRouteAttachments(initialAttachments);
177
204
  if (!initialQuery && !(attachments == null ? void 0 : attachments.length)) return;
178
205
  autoFiredRef.current = true;
206
+ firstTurnRef.current = {
207
+ text: initialQuery != null ? initialQuery : "",
208
+ attachments
209
+ };
179
210
  if (isDeepSearchMode) {
180
211
  setIsDeepSearchModalOpen(true);
181
212
  }
182
213
  void (async () => {
183
214
  var _a2;
184
215
  const mode = modeOf(isDeepSearchMode, isBuildMode);
185
- let openShop = false;
186
- try {
187
- openShop = !shopRouteChecked && await shouldRedirectToShop(initialQuery != null ? initialQuery : "", (_a2 = attachments == null ? void 0 : attachments.length) != null ? _a2 : 0, mode);
188
- } catch (e) {
189
- openShop = false;
190
- }
216
+ const [openShop, nativeRoute] = shopRouteChecked ? [false, null] : await Promise.all([shouldRedirectToShop(initialQuery != null ? initialQuery : "", (_a2 = attachments == null ? void 0 : attachments.length) != null ? _a2 : 0, mode).catch(() => false), resolveNativeHandoff(initialQuery != null ? initialQuery : "", {
217
+ mode
218
+ })]);
191
219
  if (openShop) {
220
+ firstTurnRef.current = null;
221
+ releaseRoutingTurn(FIRST_TURN);
192
222
  replaceWithShopNative(navigation, shopHandoffParams({
193
223
  orgName: params.orgName,
194
224
  query: initialQuery != null ? initialQuery : "",
@@ -198,10 +228,9 @@ function ChatScreen() {
198
228
  }));
199
229
  return;
200
230
  }
201
- const nativeRoute = shopRouteChecked ? null : await resolveNativeHandoff(initialQuery != null ? initialQuery : "", {
202
- mode
203
- });
204
231
  if (nativeRoute) {
232
+ firstTurnRef.current = null;
233
+ releaseRoutingTurn(FIRST_TURN);
205
234
  replaceWithNativeApp(navigation, nativeRoute, nativeHandoffParams({
206
235
  orgName: params.orgName,
207
236
  query: initialQuery != null ? initialQuery : "",
@@ -210,7 +239,7 @@ function ChatScreen() {
210
239
  }));
211
240
  return;
212
241
  }
213
- void sendMessage(initialQuery != null ? initialQuery : "", attachments, channelId);
242
+ setFirstTurnRouted(true);
214
243
  })();
215
244
  try {
216
245
  navigation.setParams({
@@ -219,7 +248,15 @@ function ChatScreen() {
219
248
  });
220
249
  } catch (e) {
221
250
  }
222
- }, [channelId, initialQuery, initialAttachments, sendMessage, navigation, cdecli.status, isDeepSearchMode, isBuildMode, params.orgName, shopRouteChecked]);
251
+ }, [channelId, initialQuery, initialAttachments, navigation, isDeepSearchMode, isBuildMode, params.orgName, shopRouteChecked, releaseRoutingTurn]);
252
+ useEffect(() => {
253
+ const turn = firstTurnRef.current;
254
+ if (!firstTurnRouted || !turn || !channelId) return;
255
+ if (cdecli.status !== "connected" && cdecli.status !== "error") return;
256
+ firstTurnRef.current = null;
257
+ releaseRoutingTurn(FIRST_TURN);
258
+ void sendMessage(turn.text, turn.attachments, channelId);
259
+ }, [firstTurnRouted, cdecli.status, channelId, sendMessage, releaseRoutingTurn]);
223
260
  const handleAskUserAnswer = useCallback((formatted) => {
224
261
  const text = formatted.trim();
225
262
  if (!text) return;
@@ -260,15 +297,27 @@ function ChatScreen() {
260
297
  if (isDeepSearchMode) {
261
298
  setIsDeepSearchModalOpen(true);
262
299
  }
300
+ const turnId = `turn-${routingSeqRef.current += 1}`;
301
+ setRoutingTurns((prev) => [...prev, {
302
+ id: turnId,
303
+ text: t,
304
+ attachments: pending,
305
+ placement: chatBusy ? "queue" : "thread"
306
+ }]);
263
307
  void (async () => {
264
308
  var _a3;
265
- let openShop = false;
266
- try {
267
- openShop = await shouldRedirectToShop(t, (_a3 = pending == null ? void 0 : pending.length) != null ? _a3 : 0, isDeepSearchMode ? "deep-search" : "chat");
268
- } catch (e) {
269
- openShop = false;
270
- }
309
+ const mode = isDeepSearchMode ? "deep-search" : "chat";
310
+ const [openShop, nativeRoute] = await Promise.all([
311
+ // `forSend()` is undefined with nothing attached; `.length` on it threw,
312
+ // so a typed shopping ask never reached Shop.
313
+ shouldRedirectToShop(t, (_a3 = pending == null ? void 0 : pending.length) != null ? _a3 : 0, mode).catch(() => false),
314
+ resolveNativeHandoff(t, {
315
+ mode,
316
+ followUp: hasAssistantReply
317
+ })
318
+ ]);
271
319
  if (openShop) {
320
+ releaseRoutingTurn(turnId);
272
321
  if (messages.length === 0) {
273
322
  replaceWithShopNative(navigation, shopHandoffParams({
274
323
  orgName: params.orgName,
@@ -285,11 +334,8 @@ function ChatScreen() {
285
334
  }
286
335
  return;
287
336
  }
288
- const nativeRoute = await resolveNativeHandoff(t, {
289
- mode: isDeepSearchMode ? "deep-search" : "chat",
290
- followUp: hasAssistantReply
291
- });
292
337
  if (nativeRoute) {
338
+ releaseRoutingTurn(turnId);
293
339
  if (messages.length === 0) {
294
340
  replaceWithNativeApp(navigation, nativeRoute, nativeHandoffParams({
295
341
  orgName: params.orgName,
@@ -306,10 +352,11 @@ function ChatScreen() {
306
352
  }
307
353
  return;
308
354
  }
355
+ releaseRoutingTurn(turnId);
309
356
  void sendMessage(t, pending, channelId);
310
357
  })();
311
358
  imageAttachments.clear();
312
- }, [channelId, sendMessage, isDeepSearchMode, isBuildMode, navigation, params.orgName, imageAttachments, messages.length, hasAssistantReply]);
359
+ }, [channelId, sendMessage, isDeepSearchMode, isBuildMode, navigation, params.orgName, imageAttachments, messages.length, hasAssistantReply, chatBusy, releaseRoutingTurn]);
313
360
  const handleValueChange = useCallback((e) => {
314
361
  var _a2, _b;
315
362
  setValue((_b = (_a2 = e == null ? void 0 : e.nativeEvent) == null ? void 0 : _a2.text) != null ? _b : "");
@@ -452,15 +499,28 @@ function ChatScreen() {
452
499
  onStop: isStreaming && !canSubmit ? cancel : void 0,
453
500
  steerable: isLoading || isStreaming
454
501
  }), [canSubmit, handleSend, handleMicPress, isLoading, channelId, isStreaming, cancel, hasQuery, hasAttachments]);
502
+ const bannerError = chatError || (queuedMessages.length > 0 ? null : cdecli.error);
503
+ const followUpsAboveComposer = useMemo(() => [...queuedFollowUps, ...routingTurns.filter((turn) => turn.placement === "queue").map((turn) => ({
504
+ id: `routing-${turn.id}`,
505
+ text: turn.text,
506
+ attachments: turn.attachments
507
+ }))], [queuedFollowUps, routingTurns]);
508
+ const cancelFollowUpAboveComposer = useCallback((id) => {
509
+ if (id.startsWith("routing-")) return;
510
+ void cancelQueuedFollowUp(id);
511
+ }, [cancelQueuedFollowUp]);
455
512
  const gatewayLive = cdecli.channelConnected;
513
+ const queuedCount = queuedMessages.length === 1 ? "1 message" : `${queuedMessages.length} messages`;
514
+ let queuedNotice = `${queuedCount} waiting for the gateway. They send as soon as it connects.`;
515
+ if (gatewayLive) queuedNotice = `${queuedCount} not sent yet${outboxRetrying ? " - trying again" : ""}.`;
456
516
  const queuedActionColor = isDark ? "#fbbf24" : "#b45309";
457
517
  const handleSendQueuedNow = useCallback(() => {
458
518
  void flushQueuedMessages();
459
519
  }, [flushQueuedMessages]);
460
- const isHydrating = messagesLoading && messages.length === 0 && !response && !quickReply && !isStreaming;
461
- const isWaitingForGateway = !autoFiredRef.current && !!channelId && (!!initialQuery || hasComposerAttachments(channelId)) && cdecli.status !== "connected" && cdecli.status !== "error";
462
- const showSendingLoader = waitingForAssistant;
463
- const pinSendingLoaderNearComposer = messages.length > 0 || Boolean((response != null ? response : "").trim());
520
+ const routingInThread = routingTurns.some((turn) => turn.placement === "thread");
521
+ const isHydrating = messagesLoading && messages.length === 0 && !response && !quickReply && !isStreaming && !routingInThread;
522
+ const showSendingLoader = waitingForAssistant || routingInThread;
523
+ const pinSendingLoaderNearComposer = messages.length > 0 || Boolean((response != null ? response : "").trim()) || routingInThread;
464
524
  let transcriptArea;
465
525
  if (!channelId) {
466
526
  transcriptArea = /* @__PURE__ */ jsxs(Box, { flex: 1, alignItems: "center", justifyContent: "center", children: [
@@ -471,13 +531,6 @@ function ChatScreen() {
471
531
  ] });
472
532
  } else if (isHydrating) {
473
533
  transcriptArea = /* @__PURE__ */ jsx(Box, { flex: 1, px: "$4", pt: "$8", children: /* @__PURE__ */ jsx(ThinkingIndicator, { variant: "awaiting", color: secondaryTextColor, isDark, label: "Opening conversation\u2026" }) });
474
- } else if (isWaitingForGateway) {
475
- transcriptArea = /* @__PURE__ */ jsxs(Box, { flex: 1, alignItems: "center", justifyContent: "center", children: [
476
- /* @__PURE__ */ jsx(YantraBrandLoader, { size: YANTRA_LOADER_SIZE_COMPACT }),
477
- /* @__PURE__ */ jsx(Text, { style: [styles.statusText, {
478
- color: secondaryTextColor
479
- }], children: "Connecting gateway\u2026" })
480
- ] });
481
534
  } else {
482
535
  transcriptArea = // Links in the transcript open in the app on their surface, or in a browser sheet.
483
536
  /* @__PURE__ */ jsx(ChatLinkOpenerProvider, { children: /* @__PURE__ */ jsx(View, { style: styles.transcriptPane, children: /* @__PURE__ */ jsx(ChatTranscript, { messages: transcriptDisplayRows, streamingContent: transcriptStreaming, quickReply, listFooter: showSendingLoader && pinSendingLoaderNearComposer ? /* @__PURE__ */ jsx(View, { style: styles.thinkingInThread, children: /* @__PURE__ */ jsx(ThinkingIndicator, { variant: "awaiting", color: secondaryTextColor, isDark }) }) : null, renderMessageActions, isDark, listContentStyle: {
@@ -524,14 +577,10 @@ function ChatScreen() {
524
577
  flexDirection: "row",
525
578
  alignItems: "center"
526
579
  }, children: [
527
- /* @__PURE__ */ jsxs(Text, { fontSize: "$sm", style: {
580
+ /* @__PURE__ */ jsx(Text, { fontSize: "$sm", style: {
528
581
  color: isDark ? "#fde68a" : "#92400e",
529
582
  flex: 1
530
- }, children: [
531
- queuedMessages.length === 1 ? "1 message waiting for the gateway." : `${queuedMessages.length} messages waiting for the gateway.`,
532
- " ",
533
- gatewayLive ? "Sending\u2026" : "They send as soon as it connects."
534
- ] }),
583
+ }, children: queuedNotice }),
535
584
  gatewayLive ? /* @__PURE__ */ jsx(Pressable, { onPress: handleSendQueuedNow, hitSlop: 8, style: styles.queuedAction, children: /* @__PURE__ */ jsx(Text, { fontSize: "$xs", style: [styles.queuedActionText, {
536
585
  color: queuedActionColor
537
586
  }], children: "Send now" }) }) : null,
@@ -539,13 +588,13 @@ function ChatScreen() {
539
588
  color: queuedActionColor
540
589
  }], children: "Discard" }) })
541
590
  ] }),
542
- (chatError || cdecli.error) && /* @__PURE__ */ jsx(Box, { mb: "$2", mx: "$4", p: "$3", borderRadius: "$md", style: {
591
+ bannerError && /* @__PURE__ */ jsx(Box, { mb: "$2", mx: "$4", p: "$3", borderRadius: "$md", style: {
543
592
  backgroundColor: isDark ? "#2b1a1d" : "#fef2f2",
544
593
  borderWidth: 1,
545
594
  borderColor: isDark ? "#7f1d1d" : "#fecaca"
546
595
  }, children: /* @__PURE__ */ jsx(Text, { fontSize: "$sm", style: {
547
596
  color: isDark ? "#fecaca" : "#991b1b"
548
- }, children: chatError || cdecli.error }) }),
597
+ }, children: bannerError }) }),
549
598
  transcriptArea,
550
599
  showSendingLoader && !pinSendingLoaderNearComposer ? /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles.sendingLoaderEmptyState, children: /* @__PURE__ */ jsx(ThinkingIndicator, { variant: "awaiting", color: secondaryTextColor, isDark }) }) : null,
551
600
  /* @__PURE__ */ jsxs(KeyboardComposerDock, { closedInset: insets.bottom, onSwipeUp: () => {
@@ -569,7 +618,7 @@ function ChatScreen() {
569
618
  } }),
570
619
  /* @__PURE__ */ jsx(View, { style: {
571
620
  width: contentMaxWidth
572
- }, children: /* @__PURE__ */ jsx(QueuedFollowUps, { items: queuedFollowUps, onCancel: cancelQueuedFollowUp, onSendNow: sendQueuedNow, isDark }) }),
621
+ }, children: /* @__PURE__ */ jsx(QueuedFollowUps, { items: followUpsAboveComposer, onCancel: cancelFollowUpAboveComposer, onSendNow: sendQueuedNow, isDark }) }),
573
622
  /* @__PURE__ */ jsxs(View, { style: [styles.bottomComposerInner, {
574
623
  width: contentMaxWidth
575
624
  }], children: [