@comergehq/studio 0.1.34 → 0.1.36

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.
package/dist/index.mjs CHANGED
@@ -486,8 +486,8 @@ var require_remix_x_loop_lottie = __commonJS({
486
486
  });
487
487
 
488
488
  // src/studio/ComergeStudio.tsx
489
- import * as React51 from "react";
490
- import { Platform as RNPlatform, View as View49 } from "react-native";
489
+ import * as React52 from "react";
490
+ import { Platform as RNPlatform, View as View50 } from "react-native";
491
491
  import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
492
492
  import { isSystemEventEnvelope } from "@comergehq/runtime";
493
493
 
@@ -1605,6 +1605,34 @@ function compareMessages(a, b) {
1605
1605
  if (aMs !== bMs) return aMs - bMs;
1606
1606
  return String(a.createdAt).localeCompare(String(b.createdAt));
1607
1607
  }
1608
+ function parseAttachments(payload) {
1609
+ const raw = payload == null ? void 0 : payload.attachments;
1610
+ if (!Array.isArray(raw)) return [];
1611
+ const out = [];
1612
+ for (const item of raw) {
1613
+ if (!item || typeof item !== "object") continue;
1614
+ const id = typeof item.id === "string" ? String(item.id) : "";
1615
+ const name = typeof item.name === "string" ? String(item.name) : "";
1616
+ const mimeType = typeof item.mimeType === "string" ? String(item.mimeType) : "";
1617
+ const size = typeof item.size === "number" ? Number(item.size) : 0;
1618
+ if (!id || !name || !mimeType || !Number.isFinite(size) || size <= 0) continue;
1619
+ out.push({
1620
+ id,
1621
+ name,
1622
+ mimeType,
1623
+ size,
1624
+ uri: typeof item.downloadUrl === "string" ? String(item.downloadUrl) : void 0,
1625
+ width: typeof item.width === "number" ? Number(item.width) : void 0,
1626
+ height: typeof item.height === "number" ? Number(item.height) : void 0
1627
+ });
1628
+ }
1629
+ return out;
1630
+ }
1631
+ function hasAttachmentWithoutUrl(payload) {
1632
+ const attachments = parseAttachments(payload);
1633
+ if (attachments.length === 0) return false;
1634
+ return attachments.some((att) => !att.uri);
1635
+ }
1608
1636
  function mapMessageToChatMessage(m) {
1609
1637
  var _a, _b;
1610
1638
  const kind = typeof ((_a = m.payload) == null ? void 0 : _a.type) === "string" ? String(m.payload.type) : null;
@@ -1614,7 +1642,8 @@ function mapMessageToChatMessage(m) {
1614
1642
  content: typeof ((_b = m.payload) == null ? void 0 : _b.content) === "string" ? m.payload.content : "",
1615
1643
  createdAt: m.createdAt,
1616
1644
  kind,
1617
- meta: extractMeta(m.payload)
1645
+ meta: extractMeta(m.payload),
1646
+ attachments: parseAttachments(m.payload)
1618
1647
  };
1619
1648
  }
1620
1649
  function useThreadMessages(threadId) {
@@ -1625,9 +1654,19 @@ function useThreadMessages(threadId) {
1625
1654
  const activeRequestIdRef = React4.useRef(0);
1626
1655
  const foregroundSignal = useForegroundSignal(Boolean(threadId));
1627
1656
  const hasLoadedOnceRef = React4.useRef(false);
1657
+ const attachmentRecoveryTimerRef = React4.useRef(null);
1658
+ const lastAttachmentRecoveryAtRef = React4.useRef(0);
1628
1659
  React4.useEffect(() => {
1629
1660
  hasLoadedOnceRef.current = false;
1630
1661
  }, [threadId]);
1662
+ React4.useEffect(() => {
1663
+ return () => {
1664
+ if (attachmentRecoveryTimerRef.current) {
1665
+ clearTimeout(attachmentRecoveryTimerRef.current);
1666
+ attachmentRecoveryTimerRef.current = null;
1667
+ }
1668
+ };
1669
+ }, []);
1631
1670
  const upsertSorted = React4.useCallback((prev, m) => {
1632
1671
  const next = prev.filter((x) => x.id !== m.id);
1633
1672
  next.push(m);
@@ -1668,29 +1707,56 @@ function useThreadMessages(threadId) {
1668
1707
  }
1669
1708
  }
1670
1709
  }, [threadId]);
1710
+ const recoverAttachmentUrls = React4.useCallback(() => {
1711
+ if (!threadId) return;
1712
+ if (attachmentRecoveryTimerRef.current) return;
1713
+ const now = Date.now();
1714
+ if (now - lastAttachmentRecoveryAtRef.current < 2e3) return;
1715
+ attachmentRecoveryTimerRef.current = setTimeout(() => {
1716
+ attachmentRecoveryTimerRef.current = null;
1717
+ lastAttachmentRecoveryAtRef.current = Date.now();
1718
+ void refetch({ background: true });
1719
+ }, 250);
1720
+ }, [refetch, threadId]);
1671
1721
  React4.useEffect(() => {
1672
1722
  void refetch();
1673
1723
  }, [refetch]);
1674
1724
  React4.useEffect(() => {
1675
1725
  if (!threadId) return;
1676
1726
  const unsubscribe = messagesRepository.subscribeThread(threadId, {
1677
- onInsert: (m) => setRaw((prev) => upsertSorted(prev, m)),
1678
- onUpdate: (m) => setRaw((prev) => upsertSorted(prev, m)),
1727
+ onInsert: (m) => {
1728
+ setRaw((prev) => upsertSorted(prev, m));
1729
+ if (hasAttachmentWithoutUrl(m.payload)) {
1730
+ recoverAttachmentUrls();
1731
+ }
1732
+ },
1733
+ onUpdate: (m) => {
1734
+ setRaw((prev) => upsertSorted(prev, m));
1735
+ if (hasAttachmentWithoutUrl(m.payload)) {
1736
+ recoverAttachmentUrls();
1737
+ }
1738
+ },
1679
1739
  onDelete: (m) => setRaw((prev) => prev.filter((x) => x.id !== m.id))
1680
1740
  });
1681
1741
  return unsubscribe;
1682
- }, [threadId, upsertSorted, foregroundSignal]);
1742
+ }, [threadId, upsertSorted, foregroundSignal, recoverAttachmentUrls]);
1683
1743
  React4.useEffect(() => {
1684
1744
  if (!threadId) return;
1685
1745
  if (foregroundSignal <= 0) return;
1686
1746
  void refetch({ background: true });
1687
1747
  }, [foregroundSignal, refetch, threadId]);
1748
+ React4.useEffect(() => {
1749
+ if (!threadId) return;
1750
+ if (raw.length === 0) return;
1751
+ if (!raw.some((m) => hasAttachmentWithoutUrl(m.payload))) return;
1752
+ recoverAttachmentUrls();
1753
+ }, [raw, recoverAttachmentUrls, threadId]);
1688
1754
  const messages = React4.useMemo(() => {
1689
1755
  const visible = raw.filter((m) => !isQueuedHiddenMessage(m));
1690
1756
  const resolved = visible.length > 0 ? visible : raw;
1691
1757
  return resolved.map(mapMessageToChatMessage);
1692
1758
  }, [raw]);
1693
- return { raw, messages, loading, refreshing, error, refetch };
1759
+ return { raw, messages, loading, refreshing, error, refetch, recoverAttachmentUrls };
1694
1760
  }
1695
1761
 
1696
1762
  // src/studio/hooks/useBundleManager.ts
@@ -2857,7 +2923,7 @@ function useMergeRequests(params) {
2857
2923
 
2858
2924
  // src/studio/hooks/useAttachmentUpload.ts
2859
2925
  import * as React7 from "react";
2860
- import { Platform as Platform2 } from "react-native";
2926
+ import { Image, Platform as Platform2 } from "react-native";
2861
2927
  import * as FileSystem2 from "expo-file-system/legacy";
2862
2928
 
2863
2929
  // src/data/attachment/remote.ts
@@ -2953,6 +3019,23 @@ function getMimeTypeFromDataUrl(dataUrl) {
2953
3019
  const mimeMatch = header.match(/data:(.*?);base64/i);
2954
3020
  return (mimeMatch == null ? void 0 : mimeMatch[1]) ?? "image/png";
2955
3021
  }
3022
+ async function getImageDimensionsFromDataUrl(dataUrl) {
3023
+ try {
3024
+ const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
3025
+ const dims = await new Promise((resolve, reject) => {
3026
+ Image.getSize(
3027
+ normalized,
3028
+ (width, height) => resolve({ width, height }),
3029
+ (err) => reject(err)
3030
+ );
3031
+ });
3032
+ if (dims.width > 0 && dims.height > 0) {
3033
+ return { width: Math.round(dims.width), height: Math.round(dims.height) };
3034
+ }
3035
+ } catch {
3036
+ }
3037
+ return {};
3038
+ }
2956
3039
  function useAttachmentUpload() {
2957
3040
  const [uploading, setUploading] = React7.useState(false);
2958
3041
  const [error, setError] = React7.useState(null);
@@ -2967,17 +3050,24 @@ function useAttachmentUpload() {
2967
3050
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
2968
3051
  const blob = Platform2.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
2969
3052
  const mimeType = getMimeTypeFromDataUrl(normalized);
2970
- return { blob, idx, mimeType };
3053
+ const dimensions = mimeType.startsWith("image/") ? await getImageDimensionsFromDataUrl(normalized) : {};
3054
+ return { blob, idx, mimeType, ...dimensions };
2971
3055
  })
2972
3056
  );
2973
- const files = blobs.map(({ blob, mimeType }, idx) => ({
3057
+ const files = blobs.map(({ blob, mimeType, width, height }, idx) => ({
2974
3058
  name: `attachment-${Date.now()}-${idx}.png`,
2975
3059
  size: blob.size,
2976
- mimeType
3060
+ mimeType,
3061
+ width,
3062
+ height
2977
3063
  }));
2978
3064
  const presign = await attachmentRepository.presign({ threadId, appId, files });
2979
3065
  await Promise.all(presign.uploads.map((u, index) => attachmentRepository.upload(u, blobs[index].blob)));
2980
- return presign.uploads.map((u) => u.attachment);
3066
+ return presign.uploads.map((u, index) => ({
3067
+ ...u.attachment,
3068
+ width: blobs[index].width,
3069
+ height: blobs[index].height
3070
+ }));
2981
3071
  } catch (e) {
2982
3072
  const err = e instanceof Error ? e : new Error(String(e));
2983
3073
  setError(err);
@@ -2996,13 +3086,16 @@ function useAttachmentUpload() {
2996
3086
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
2997
3087
  const blob = Platform2.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
2998
3088
  const mimeType = getMimeTypeFromDataUrl(normalized);
2999
- return { blob, mimeType };
3089
+ const dimensions = mimeType.startsWith("image/") ? await getImageDimensionsFromDataUrl(normalized) : {};
3090
+ return { blob, mimeType, ...dimensions };
3000
3091
  })
3001
3092
  );
3002
- const files = blobs.map(({ blob, mimeType }, idx) => ({
3093
+ const files = blobs.map(({ blob, mimeType, width, height }, idx) => ({
3003
3094
  name: `attachment-${Date.now()}-${idx}.png`,
3004
3095
  size: blob.size,
3005
- mimeType
3096
+ mimeType,
3097
+ width,
3098
+ height
3006
3099
  }));
3007
3100
  const presign = await attachmentRepository.stagePresign({ files });
3008
3101
  await Promise.all(presign.uploads.map((u, index) => attachmentRepository.uploadStaged(u, blobs[index].blob)));
@@ -3285,8 +3378,8 @@ function RuntimeRenderer({
3285
3378
  }
3286
3379
 
3287
3380
  // src/studio/ui/StudioOverlay.tsx
3288
- import * as React46 from "react";
3289
- import { InteractionManager, Keyboard as Keyboard5, Platform as Platform11, View as View48, useWindowDimensions as useWindowDimensions4 } from "react-native";
3381
+ import * as React47 from "react";
3382
+ import { InteractionManager, Keyboard as Keyboard5, Platform as Platform11, View as View49, useWindowDimensions as useWindowDimensions5 } from "react-native";
3290
3383
 
3291
3384
  // src/components/studio-sheet/StudioBottomSheet.tsx
3292
3385
  import * as React12 from "react";
@@ -3845,6 +3938,7 @@ function EdgeGlowFrame({
3845
3938
  role = "accent",
3846
3939
  thickness = 40,
3847
3940
  intensity = 1,
3941
+ animationDurationMs = 300,
3848
3942
  style
3849
3943
  }) {
3850
3944
  const theme = useTheme();
@@ -3853,10 +3947,10 @@ function EdgeGlowFrame({
3853
3947
  React14.useEffect(() => {
3854
3948
  Animated3.timing(anim, {
3855
3949
  toValue: visible ? 1 : 0,
3856
- duration: 300,
3950
+ duration: animationDurationMs,
3857
3951
  useNativeDriver: true
3858
3952
  }).start();
3859
- }, [anim, visible]);
3953
+ }, [anim, visible, animationDurationMs]);
3860
3954
  const c = baseColor(role, theme);
3861
3955
  const strong = withAlpha(c, 0.6 * alpha);
3862
3956
  const soft = withAlpha(c, 0.22 * alpha);
@@ -4382,7 +4476,16 @@ function DrawModeOverlay({
4382
4476
  }, [captureTargetRef, capturing, onCapture]);
4383
4477
  if (!visible) return null;
4384
4478
  return /* @__PURE__ */ jsxs7(View10, { style: [StyleSheet3.absoluteFill, styles3.root, style], pointerEvents: "box-none", children: [
4385
- /* @__PURE__ */ jsx14(EdgeGlowFrame, { visible: !hideUi, role: "danger", thickness: 50, intensity: 1 }),
4479
+ /* @__PURE__ */ jsx14(
4480
+ EdgeGlowFrame,
4481
+ {
4482
+ visible: !hideUi,
4483
+ role: "danger",
4484
+ thickness: 50,
4485
+ intensity: 1,
4486
+ animationDurationMs: hideUi ? 0 : 300
4487
+ }
4488
+ ),
4386
4489
  /* @__PURE__ */ jsx14(
4387
4490
  DrawSurface,
4388
4491
  {
@@ -4436,7 +4539,7 @@ import {
4436
4539
  ActivityIndicator as ActivityIndicator2,
4437
4540
  Animated as Animated5,
4438
4541
  Dimensions,
4439
- Image,
4542
+ Image as Image2,
4440
4543
  Pressable as Pressable4,
4441
4544
  ScrollView,
4442
4545
  View as View11
@@ -4538,7 +4641,7 @@ function AspectRatioThumbnail({
4538
4641
  const [aspectRatio, setAspectRatio] = React19.useState(1);
4539
4642
  return /* @__PURE__ */ jsxs8(View11, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
4540
4643
  /* @__PURE__ */ jsx17(View11, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx17(
4541
- Image,
4644
+ Image2,
4542
4645
  {
4543
4646
  source: { uri },
4544
4647
  style: { width: "100%", height: "100%" },
@@ -4747,7 +4850,7 @@ import * as React20 from "react";
4747
4850
  import { View as View13 } from "react-native";
4748
4851
 
4749
4852
  // src/components/primitives/Avatar.tsx
4750
- import { Image as Image2, View as View12 } from "react-native";
4853
+ import { Image as Image3, View as View12 } from "react-native";
4751
4854
  import { jsx as jsx18 } from "react/jsx-runtime";
4752
4855
  function initialsFrom(name) {
4753
4856
  var _a, _b;
@@ -4782,7 +4885,7 @@ function Avatar({
4782
4885
  style
4783
4886
  ],
4784
4887
  children: uri ? /* @__PURE__ */ jsx18(
4785
- Image2,
4888
+ Image3,
4786
4889
  {
4787
4890
  source: { uri },
4788
4891
  style: [{ width: size, height: size }, imageStyle],
@@ -5587,12 +5690,12 @@ function PreviewPlaceholder({ visible, style }) {
5587
5690
  }
5588
5691
 
5589
5692
  // src/components/preview/PreviewImage.tsx
5590
- import { Image as Image3 } from "react-native";
5693
+ import { Image as Image4 } from "react-native";
5591
5694
  import { jsx as jsx29 } from "react/jsx-runtime";
5592
5695
  function PreviewImage({ uri, onLoad, style }) {
5593
5696
  if (!uri) return null;
5594
5697
  return /* @__PURE__ */ jsx29(
5595
- Image3,
5698
+ Image4,
5596
5699
  {
5597
5700
  source: { uri },
5598
5701
  resizeMode: "cover",
@@ -5819,7 +5922,7 @@ function PreviewHeroSection({
5819
5922
 
5820
5923
  // src/studio/ui/preview-panel/PreviewRelatedAppsSection.tsx
5821
5924
  import * as React27 from "react";
5822
- import { ActivityIndicator as ActivityIndicator4, Pressable as Pressable9, ScrollView as ScrollView2, View as View24 } from "react-native";
5925
+ import { ActivityIndicator as ActivityIndicator4, Pressable as Pressable9, ScrollView as ScrollView2, View as View24, useWindowDimensions as useWindowDimensions3 } from "react-native";
5823
5926
 
5824
5927
  // src/components/primitives/Modal.tsx
5825
5928
  import {
@@ -5907,7 +6010,10 @@ function PreviewRelatedAppsSection({
5907
6010
  }) {
5908
6011
  var _a;
5909
6012
  const theme = useTheme();
6013
+ const { height: windowHeight } = useWindowDimensions3();
5910
6014
  const [relatedAppsOpen, setRelatedAppsOpen] = React27.useState(false);
6015
+ const modalMaxHeight = Math.max(240, windowHeight * 0.5);
6016
+ const modalScrollMaxHeight = Math.max(140, modalMaxHeight - 96);
5911
6017
  const relatedAppItems = React27.useMemo(() => {
5912
6018
  if (!relatedApps) return [];
5913
6019
  const items = [];
@@ -6040,10 +6146,7 @@ function PreviewRelatedAppsSection({
6040
6146
  }
6041
6147
  )
6042
6148
  ] }),
6043
- /* @__PURE__ */ jsxs19(View24, { style: { alignItems: "flex-end", gap: 6 }, children: [
6044
- /* @__PURE__ */ jsx36(View24, { style: { minHeight: 20, justifyContent: "center" }, children: item.app.status !== "ready" ? /* @__PURE__ */ jsx36(PreviewStatusBadge, { status: item.app.status }) : null }),
6045
- isSwitching ? /* @__PURE__ */ jsx36(ActivityIndicator4, { size: "small", color: theme.colors.primary }) : null
6046
- ] })
6149
+ /* @__PURE__ */ jsx36(View24, { style: { alignItems: "flex-end", gap: 6 }, children: isSwitching ? /* @__PURE__ */ jsx36(ActivityIndicator4, { size: "small", color: theme.colors.primary }) : null })
6047
6150
  ] })
6048
6151
  },
6049
6152
  item.app.id
@@ -6086,17 +6189,35 @@ function PreviewRelatedAppsSection({
6086
6189
  children: inlineItems.map((item) => renderRelatedCard(item))
6087
6190
  }
6088
6191
  ),
6089
- /* @__PURE__ */ jsx36(Modal, { visible: relatedAppsOpen, onRequestClose: closeRelatedApps, children: /* @__PURE__ */ jsxs19(View24, { style: { gap: theme.spacing.sm }, children: [
6090
- /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.text, fontSize: 18, fontWeight: theme.typography.fontWeight.semibold }, children: "Related apps" }),
6091
- sectionedRelatedApps.original.length > 0 ? /* @__PURE__ */ jsxs19(View24, { children: [
6092
- /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Original" }),
6093
- sectionedRelatedApps.original.map((item) => renderRelatedCard(item, { fullWidth: true }))
6094
- ] }) : null,
6095
- sectionedRelatedApps.remixes.length > 0 ? /* @__PURE__ */ jsxs19(View24, { children: [
6096
- /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Remixes" }),
6097
- sectionedRelatedApps.remixes.map((item) => renderRelatedCard(item, { fullWidth: true }))
6098
- ] }) : null
6099
- ] }) })
6192
+ /* @__PURE__ */ jsx36(
6193
+ Modal,
6194
+ {
6195
+ visible: relatedAppsOpen,
6196
+ onRequestClose: closeRelatedApps,
6197
+ contentStyle: { maxHeight: modalMaxHeight, overflow: "hidden" },
6198
+ children: /* @__PURE__ */ jsxs19(View24, { style: { gap: theme.spacing.sm, minHeight: 0 }, children: [
6199
+ /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.text, fontSize: 18, fontWeight: theme.typography.fontWeight.semibold }, children: "Related apps" }),
6200
+ /* @__PURE__ */ jsxs19(
6201
+ ScrollView2,
6202
+ {
6203
+ style: { maxHeight: modalScrollMaxHeight },
6204
+ contentContainerStyle: { paddingBottom: theme.spacing.xs, gap: theme.spacing.sm },
6205
+ showsVerticalScrollIndicator: true,
6206
+ children: [
6207
+ sectionedRelatedApps.original.length > 0 ? /* @__PURE__ */ jsxs19(View24, { children: [
6208
+ /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Original" }),
6209
+ sectionedRelatedApps.original.map((item) => renderRelatedCard(item, { fullWidth: true }))
6210
+ ] }) : null,
6211
+ sectionedRelatedApps.remixes.length > 0 ? /* @__PURE__ */ jsxs19(View24, { children: [
6212
+ /* @__PURE__ */ jsx36(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Remixes" }),
6213
+ sectionedRelatedApps.remixes.map((item) => renderRelatedCard(item, { fullWidth: true }))
6214
+ ] }) : null
6215
+ ]
6216
+ }
6217
+ )
6218
+ ] })
6219
+ }
6220
+ )
6100
6221
  ] });
6101
6222
  }
6102
6223
 
@@ -6713,7 +6834,7 @@ function MergeRequestStatusCard({
6713
6834
 
6714
6835
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
6715
6836
  import * as React32 from "react";
6716
- import { Animated as Animated9, FlatList, View as View33, useWindowDimensions as useWindowDimensions3 } from "react-native";
6837
+ import { Animated as Animated9, FlatList, View as View33, useWindowDimensions as useWindowDimensions4 } from "react-native";
6717
6838
 
6718
6839
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
6719
6840
  import * as React31 from "react";
@@ -6928,7 +7049,7 @@ function ReviewMergeRequestCarousel({
6928
7049
  style
6929
7050
  }) {
6930
7051
  const theme = useTheme();
6931
- const { width } = useWindowDimensions3();
7052
+ const { width } = useWindowDimensions4();
6932
7053
  const [expanded, setExpanded] = React32.useState({});
6933
7054
  const carouselScrollX = React32.useRef(new Animated9.Value(0)).current;
6934
7055
  const peekAmount = 24;
@@ -7669,22 +7790,22 @@ ${shareUrl}`;
7669
7790
  }
7670
7791
 
7671
7792
  // src/studio/ui/ChatPanel.tsx
7672
- import * as React43 from "react";
7673
- import { ActivityIndicator as ActivityIndicator10, View as View46 } from "react-native";
7793
+ import * as React44 from "react";
7794
+ import { ActivityIndicator as ActivityIndicator10, View as View47 } from "react-native";
7674
7795
 
7675
7796
  // src/components/chat/ChatPage.tsx
7676
- import * as React40 from "react";
7677
- import { Platform as Platform10, View as View39 } from "react-native";
7797
+ import * as React41 from "react";
7798
+ import { Platform as Platform10, View as View40 } from "react-native";
7678
7799
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
7679
7800
 
7680
7801
  // src/components/chat/ChatMessageList.tsx
7681
- import * as React39 from "react";
7682
- import { View as View38 } from "react-native";
7802
+ import * as React40 from "react";
7803
+ import { View as View39 } from "react-native";
7683
7804
  import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
7684
7805
 
7685
7806
  // src/components/chat/ChatMessageBubble.tsx
7686
- import * as React37 from "react";
7687
- import { View as View36 } from "react-native";
7807
+ import * as React38 from "react";
7808
+ import { View as View37 } from "react-native";
7688
7809
  import { CheckCheck as CheckCheck2, GitMerge as GitMerge2, RotateCcw } from "lucide-react-native";
7689
7810
 
7690
7811
  // src/components/primitives/Button.tsx
@@ -7752,19 +7873,269 @@ function Button({
7752
7873
  );
7753
7874
  }
7754
7875
 
7876
+ // src/components/chat/ChatMessageAttachments.tsx
7877
+ import * as React37 from "react";
7878
+ import {
7879
+ Animated as Animated10,
7880
+ Dimensions as Dimensions3,
7881
+ FlatList as FlatList2,
7882
+ Modal as Modal3,
7883
+ Pressable as Pressable16,
7884
+ StyleSheet as StyleSheet4,
7885
+ View as View36
7886
+ } from "react-native";
7887
+ import { Image as ExpoImage } from "expo-image";
7888
+ import { Fragment as Fragment7, jsx as jsx49, jsxs as jsxs30 } from "react/jsx-runtime";
7889
+ function ChatMessageAttachments({
7890
+ messageId,
7891
+ attachments,
7892
+ align = "left",
7893
+ onAttachmentLoadError
7894
+ }) {
7895
+ const theme = useTheme();
7896
+ const [viewerVisible, setViewerVisible] = React37.useState(false);
7897
+ const [viewerIndex, setViewerIndex] = React37.useState(0);
7898
+ const failedKeysRef = React37.useRef(/* @__PURE__ */ new Set());
7899
+ const [loadingById, setLoadingById] = React37.useState({});
7900
+ const [modalLoadingById, setModalLoadingById] = React37.useState({});
7901
+ const pulse = React37.useRef(new Animated10.Value(0.45)).current;
7902
+ const imageAttachments = React37.useMemo(
7903
+ () => attachments.filter(
7904
+ (att) => att.mimeType.startsWith("image/") && typeof att.uri === "string" && att.uri.length > 0
7905
+ ),
7906
+ [attachments]
7907
+ );
7908
+ const itemHeight = imageAttachments.length === 1 ? 180 : 124;
7909
+ const maxItemWidth = imageAttachments.length === 1 ? 280 : 180;
7910
+ const getAspectRatio = (att) => {
7911
+ const width = typeof att.width === "number" ? att.width : 0;
7912
+ const height = typeof att.height === "number" ? att.height : 0;
7913
+ if (width > 0 && height > 0) {
7914
+ return Math.max(0.35, Math.min(2.4, width / height));
7915
+ }
7916
+ return 0.8;
7917
+ };
7918
+ React37.useEffect(() => {
7919
+ Animated10.loop(
7920
+ Animated10.sequence([
7921
+ Animated10.timing(pulse, { toValue: 0.85, duration: 650, useNativeDriver: true }),
7922
+ Animated10.timing(pulse, { toValue: 0.45, duration: 650, useNativeDriver: true })
7923
+ ])
7924
+ ).start();
7925
+ }, [pulse]);
7926
+ React37.useEffect(() => {
7927
+ if (imageAttachments.length === 0) {
7928
+ setLoadingById({});
7929
+ setModalLoadingById({});
7930
+ return;
7931
+ }
7932
+ setLoadingById((prev) => {
7933
+ const next = {};
7934
+ for (const att of imageAttachments) {
7935
+ next[att.id] = prev[att.id] ?? true;
7936
+ }
7937
+ return next;
7938
+ });
7939
+ }, [imageAttachments]);
7940
+ React37.useEffect(() => {
7941
+ if (!viewerVisible) return;
7942
+ if (imageAttachments.length === 0) {
7943
+ setModalLoadingById({});
7944
+ return;
7945
+ }
7946
+ setModalLoadingById(() => {
7947
+ const next = {};
7948
+ for (const att of imageAttachments) {
7949
+ next[att.id] = true;
7950
+ }
7951
+ return next;
7952
+ });
7953
+ }, [viewerVisible, imageAttachments]);
7954
+ if (imageAttachments.length === 0) return null;
7955
+ const handleError = (attachmentId) => {
7956
+ const key = `${messageId}:${attachmentId}`;
7957
+ if (failedKeysRef.current.has(key)) return;
7958
+ failedKeysRef.current.add(key);
7959
+ onAttachmentLoadError == null ? void 0 : onAttachmentLoadError(messageId, attachmentId);
7960
+ };
7961
+ return /* @__PURE__ */ jsxs30(Fragment7, { children: [
7962
+ /* @__PURE__ */ jsx49(
7963
+ View36,
7964
+ {
7965
+ style: {
7966
+ flexDirection: "row",
7967
+ flexWrap: "wrap",
7968
+ justifyContent: align === "right" ? "flex-end" : "flex-start",
7969
+ alignSelf: align === "right" ? "flex-end" : "flex-start",
7970
+ gap: theme.spacing.sm,
7971
+ marginBottom: theme.spacing.sm
7972
+ },
7973
+ children: imageAttachments.map((att, index) => /* @__PURE__ */ jsxs30(
7974
+ Pressable16,
7975
+ {
7976
+ onPress: () => {
7977
+ setViewerIndex(index);
7978
+ setViewerVisible(true);
7979
+ },
7980
+ accessibilityRole: "button",
7981
+ accessibilityLabel: `Attachment ${index + 1} of ${imageAttachments.length}`,
7982
+ style: {
7983
+ height: itemHeight,
7984
+ aspectRatio: getAspectRatio(att),
7985
+ maxWidth: maxItemWidth,
7986
+ borderRadius: theme.radii.lg,
7987
+ overflow: "hidden"
7988
+ },
7989
+ children: [
7990
+ loadingById[att.id] ? /* @__PURE__ */ jsx49(
7991
+ Animated10.View,
7992
+ {
7993
+ style: {
7994
+ ...StyleSheet4.absoluteFillObject,
7995
+ opacity: pulse,
7996
+ backgroundColor: theme.colors.border
7997
+ }
7998
+ }
7999
+ ) : null,
8000
+ /* @__PURE__ */ jsx49(
8001
+ ExpoImage,
8002
+ {
8003
+ source: { uri: att.uri },
8004
+ style: { width: "100%", height: "100%" },
8005
+ contentFit: "contain",
8006
+ transition: 140,
8007
+ cachePolicy: "memory-disk",
8008
+ onLoadStart: () => {
8009
+ setLoadingById((prev) => ({ ...prev, [att.id]: true }));
8010
+ },
8011
+ onLoadEnd: () => {
8012
+ setLoadingById((prev) => ({ ...prev, [att.id]: false }));
8013
+ },
8014
+ onError: () => handleError(att.id)
8015
+ }
8016
+ )
8017
+ ]
8018
+ },
8019
+ att.id
8020
+ ))
8021
+ }
8022
+ ),
8023
+ /* @__PURE__ */ jsx49(Modal3, { visible: viewerVisible, transparent: true, animationType: "fade", onRequestClose: () => setViewerVisible(false), children: /* @__PURE__ */ jsxs30(View36, { style: { flex: 1, backgroundColor: "rgba(0,0,0,0.95)" }, children: [
8024
+ /* @__PURE__ */ jsx49(
8025
+ View36,
8026
+ {
8027
+ style: {
8028
+ position: "absolute",
8029
+ top: 56,
8030
+ right: 16,
8031
+ zIndex: 2
8032
+ },
8033
+ children: /* @__PURE__ */ jsx49(
8034
+ Pressable16,
8035
+ {
8036
+ accessibilityRole: "button",
8037
+ accessibilityLabel: "Close attachment viewer",
8038
+ onPress: () => setViewerVisible(false),
8039
+ style: {
8040
+ width: 44,
8041
+ height: 44,
8042
+ borderRadius: 22,
8043
+ alignItems: "center",
8044
+ justifyContent: "center",
8045
+ backgroundColor: "rgba(255,255,255,0.15)"
8046
+ },
8047
+ children: /* @__PURE__ */ jsx49(IconClose, { size: 18, colorToken: "onPrimary" })
8048
+ }
8049
+ )
8050
+ }
8051
+ ),
8052
+ /* @__PURE__ */ jsx49(
8053
+ FlatList2,
8054
+ {
8055
+ data: imageAttachments,
8056
+ horizontal: true,
8057
+ pagingEnabled: true,
8058
+ initialScrollIndex: viewerIndex,
8059
+ keyExtractor: (item) => item.id,
8060
+ showsHorizontalScrollIndicator: false,
8061
+ getItemLayout: (_, index) => {
8062
+ const width = Dimensions3.get("window").width;
8063
+ return { length: width, offset: width * index, index };
8064
+ },
8065
+ renderItem: ({ item, index }) => /* @__PURE__ */ jsxs30(View36, { style: { width: Dimensions3.get("window").width, height: "100%", justifyContent: "center" }, children: [
8066
+ modalLoadingById[item.id] ? /* @__PURE__ */ jsx49(
8067
+ Animated10.View,
8068
+ {
8069
+ style: {
8070
+ ...StyleSheet4.absoluteFillObject,
8071
+ opacity: pulse,
8072
+ backgroundColor: theme.colors.border
8073
+ }
8074
+ }
8075
+ ) : null,
8076
+ /* @__PURE__ */ jsx49(
8077
+ ExpoImage,
8078
+ {
8079
+ source: { uri: item.uri },
8080
+ style: { width: "100%", height: "78%" },
8081
+ contentFit: "contain",
8082
+ transition: 140,
8083
+ cachePolicy: "memory-disk",
8084
+ onLoadStart: () => {
8085
+ setModalLoadingById((prev) => ({ ...prev, [item.id]: true }));
8086
+ },
8087
+ onLoadEnd: () => {
8088
+ setModalLoadingById((prev) => ({ ...prev, [item.id]: false }));
8089
+ },
8090
+ onError: () => handleError(item.id)
8091
+ }
8092
+ ),
8093
+ /* @__PURE__ */ jsxs30(
8094
+ Text,
8095
+ {
8096
+ variant: "caption",
8097
+ color: "#FFFFFF",
8098
+ style: { textAlign: "center", marginTop: theme.spacing.sm },
8099
+ children: [
8100
+ index + 1,
8101
+ " / ",
8102
+ imageAttachments.length
8103
+ ]
8104
+ }
8105
+ )
8106
+ ] })
8107
+ }
8108
+ )
8109
+ ] }) })
8110
+ ] });
8111
+ }
8112
+
7755
8113
  // src/components/chat/ChatMessageBubble.tsx
7756
- import { jsx as jsx49, jsxs as jsxs30 } from "react/jsx-runtime";
8114
+ import { jsx as jsx50, jsxs as jsxs31 } from "react/jsx-runtime";
7757
8115
  function areMessageMetaEqual(a, b) {
7758
8116
  if (a === b) return true;
7759
8117
  if (!a || !b) return a === b;
7760
8118
  return a.kind === b.kind && a.event === b.event && a.status === b.status && a.mergeRequestId === b.mergeRequestId && a.sourceAppId === b.sourceAppId && a.targetAppId === b.targetAppId && a.appId === b.appId && a.threadId === b.threadId;
7761
8119
  }
8120
+ function areMessageAttachmentsEqual(a, b) {
8121
+ if (a === b) return true;
8122
+ const left = a ?? [];
8123
+ const right = b ?? [];
8124
+ if (left.length !== right.length) return false;
8125
+ for (let i = 0; i < left.length; i += 1) {
8126
+ if (left[i].id !== right[i].id || left[i].name !== right[i].name || left[i].mimeType !== right[i].mimeType || left[i].size !== right[i].size || left[i].uri !== right[i].uri || left[i].width !== right[i].width || left[i].height !== right[i].height) {
8127
+ return false;
8128
+ }
8129
+ }
8130
+ return true;
8131
+ }
7762
8132
  function ChatMessageBubbleInner({
7763
8133
  message,
7764
8134
  renderContent,
7765
8135
  isLast,
7766
8136
  retrying,
7767
8137
  onRetryMessage,
8138
+ onAttachmentLoadError,
7768
8139
  style
7769
8140
  }) {
7770
8141
  var _a, _b;
@@ -7783,11 +8154,36 @@ function ChatMessageBubbleInner({
7783
8154
  const bodyColor = metaStatus === "success" ? theme.colors.success : metaStatus === "error" ? theme.colors.danger : void 0;
7784
8155
  const showRetry = Boolean(onRetryMessage) && isLast && metaStatus === "error" && message.author === "human";
7785
8156
  const retryLabel = retrying ? "Retrying..." : "Retry";
7786
- const handleRetryPress = React37.useCallback(() => {
8157
+ const hasText = message.content.trim().length > 0;
8158
+ const attachments = message.attachments ?? [];
8159
+ const hasAttachments = attachments.length > 0;
8160
+ const hasStatusIcon = isMergeCompleted || isSyncCompleted || isMergeApproved || isSyncStarted;
8161
+ const shouldRenderBubble = hasText || hasStatusIcon;
8162
+ const handleRetryPress = React38.useCallback(() => {
7787
8163
  onRetryMessage == null ? void 0 : onRetryMessage(message.id);
7788
8164
  }, [message.id, onRetryMessage]);
7789
- return /* @__PURE__ */ jsxs30(View36, { style: [align, style], children: [
7790
- /* @__PURE__ */ jsx49(
8165
+ return /* @__PURE__ */ jsxs31(View37, { style: [align, style], children: [
8166
+ hasAttachments ? /* @__PURE__ */ jsx50(
8167
+ View37,
8168
+ {
8169
+ style: {
8170
+ maxWidth: "85%",
8171
+ marginBottom: shouldRenderBubble ? theme.spacing.xs : 0,
8172
+ alignSelf: isHuman ? "flex-end" : "flex-start",
8173
+ alignItems: isHuman ? "flex-end" : "flex-start"
8174
+ },
8175
+ children: /* @__PURE__ */ jsx50(
8176
+ ChatMessageAttachments,
8177
+ {
8178
+ messageId: message.id,
8179
+ attachments,
8180
+ align: isHuman ? "right" : "left",
8181
+ onAttachmentLoadError
8182
+ }
8183
+ )
8184
+ }
8185
+ ) : null,
8186
+ shouldRenderBubble ? /* @__PURE__ */ jsx50(
7791
8187
  Surface,
7792
8188
  {
7793
8189
  variant: bubbleVariant,
@@ -7802,14 +8198,14 @@ function ChatMessageBubbleInner({
7802
8198
  },
7803
8199
  cornerStyle
7804
8200
  ],
7805
- children: /* @__PURE__ */ jsxs30(View36, { style: { flexDirection: "row", alignItems: "center" }, children: [
7806
- isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ jsx49(CheckCheck2, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
7807
- isMergeApproved || isSyncStarted ? /* @__PURE__ */ jsx49(GitMerge2, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
7808
- /* @__PURE__ */ jsx49(View36, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ jsx49(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
8201
+ children: /* @__PURE__ */ jsxs31(View37, { style: { flexDirection: "row", alignItems: "center" }, children: [
8202
+ isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ jsx50(CheckCheck2, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
8203
+ isMergeApproved || isSyncStarted ? /* @__PURE__ */ jsx50(GitMerge2, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
8204
+ /* @__PURE__ */ jsx50(View37, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ jsx50(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
7809
8205
  ] })
7810
8206
  }
7811
- ),
7812
- showRetry ? /* @__PURE__ */ jsx49(View36, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ jsx49(
8207
+ ) : null,
8208
+ showRetry ? /* @__PURE__ */ jsx50(View37, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ jsx50(
7813
8209
  Button,
7814
8210
  {
7815
8211
  variant: "ghost",
@@ -7818,9 +8214,9 @@ function ChatMessageBubbleInner({
7818
8214
  disabled: retrying,
7819
8215
  style: { borderColor: theme.colors.danger },
7820
8216
  accessibilityLabel: "Retry send",
7821
- children: /* @__PURE__ */ jsxs30(View36, { style: { flexDirection: "row", alignItems: "center" }, children: [
7822
- !retrying ? /* @__PURE__ */ jsx49(RotateCcw, { size: 14, color: theme.colors.danger }) : null,
7823
- /* @__PURE__ */ jsx49(
8217
+ children: /* @__PURE__ */ jsxs31(View37, { style: { flexDirection: "row", alignItems: "center" }, children: [
8218
+ !retrying ? /* @__PURE__ */ jsx50(RotateCcw, { size: 14, color: theme.colors.danger }) : null,
8219
+ /* @__PURE__ */ jsx50(
7824
8220
  Text,
7825
8221
  {
7826
8222
  variant: "caption",
@@ -7836,30 +8232,30 @@ function ChatMessageBubbleInner({
7836
8232
  ] });
7837
8233
  }
7838
8234
  function areEqual(prev, next) {
7839
- return prev.message.id === next.message.id && prev.message.author === next.message.author && prev.message.content === next.message.content && prev.message.kind === next.message.kind && String(prev.message.createdAt) === String(next.message.createdAt) && areMessageMetaEqual(prev.message.meta, next.message.meta) && prev.renderContent === next.renderContent && prev.isLast === next.isLast && prev.retrying === next.retrying && prev.onRetryMessage === next.onRetryMessage && prev.style === next.style;
8235
+ return prev.message.id === next.message.id && prev.message.author === next.message.author && prev.message.content === next.message.content && areMessageAttachmentsEqual(prev.message.attachments, next.message.attachments) && prev.message.kind === next.message.kind && String(prev.message.createdAt) === String(next.message.createdAt) && areMessageMetaEqual(prev.message.meta, next.message.meta) && prev.renderContent === next.renderContent && prev.isLast === next.isLast && prev.retrying === next.retrying && prev.onRetryMessage === next.onRetryMessage && prev.onAttachmentLoadError === next.onAttachmentLoadError && prev.style === next.style;
7840
8236
  }
7841
- var ChatMessageBubble = React37.memo(ChatMessageBubbleInner, areEqual);
8237
+ var ChatMessageBubble = React38.memo(ChatMessageBubbleInner, areEqual);
7842
8238
  ChatMessageBubble.displayName = "ChatMessageBubble";
7843
8239
 
7844
8240
  // src/components/chat/TypingIndicator.tsx
7845
- import * as React38 from "react";
7846
- import { Animated as Animated10, View as View37 } from "react-native";
7847
- import { jsx as jsx50 } from "react/jsx-runtime";
8241
+ import * as React39 from "react";
8242
+ import { Animated as Animated11, View as View38 } from "react-native";
8243
+ import { jsx as jsx51 } from "react/jsx-runtime";
7848
8244
  function TypingIndicator({ style }) {
7849
8245
  const theme = useTheme();
7850
8246
  const dotColor = theme.colors.textSubtle;
7851
- const anims = React38.useMemo(
7852
- () => [new Animated10.Value(0.3), new Animated10.Value(0.3), new Animated10.Value(0.3)],
8247
+ const anims = React39.useMemo(
8248
+ () => [new Animated11.Value(0.3), new Animated11.Value(0.3), new Animated11.Value(0.3)],
7853
8249
  []
7854
8250
  );
7855
- React38.useEffect(() => {
8251
+ React39.useEffect(() => {
7856
8252
  const loops = [];
7857
8253
  anims.forEach((a, idx) => {
7858
- const seq = Animated10.sequence([
7859
- Animated10.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
7860
- Animated10.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
8254
+ const seq = Animated11.sequence([
8255
+ Animated11.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
8256
+ Animated11.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
7861
8257
  ]);
7862
- const loop = Animated10.loop(seq);
8258
+ const loop = Animated11.loop(seq);
7863
8259
  loops.push(loop);
7864
8260
  loop.start();
7865
8261
  });
@@ -7867,8 +8263,8 @@ function TypingIndicator({ style }) {
7867
8263
  loops.forEach((l) => l.stop());
7868
8264
  };
7869
8265
  }, [anims]);
7870
- return /* @__PURE__ */ jsx50(View37, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ jsx50(
7871
- Animated10.View,
8266
+ return /* @__PURE__ */ jsx51(View38, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ jsx51(
8267
+ Animated11.View,
7872
8268
  {
7873
8269
  style: {
7874
8270
  width: 8,
@@ -7877,7 +8273,7 @@ function TypingIndicator({ style }) {
7877
8273
  marginHorizontal: 3,
7878
8274
  backgroundColor: dotColor,
7879
8275
  opacity: a,
7880
- transform: [{ translateY: Animated10.multiply(Animated10.subtract(a, 0.3), 2) }]
8276
+ transform: [{ translateY: Animated11.multiply(Animated11.subtract(a, 0.3), 2) }]
7881
8277
  }
7882
8278
  },
7883
8279
  i
@@ -7885,36 +8281,37 @@ function TypingIndicator({ style }) {
7885
8281
  }
7886
8282
 
7887
8283
  // src/components/chat/ChatMessageList.tsx
7888
- import { jsx as jsx51, jsxs as jsxs31 } from "react/jsx-runtime";
7889
- var ChatMessageList = React39.forwardRef(
8284
+ import { jsx as jsx52, jsxs as jsxs32 } from "react/jsx-runtime";
8285
+ var ChatMessageList = React40.forwardRef(
7890
8286
  ({
7891
8287
  messages,
7892
8288
  showTypingIndicator = false,
7893
8289
  renderMessageContent,
7894
8290
  onRetryMessage,
7895
8291
  isRetryingMessage,
8292
+ onAttachmentLoadError,
7896
8293
  contentStyle,
7897
8294
  bottomInset = 0,
7898
8295
  onNearBottomChange,
7899
8296
  nearBottomThreshold = 200
7900
8297
  }, ref) => {
7901
8298
  const theme = useTheme();
7902
- const listRef = React39.useRef(null);
7903
- const nearBottomRef = React39.useRef(true);
7904
- const initialScrollDoneRef = React39.useRef(false);
7905
- const lastMessageIdRef = React39.useRef(null);
7906
- const data = React39.useMemo(() => {
8299
+ const listRef = React40.useRef(null);
8300
+ const nearBottomRef = React40.useRef(true);
8301
+ const initialScrollDoneRef = React40.useRef(false);
8302
+ const lastMessageIdRef = React40.useRef(null);
8303
+ const data = React40.useMemo(() => {
7907
8304
  return [...messages].reverse();
7908
8305
  }, [messages]);
7909
8306
  const lastMessageId = messages.length > 0 ? messages[messages.length - 1].id : null;
7910
- const keyExtractor = React39.useCallback((m) => m.id, []);
7911
- const scrollToBottom = React39.useCallback((options) => {
8307
+ const keyExtractor = React40.useCallback((m) => m.id, []);
8308
+ const scrollToBottom = React40.useCallback((options) => {
7912
8309
  var _a;
7913
8310
  const animated = (options == null ? void 0 : options.animated) ?? true;
7914
8311
  (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
7915
8312
  }, []);
7916
- React39.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
7917
- const handleScroll = React39.useCallback(
8313
+ React40.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
8314
+ const handleScroll = React40.useCallback(
7918
8315
  (e) => {
7919
8316
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
7920
8317
  const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
@@ -7926,7 +8323,7 @@ var ChatMessageList = React39.forwardRef(
7926
8323
  },
7927
8324
  [bottomInset, nearBottomThreshold, onNearBottomChange]
7928
8325
  );
7929
- React39.useEffect(() => {
8326
+ React40.useEffect(() => {
7930
8327
  if (!initialScrollDoneRef.current) return;
7931
8328
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
7932
8329
  const prevLastId = lastMessageIdRef.current;
@@ -7936,14 +8333,14 @@ var ChatMessageList = React39.forwardRef(
7936
8333
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
7937
8334
  return () => cancelAnimationFrame(id);
7938
8335
  }, [messages, scrollToBottom]);
7939
- React39.useEffect(() => {
8336
+ React40.useEffect(() => {
7940
8337
  if (showTypingIndicator && nearBottomRef.current) {
7941
8338
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
7942
8339
  return () => cancelAnimationFrame(id);
7943
8340
  }
7944
8341
  return void 0;
7945
8342
  }, [showTypingIndicator, scrollToBottom]);
7946
- const handleContentSizeChange = React39.useCallback(() => {
8343
+ const handleContentSizeChange = React40.useCallback(() => {
7947
8344
  if (initialScrollDoneRef.current) return;
7948
8345
  initialScrollDoneRef.current = true;
7949
8346
  lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
@@ -7951,7 +8348,7 @@ var ChatMessageList = React39.forwardRef(
7951
8348
  onNearBottomChange == null ? void 0 : onNearBottomChange(true);
7952
8349
  requestAnimationFrame(() => scrollToBottom({ animated: false }));
7953
8350
  }, [messages, onNearBottomChange, scrollToBottom]);
7954
- const contentContainerStyle = React39.useMemo(
8351
+ const contentContainerStyle = React40.useMemo(
7955
8352
  () => [
7956
8353
  {
7957
8354
  paddingHorizontal: theme.spacing.lg,
@@ -7961,28 +8358,29 @@ var ChatMessageList = React39.forwardRef(
7961
8358
  ],
7962
8359
  [contentStyle, theme.spacing.lg, theme.spacing.sm]
7963
8360
  );
7964
- const renderSeparator = React39.useCallback(() => /* @__PURE__ */ jsx51(View38, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
7965
- const listHeader = React39.useMemo(
7966
- () => /* @__PURE__ */ jsxs31(View38, { children: [
7967
- showTypingIndicator ? /* @__PURE__ */ jsx51(View38, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx51(TypingIndicator, {}) }) : null,
7968
- bottomInset > 0 ? /* @__PURE__ */ jsx51(View38, { style: { height: bottomInset } }) : null
8361
+ const renderSeparator = React40.useCallback(() => /* @__PURE__ */ jsx52(View39, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
8362
+ const listHeader = React40.useMemo(
8363
+ () => /* @__PURE__ */ jsxs32(View39, { children: [
8364
+ showTypingIndicator ? /* @__PURE__ */ jsx52(View39, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx52(TypingIndicator, {}) }) : null,
8365
+ bottomInset > 0 ? /* @__PURE__ */ jsx52(View39, { style: { height: bottomInset } }) : null
7969
8366
  ] }),
7970
8367
  [bottomInset, showTypingIndicator, theme.spacing.lg, theme.spacing.sm]
7971
8368
  );
7972
- const renderItem = React39.useCallback(
7973
- ({ item }) => /* @__PURE__ */ jsx51(
8369
+ const renderItem = React40.useCallback(
8370
+ ({ item }) => /* @__PURE__ */ jsx52(
7974
8371
  ChatMessageBubble,
7975
8372
  {
7976
8373
  message: item,
7977
8374
  renderContent: renderMessageContent,
7978
8375
  isLast: Boolean(lastMessageId && item.id === lastMessageId),
7979
8376
  retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
7980
- onRetryMessage
8377
+ onRetryMessage,
8378
+ onAttachmentLoadError
7981
8379
  }
7982
8380
  ),
7983
- [isRetryingMessage, lastMessageId, onRetryMessage, renderMessageContent]
8381
+ [isRetryingMessage, lastMessageId, onAttachmentLoadError, onRetryMessage, renderMessageContent]
7984
8382
  );
7985
- return /* @__PURE__ */ jsx51(
8383
+ return /* @__PURE__ */ jsx52(
7986
8384
  BottomSheetFlatList,
7987
8385
  {
7988
8386
  ref: listRef,
@@ -8005,7 +8403,7 @@ var ChatMessageList = React39.forwardRef(
8005
8403
  ChatMessageList.displayName = "ChatMessageList";
8006
8404
 
8007
8405
  // src/components/chat/ChatPage.tsx
8008
- import { jsx as jsx52, jsxs as jsxs32 } from "react/jsx-runtime";
8406
+ import { jsx as jsx53, jsxs as jsxs33 } from "react/jsx-runtime";
8009
8407
  function ChatPage({
8010
8408
  header,
8011
8409
  messages,
@@ -8013,6 +8411,7 @@ function ChatPage({
8013
8411
  renderMessageContent,
8014
8412
  onRetryMessage,
8015
8413
  isRetryingMessage,
8414
+ onAttachmentLoadError,
8016
8415
  topBanner,
8017
8416
  composerTop,
8018
8417
  composer,
@@ -8024,35 +8423,35 @@ function ChatPage({
8024
8423
  }) {
8025
8424
  const theme = useTheme();
8026
8425
  const insets = useSafeAreaInsets4();
8027
- const [composerHeight, setComposerHeight] = React40.useState(0);
8028
- const [composerTopHeight, setComposerTopHeight] = React40.useState(0);
8426
+ const [composerHeight, setComposerHeight] = React41.useState(0);
8427
+ const [composerTopHeight, setComposerTopHeight] = React41.useState(0);
8029
8428
  const footerBottomPadding = Platform10.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
8030
8429
  const totalComposerHeight = composerHeight + composerTopHeight;
8031
8430
  const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
8032
8431
  const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
8033
- const resolvedOverlay = React40.useMemo(() => {
8432
+ const resolvedOverlay = React41.useMemo(() => {
8034
8433
  var _a;
8035
8434
  if (!overlay) return null;
8036
- if (!React40.isValidElement(overlay)) return overlay;
8435
+ if (!React41.isValidElement(overlay)) return overlay;
8037
8436
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
8038
- return React40.cloneElement(overlay, {
8437
+ return React41.cloneElement(overlay, {
8039
8438
  style: [prevStyle, { bottom: overlayBottom }]
8040
8439
  });
8041
8440
  }, [overlay, overlayBottom]);
8042
- React40.useEffect(() => {
8441
+ React41.useEffect(() => {
8043
8442
  if (composerTop) return;
8044
8443
  setComposerTopHeight(0);
8045
8444
  }, [composerTop]);
8046
- return /* @__PURE__ */ jsxs32(View39, { style: [{ flex: 1 }, style], children: [
8047
- header ? /* @__PURE__ */ jsx52(View39, { children: header }) : null,
8048
- topBanner ? /* @__PURE__ */ jsx52(View39, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
8049
- /* @__PURE__ */ jsxs32(View39, { style: { flex: 1 }, children: [
8050
- /* @__PURE__ */ jsxs32(
8051
- View39,
8445
+ return /* @__PURE__ */ jsxs33(View40, { style: [{ flex: 1 }, style], children: [
8446
+ header ? /* @__PURE__ */ jsx53(View40, { children: header }) : null,
8447
+ topBanner ? /* @__PURE__ */ jsx53(View40, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
8448
+ /* @__PURE__ */ jsxs33(View40, { style: { flex: 1 }, children: [
8449
+ /* @__PURE__ */ jsxs33(
8450
+ View40,
8052
8451
  {
8053
8452
  style: { flex: 1 },
8054
8453
  children: [
8055
- /* @__PURE__ */ jsx52(
8454
+ /* @__PURE__ */ jsx53(
8056
8455
  ChatMessageList,
8057
8456
  {
8058
8457
  ref: listRef,
@@ -8061,6 +8460,7 @@ function ChatPage({
8061
8460
  renderMessageContent,
8062
8461
  onRetryMessage,
8063
8462
  isRetryingMessage,
8463
+ onAttachmentLoadError,
8064
8464
  onNearBottomChange,
8065
8465
  bottomInset
8066
8466
  }
@@ -8069,8 +8469,8 @@ function ChatPage({
8069
8469
  ]
8070
8470
  }
8071
8471
  ),
8072
- /* @__PURE__ */ jsxs32(
8073
- View39,
8472
+ /* @__PURE__ */ jsxs33(
8473
+ View40,
8074
8474
  {
8075
8475
  style: {
8076
8476
  position: "absolute",
@@ -8082,15 +8482,15 @@ function ChatPage({
8082
8482
  paddingBottom: footerBottomPadding
8083
8483
  },
8084
8484
  children: [
8085
- composerTop ? /* @__PURE__ */ jsx52(
8086
- View39,
8485
+ composerTop ? /* @__PURE__ */ jsx53(
8486
+ View40,
8087
8487
  {
8088
8488
  style: { marginBottom: theme.spacing.sm },
8089
8489
  onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
8090
8490
  children: composerTop
8091
8491
  }
8092
8492
  ) : null,
8093
- /* @__PURE__ */ jsx52(
8493
+ /* @__PURE__ */ jsx53(
8094
8494
  ChatComposer,
8095
8495
  {
8096
8496
  ...composer,
@@ -8106,15 +8506,15 @@ function ChatPage({
8106
8506
  }
8107
8507
 
8108
8508
  // src/components/chat/ScrollToBottomButton.tsx
8109
- import * as React41 from "react";
8110
- import { Pressable as Pressable16, View as View40 } from "react-native";
8111
- import Animated11, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle2, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
8112
- import { jsx as jsx53 } from "react/jsx-runtime";
8509
+ import * as React42 from "react";
8510
+ import { Pressable as Pressable17, View as View41 } from "react-native";
8511
+ import Animated12, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle2, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
8512
+ import { jsx as jsx54 } from "react/jsx-runtime";
8113
8513
  function ScrollToBottomButton({ visible, onPress, children, style }) {
8114
8514
  const theme = useTheme();
8115
8515
  const progress = useSharedValue2(visible ? 1 : 0);
8116
- const [pressed, setPressed] = React41.useState(false);
8117
- React41.useEffect(() => {
8516
+ const [pressed, setPressed] = React42.useState(false);
8517
+ React42.useEffect(() => {
8118
8518
  progress.value = withTiming2(visible ? 1 : 0, { duration: 200, easing: Easing2.out(Easing2.ease) });
8119
8519
  }, [progress, visible]);
8120
8520
  const animStyle = useAnimatedStyle2(() => ({
@@ -8123,8 +8523,8 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8123
8523
  }));
8124
8524
  const bg = theme.scheme === "dark" ? "rgba(39,39,42,0.9)" : "rgba(244,244,245,0.95)";
8125
8525
  const border = theme.scheme === "dark" ? withAlpha("#FFFFFF", 0.12) : withAlpha("#000000", 0.08);
8126
- return /* @__PURE__ */ jsx53(
8127
- Animated11.View,
8526
+ return /* @__PURE__ */ jsx54(
8527
+ Animated12.View,
8128
8528
  {
8129
8529
  pointerEvents: visible ? "auto" : "none",
8130
8530
  style: [
@@ -8137,8 +8537,8 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8137
8537
  style,
8138
8538
  animStyle
8139
8539
  ],
8140
- children: /* @__PURE__ */ jsx53(
8141
- View40,
8540
+ children: /* @__PURE__ */ jsx54(
8541
+ View41,
8142
8542
  {
8143
8543
  style: {
8144
8544
  width: 44,
@@ -8156,8 +8556,8 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8156
8556
  elevation: 5,
8157
8557
  opacity: pressed ? 0.85 : 1
8158
8558
  },
8159
- children: /* @__PURE__ */ jsx53(
8160
- Pressable16,
8559
+ children: /* @__PURE__ */ jsx54(
8560
+ Pressable17,
8161
8561
  {
8162
8562
  onPress,
8163
8563
  onPressIn: () => setPressed(true),
@@ -8174,16 +8574,16 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8174
8574
  }
8175
8575
 
8176
8576
  // src/components/chat/ChatHeader.tsx
8177
- import { StyleSheet as StyleSheet4 } from "react-native";
8178
- import { jsx as jsx54 } from "react/jsx-runtime";
8577
+ import { StyleSheet as StyleSheet5 } from "react-native";
8578
+ import { jsx as jsx55 } from "react/jsx-runtime";
8179
8579
  function ChatHeader({ left, right, center, style }) {
8180
- const flattenedStyle = StyleSheet4.flatten([
8580
+ const flattenedStyle = StyleSheet5.flatten([
8181
8581
  {
8182
8582
  paddingTop: 0
8183
8583
  },
8184
8584
  style
8185
8585
  ]);
8186
- return /* @__PURE__ */ jsx54(
8586
+ return /* @__PURE__ */ jsx55(
8187
8587
  StudioSheetHeader,
8188
8588
  {
8189
8589
  left,
@@ -8195,13 +8595,13 @@ function ChatHeader({ left, right, center, style }) {
8195
8595
  }
8196
8596
 
8197
8597
  // src/components/chat/ForkNoticeBanner.tsx
8198
- import { View as View42 } from "react-native";
8199
- import { jsx as jsx55, jsxs as jsxs33 } from "react/jsx-runtime";
8598
+ import { View as View43 } from "react-native";
8599
+ import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
8200
8600
  function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8201
8601
  const theme = useTheme();
8202
8602
  const resolvedTitle = title ?? (isOwner ? "Remixed app" : "Remix app");
8203
8603
  const resolvedDescription = description ?? (isOwner ? "Any changes you make will be a remix of the original app. You can view the edited version in the Remix tab in your apps page." : "Once you make edits, this remixed version will appear on your Remixed apps page.");
8204
- return /* @__PURE__ */ jsx55(
8604
+ return /* @__PURE__ */ jsx56(
8205
8605
  Card,
8206
8606
  {
8207
8607
  variant: "surfaceRaised",
@@ -8216,8 +8616,8 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8216
8616
  },
8217
8617
  style
8218
8618
  ],
8219
- children: /* @__PURE__ */ jsxs33(View42, { style: { minWidth: 0 }, children: [
8220
- /* @__PURE__ */ jsx55(
8619
+ children: /* @__PURE__ */ jsxs34(View43, { style: { minWidth: 0 }, children: [
8620
+ /* @__PURE__ */ jsx56(
8221
8621
  Text,
8222
8622
  {
8223
8623
  style: {
@@ -8231,7 +8631,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8231
8631
  children: resolvedTitle
8232
8632
  }
8233
8633
  ),
8234
- /* @__PURE__ */ jsx55(
8634
+ /* @__PURE__ */ jsx56(
8235
8635
  Text,
8236
8636
  {
8237
8637
  style: {
@@ -8249,16 +8649,16 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8249
8649
  }
8250
8650
 
8251
8651
  // src/components/chat/ChatQueue.tsx
8252
- import * as React42 from "react";
8253
- import { ActivityIndicator as ActivityIndicator9, Pressable as Pressable17, View as View43 } from "react-native";
8254
- import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
8652
+ import * as React43 from "react";
8653
+ import { ActivityIndicator as ActivityIndicator9, Pressable as Pressable18, View as View44 } from "react-native";
8654
+ import { jsx as jsx57, jsxs as jsxs35 } from "react/jsx-runtime";
8255
8655
  function ChatQueue({ items, onRemove }) {
8256
8656
  const theme = useTheme();
8257
- const [expanded, setExpanded] = React42.useState({});
8258
- const [canExpand, setCanExpand] = React42.useState({});
8259
- const [collapsedText, setCollapsedText] = React42.useState({});
8260
- const [removing, setRemoving] = React42.useState({});
8261
- const buildCollapsedText = React42.useCallback((lines) => {
8657
+ const [expanded, setExpanded] = React43.useState({});
8658
+ const [canExpand, setCanExpand] = React43.useState({});
8659
+ const [collapsedText, setCollapsedText] = React43.useState({});
8660
+ const [removing, setRemoving] = React43.useState({});
8661
+ const buildCollapsedText = React43.useCallback((lines) => {
8262
8662
  var _a, _b;
8263
8663
  const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
8264
8664
  const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
@@ -8274,7 +8674,7 @@ function ChatQueue({ items, onRemove }) {
8274
8674
  return `${line1}
8275
8675
  ${trimmedLine2}\u2026 `;
8276
8676
  }, []);
8277
- React42.useEffect(() => {
8677
+ React43.useEffect(() => {
8278
8678
  if (items.length === 0) return;
8279
8679
  const ids = new Set(items.map((item) => item.id));
8280
8680
  setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
@@ -8283,8 +8683,8 @@ ${trimmedLine2}\u2026 `;
8283
8683
  setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
8284
8684
  }, [items]);
8285
8685
  if (items.length === 0) return null;
8286
- return /* @__PURE__ */ jsxs34(
8287
- View43,
8686
+ return /* @__PURE__ */ jsxs35(
8687
+ View44,
8288
8688
  {
8289
8689
  style: {
8290
8690
  borderWidth: 1,
@@ -8295,16 +8695,16 @@ ${trimmedLine2}\u2026 `;
8295
8695
  backgroundColor: "transparent"
8296
8696
  },
8297
8697
  children: [
8298
- /* @__PURE__ */ jsx56(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
8299
- /* @__PURE__ */ jsx56(View43, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
8698
+ /* @__PURE__ */ jsx57(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
8699
+ /* @__PURE__ */ jsx57(View44, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
8300
8700
  const isExpanded = Boolean(expanded[item.id]);
8301
8701
  const showToggle = Boolean(canExpand[item.id]);
8302
8702
  const prompt = item.prompt ?? "";
8303
8703
  const moreLabel = "more";
8304
8704
  const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
8305
8705
  const isRemoving = Boolean(removing[item.id]);
8306
- return /* @__PURE__ */ jsxs34(
8307
- View43,
8706
+ return /* @__PURE__ */ jsxs35(
8707
+ View44,
8308
8708
  {
8309
8709
  style: {
8310
8710
  flexDirection: "row",
@@ -8316,8 +8716,8 @@ ${trimmedLine2}\u2026 `;
8316
8716
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
8317
8717
  },
8318
8718
  children: [
8319
- /* @__PURE__ */ jsxs34(View43, { style: { flex: 1 }, children: [
8320
- !canExpand[item.id] ? /* @__PURE__ */ jsx56(
8719
+ /* @__PURE__ */ jsxs35(View44, { style: { flex: 1 }, children: [
8720
+ !canExpand[item.id] ? /* @__PURE__ */ jsx57(
8321
8721
  Text,
8322
8722
  {
8323
8723
  style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
@@ -8336,14 +8736,14 @@ ${trimmedLine2}\u2026 `;
8336
8736
  children: prompt
8337
8737
  }
8338
8738
  ) : null,
8339
- /* @__PURE__ */ jsxs34(
8739
+ /* @__PURE__ */ jsxs35(
8340
8740
  Text,
8341
8741
  {
8342
8742
  variant: "bodyMuted",
8343
8743
  numberOfLines: isExpanded ? void 0 : 2,
8344
8744
  children: [
8345
8745
  displayPrompt,
8346
- !isExpanded && showToggle ? /* @__PURE__ */ jsx56(
8746
+ !isExpanded && showToggle ? /* @__PURE__ */ jsx57(
8347
8747
  Text,
8348
8748
  {
8349
8749
  color: theme.colors.text,
@@ -8355,18 +8755,18 @@ ${trimmedLine2}\u2026 `;
8355
8755
  ]
8356
8756
  }
8357
8757
  ),
8358
- showToggle && isExpanded ? /* @__PURE__ */ jsx56(
8359
- Pressable17,
8758
+ showToggle && isExpanded ? /* @__PURE__ */ jsx57(
8759
+ Pressable18,
8360
8760
  {
8361
8761
  onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
8362
8762
  hitSlop: 6,
8363
8763
  style: { alignSelf: "flex-start", marginTop: 4 },
8364
- children: /* @__PURE__ */ jsx56(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
8764
+ children: /* @__PURE__ */ jsx57(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
8365
8765
  }
8366
8766
  ) : null
8367
8767
  ] }),
8368
- /* @__PURE__ */ jsx56(
8369
- Pressable17,
8768
+ /* @__PURE__ */ jsx57(
8769
+ Pressable18,
8370
8770
  {
8371
8771
  onPress: () => {
8372
8772
  if (!onRemove || isRemoving) return;
@@ -8381,7 +8781,7 @@ ${trimmedLine2}\u2026 `;
8381
8781
  },
8382
8782
  hitSlop: 8,
8383
8783
  style: { alignSelf: "center" },
8384
- children: isRemoving ? /* @__PURE__ */ jsx56(ActivityIndicator9, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ jsx56(IconClose, { size: 14, colorToken: "text" })
8784
+ children: isRemoving ? /* @__PURE__ */ jsx57(ActivityIndicator9, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ jsx57(IconClose, { size: 14, colorToken: "text" })
8385
8785
  }
8386
8786
  )
8387
8787
  ]
@@ -8395,8 +8795,27 @@ ${trimmedLine2}\u2026 `;
8395
8795
  }
8396
8796
 
8397
8797
  // src/components/chat/AgentProgressCard.tsx
8398
- import { View as View44 } from "react-native";
8399
- import { jsx as jsx57, jsxs as jsxs35 } from "react/jsx-runtime";
8798
+ import { View as View45 } from "react-native";
8799
+
8800
+ // src/components/icons/RemixXLoopLottie.tsx
8801
+ import LottieView from "lottie-react-native";
8802
+ import { jsx as jsx58 } from "react/jsx-runtime";
8803
+ var remixXLoopSource = require_remix_x_loop_lottie();
8804
+ var Lottie = LottieView;
8805
+ function RemixXLoopLottie({ size = 24, style }) {
8806
+ return /* @__PURE__ */ jsx58(
8807
+ Lottie,
8808
+ {
8809
+ source: remixXLoopSource,
8810
+ autoPlay: true,
8811
+ loop: true,
8812
+ style: [{ width: size, height: size }, style]
8813
+ }
8814
+ );
8815
+ }
8816
+
8817
+ // src/components/chat/AgentProgressCard.tsx
8818
+ import { jsx as jsx59, jsxs as jsxs36 } from "react/jsx-runtime";
8400
8819
  function titleForPhase(phase) {
8401
8820
  if (phase === "planning") return "Planning";
8402
8821
  if (phase === "reasoning") return "Reasoning";
@@ -8418,10 +8837,11 @@ function AgentProgressCard({ progress }) {
8418
8837
  const theme = useTheme();
8419
8838
  const statusLabel = titleForStatus(progress.status);
8420
8839
  const phaseLabel = titleForPhase(progress.phase);
8840
+ const showAnimatedStatusIcon = progress.status === "running";
8421
8841
  const subtitle = progress.latestMessage || `Agent is ${phaseLabel.toLowerCase()}...`;
8422
8842
  const todo = progress.todoSummary;
8423
- return /* @__PURE__ */ jsxs35(
8424
- View44,
8843
+ return /* @__PURE__ */ jsxs36(
8844
+ View45,
8425
8845
  {
8426
8846
  style: {
8427
8847
  borderWidth: 1,
@@ -8432,20 +8852,23 @@ function AgentProgressCard({ progress }) {
8432
8852
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8433
8853
  },
8434
8854
  children: [
8435
- /* @__PURE__ */ jsxs35(View44, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
8436
- /* @__PURE__ */ jsx57(Text, { variant: "caption", children: statusLabel }),
8437
- /* @__PURE__ */ jsx57(Text, { variant: "captionMuted", children: phaseLabel })
8855
+ /* @__PURE__ */ jsxs36(View45, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
8856
+ /* @__PURE__ */ jsx59(Text, { variant: "caption", children: statusLabel }),
8857
+ /* @__PURE__ */ jsxs36(View45, { style: { flexDirection: "row", alignItems: "center" }, children: [
8858
+ /* @__PURE__ */ jsx59(Text, { variant: "captionMuted", children: phaseLabel }),
8859
+ showAnimatedStatusIcon ? /* @__PURE__ */ jsx59(RemixXLoopLottie, { size: 20, style: { marginLeft: 8 } }) : null
8860
+ ] })
8438
8861
  ] }),
8439
- /* @__PURE__ */ jsx57(Text, { variant: "bodyMuted", children: subtitle }),
8440
- progress.changedFilesCount > 0 ? /* @__PURE__ */ jsxs35(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8862
+ /* @__PURE__ */ jsx59(Text, { variant: "bodyMuted", children: subtitle }),
8863
+ progress.changedFilesCount > 0 ? /* @__PURE__ */ jsxs36(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8441
8864
  "Updated files: ",
8442
8865
  progress.changedFilesCount
8443
8866
  ] }) : null,
8444
- progress.recentFiles.length > 0 ? /* @__PURE__ */ jsx57(View44, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ jsxs35(Text, { variant: "captionMuted", numberOfLines: 1, children: [
8867
+ progress.recentFiles.length > 0 ? /* @__PURE__ */ jsx59(View45, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ jsxs36(Text, { variant: "captionMuted", numberOfLines: 1, children: [
8445
8868
  "\u2022 ",
8446
8869
  path
8447
8870
  ] }, path)) }) : null,
8448
- todo ? /* @__PURE__ */ jsxs35(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8871
+ todo ? /* @__PURE__ */ jsxs36(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8449
8872
  "Todos: ",
8450
8873
  todo.completed,
8451
8874
  "/",
@@ -8459,8 +8882,8 @@ function AgentProgressCard({ progress }) {
8459
8882
  }
8460
8883
 
8461
8884
  // src/components/chat/BundleProgressCard.tsx
8462
- import { View as View45 } from "react-native";
8463
- import { jsx as jsx58, jsxs as jsxs36 } from "react/jsx-runtime";
8885
+ import { View as View46 } from "react-native";
8886
+ import { jsx as jsx60, jsxs as jsxs37 } from "react/jsx-runtime";
8464
8887
  function titleForStatus2(status) {
8465
8888
  if (status === "succeeded") return "Completed";
8466
8889
  if (status === "failed") return "Failed";
@@ -8472,8 +8895,8 @@ function BundleProgressCard({ progress }) {
8472
8895
  const percent = Math.round(Math.max(0, Math.min(1, progress.progressValue)) * 100);
8473
8896
  const fillColor = progress.status === "failed" ? theme.colors.danger : progress.status === "succeeded" ? theme.colors.success : theme.colors.warning;
8474
8897
  const detail = progress.errorMessage || progress.phaseLabel;
8475
- return /* @__PURE__ */ jsxs36(
8476
- View45,
8898
+ return /* @__PURE__ */ jsxs37(
8899
+ View46,
8477
8900
  {
8478
8901
  accessible: true,
8479
8902
  accessibilityRole: "progressbar",
@@ -8488,15 +8911,15 @@ function BundleProgressCard({ progress }) {
8488
8911
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8489
8912
  },
8490
8913
  children: [
8491
- /* @__PURE__ */ jsxs36(View45, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
8492
- /* @__PURE__ */ jsx58(Text, { variant: "caption", children: statusLabel }),
8493
- /* @__PURE__ */ jsxs36(Text, { variant: "captionMuted", children: [
8914
+ /* @__PURE__ */ jsxs37(View46, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
8915
+ /* @__PURE__ */ jsx60(Text, { variant: "caption", children: statusLabel }),
8916
+ /* @__PURE__ */ jsxs37(Text, { variant: "captionMuted", children: [
8494
8917
  percent,
8495
8918
  "%"
8496
8919
  ] })
8497
8920
  ] }),
8498
- /* @__PURE__ */ jsx58(
8499
- View45,
8921
+ /* @__PURE__ */ jsx60(
8922
+ View46,
8500
8923
  {
8501
8924
  style: {
8502
8925
  width: "100%",
@@ -8505,8 +8928,8 @@ function BundleProgressCard({ progress }) {
8505
8928
  backgroundColor: withAlpha(theme.colors.border, theme.scheme === "dark" ? 0.5 : 0.6),
8506
8929
  overflow: "hidden"
8507
8930
  },
8508
- children: /* @__PURE__ */ jsx58(
8509
- View45,
8931
+ children: /* @__PURE__ */ jsx60(
8932
+ View46,
8510
8933
  {
8511
8934
  style: {
8512
8935
  width: `${percent}%`,
@@ -8517,14 +8940,14 @@ function BundleProgressCard({ progress }) {
8517
8940
  )
8518
8941
  }
8519
8942
  ),
8520
- /* @__PURE__ */ jsx58(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
8943
+ /* @__PURE__ */ jsx60(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
8521
8944
  ]
8522
8945
  }
8523
8946
  );
8524
8947
  }
8525
8948
 
8526
8949
  // src/studio/ui/ChatPanel.tsx
8527
- import { jsx as jsx59, jsxs as jsxs37 } from "react/jsx-runtime";
8950
+ import { jsx as jsx61, jsxs as jsxs38 } from "react/jsx-runtime";
8528
8951
  function ChatPanel({
8529
8952
  title = "Chat",
8530
8953
  messages,
@@ -8544,14 +8967,15 @@ function ChatPanel({
8544
8967
  onSend,
8545
8968
  onRetryMessage,
8546
8969
  isRetryingMessage,
8970
+ onAttachmentLoadError,
8547
8971
  queueItems = [],
8548
8972
  onRemoveQueueItem,
8549
8973
  progress = null
8550
8974
  }) {
8551
8975
  const theme = useTheme();
8552
- const listRef = React43.useRef(null);
8553
- const [nearBottom, setNearBottom] = React43.useState(true);
8554
- const handleSend = React43.useCallback(
8976
+ const listRef = React44.useRef(null);
8977
+ const [nearBottom, setNearBottom] = React44.useState(true);
8978
+ const handleSend = React44.useCallback(
8555
8979
  async (text, composerAttachments) => {
8556
8980
  const all = composerAttachments ?? attachments;
8557
8981
  await onSend(text, all.length > 0 ? all : void 0);
@@ -8565,25 +8989,25 @@ function ChatPanel({
8565
8989
  },
8566
8990
  [attachments, nearBottom, onClearAttachments, onSend]
8567
8991
  );
8568
- const handleScrollToBottom = React43.useCallback(() => {
8992
+ const handleScrollToBottom = React44.useCallback(() => {
8569
8993
  var _a;
8570
8994
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
8571
8995
  }, []);
8572
- const header = /* @__PURE__ */ jsx59(
8996
+ const header = /* @__PURE__ */ jsx61(
8573
8997
  ChatHeader,
8574
8998
  {
8575
- left: /* @__PURE__ */ jsxs37(View46, { style: { flexDirection: "row", alignItems: "center" }, children: [
8576
- /* @__PURE__ */ jsx59(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx59(IconBack, { size: 20, colorToken: "floatingContent" }) }),
8577
- onNavigateHome ? /* @__PURE__ */ jsx59(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx59(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
8999
+ left: /* @__PURE__ */ jsxs38(View47, { style: { flexDirection: "row", alignItems: "center" }, children: [
9000
+ /* @__PURE__ */ jsx61(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx61(IconBack, { size: 20, colorToken: "floatingContent" }) }),
9001
+ onNavigateHome ? /* @__PURE__ */ jsx61(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx61(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
8578
9002
  ] }),
8579
- right: /* @__PURE__ */ jsxs37(View46, { style: { flexDirection: "row", alignItems: "center" }, children: [
8580
- onStartDraw ? /* @__PURE__ */ jsx59(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx59(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
8581
- /* @__PURE__ */ jsx59(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx59(IconClose, { size: 20, colorToken: "floatingContent" }) })
9003
+ right: /* @__PURE__ */ jsxs38(View47, { style: { flexDirection: "row", alignItems: "center" }, children: [
9004
+ onStartDraw ? /* @__PURE__ */ jsx61(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx61(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
9005
+ /* @__PURE__ */ jsx61(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx61(IconClose, { size: 20, colorToken: "floatingContent" }) })
8582
9006
  ] }),
8583
9007
  center: null
8584
9008
  }
8585
9009
  );
8586
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx59(
9010
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx61(
8587
9011
  ForkNoticeBanner,
8588
9012
  {
8589
9013
  isOwner: !shouldForkOnEdit,
@@ -8592,22 +9016,22 @@ function ChatPanel({
8592
9016
  ) : null;
8593
9017
  const showMessagesLoading = Boolean(loading) && messages.length === 0;
8594
9018
  if (showMessagesLoading) {
8595
- return /* @__PURE__ */ jsxs37(View46, { style: { flex: 1 }, children: [
8596
- /* @__PURE__ */ jsx59(View46, { children: header }),
8597
- topBanner ? /* @__PURE__ */ jsx59(View46, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
8598
- /* @__PURE__ */ jsxs37(View46, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
8599
- /* @__PURE__ */ jsx59(ActivityIndicator10, {}),
8600
- /* @__PURE__ */ jsx59(View46, { style: { height: 12 } }),
8601
- /* @__PURE__ */ jsx59(Text, { variant: "bodyMuted", children: "Loading messages\u2026" })
9019
+ return /* @__PURE__ */ jsxs38(View47, { style: { flex: 1 }, children: [
9020
+ /* @__PURE__ */ jsx61(View47, { children: header }),
9021
+ topBanner ? /* @__PURE__ */ jsx61(View47, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
9022
+ /* @__PURE__ */ jsxs38(View47, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
9023
+ /* @__PURE__ */ jsx61(ActivityIndicator10, {}),
9024
+ /* @__PURE__ */ jsx61(View47, { style: { height: 12 } }),
9025
+ /* @__PURE__ */ jsx61(Text, { variant: "bodyMuted", children: "Loading messages\u2026" })
8602
9026
  ] })
8603
9027
  ] });
8604
9028
  }
8605
9029
  const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
8606
- const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ jsxs37(View46, { style: { gap: theme.spacing.sm }, children: [
8607
- progress ? bundleProgress ? /* @__PURE__ */ jsx59(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ jsx59(AgentProgressCard, { progress }) : null,
8608
- !progress && queueItems.length > 0 ? /* @__PURE__ */ jsx59(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
9030
+ const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ jsxs38(View47, { style: { gap: theme.spacing.sm }, children: [
9031
+ progress ? bundleProgress ? /* @__PURE__ */ jsx61(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ jsx61(AgentProgressCard, { progress }) : null,
9032
+ !progress && queueItems.length > 0 ? /* @__PURE__ */ jsx61(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
8609
9033
  ] }) : null;
8610
- return /* @__PURE__ */ jsx59(
9034
+ return /* @__PURE__ */ jsx61(
8611
9035
  ChatPage,
8612
9036
  {
8613
9037
  header,
@@ -8615,18 +9039,19 @@ function ChatPanel({
8615
9039
  showTypingIndicator,
8616
9040
  onRetryMessage,
8617
9041
  isRetryingMessage,
9042
+ onAttachmentLoadError,
8618
9043
  topBanner,
8619
9044
  composerTop: queueTop,
8620
9045
  composerHorizontalPadding: 0,
8621
9046
  listRef,
8622
9047
  onNearBottomChange: setNearBottom,
8623
- overlay: /* @__PURE__ */ jsx59(
9048
+ overlay: /* @__PURE__ */ jsx61(
8624
9049
  ScrollToBottomButton,
8625
9050
  {
8626
9051
  visible: !nearBottom,
8627
9052
  onPress: handleScrollToBottom,
8628
9053
  style: { bottom: 80 },
8629
- children: /* @__PURE__ */ jsx59(IconArrowDown, { size: 20, colorToken: "floatingContent" })
9054
+ children: /* @__PURE__ */ jsx61(IconArrowDown, { size: 20, colorToken: "floatingContent" })
8630
9055
  }
8631
9056
  ),
8632
9057
  composer: {
@@ -8646,9 +9071,9 @@ function ChatPanel({
8646
9071
  }
8647
9072
 
8648
9073
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
8649
- import * as React44 from "react";
8650
- import { Pressable as Pressable18, View as View47 } from "react-native";
8651
- import { jsx as jsx60, jsxs as jsxs38 } from "react/jsx-runtime";
9074
+ import * as React45 from "react";
9075
+ import { Pressable as Pressable19, View as View48 } from "react-native";
9076
+ import { jsx as jsx62, jsxs as jsxs39 } from "react/jsx-runtime";
8652
9077
  function ConfirmMergeRequestDialog({
8653
9078
  visible,
8654
9079
  onOpenChange,
@@ -8659,14 +9084,14 @@ function ConfirmMergeRequestDialog({
8659
9084
  onTestFirst
8660
9085
  }) {
8661
9086
  const theme = useTheme();
8662
- const close = React44.useCallback(() => onOpenChange(false), [onOpenChange]);
9087
+ const close = React45.useCallback(() => onOpenChange(false), [onOpenChange]);
8663
9088
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
8664
- const handleConfirm = React44.useCallback(() => {
9089
+ const handleConfirm = React45.useCallback(() => {
8665
9090
  if (!mergeRequest) return;
8666
9091
  onOpenChange(false);
8667
9092
  void onConfirm();
8668
9093
  }, [mergeRequest, onConfirm, onOpenChange]);
8669
- const handleTestFirst = React44.useCallback(() => {
9094
+ const handleTestFirst = React45.useCallback(() => {
8670
9095
  if (!mergeRequest) return;
8671
9096
  onOpenChange(false);
8672
9097
  void onTestFirst(mergeRequest);
@@ -8678,7 +9103,7 @@ function ConfirmMergeRequestDialog({
8678
9103
  justifyContent: "center",
8679
9104
  alignSelf: "stretch"
8680
9105
  };
8681
- return /* @__PURE__ */ jsxs38(
9106
+ return /* @__PURE__ */ jsxs39(
8682
9107
  Modal,
8683
9108
  {
8684
9109
  visible,
@@ -8689,7 +9114,7 @@ function ConfirmMergeRequestDialog({
8689
9114
  backgroundColor: theme.colors.background
8690
9115
  },
8691
9116
  children: [
8692
- /* @__PURE__ */ jsx60(View47, { children: /* @__PURE__ */ jsx60(
9117
+ /* @__PURE__ */ jsx62(View48, { children: /* @__PURE__ */ jsx62(
8693
9118
  Text,
8694
9119
  {
8695
9120
  style: {
@@ -8701,9 +9126,9 @@ function ConfirmMergeRequestDialog({
8701
9126
  children: "Are you sure you want to approve this merge request?"
8702
9127
  }
8703
9128
  ) }),
8704
- /* @__PURE__ */ jsxs38(View47, { style: { marginTop: 16 }, children: [
8705
- /* @__PURE__ */ jsx60(
8706
- View47,
9129
+ /* @__PURE__ */ jsxs39(View48, { style: { marginTop: 16 }, children: [
9130
+ /* @__PURE__ */ jsx62(
9131
+ View48,
8707
9132
  {
8708
9133
  style: [
8709
9134
  fullWidthButtonBase,
@@ -8712,22 +9137,22 @@ function ConfirmMergeRequestDialog({
8712
9137
  opacity: canConfirm ? 1 : 0.5
8713
9138
  }
8714
9139
  ],
8715
- children: /* @__PURE__ */ jsx60(
8716
- Pressable18,
9140
+ children: /* @__PURE__ */ jsx62(
9141
+ Pressable19,
8717
9142
  {
8718
9143
  accessibilityRole: "button",
8719
9144
  accessibilityLabel: "Approve Merge",
8720
9145
  disabled: !canConfirm,
8721
9146
  onPress: handleConfirm,
8722
9147
  style: [fullWidthButtonBase, { flex: 1 }],
8723
- children: /* @__PURE__ */ jsx60(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
9148
+ children: /* @__PURE__ */ jsx62(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
8724
9149
  }
8725
9150
  )
8726
9151
  }
8727
9152
  ),
8728
- /* @__PURE__ */ jsx60(View47, { style: { height: 8 } }),
8729
- /* @__PURE__ */ jsx60(
8730
- View47,
9153
+ /* @__PURE__ */ jsx62(View48, { style: { height: 8 } }),
9154
+ /* @__PURE__ */ jsx62(
9155
+ View48,
8731
9156
  {
8732
9157
  style: [
8733
9158
  fullWidthButtonBase,
@@ -8738,22 +9163,22 @@ function ConfirmMergeRequestDialog({
8738
9163
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
8739
9164
  }
8740
9165
  ],
8741
- children: /* @__PURE__ */ jsx60(
8742
- Pressable18,
9166
+ children: /* @__PURE__ */ jsx62(
9167
+ Pressable19,
8743
9168
  {
8744
9169
  accessibilityRole: "button",
8745
9170
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
8746
9171
  disabled: isBuilding || !mergeRequest,
8747
9172
  onPress: handleTestFirst,
8748
9173
  style: [fullWidthButtonBase, { flex: 1 }],
8749
- children: /* @__PURE__ */ jsx60(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
9174
+ children: /* @__PURE__ */ jsx62(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
8750
9175
  }
8751
9176
  )
8752
9177
  }
8753
9178
  ),
8754
- /* @__PURE__ */ jsx60(View47, { style: { height: 8 } }),
8755
- /* @__PURE__ */ jsx60(
8756
- View47,
9179
+ /* @__PURE__ */ jsx62(View48, { style: { height: 8 } }),
9180
+ /* @__PURE__ */ jsx62(
9181
+ View48,
8757
9182
  {
8758
9183
  style: [
8759
9184
  fullWidthButtonBase,
@@ -8763,14 +9188,14 @@ function ConfirmMergeRequestDialog({
8763
9188
  borderColor: theme.colors.border
8764
9189
  }
8765
9190
  ],
8766
- children: /* @__PURE__ */ jsx60(
8767
- Pressable18,
9191
+ children: /* @__PURE__ */ jsx62(
9192
+ Pressable19,
8768
9193
  {
8769
9194
  accessibilityRole: "button",
8770
9195
  accessibilityLabel: "Cancel",
8771
9196
  onPress: close,
8772
9197
  style: [fullWidthButtonBase, { flex: 1 }],
8773
- children: /* @__PURE__ */ jsx60(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
9198
+ children: /* @__PURE__ */ jsx62(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
8774
9199
  }
8775
9200
  )
8776
9201
  }
@@ -8782,7 +9207,7 @@ function ConfirmMergeRequestDialog({
8782
9207
  }
8783
9208
 
8784
9209
  // src/studio/ui/ConfirmMergeFlow.tsx
8785
- import { jsx as jsx61 } from "react/jsx-runtime";
9210
+ import { jsx as jsx63 } from "react/jsx-runtime";
8786
9211
  function ConfirmMergeFlow({
8787
9212
  visible,
8788
9213
  onOpenChange,
@@ -8793,7 +9218,7 @@ function ConfirmMergeFlow({
8793
9218
  onConfirm,
8794
9219
  onTestFirst
8795
9220
  }) {
8796
- return /* @__PURE__ */ jsx61(
9221
+ return /* @__PURE__ */ jsx63(
8797
9222
  ConfirmMergeRequestDialog,
8798
9223
  {
8799
9224
  visible,
@@ -8815,7 +9240,8 @@ function ConfirmMergeFlow({
8815
9240
  }
8816
9241
 
8817
9242
  // src/studio/hooks/useOptimisticChatMessages.ts
8818
- import * as React45 from "react";
9243
+ import * as React46 from "react";
9244
+ import { Image as Image5 } from "react-native";
8819
9245
  function makeOptimisticId() {
8820
9246
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
8821
9247
  }
@@ -8826,6 +9252,28 @@ function toEpochMs2(createdAt) {
8826
9252
  const t = Date.parse(String(createdAt));
8827
9253
  return Number.isFinite(t) ? t : 0;
8828
9254
  }
9255
+ async function resolveAttachmentDimensions(uris) {
9256
+ return Promise.all(
9257
+ uris.map(
9258
+ async (uri) => {
9259
+ try {
9260
+ const { width, height } = await new Promise((resolve, reject) => {
9261
+ Image5.getSize(
9262
+ uri,
9263
+ (w, h) => resolve({ width: w, height: h }),
9264
+ (err) => reject(err)
9265
+ );
9266
+ });
9267
+ if (width > 0 && height > 0) {
9268
+ return { uri, width: Math.round(width), height: Math.round(height) };
9269
+ }
9270
+ } catch {
9271
+ }
9272
+ return { uri };
9273
+ }
9274
+ )
9275
+ );
9276
+ }
8829
9277
  function isOptimisticResolvedByServer(chatMessages, o) {
8830
9278
  if (o.failed) return false;
8831
9279
  const normalize = (s) => s.trim();
@@ -8854,11 +9302,11 @@ function useOptimisticChatMessages({
8854
9302
  chatMessages,
8855
9303
  onSendChat
8856
9304
  }) {
8857
- const [optimisticChat, setOptimisticChat] = React45.useState([]);
8858
- React45.useEffect(() => {
9305
+ const [optimisticChat, setOptimisticChat] = React46.useState([]);
9306
+ React46.useEffect(() => {
8859
9307
  setOptimisticChat([]);
8860
9308
  }, [threadId]);
8861
- const messages = React45.useMemo(() => {
9309
+ const messages = React46.useMemo(() => {
8862
9310
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
8863
9311
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
8864
9312
  if (unresolved.length === 0) return chatMessages;
@@ -8868,13 +9316,22 @@ function useOptimisticChatMessages({
8868
9316
  content: o.content,
8869
9317
  createdAt: o.createdAtIso,
8870
9318
  kind: "optimistic",
9319
+ attachments: (o.attachments ?? []).map((attachment, index) => ({
9320
+ id: `${o.id}:attachment:${index}`,
9321
+ name: `attachment-${index + 1}.png`,
9322
+ mimeType: "image/png",
9323
+ size: 1,
9324
+ uri: attachment.uri,
9325
+ width: attachment.width,
9326
+ height: attachment.height
9327
+ })),
8871
9328
  meta: o.failed ? { kind: "optimistic", event: "send.failed", status: "error" } : { kind: "optimistic", event: "send.pending", status: "info" }
8872
9329
  }));
8873
9330
  const merged = [...chatMessages, ...optimisticAsChat];
8874
9331
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
8875
9332
  return merged;
8876
9333
  }, [chatMessages, optimisticChat]);
8877
- React45.useEffect(() => {
9334
+ React46.useEffect(() => {
8878
9335
  if (optimisticChat.length === 0) return;
8879
9336
  setOptimisticChat((prev) => {
8880
9337
  if (prev.length === 0) return prev;
@@ -8882,7 +9339,7 @@ function useOptimisticChatMessages({
8882
9339
  return next.length === prev.length ? prev : next;
8883
9340
  });
8884
9341
  }, [chatMessages, optimisticChat.length]);
8885
- const onSend = React45.useCallback(
9342
+ const onSend = React46.useCallback(
8886
9343
  async (text, attachments) => {
8887
9344
  if (disableOptimistic) {
8888
9345
  await onSendChat(text, attachments);
@@ -8891,7 +9348,7 @@ function useOptimisticChatMessages({
8891
9348
  const createdAtIso = (/* @__PURE__ */ new Date()).toISOString();
8892
9349
  const baseServerLastId = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1].id : null;
8893
9350
  const id = makeOptimisticId();
8894
- const normalizedAttachments = attachments && attachments.length > 0 ? [...attachments] : void 0;
9351
+ const normalizedAttachments = attachments && attachments.length > 0 ? await resolveAttachmentDimensions(attachments) : void 0;
8895
9352
  setOptimisticChat((prev) => [
8896
9353
  ...prev,
8897
9354
  {
@@ -8910,8 +9367,9 @@ function useOptimisticChatMessages({
8910
9367
  },
8911
9368
  [chatMessages, disableOptimistic, onSendChat]
8912
9369
  );
8913
- const onRetry = React45.useCallback(
9370
+ const onRetry = React46.useCallback(
8914
9371
  async (messageId) => {
9372
+ var _a;
8915
9373
  if (disableOptimistic) return;
8916
9374
  const target = optimisticChat.find((m) => m.id === messageId);
8917
9375
  if (!target || target.retrying) return;
@@ -8922,7 +9380,10 @@ function useOptimisticChatMessages({
8922
9380
  )
8923
9381
  );
8924
9382
  try {
8925
- await onSendChat(target.content, target.attachments);
9383
+ await onSendChat(
9384
+ target.content,
9385
+ (_a = target.attachments) == null ? void 0 : _a.map((att) => att.uri)
9386
+ );
8926
9387
  setOptimisticChat(
8927
9388
  (prev) => prev.map((m) => m.id === messageId ? { ...m, retrying: false } : m)
8928
9389
  );
@@ -8934,7 +9395,7 @@ function useOptimisticChatMessages({
8934
9395
  },
8935
9396
  [chatMessages, disableOptimistic, onSendChat, optimisticChat]
8936
9397
  );
8937
- const isRetrying = React45.useCallback(
9398
+ const isRetrying = React46.useCallback(
8938
9399
  (messageId) => {
8939
9400
  return optimisticChat.some((m) => m.id === messageId && m.retrying);
8940
9401
  },
@@ -8951,24 +9412,24 @@ import {
8951
9412
 
8952
9413
  // src/components/icons/RemixUpIcon.tsx
8953
9414
  import Svg3, { Path as Path3 } from "react-native-svg";
8954
- import { jsx as jsx62, jsxs as jsxs39 } from "react/jsx-runtime";
9415
+ import { jsx as jsx64, jsxs as jsxs40 } from "react/jsx-runtime";
8955
9416
  function RemixUpIcon({ width = 24, height = 24, ...props }) {
8956
- return /* @__PURE__ */ jsxs39(Svg3, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
8957
- /* @__PURE__ */ jsx62(
9417
+ return /* @__PURE__ */ jsxs40(Svg3, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
9418
+ /* @__PURE__ */ jsx64(
8958
9419
  Path3,
8959
9420
  {
8960
9421
  d: "M34.706 7.62939e-05L34.7656 2.28882e-05L21.44 13.2661L0 34.8401L13.266 48.1061L34.706 26.5321L21.44 13.2661L34.706 7.62939e-05Z",
8961
9422
  fill: "#00CBC0"
8962
9423
  }
8963
9424
  ),
8964
- /* @__PURE__ */ jsx62(
9425
+ /* @__PURE__ */ jsx64(
8965
9426
  Path3,
8966
9427
  {
8967
9428
  d: "M47.972 13.266L34.7656 2.28882e-05L34.706 7.62939e-05L47.972 13.266L34.706 26.5321L56.28 48.106L69.546 34.84L47.972 13.266Z",
8968
9429
  fill: "#FF1820"
8969
9430
  }
8970
9431
  ),
8971
- /* @__PURE__ */ jsx62(
9432
+ /* @__PURE__ */ jsx64(
8972
9433
  Path3,
8973
9434
  {
8974
9435
  d: "M34.7656 2.28882e-05L21.44 13.2661L34.706 26.5321L47.972 13.266L34.7656 2.28882e-05Z",
@@ -8978,25 +9439,8 @@ function RemixUpIcon({ width = 24, height = 24, ...props }) {
8978
9439
  ] });
8979
9440
  }
8980
9441
 
8981
- // src/components/icons/RemixXLoopLottie.tsx
8982
- import LottieView from "lottie-react-native";
8983
- import { jsx as jsx63 } from "react/jsx-runtime";
8984
- var remixXLoopSource = require_remix_x_loop_lottie();
8985
- var Lottie = LottieView;
8986
- function RemixXLoopLottie({ size = 24, style }) {
8987
- return /* @__PURE__ */ jsx63(
8988
- Lottie,
8989
- {
8990
- source: remixXLoopSource,
8991
- autoPlay: true,
8992
- loop: true,
8993
- style: [{ width: size, height: size }, style]
8994
- }
8995
- );
8996
- }
8997
-
8998
9442
  // src/studio/ui/StudioOverlay.tsx
8999
- import { Fragment as Fragment7, jsx as jsx64, jsxs as jsxs40 } from "react/jsx-runtime";
9443
+ import { Fragment as Fragment8, jsx as jsx65, jsxs as jsxs41 } from "react/jsx-runtime";
9000
9444
  function StudioOverlay({
9001
9445
  captureTargetRef,
9002
9446
  app,
@@ -9027,6 +9471,7 @@ function StudioOverlay({
9027
9471
  chatSending,
9028
9472
  chatShowTypingIndicator,
9029
9473
  onSendChat,
9474
+ onChatAttachmentLoadError,
9030
9475
  chatQueueItems,
9031
9476
  onRemoveQueueItem,
9032
9477
  chatProgress,
@@ -9040,15 +9485,15 @@ function StudioOverlay({
9040
9485
  onSwitchRelatedApp
9041
9486
  }) {
9042
9487
  const theme = useTheme();
9043
- const { width } = useWindowDimensions4();
9044
- const [sheetOpen, setSheetOpen] = React46.useState(false);
9045
- const sheetOpenRef = React46.useRef(sheetOpen);
9046
- const pendingNavigateHomeRef = React46.useRef(false);
9047
- const [activePage, setActivePage] = React46.useState("preview");
9048
- const [drawing, setDrawing] = React46.useState(false);
9049
- const [chatAttachments, setChatAttachments] = React46.useState([]);
9050
- const [commentsAppId, setCommentsAppId] = React46.useState(null);
9051
- const [commentsCount, setCommentsCount] = React46.useState(null);
9488
+ const { width } = useWindowDimensions5();
9489
+ const [sheetOpen, setSheetOpen] = React47.useState(false);
9490
+ const sheetOpenRef = React47.useRef(sheetOpen);
9491
+ const pendingNavigateHomeRef = React47.useRef(false);
9492
+ const [activePage, setActivePage] = React47.useState("preview");
9493
+ const [drawing, setDrawing] = React47.useState(false);
9494
+ const [chatAttachments, setChatAttachments] = React47.useState([]);
9495
+ const [commentsAppId, setCommentsAppId] = React47.useState(null);
9496
+ const [commentsCount, setCommentsCount] = React47.useState(null);
9052
9497
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
9053
9498
  const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
9054
9499
  const isBubbleLoading = (app == null ? void 0 : app.status) === "editing" || isBaseBundleDownloading;
@@ -9061,24 +9506,24 @@ function StudioOverlay({
9061
9506
  chatMessages,
9062
9507
  onSendChat
9063
9508
  });
9064
- const [confirmMrId, setConfirmMrId] = React46.useState(null);
9065
- const confirmMr = React46.useMemo(
9509
+ const [confirmMrId, setConfirmMrId] = React47.useState(null);
9510
+ const confirmMr = React47.useMemo(
9066
9511
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
9067
9512
  [confirmMrId, incomingMergeRequests]
9068
9513
  );
9069
- const handleSheetOpenChange = React46.useCallback((open) => {
9514
+ const handleSheetOpenChange = React47.useCallback((open) => {
9070
9515
  setSheetOpen(open);
9071
9516
  if (!open) Keyboard5.dismiss();
9072
9517
  }, []);
9073
- const closeSheet = React46.useCallback(() => {
9518
+ const closeSheet = React47.useCallback(() => {
9074
9519
  handleSheetOpenChange(false);
9075
9520
  }, [handleSheetOpenChange]);
9076
- const openSheet = React46.useCallback(() => setSheetOpen(true), []);
9077
- const goToChat = React46.useCallback(() => {
9521
+ const openSheet = React47.useCallback(() => setSheetOpen(true), []);
9522
+ const goToChat = React47.useCallback(() => {
9078
9523
  setActivePage("chat");
9079
9524
  openSheet();
9080
9525
  }, [openSheet]);
9081
- const backToPreview = React46.useCallback(() => {
9526
+ const backToPreview = React47.useCallback(() => {
9082
9527
  if (Platform11.OS !== "ios") {
9083
9528
  Keyboard5.dismiss();
9084
9529
  setActivePage("preview");
@@ -9096,11 +9541,11 @@ function StudioOverlay({
9096
9541
  const t = setTimeout(finalize, 350);
9097
9542
  Keyboard5.dismiss();
9098
9543
  }, []);
9099
- const startDraw = React46.useCallback(() => {
9544
+ const startDraw = React47.useCallback(() => {
9100
9545
  setDrawing(true);
9101
9546
  closeSheet();
9102
9547
  }, [closeSheet]);
9103
- const handleDrawCapture = React46.useCallback(
9548
+ const handleDrawCapture = React47.useCallback(
9104
9549
  (dataUrl) => {
9105
9550
  setChatAttachments((prev) => [...prev, dataUrl]);
9106
9551
  setDrawing(false);
@@ -9109,7 +9554,7 @@ function StudioOverlay({
9109
9554
  },
9110
9555
  [openSheet]
9111
9556
  );
9112
- const toggleSheet = React46.useCallback(async () => {
9557
+ const toggleSheet = React47.useCallback(async () => {
9113
9558
  if (!sheetOpen) {
9114
9559
  const shouldExitTest = Boolean(testingMrId) || isTesting;
9115
9560
  if (shouldExitTest) {
@@ -9121,7 +9566,7 @@ function StudioOverlay({
9121
9566
  closeSheet();
9122
9567
  }
9123
9568
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
9124
- const handleTestMr = React46.useCallback(
9569
+ const handleTestMr = React47.useCallback(
9125
9570
  async (mr) => {
9126
9571
  if (!onTestMr) return;
9127
9572
  await onTestMr(mr);
@@ -9129,7 +9574,7 @@ function StudioOverlay({
9129
9574
  },
9130
9575
  [closeSheet, onTestMr]
9131
9576
  );
9132
- const handleNavigateHome = React46.useCallback(() => {
9577
+ const handleNavigateHome = React47.useCallback(() => {
9133
9578
  if (!onNavigateHome) return;
9134
9579
  if (Platform11.OS !== "android") {
9135
9580
  onNavigateHome();
@@ -9146,7 +9591,7 @@ function StudioOverlay({
9146
9591
  setActivePage("preview");
9147
9592
  closeSheet();
9148
9593
  }, [closeSheet, onNavigateHome]);
9149
- const handleSheetDismiss = React46.useCallback(() => {
9594
+ const handleSheetDismiss = React47.useCallback(() => {
9150
9595
  if (Platform11.OS !== "android") return;
9151
9596
  if (!pendingNavigateHomeRef.current) return;
9152
9597
  pendingNavigateHomeRef.current = false;
@@ -9154,21 +9599,21 @@ function StudioOverlay({
9154
9599
  onNavigateHome == null ? void 0 : onNavigateHome();
9155
9600
  });
9156
9601
  }, [onNavigateHome]);
9157
- React46.useEffect(() => {
9602
+ React47.useEffect(() => {
9158
9603
  if (!sheetOpen) {
9159
9604
  return;
9160
9605
  }
9161
9606
  pendingNavigateHomeRef.current = false;
9162
9607
  }, [sheetOpen]);
9163
- React46.useEffect(() => {
9608
+ React47.useEffect(() => {
9164
9609
  return () => {
9165
9610
  pendingNavigateHomeRef.current = false;
9166
9611
  };
9167
9612
  }, []);
9168
- React46.useEffect(() => {
9613
+ React47.useEffect(() => {
9169
9614
  sheetOpenRef.current = sheetOpen;
9170
9615
  }, [sheetOpen]);
9171
- React46.useEffect(() => {
9616
+ React47.useEffect(() => {
9172
9617
  const poller = startStudioControlPolling((action) => {
9173
9618
  if (action === "show" && !sheetOpenRef.current) openSheet();
9174
9619
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -9176,17 +9621,17 @@ function StudioOverlay({
9176
9621
  }, studioControlOptions);
9177
9622
  return () => poller.stop();
9178
9623
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
9179
- React46.useEffect(() => {
9624
+ React47.useEffect(() => {
9180
9625
  void publishComergeStudioUIState(sheetOpen, studioControlOptions);
9181
9626
  }, [sheetOpen, studioControlOptions]);
9182
- return /* @__PURE__ */ jsxs40(Fragment7, { children: [
9183
- /* @__PURE__ */ jsx64(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
9184
- /* @__PURE__ */ jsx64(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, onDismiss: handleSheetDismiss, children: /* @__PURE__ */ jsx64(
9627
+ return /* @__PURE__ */ jsxs41(Fragment8, { children: [
9628
+ /* @__PURE__ */ jsx65(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
9629
+ /* @__PURE__ */ jsx65(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, onDismiss: handleSheetDismiss, children: /* @__PURE__ */ jsx65(
9185
9630
  StudioSheetPager,
9186
9631
  {
9187
9632
  activePage,
9188
9633
  width,
9189
- preview: /* @__PURE__ */ jsx64(
9634
+ preview: /* @__PURE__ */ jsx65(
9190
9635
  PreviewPanel,
9191
9636
  {
9192
9637
  app,
@@ -9220,7 +9665,7 @@ function StudioOverlay({
9220
9665
  onSwitchRelatedApp
9221
9666
  }
9222
9667
  ),
9223
- chat: /* @__PURE__ */ jsx64(
9668
+ chat: /* @__PURE__ */ jsx65(
9224
9669
  ChatPanel,
9225
9670
  {
9226
9671
  messages: optimistic.messages,
@@ -9240,6 +9685,7 @@ function StudioOverlay({
9240
9685
  onSend: optimistic.onSend,
9241
9686
  onRetryMessage: optimistic.onRetry,
9242
9687
  isRetryingMessage: optimistic.isRetrying,
9688
+ onAttachmentLoadError: onChatAttachmentLoadError,
9243
9689
  queueItems: queueItemsForChat,
9244
9690
  onRemoveQueueItem,
9245
9691
  progress: chatProgress
@@ -9247,7 +9693,7 @@ function StudioOverlay({
9247
9693
  )
9248
9694
  }
9249
9695
  ) }),
9250
- showBubble && /* @__PURE__ */ jsx64(
9696
+ showBubble && /* @__PURE__ */ jsx65(
9251
9697
  Bubble,
9252
9698
  {
9253
9699
  visible: !sheetOpen && !drawing,
@@ -9256,10 +9702,10 @@ function StudioOverlay({
9256
9702
  onPress: toggleSheet,
9257
9703
  isLoading: isBubbleLoading,
9258
9704
  loadingBorderTone: isBaseBundleDownloading ? "warning" : "default",
9259
- children: /* @__PURE__ */ jsx64(View48, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: isBubbleLoading ? /* @__PURE__ */ jsx64(RemixXLoopLottie, { size: 24 }) : /* @__PURE__ */ jsx64(RemixUpIcon, { width: 24, height: 24 }) })
9705
+ children: /* @__PURE__ */ jsx65(View49, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: isBubbleLoading ? /* @__PURE__ */ jsx65(RemixXLoopLottie, { size: 24 }) : /* @__PURE__ */ jsx65(RemixUpIcon, { width: 24, height: 24 }) })
9260
9706
  }
9261
9707
  ),
9262
- /* @__PURE__ */ jsx64(
9708
+ /* @__PURE__ */ jsx65(
9263
9709
  DrawModeOverlay,
9264
9710
  {
9265
9711
  visible: drawing,
@@ -9268,7 +9714,7 @@ function StudioOverlay({
9268
9714
  onCapture: handleDrawCapture
9269
9715
  }
9270
9716
  ),
9271
- /* @__PURE__ */ jsx64(
9717
+ /* @__PURE__ */ jsx65(
9272
9718
  ConfirmMergeFlow,
9273
9719
  {
9274
9720
  visible: Boolean(confirmMr),
@@ -9282,7 +9728,7 @@ function StudioOverlay({
9282
9728
  onTestFirst: handleTestMr
9283
9729
  }
9284
9730
  ),
9285
- /* @__PURE__ */ jsx64(
9731
+ /* @__PURE__ */ jsx65(
9286
9732
  AppCommentsSheet,
9287
9733
  {
9288
9734
  appId: commentsAppId,
@@ -9295,7 +9741,7 @@ function StudioOverlay({
9295
9741
  }
9296
9742
 
9297
9743
  // src/studio/hooks/useEditQueue.ts
9298
- import * as React47 from "react";
9744
+ import * as React48 from "react";
9299
9745
 
9300
9746
  // src/data/apps/edit-queue/remote.ts
9301
9747
  var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
@@ -9404,17 +9850,17 @@ var editQueueRepository = new EditQueueRepositoryImpl(
9404
9850
 
9405
9851
  // src/studio/hooks/useEditQueue.ts
9406
9852
  function useEditQueue(appId) {
9407
- const [items, setItems] = React47.useState([]);
9408
- const [loading, setLoading] = React47.useState(false);
9409
- const [error, setError] = React47.useState(null);
9410
- const activeRequestIdRef = React47.useRef(0);
9853
+ const [items, setItems] = React48.useState([]);
9854
+ const [loading, setLoading] = React48.useState(false);
9855
+ const [error, setError] = React48.useState(null);
9856
+ const activeRequestIdRef = React48.useRef(0);
9411
9857
  const foregroundSignal = useForegroundSignal(Boolean(appId));
9412
- const upsertSorted = React47.useCallback((prev, nextItem) => {
9858
+ const upsertSorted = React48.useCallback((prev, nextItem) => {
9413
9859
  const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
9414
9860
  next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
9415
9861
  return next;
9416
9862
  }, []);
9417
- const refetch = React47.useCallback(async () => {
9863
+ const refetch = React48.useCallback(async () => {
9418
9864
  if (!appId) {
9419
9865
  setItems([]);
9420
9866
  return;
@@ -9434,10 +9880,10 @@ function useEditQueue(appId) {
9434
9880
  if (activeRequestIdRef.current === requestId) setLoading(false);
9435
9881
  }
9436
9882
  }, [appId]);
9437
- React47.useEffect(() => {
9883
+ React48.useEffect(() => {
9438
9884
  void refetch();
9439
9885
  }, [refetch]);
9440
- React47.useEffect(() => {
9886
+ React48.useEffect(() => {
9441
9887
  if (!appId) return;
9442
9888
  const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
9443
9889
  onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
@@ -9446,7 +9892,7 @@ function useEditQueue(appId) {
9446
9892
  });
9447
9893
  return unsubscribe;
9448
9894
  }, [appId, upsertSorted, foregroundSignal]);
9449
- React47.useEffect(() => {
9895
+ React48.useEffect(() => {
9450
9896
  if (!appId) return;
9451
9897
  if (foregroundSignal <= 0) return;
9452
9898
  void refetch();
@@ -9455,16 +9901,16 @@ function useEditQueue(appId) {
9455
9901
  }
9456
9902
 
9457
9903
  // src/studio/hooks/useEditQueueActions.ts
9458
- import * as React48 from "react";
9904
+ import * as React49 from "react";
9459
9905
  function useEditQueueActions(appId) {
9460
- const update = React48.useCallback(
9906
+ const update = React49.useCallback(
9461
9907
  async (queueItemId, payload) => {
9462
9908
  if (!appId) return;
9463
9909
  await editQueueRepository.update(appId, queueItemId, payload);
9464
9910
  },
9465
9911
  [appId]
9466
9912
  );
9467
- const cancel = React48.useCallback(
9913
+ const cancel = React49.useCallback(
9468
9914
  async (queueItemId) => {
9469
9915
  if (!appId) return;
9470
9916
  await editQueueRepository.cancel(appId, queueItemId);
@@ -9475,7 +9921,7 @@ function useEditQueueActions(appId) {
9475
9921
  }
9476
9922
 
9477
9923
  // src/studio/hooks/useAgentRunProgress.ts
9478
- import * as React49 from "react";
9924
+ import * as React50 from "react";
9479
9925
 
9480
9926
  // src/data/agent-progress/repository.ts
9481
9927
  function mapRun(row) {
@@ -9741,23 +10187,23 @@ function deriveView(run, events, nowMs) {
9741
10187
  function useAgentRunProgress(threadId, opts) {
9742
10188
  var _a;
9743
10189
  const enabled = Boolean((opts == null ? void 0 : opts.enabled) ?? true);
9744
- const [run, setRun] = React49.useState(null);
9745
- const [events, setEvents] = React49.useState([]);
9746
- const [loading, setLoading] = React49.useState(false);
9747
- const [error, setError] = React49.useState(null);
9748
- const activeRequestIdRef = React49.useRef(0);
9749
- const lastSeqRef = React49.useRef(0);
9750
- const runRef = React49.useRef(null);
10190
+ const [run, setRun] = React50.useState(null);
10191
+ const [events, setEvents] = React50.useState([]);
10192
+ const [loading, setLoading] = React50.useState(false);
10193
+ const [error, setError] = React50.useState(null);
10194
+ const activeRequestIdRef = React50.useRef(0);
10195
+ const lastSeqRef = React50.useRef(0);
10196
+ const runRef = React50.useRef(null);
9751
10197
  const foregroundSignal = useForegroundSignal(Boolean(threadId) && enabled);
9752
- const [bundleTick, setBundleTick] = React49.useState(0);
9753
- React49.useEffect(() => {
10198
+ const [bundleTick, setBundleTick] = React50.useState(0);
10199
+ React50.useEffect(() => {
9754
10200
  lastSeqRef.current = 0;
9755
10201
  runRef.current = null;
9756
10202
  }, [threadId]);
9757
- React49.useEffect(() => {
10203
+ React50.useEffect(() => {
9758
10204
  runRef.current = run;
9759
10205
  }, [run]);
9760
- const refetch = React49.useCallback(async () => {
10206
+ const refetch = React50.useCallback(async () => {
9761
10207
  if (!threadId || !enabled) {
9762
10208
  setRun(null);
9763
10209
  setEvents([]);
@@ -9793,15 +10239,15 @@ function useAgentRunProgress(threadId, opts) {
9793
10239
  if (activeRequestIdRef.current === requestId) setLoading(false);
9794
10240
  }
9795
10241
  }, [enabled, threadId]);
9796
- React49.useEffect(() => {
10242
+ React50.useEffect(() => {
9797
10243
  void refetch();
9798
10244
  }, [refetch]);
9799
- React49.useEffect(() => {
10245
+ React50.useEffect(() => {
9800
10246
  if (!threadId || !enabled) return;
9801
10247
  if (foregroundSignal <= 0) return;
9802
10248
  void refetch();
9803
10249
  }, [enabled, foregroundSignal, refetch, threadId]);
9804
- React49.useEffect(() => {
10250
+ React50.useEffect(() => {
9805
10251
  if (!threadId || !enabled) return;
9806
10252
  const unsubRuns = agentProgressRepository.subscribeThreadRuns(threadId, {
9807
10253
  onInsert: (nextRun) => {
@@ -9831,7 +10277,7 @@ function useAgentRunProgress(threadId, opts) {
9831
10277
  });
9832
10278
  return unsubRuns;
9833
10279
  }, [enabled, threadId, foregroundSignal]);
9834
- React49.useEffect(() => {
10280
+ React50.useEffect(() => {
9835
10281
  if (!enabled || !(run == null ? void 0 : run.id)) return;
9836
10282
  const runId = run.id;
9837
10283
  const processIncoming = (incoming) => {
@@ -9863,8 +10309,8 @@ function useAgentRunProgress(threadId, opts) {
9863
10309
  });
9864
10310
  return unsubscribe;
9865
10311
  }, [enabled, run == null ? void 0 : run.id, foregroundSignal]);
9866
- const view = React49.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
9867
- React49.useEffect(() => {
10312
+ const view = React50.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
10313
+ React50.useEffect(() => {
9868
10314
  var _a2;
9869
10315
  if (!((_a2 = view.bundle) == null ? void 0 : _a2.active)) return;
9870
10316
  const interval = setInterval(() => {
@@ -9877,13 +10323,13 @@ function useAgentRunProgress(threadId, opts) {
9877
10323
  }
9878
10324
 
9879
10325
  // src/studio/hooks/useRelatedApps.ts
9880
- import * as React50 from "react";
10326
+ import * as React51 from "react";
9881
10327
  function useRelatedApps(appId) {
9882
- const [relatedApps, setRelatedApps] = React50.useState(null);
9883
- const [loading, setLoading] = React50.useState(false);
9884
- const [error, setError] = React50.useState(null);
9885
- const requestSeqRef = React50.useRef(0);
9886
- const fetchRelatedApps = React50.useCallback(async () => {
10328
+ const [relatedApps, setRelatedApps] = React51.useState(null);
10329
+ const [loading, setLoading] = React51.useState(false);
10330
+ const [error, setError] = React51.useState(null);
10331
+ const requestSeqRef = React51.useRef(0);
10332
+ const fetchRelatedApps = React51.useCallback(async () => {
9887
10333
  if (!appId) {
9888
10334
  setRelatedApps(null);
9889
10335
  setError(null);
@@ -9909,7 +10355,7 @@ function useRelatedApps(appId) {
9909
10355
  }
9910
10356
  }
9911
10357
  }, [appId]);
9912
- React50.useEffect(() => {
10358
+ React51.useEffect(() => {
9913
10359
  void fetchRelatedApps();
9914
10360
  }, [fetchRelatedApps]);
9915
10361
  return {
@@ -9921,7 +10367,7 @@ function useRelatedApps(appId) {
9921
10367
  }
9922
10368
 
9923
10369
  // src/studio/ComergeStudio.tsx
9924
- import { jsx as jsx65, jsxs as jsxs41 } from "react/jsx-runtime";
10370
+ import { jsx as jsx66, jsxs as jsxs42 } from "react/jsx-runtime";
9925
10371
  function ComergeStudio({
9926
10372
  appId,
9927
10373
  clientKey: clientKey2,
@@ -9937,13 +10383,13 @@ function ComergeStudio({
9937
10383
  embeddedBaseBundles,
9938
10384
  onSystemEvent
9939
10385
  }) {
9940
- const [activeAppId, setActiveAppId] = React51.useState(appId);
9941
- const [runtimeAppId, setRuntimeAppId] = React51.useState(appId);
9942
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React51.useState(null);
9943
- const didSyncFromHostRef = React51.useRef(false);
9944
- const lastNotifiedRef = React51.useRef(null);
9945
- const platform = React51.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
9946
- const notifyActiveAppChanged = React51.useCallback(
10386
+ const [activeAppId, setActiveAppId] = React52.useState(appId);
10387
+ const [runtimeAppId, setRuntimeAppId] = React52.useState(appId);
10388
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React52.useState(null);
10389
+ const didSyncFromHostRef = React52.useRef(false);
10390
+ const lastNotifiedRef = React52.useRef(null);
10391
+ const platform = React52.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
10392
+ const notifyActiveAppChanged = React52.useCallback(
9947
10393
  (nextAppId, source) => {
9948
10394
  if (!onActiveAppChanged) return;
9949
10395
  const trimmedAppId = nextAppId.trim();
@@ -9956,28 +10402,28 @@ function ComergeStudio({
9956
10402
  },
9957
10403
  [appKey, onActiveAppChanged]
9958
10404
  );
9959
- const setActiveAppIdWithSource = React51.useCallback(
10405
+ const setActiveAppIdWithSource = React52.useCallback(
9960
10406
  (nextAppId, source) => {
9961
10407
  setActiveAppId(nextAppId);
9962
10408
  notifyActiveAppChanged(nextAppId, source);
9963
10409
  },
9964
10410
  [notifyActiveAppChanged]
9965
10411
  );
9966
- React51.useEffect(() => {
10412
+ React52.useEffect(() => {
9967
10413
  const source = didSyncFromHostRef.current ? "host_route_sync" : "initial";
9968
10414
  didSyncFromHostRef.current = true;
9969
10415
  setActiveAppIdWithSource(appId, source);
9970
10416
  setRuntimeAppId(appId);
9971
10417
  setPendingRuntimeTargetAppId(null);
9972
10418
  }, [appId, setActiveAppIdWithSource]);
9973
- const captureTargetRef = React51.useRef(null);
9974
- return /* @__PURE__ */ jsx65(
10419
+ const captureTargetRef = React52.useRef(null);
10420
+ return /* @__PURE__ */ jsx66(
9975
10421
  StudioBootstrap,
9976
10422
  {
9977
10423
  clientKey: clientKey2,
9978
10424
  analyticsEnabled,
9979
- fallback: /* @__PURE__ */ jsx65(View49, { style: { flex: 1 } }),
9980
- children: ({ userId }) => /* @__PURE__ */ jsx65(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx65(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx65(
10425
+ fallback: /* @__PURE__ */ jsx66(View50, { style: { flex: 1 } }),
10426
+ children: ({ userId }) => /* @__PURE__ */ jsx66(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx66(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx66(
9981
10427
  ComergeStudioInner,
9982
10428
  {
9983
10429
  userId,
@@ -10027,11 +10473,11 @@ function ComergeStudioInner({
10027
10473
  const { app, loading: appLoading } = useApp(activeAppId);
10028
10474
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
10029
10475
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
10030
- const sawEditingOnPendingTargetRef = React51.useRef(false);
10031
- React51.useEffect(() => {
10476
+ const sawEditingOnPendingTargetRef = React52.useRef(false);
10477
+ React52.useEffect(() => {
10032
10478
  sawEditingOnPendingTargetRef.current = false;
10033
10479
  }, [pendingRuntimeTargetAppId]);
10034
- React51.useEffect(() => {
10480
+ React52.useEffect(() => {
10035
10481
  if (!pendingRuntimeTargetAppId) return;
10036
10482
  if (activeAppId !== pendingRuntimeTargetAppId) return;
10037
10483
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -10049,13 +10495,13 @@ function ComergeStudioInner({
10049
10495
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
10050
10496
  embeddedBaseBundles
10051
10497
  });
10052
- const sawEditingOnActiveAppRef = React51.useRef(false);
10053
- const [showPostEditPreparing, setShowPostEditPreparing] = React51.useState(false);
10054
- React51.useEffect(() => {
10498
+ const sawEditingOnActiveAppRef = React52.useRef(false);
10499
+ const [showPostEditPreparing, setShowPostEditPreparing] = React52.useState(false);
10500
+ React52.useEffect(() => {
10055
10501
  sawEditingOnActiveAppRef.current = false;
10056
10502
  setShowPostEditPreparing(false);
10057
10503
  }, [activeAppId]);
10058
- React51.useEffect(() => {
10504
+ React52.useEffect(() => {
10059
10505
  if (!(app == null ? void 0 : app.id)) return;
10060
10506
  if (app.status === "editing") {
10061
10507
  sawEditingOnActiveAppRef.current = true;
@@ -10067,7 +10513,7 @@ function ComergeStudioInner({
10067
10513
  sawEditingOnActiveAppRef.current = false;
10068
10514
  }
10069
10515
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
10070
- React51.useEffect(() => {
10516
+ React52.useEffect(() => {
10071
10517
  if (!showPostEditPreparing) return;
10072
10518
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10073
10519
  if (!stillProcessingBaseBundle) {
@@ -10079,19 +10525,19 @@ function ComergeStudioInner({
10079
10525
  const editQueue = useEditQueue(activeAppId);
10080
10526
  const agentProgress = useAgentRunProgress(threadId, { enabled: enableAgentProgress });
10081
10527
  const editQueueActions = useEditQueueActions(activeAppId);
10082
- const [lastEditQueueInfo, setLastEditQueueInfo] = React51.useState(null);
10083
- const lastEditQueueInfoRef = React51.useRef(null);
10084
- const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React51.useState(false);
10528
+ const [lastEditQueueInfo, setLastEditQueueInfo] = React52.useState(null);
10529
+ const lastEditQueueInfoRef = React52.useRef(null);
10530
+ const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React52.useState(false);
10085
10531
  const mergeRequests = useMergeRequests({ appId: activeAppId });
10086
- const hasOpenOutgoingMr = React51.useMemo(() => {
10532
+ const hasOpenOutgoingMr = React52.useMemo(() => {
10087
10533
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
10088
10534
  }, [mergeRequests.lists.outgoing]);
10089
- const incomingReviewMrs = React51.useMemo(() => {
10535
+ const incomingReviewMrs = React52.useMemo(() => {
10090
10536
  if (!userId) return mergeRequests.lists.incoming;
10091
10537
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
10092
10538
  }, [mergeRequests.lists.incoming, userId]);
10093
10539
  const uploader = useAttachmentUpload();
10094
- const updateLastEditQueueInfo = React51.useCallback(
10540
+ const updateLastEditQueueInfo = React52.useCallback(
10095
10541
  (info) => {
10096
10542
  lastEditQueueInfoRef.current = info;
10097
10543
  setLastEditQueueInfo(info);
@@ -10133,13 +10579,13 @@ function ComergeStudioInner({
10133
10579
  }
10134
10580
  });
10135
10581
  const chatSendDisabled = false;
10136
- const [processingMrId, setProcessingMrId] = React51.useState(null);
10137
- const [testingMrId, setTestingMrId] = React51.useState(null);
10138
- const [syncingUpstream, setSyncingUpstream] = React51.useState(false);
10139
- const [upstreamSyncStatus, setUpstreamSyncStatus] = React51.useState(null);
10582
+ const [processingMrId, setProcessingMrId] = React52.useState(null);
10583
+ const [testingMrId, setTestingMrId] = React52.useState(null);
10584
+ const [syncingUpstream, setSyncingUpstream] = React52.useState(false);
10585
+ const [upstreamSyncStatus, setUpstreamSyncStatus] = React52.useState(null);
10140
10586
  const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
10141
10587
  const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10142
- const runtimePreparingText = React51.useMemo(() => {
10588
+ const runtimePreparingText = React52.useMemo(() => {
10143
10589
  const status = app == null ? void 0 : app.status;
10144
10590
  if (status === "ready" && bundle.bundleStatus === "pending") {
10145
10591
  return "Bundling app\u2026 this may take a few minutes";
@@ -10159,7 +10605,7 @@ function ComergeStudioInner({
10159
10605
  return "Preparing app\u2026";
10160
10606
  }
10161
10607
  }, [app == null ? void 0 : app.status, bundle.bundleStatus]);
10162
- const chatShowTypingIndicator = React51.useMemo(() => {
10608
+ const chatShowTypingIndicator = React52.useMemo(() => {
10163
10609
  var _a2;
10164
10610
  if (agentProgress.hasLiveProgress) return false;
10165
10611
  if (!thread.raw || thread.raw.length === 0) return false;
@@ -10168,12 +10614,12 @@ function ComergeStudioInner({
10168
10614
  return payloadType !== "outcome";
10169
10615
  }, [agentProgress.hasLiveProgress, thread.raw]);
10170
10616
  const showChatProgress = agentProgress.hasLiveProgress || Boolean((_a = agentProgress.view.bundle) == null ? void 0 : _a.active);
10171
- React51.useEffect(() => {
10617
+ React52.useEffect(() => {
10172
10618
  updateLastEditQueueInfo(null);
10173
10619
  setSuppressQueueUntilResponse(false);
10174
10620
  setUpstreamSyncStatus(null);
10175
10621
  }, [activeAppId, updateLastEditQueueInfo]);
10176
- const handleSyncUpstream = React51.useCallback(async () => {
10622
+ const handleSyncUpstream = React52.useCallback(async () => {
10177
10623
  if (!(app == null ? void 0 : app.id)) {
10178
10624
  throw new Error("Missing app");
10179
10625
  }
@@ -10186,7 +10632,7 @@ function ComergeStudioInner({
10186
10632
  setSyncingUpstream(false);
10187
10633
  }
10188
10634
  }, [activeAppId, app == null ? void 0 : app.id]);
10189
- React51.useEffect(() => {
10635
+ React52.useEffect(() => {
10190
10636
  if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
10191
10637
  const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
10192
10638
  if (!stillPresent) {
@@ -10194,7 +10640,7 @@ function ComergeStudioInner({
10194
10640
  setSuppressQueueUntilResponse(false);
10195
10641
  }
10196
10642
  }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
10197
- const chatQueueItems = React51.useMemo(() => {
10643
+ const chatQueueItems = React52.useMemo(() => {
10198
10644
  var _a2;
10199
10645
  if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
10200
10646
  return [];
@@ -10208,8 +10654,8 @@ function ComergeStudioInner({
10208
10654
  return editQueue.items;
10209
10655
  }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
10210
10656
  const { relatedApps, loading: relatedAppsLoading } = useRelatedApps(activeAppId);
10211
- const [switchingRelatedAppId, setSwitchingRelatedAppId] = React51.useState(null);
10212
- const handleOpenRelatedApps = React51.useCallback(() => {
10657
+ const [switchingRelatedAppId, setSwitchingRelatedAppId] = React52.useState(null);
10658
+ const handleOpenRelatedApps = React52.useCallback(() => {
10213
10659
  var _a2;
10214
10660
  if (!relatedApps) return;
10215
10661
  const ids = /* @__PURE__ */ new Set();
@@ -10218,7 +10664,7 @@ function ComergeStudioInner({
10218
10664
  for (const remix of relatedApps.remixes) ids.add(remix.id);
10219
10665
  void trackRelatedAppsOpened({ appId: relatedApps.current.id, relatedCount: ids.size });
10220
10666
  }, [relatedApps]);
10221
- const handleSwitchRelatedApp = React51.useCallback(
10667
+ const handleSwitchRelatedApp = React52.useCallback(
10222
10668
  async (targetAppId) => {
10223
10669
  var _a2;
10224
10670
  if (!targetAppId || targetAppId === activeAppId) return;
@@ -10279,8 +10725,8 @@ function ComergeStudioInner({
10279
10725
  setRuntimeAppId
10280
10726
  ]
10281
10727
  );
10282
- return /* @__PURE__ */ jsx65(View49, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs41(View49, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
10283
- /* @__PURE__ */ jsx65(
10728
+ return /* @__PURE__ */ jsx66(View50, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs42(View50, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
10729
+ /* @__PURE__ */ jsx66(
10284
10730
  RuntimeRenderer,
10285
10731
  {
10286
10732
  appKey,
@@ -10304,7 +10750,7 @@ function ComergeStudioInner({
10304
10750
  }
10305
10751
  }
10306
10752
  ),
10307
- /* @__PURE__ */ jsx65(
10753
+ /* @__PURE__ */ jsx66(
10308
10754
  StudioOverlay,
10309
10755
  {
10310
10756
  captureTargetRef,
@@ -10361,6 +10807,9 @@ function ComergeStudioInner({
10361
10807
  chatSending: actions.sending,
10362
10808
  chatShowTypingIndicator,
10363
10809
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
10810
+ onChatAttachmentLoadError: () => {
10811
+ thread.recoverAttachmentUrls();
10812
+ },
10364
10813
  chatQueueItems,
10365
10814
  onRemoveQueueItem: (id) => editQueueActions.cancel(id),
10366
10815
  chatProgress: showChatProgress ? agentProgress.view : null,