@comergehq/studio 0.1.35 → 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,15 +8795,15 @@ ${trimmedLine2}\u2026 `;
8395
8795
  }
8396
8796
 
8397
8797
  // src/components/chat/AgentProgressCard.tsx
8398
- import { View as View44 } from "react-native";
8798
+ import { View as View45 } from "react-native";
8399
8799
 
8400
8800
  // src/components/icons/RemixXLoopLottie.tsx
8401
8801
  import LottieView from "lottie-react-native";
8402
- import { jsx as jsx57 } from "react/jsx-runtime";
8802
+ import { jsx as jsx58 } from "react/jsx-runtime";
8403
8803
  var remixXLoopSource = require_remix_x_loop_lottie();
8404
8804
  var Lottie = LottieView;
8405
8805
  function RemixXLoopLottie({ size = 24, style }) {
8406
- return /* @__PURE__ */ jsx57(
8806
+ return /* @__PURE__ */ jsx58(
8407
8807
  Lottie,
8408
8808
  {
8409
8809
  source: remixXLoopSource,
@@ -8415,7 +8815,7 @@ function RemixXLoopLottie({ size = 24, style }) {
8415
8815
  }
8416
8816
 
8417
8817
  // src/components/chat/AgentProgressCard.tsx
8418
- import { jsx as jsx58, jsxs as jsxs35 } from "react/jsx-runtime";
8818
+ import { jsx as jsx59, jsxs as jsxs36 } from "react/jsx-runtime";
8419
8819
  function titleForPhase(phase) {
8420
8820
  if (phase === "planning") return "Planning";
8421
8821
  if (phase === "reasoning") return "Reasoning";
@@ -8440,8 +8840,8 @@ function AgentProgressCard({ progress }) {
8440
8840
  const showAnimatedStatusIcon = progress.status === "running";
8441
8841
  const subtitle = progress.latestMessage || `Agent is ${phaseLabel.toLowerCase()}...`;
8442
8842
  const todo = progress.todoSummary;
8443
- return /* @__PURE__ */ jsxs35(
8444
- View44,
8843
+ return /* @__PURE__ */ jsxs36(
8844
+ View45,
8445
8845
  {
8446
8846
  style: {
8447
8847
  borderWidth: 1,
@@ -8452,23 +8852,23 @@ function AgentProgressCard({ progress }) {
8452
8852
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8453
8853
  },
8454
8854
  children: [
8455
- /* @__PURE__ */ jsxs35(View44, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
8456
- /* @__PURE__ */ jsx58(Text, { variant: "caption", children: statusLabel }),
8457
- /* @__PURE__ */ jsxs35(View44, { style: { flexDirection: "row", alignItems: "center" }, children: [
8458
- /* @__PURE__ */ jsx58(Text, { variant: "captionMuted", children: phaseLabel }),
8459
- showAnimatedStatusIcon ? /* @__PURE__ */ jsx58(RemixXLoopLottie, { size: 20, style: { marginLeft: 8 } }) : null
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
8460
8860
  ] })
8461
8861
  ] }),
8462
- /* @__PURE__ */ jsx58(Text, { variant: "bodyMuted", children: subtitle }),
8463
- 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: [
8464
8864
  "Updated files: ",
8465
8865
  progress.changedFilesCount
8466
8866
  ] }) : null,
8467
- progress.recentFiles.length > 0 ? /* @__PURE__ */ jsx58(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: [
8468
8868
  "\u2022 ",
8469
8869
  path
8470
8870
  ] }, path)) }) : null,
8471
- todo ? /* @__PURE__ */ jsxs35(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8871
+ todo ? /* @__PURE__ */ jsxs36(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8472
8872
  "Todos: ",
8473
8873
  todo.completed,
8474
8874
  "/",
@@ -8482,8 +8882,8 @@ function AgentProgressCard({ progress }) {
8482
8882
  }
8483
8883
 
8484
8884
  // src/components/chat/BundleProgressCard.tsx
8485
- import { View as View45 } from "react-native";
8486
- import { jsx as jsx59, 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";
8487
8887
  function titleForStatus2(status) {
8488
8888
  if (status === "succeeded") return "Completed";
8489
8889
  if (status === "failed") return "Failed";
@@ -8495,8 +8895,8 @@ function BundleProgressCard({ progress }) {
8495
8895
  const percent = Math.round(Math.max(0, Math.min(1, progress.progressValue)) * 100);
8496
8896
  const fillColor = progress.status === "failed" ? theme.colors.danger : progress.status === "succeeded" ? theme.colors.success : theme.colors.warning;
8497
8897
  const detail = progress.errorMessage || progress.phaseLabel;
8498
- return /* @__PURE__ */ jsxs36(
8499
- View45,
8898
+ return /* @__PURE__ */ jsxs37(
8899
+ View46,
8500
8900
  {
8501
8901
  accessible: true,
8502
8902
  accessibilityRole: "progressbar",
@@ -8511,15 +8911,15 @@ function BundleProgressCard({ progress }) {
8511
8911
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8512
8912
  },
8513
8913
  children: [
8514
- /* @__PURE__ */ jsxs36(View45, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
8515
- /* @__PURE__ */ jsx59(Text, { variant: "caption", children: statusLabel }),
8516
- /* @__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: [
8517
8917
  percent,
8518
8918
  "%"
8519
8919
  ] })
8520
8920
  ] }),
8521
- /* @__PURE__ */ jsx59(
8522
- View45,
8921
+ /* @__PURE__ */ jsx60(
8922
+ View46,
8523
8923
  {
8524
8924
  style: {
8525
8925
  width: "100%",
@@ -8528,8 +8928,8 @@ function BundleProgressCard({ progress }) {
8528
8928
  backgroundColor: withAlpha(theme.colors.border, theme.scheme === "dark" ? 0.5 : 0.6),
8529
8929
  overflow: "hidden"
8530
8930
  },
8531
- children: /* @__PURE__ */ jsx59(
8532
- View45,
8931
+ children: /* @__PURE__ */ jsx60(
8932
+ View46,
8533
8933
  {
8534
8934
  style: {
8535
8935
  width: `${percent}%`,
@@ -8540,14 +8940,14 @@ function BundleProgressCard({ progress }) {
8540
8940
  )
8541
8941
  }
8542
8942
  ),
8543
- /* @__PURE__ */ jsx59(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 })
8544
8944
  ]
8545
8945
  }
8546
8946
  );
8547
8947
  }
8548
8948
 
8549
8949
  // src/studio/ui/ChatPanel.tsx
8550
- import { jsx as jsx60, jsxs as jsxs37 } from "react/jsx-runtime";
8950
+ import { jsx as jsx61, jsxs as jsxs38 } from "react/jsx-runtime";
8551
8951
  function ChatPanel({
8552
8952
  title = "Chat",
8553
8953
  messages,
@@ -8567,14 +8967,15 @@ function ChatPanel({
8567
8967
  onSend,
8568
8968
  onRetryMessage,
8569
8969
  isRetryingMessage,
8970
+ onAttachmentLoadError,
8570
8971
  queueItems = [],
8571
8972
  onRemoveQueueItem,
8572
8973
  progress = null
8573
8974
  }) {
8574
8975
  const theme = useTheme();
8575
- const listRef = React43.useRef(null);
8576
- const [nearBottom, setNearBottom] = React43.useState(true);
8577
- const handleSend = React43.useCallback(
8976
+ const listRef = React44.useRef(null);
8977
+ const [nearBottom, setNearBottom] = React44.useState(true);
8978
+ const handleSend = React44.useCallback(
8578
8979
  async (text, composerAttachments) => {
8579
8980
  const all = composerAttachments ?? attachments;
8580
8981
  await onSend(text, all.length > 0 ? all : void 0);
@@ -8588,25 +8989,25 @@ function ChatPanel({
8588
8989
  },
8589
8990
  [attachments, nearBottom, onClearAttachments, onSend]
8590
8991
  );
8591
- const handleScrollToBottom = React43.useCallback(() => {
8992
+ const handleScrollToBottom = React44.useCallback(() => {
8592
8993
  var _a;
8593
8994
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
8594
8995
  }, []);
8595
- const header = /* @__PURE__ */ jsx60(
8996
+ const header = /* @__PURE__ */ jsx61(
8596
8997
  ChatHeader,
8597
8998
  {
8598
- left: /* @__PURE__ */ jsxs37(View46, { style: { flexDirection: "row", alignItems: "center" }, children: [
8599
- /* @__PURE__ */ jsx60(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx60(IconBack, { size: 20, colorToken: "floatingContent" }) }),
8600
- onNavigateHome ? /* @__PURE__ */ jsx60(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx60(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
8601
9002
  ] }),
8602
- right: /* @__PURE__ */ jsxs37(View46, { style: { flexDirection: "row", alignItems: "center" }, children: [
8603
- onStartDraw ? /* @__PURE__ */ jsx60(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx60(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
8604
- /* @__PURE__ */ jsx60(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx60(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" }) })
8605
9006
  ] }),
8606
9007
  center: null
8607
9008
  }
8608
9009
  );
8609
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx60(
9010
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx61(
8610
9011
  ForkNoticeBanner,
8611
9012
  {
8612
9013
  isOwner: !shouldForkOnEdit,
@@ -8615,22 +9016,22 @@ function ChatPanel({
8615
9016
  ) : null;
8616
9017
  const showMessagesLoading = Boolean(loading) && messages.length === 0;
8617
9018
  if (showMessagesLoading) {
8618
- return /* @__PURE__ */ jsxs37(View46, { style: { flex: 1 }, children: [
8619
- /* @__PURE__ */ jsx60(View46, { children: header }),
8620
- topBanner ? /* @__PURE__ */ jsx60(View46, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
8621
- /* @__PURE__ */ jsxs37(View46, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
8622
- /* @__PURE__ */ jsx60(ActivityIndicator10, {}),
8623
- /* @__PURE__ */ jsx60(View46, { style: { height: 12 } }),
8624
- /* @__PURE__ */ jsx60(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" })
8625
9026
  ] })
8626
9027
  ] });
8627
9028
  }
8628
9029
  const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
8629
- const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ jsxs37(View46, { style: { gap: theme.spacing.sm }, children: [
8630
- progress ? bundleProgress ? /* @__PURE__ */ jsx60(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ jsx60(AgentProgressCard, { progress }) : null,
8631
- !progress && queueItems.length > 0 ? /* @__PURE__ */ jsx60(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
8632
9033
  ] }) : null;
8633
- return /* @__PURE__ */ jsx60(
9034
+ return /* @__PURE__ */ jsx61(
8634
9035
  ChatPage,
8635
9036
  {
8636
9037
  header,
@@ -8638,18 +9039,19 @@ function ChatPanel({
8638
9039
  showTypingIndicator,
8639
9040
  onRetryMessage,
8640
9041
  isRetryingMessage,
9042
+ onAttachmentLoadError,
8641
9043
  topBanner,
8642
9044
  composerTop: queueTop,
8643
9045
  composerHorizontalPadding: 0,
8644
9046
  listRef,
8645
9047
  onNearBottomChange: setNearBottom,
8646
- overlay: /* @__PURE__ */ jsx60(
9048
+ overlay: /* @__PURE__ */ jsx61(
8647
9049
  ScrollToBottomButton,
8648
9050
  {
8649
9051
  visible: !nearBottom,
8650
9052
  onPress: handleScrollToBottom,
8651
9053
  style: { bottom: 80 },
8652
- children: /* @__PURE__ */ jsx60(IconArrowDown, { size: 20, colorToken: "floatingContent" })
9054
+ children: /* @__PURE__ */ jsx61(IconArrowDown, { size: 20, colorToken: "floatingContent" })
8653
9055
  }
8654
9056
  ),
8655
9057
  composer: {
@@ -8669,9 +9071,9 @@ function ChatPanel({
8669
9071
  }
8670
9072
 
8671
9073
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
8672
- import * as React44 from "react";
8673
- import { Pressable as Pressable18, View as View47 } from "react-native";
8674
- import { jsx as jsx61, 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";
8675
9077
  function ConfirmMergeRequestDialog({
8676
9078
  visible,
8677
9079
  onOpenChange,
@@ -8682,14 +9084,14 @@ function ConfirmMergeRequestDialog({
8682
9084
  onTestFirst
8683
9085
  }) {
8684
9086
  const theme = useTheme();
8685
- const close = React44.useCallback(() => onOpenChange(false), [onOpenChange]);
9087
+ const close = React45.useCallback(() => onOpenChange(false), [onOpenChange]);
8686
9088
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
8687
- const handleConfirm = React44.useCallback(() => {
9089
+ const handleConfirm = React45.useCallback(() => {
8688
9090
  if (!mergeRequest) return;
8689
9091
  onOpenChange(false);
8690
9092
  void onConfirm();
8691
9093
  }, [mergeRequest, onConfirm, onOpenChange]);
8692
- const handleTestFirst = React44.useCallback(() => {
9094
+ const handleTestFirst = React45.useCallback(() => {
8693
9095
  if (!mergeRequest) return;
8694
9096
  onOpenChange(false);
8695
9097
  void onTestFirst(mergeRequest);
@@ -8701,7 +9103,7 @@ function ConfirmMergeRequestDialog({
8701
9103
  justifyContent: "center",
8702
9104
  alignSelf: "stretch"
8703
9105
  };
8704
- return /* @__PURE__ */ jsxs38(
9106
+ return /* @__PURE__ */ jsxs39(
8705
9107
  Modal,
8706
9108
  {
8707
9109
  visible,
@@ -8712,7 +9114,7 @@ function ConfirmMergeRequestDialog({
8712
9114
  backgroundColor: theme.colors.background
8713
9115
  },
8714
9116
  children: [
8715
- /* @__PURE__ */ jsx61(View47, { children: /* @__PURE__ */ jsx61(
9117
+ /* @__PURE__ */ jsx62(View48, { children: /* @__PURE__ */ jsx62(
8716
9118
  Text,
8717
9119
  {
8718
9120
  style: {
@@ -8724,9 +9126,9 @@ function ConfirmMergeRequestDialog({
8724
9126
  children: "Are you sure you want to approve this merge request?"
8725
9127
  }
8726
9128
  ) }),
8727
- /* @__PURE__ */ jsxs38(View47, { style: { marginTop: 16 }, children: [
8728
- /* @__PURE__ */ jsx61(
8729
- View47,
9129
+ /* @__PURE__ */ jsxs39(View48, { style: { marginTop: 16 }, children: [
9130
+ /* @__PURE__ */ jsx62(
9131
+ View48,
8730
9132
  {
8731
9133
  style: [
8732
9134
  fullWidthButtonBase,
@@ -8735,22 +9137,22 @@ function ConfirmMergeRequestDialog({
8735
9137
  opacity: canConfirm ? 1 : 0.5
8736
9138
  }
8737
9139
  ],
8738
- children: /* @__PURE__ */ jsx61(
8739
- Pressable18,
9140
+ children: /* @__PURE__ */ jsx62(
9141
+ Pressable19,
8740
9142
  {
8741
9143
  accessibilityRole: "button",
8742
9144
  accessibilityLabel: "Approve Merge",
8743
9145
  disabled: !canConfirm,
8744
9146
  onPress: handleConfirm,
8745
9147
  style: [fullWidthButtonBase, { flex: 1 }],
8746
- children: /* @__PURE__ */ jsx61(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" })
8747
9149
  }
8748
9150
  )
8749
9151
  }
8750
9152
  ),
8751
- /* @__PURE__ */ jsx61(View47, { style: { height: 8 } }),
8752
- /* @__PURE__ */ jsx61(
8753
- View47,
9153
+ /* @__PURE__ */ jsx62(View48, { style: { height: 8 } }),
9154
+ /* @__PURE__ */ jsx62(
9155
+ View48,
8754
9156
  {
8755
9157
  style: [
8756
9158
  fullWidthButtonBase,
@@ -8761,22 +9163,22 @@ function ConfirmMergeRequestDialog({
8761
9163
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
8762
9164
  }
8763
9165
  ],
8764
- children: /* @__PURE__ */ jsx61(
8765
- Pressable18,
9166
+ children: /* @__PURE__ */ jsx62(
9167
+ Pressable19,
8766
9168
  {
8767
9169
  accessibilityRole: "button",
8768
9170
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
8769
9171
  disabled: isBuilding || !mergeRequest,
8770
9172
  onPress: handleTestFirst,
8771
9173
  style: [fullWidthButtonBase, { flex: 1 }],
8772
- children: /* @__PURE__ */ jsx61(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" })
8773
9175
  }
8774
9176
  )
8775
9177
  }
8776
9178
  ),
8777
- /* @__PURE__ */ jsx61(View47, { style: { height: 8 } }),
8778
- /* @__PURE__ */ jsx61(
8779
- View47,
9179
+ /* @__PURE__ */ jsx62(View48, { style: { height: 8 } }),
9180
+ /* @__PURE__ */ jsx62(
9181
+ View48,
8780
9182
  {
8781
9183
  style: [
8782
9184
  fullWidthButtonBase,
@@ -8786,14 +9188,14 @@ function ConfirmMergeRequestDialog({
8786
9188
  borderColor: theme.colors.border
8787
9189
  }
8788
9190
  ],
8789
- children: /* @__PURE__ */ jsx61(
8790
- Pressable18,
9191
+ children: /* @__PURE__ */ jsx62(
9192
+ Pressable19,
8791
9193
  {
8792
9194
  accessibilityRole: "button",
8793
9195
  accessibilityLabel: "Cancel",
8794
9196
  onPress: close,
8795
9197
  style: [fullWidthButtonBase, { flex: 1 }],
8796
- children: /* @__PURE__ */ jsx61(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
9198
+ children: /* @__PURE__ */ jsx62(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
8797
9199
  }
8798
9200
  )
8799
9201
  }
@@ -8805,7 +9207,7 @@ function ConfirmMergeRequestDialog({
8805
9207
  }
8806
9208
 
8807
9209
  // src/studio/ui/ConfirmMergeFlow.tsx
8808
- import { jsx as jsx62 } from "react/jsx-runtime";
9210
+ import { jsx as jsx63 } from "react/jsx-runtime";
8809
9211
  function ConfirmMergeFlow({
8810
9212
  visible,
8811
9213
  onOpenChange,
@@ -8816,7 +9218,7 @@ function ConfirmMergeFlow({
8816
9218
  onConfirm,
8817
9219
  onTestFirst
8818
9220
  }) {
8819
- return /* @__PURE__ */ jsx62(
9221
+ return /* @__PURE__ */ jsx63(
8820
9222
  ConfirmMergeRequestDialog,
8821
9223
  {
8822
9224
  visible,
@@ -8838,7 +9240,8 @@ function ConfirmMergeFlow({
8838
9240
  }
8839
9241
 
8840
9242
  // src/studio/hooks/useOptimisticChatMessages.ts
8841
- import * as React45 from "react";
9243
+ import * as React46 from "react";
9244
+ import { Image as Image5 } from "react-native";
8842
9245
  function makeOptimisticId() {
8843
9246
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
8844
9247
  }
@@ -8849,6 +9252,28 @@ function toEpochMs2(createdAt) {
8849
9252
  const t = Date.parse(String(createdAt));
8850
9253
  return Number.isFinite(t) ? t : 0;
8851
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
+ }
8852
9277
  function isOptimisticResolvedByServer(chatMessages, o) {
8853
9278
  if (o.failed) return false;
8854
9279
  const normalize = (s) => s.trim();
@@ -8877,11 +9302,11 @@ function useOptimisticChatMessages({
8877
9302
  chatMessages,
8878
9303
  onSendChat
8879
9304
  }) {
8880
- const [optimisticChat, setOptimisticChat] = React45.useState([]);
8881
- React45.useEffect(() => {
9305
+ const [optimisticChat, setOptimisticChat] = React46.useState([]);
9306
+ React46.useEffect(() => {
8882
9307
  setOptimisticChat([]);
8883
9308
  }, [threadId]);
8884
- const messages = React45.useMemo(() => {
9309
+ const messages = React46.useMemo(() => {
8885
9310
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
8886
9311
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
8887
9312
  if (unresolved.length === 0) return chatMessages;
@@ -8891,13 +9316,22 @@ function useOptimisticChatMessages({
8891
9316
  content: o.content,
8892
9317
  createdAt: o.createdAtIso,
8893
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
+ })),
8894
9328
  meta: o.failed ? { kind: "optimistic", event: "send.failed", status: "error" } : { kind: "optimistic", event: "send.pending", status: "info" }
8895
9329
  }));
8896
9330
  const merged = [...chatMessages, ...optimisticAsChat];
8897
9331
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
8898
9332
  return merged;
8899
9333
  }, [chatMessages, optimisticChat]);
8900
- React45.useEffect(() => {
9334
+ React46.useEffect(() => {
8901
9335
  if (optimisticChat.length === 0) return;
8902
9336
  setOptimisticChat((prev) => {
8903
9337
  if (prev.length === 0) return prev;
@@ -8905,7 +9339,7 @@ function useOptimisticChatMessages({
8905
9339
  return next.length === prev.length ? prev : next;
8906
9340
  });
8907
9341
  }, [chatMessages, optimisticChat.length]);
8908
- const onSend = React45.useCallback(
9342
+ const onSend = React46.useCallback(
8909
9343
  async (text, attachments) => {
8910
9344
  if (disableOptimistic) {
8911
9345
  await onSendChat(text, attachments);
@@ -8914,7 +9348,7 @@ function useOptimisticChatMessages({
8914
9348
  const createdAtIso = (/* @__PURE__ */ new Date()).toISOString();
8915
9349
  const baseServerLastId = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1].id : null;
8916
9350
  const id = makeOptimisticId();
8917
- const normalizedAttachments = attachments && attachments.length > 0 ? [...attachments] : void 0;
9351
+ const normalizedAttachments = attachments && attachments.length > 0 ? await resolveAttachmentDimensions(attachments) : void 0;
8918
9352
  setOptimisticChat((prev) => [
8919
9353
  ...prev,
8920
9354
  {
@@ -8933,8 +9367,9 @@ function useOptimisticChatMessages({
8933
9367
  },
8934
9368
  [chatMessages, disableOptimistic, onSendChat]
8935
9369
  );
8936
- const onRetry = React45.useCallback(
9370
+ const onRetry = React46.useCallback(
8937
9371
  async (messageId) => {
9372
+ var _a;
8938
9373
  if (disableOptimistic) return;
8939
9374
  const target = optimisticChat.find((m) => m.id === messageId);
8940
9375
  if (!target || target.retrying) return;
@@ -8945,7 +9380,10 @@ function useOptimisticChatMessages({
8945
9380
  )
8946
9381
  );
8947
9382
  try {
8948
- 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
+ );
8949
9387
  setOptimisticChat(
8950
9388
  (prev) => prev.map((m) => m.id === messageId ? { ...m, retrying: false } : m)
8951
9389
  );
@@ -8957,7 +9395,7 @@ function useOptimisticChatMessages({
8957
9395
  },
8958
9396
  [chatMessages, disableOptimistic, onSendChat, optimisticChat]
8959
9397
  );
8960
- const isRetrying = React45.useCallback(
9398
+ const isRetrying = React46.useCallback(
8961
9399
  (messageId) => {
8962
9400
  return optimisticChat.some((m) => m.id === messageId && m.retrying);
8963
9401
  },
@@ -8974,24 +9412,24 @@ import {
8974
9412
 
8975
9413
  // src/components/icons/RemixUpIcon.tsx
8976
9414
  import Svg3, { Path as Path3 } from "react-native-svg";
8977
- import { jsx as jsx63, jsxs as jsxs39 } from "react/jsx-runtime";
9415
+ import { jsx as jsx64, jsxs as jsxs40 } from "react/jsx-runtime";
8978
9416
  function RemixUpIcon({ width = 24, height = 24, ...props }) {
8979
- return /* @__PURE__ */ jsxs39(Svg3, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
8980
- /* @__PURE__ */ jsx63(
9417
+ return /* @__PURE__ */ jsxs40(Svg3, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
9418
+ /* @__PURE__ */ jsx64(
8981
9419
  Path3,
8982
9420
  {
8983
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",
8984
9422
  fill: "#00CBC0"
8985
9423
  }
8986
9424
  ),
8987
- /* @__PURE__ */ jsx63(
9425
+ /* @__PURE__ */ jsx64(
8988
9426
  Path3,
8989
9427
  {
8990
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",
8991
9429
  fill: "#FF1820"
8992
9430
  }
8993
9431
  ),
8994
- /* @__PURE__ */ jsx63(
9432
+ /* @__PURE__ */ jsx64(
8995
9433
  Path3,
8996
9434
  {
8997
9435
  d: "M34.7656 2.28882e-05L21.44 13.2661L34.706 26.5321L47.972 13.266L34.7656 2.28882e-05Z",
@@ -9002,7 +9440,7 @@ function RemixUpIcon({ width = 24, height = 24, ...props }) {
9002
9440
  }
9003
9441
 
9004
9442
  // src/studio/ui/StudioOverlay.tsx
9005
- 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";
9006
9444
  function StudioOverlay({
9007
9445
  captureTargetRef,
9008
9446
  app,
@@ -9033,6 +9471,7 @@ function StudioOverlay({
9033
9471
  chatSending,
9034
9472
  chatShowTypingIndicator,
9035
9473
  onSendChat,
9474
+ onChatAttachmentLoadError,
9036
9475
  chatQueueItems,
9037
9476
  onRemoveQueueItem,
9038
9477
  chatProgress,
@@ -9046,15 +9485,15 @@ function StudioOverlay({
9046
9485
  onSwitchRelatedApp
9047
9486
  }) {
9048
9487
  const theme = useTheme();
9049
- const { width } = useWindowDimensions4();
9050
- const [sheetOpen, setSheetOpen] = React46.useState(false);
9051
- const sheetOpenRef = React46.useRef(sheetOpen);
9052
- const pendingNavigateHomeRef = React46.useRef(false);
9053
- const [activePage, setActivePage] = React46.useState("preview");
9054
- const [drawing, setDrawing] = React46.useState(false);
9055
- const [chatAttachments, setChatAttachments] = React46.useState([]);
9056
- const [commentsAppId, setCommentsAppId] = React46.useState(null);
9057
- 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);
9058
9497
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
9059
9498
  const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
9060
9499
  const isBubbleLoading = (app == null ? void 0 : app.status) === "editing" || isBaseBundleDownloading;
@@ -9067,24 +9506,24 @@ function StudioOverlay({
9067
9506
  chatMessages,
9068
9507
  onSendChat
9069
9508
  });
9070
- const [confirmMrId, setConfirmMrId] = React46.useState(null);
9071
- const confirmMr = React46.useMemo(
9509
+ const [confirmMrId, setConfirmMrId] = React47.useState(null);
9510
+ const confirmMr = React47.useMemo(
9072
9511
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
9073
9512
  [confirmMrId, incomingMergeRequests]
9074
9513
  );
9075
- const handleSheetOpenChange = React46.useCallback((open) => {
9514
+ const handleSheetOpenChange = React47.useCallback((open) => {
9076
9515
  setSheetOpen(open);
9077
9516
  if (!open) Keyboard5.dismiss();
9078
9517
  }, []);
9079
- const closeSheet = React46.useCallback(() => {
9518
+ const closeSheet = React47.useCallback(() => {
9080
9519
  handleSheetOpenChange(false);
9081
9520
  }, [handleSheetOpenChange]);
9082
- const openSheet = React46.useCallback(() => setSheetOpen(true), []);
9083
- const goToChat = React46.useCallback(() => {
9521
+ const openSheet = React47.useCallback(() => setSheetOpen(true), []);
9522
+ const goToChat = React47.useCallback(() => {
9084
9523
  setActivePage("chat");
9085
9524
  openSheet();
9086
9525
  }, [openSheet]);
9087
- const backToPreview = React46.useCallback(() => {
9526
+ const backToPreview = React47.useCallback(() => {
9088
9527
  if (Platform11.OS !== "ios") {
9089
9528
  Keyboard5.dismiss();
9090
9529
  setActivePage("preview");
@@ -9102,11 +9541,11 @@ function StudioOverlay({
9102
9541
  const t = setTimeout(finalize, 350);
9103
9542
  Keyboard5.dismiss();
9104
9543
  }, []);
9105
- const startDraw = React46.useCallback(() => {
9544
+ const startDraw = React47.useCallback(() => {
9106
9545
  setDrawing(true);
9107
9546
  closeSheet();
9108
9547
  }, [closeSheet]);
9109
- const handleDrawCapture = React46.useCallback(
9548
+ const handleDrawCapture = React47.useCallback(
9110
9549
  (dataUrl) => {
9111
9550
  setChatAttachments((prev) => [...prev, dataUrl]);
9112
9551
  setDrawing(false);
@@ -9115,7 +9554,7 @@ function StudioOverlay({
9115
9554
  },
9116
9555
  [openSheet]
9117
9556
  );
9118
- const toggleSheet = React46.useCallback(async () => {
9557
+ const toggleSheet = React47.useCallback(async () => {
9119
9558
  if (!sheetOpen) {
9120
9559
  const shouldExitTest = Boolean(testingMrId) || isTesting;
9121
9560
  if (shouldExitTest) {
@@ -9127,7 +9566,7 @@ function StudioOverlay({
9127
9566
  closeSheet();
9128
9567
  }
9129
9568
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
9130
- const handleTestMr = React46.useCallback(
9569
+ const handleTestMr = React47.useCallback(
9131
9570
  async (mr) => {
9132
9571
  if (!onTestMr) return;
9133
9572
  await onTestMr(mr);
@@ -9135,7 +9574,7 @@ function StudioOverlay({
9135
9574
  },
9136
9575
  [closeSheet, onTestMr]
9137
9576
  );
9138
- const handleNavigateHome = React46.useCallback(() => {
9577
+ const handleNavigateHome = React47.useCallback(() => {
9139
9578
  if (!onNavigateHome) return;
9140
9579
  if (Platform11.OS !== "android") {
9141
9580
  onNavigateHome();
@@ -9152,7 +9591,7 @@ function StudioOverlay({
9152
9591
  setActivePage("preview");
9153
9592
  closeSheet();
9154
9593
  }, [closeSheet, onNavigateHome]);
9155
- const handleSheetDismiss = React46.useCallback(() => {
9594
+ const handleSheetDismiss = React47.useCallback(() => {
9156
9595
  if (Platform11.OS !== "android") return;
9157
9596
  if (!pendingNavigateHomeRef.current) return;
9158
9597
  pendingNavigateHomeRef.current = false;
@@ -9160,21 +9599,21 @@ function StudioOverlay({
9160
9599
  onNavigateHome == null ? void 0 : onNavigateHome();
9161
9600
  });
9162
9601
  }, [onNavigateHome]);
9163
- React46.useEffect(() => {
9602
+ React47.useEffect(() => {
9164
9603
  if (!sheetOpen) {
9165
9604
  return;
9166
9605
  }
9167
9606
  pendingNavigateHomeRef.current = false;
9168
9607
  }, [sheetOpen]);
9169
- React46.useEffect(() => {
9608
+ React47.useEffect(() => {
9170
9609
  return () => {
9171
9610
  pendingNavigateHomeRef.current = false;
9172
9611
  };
9173
9612
  }, []);
9174
- React46.useEffect(() => {
9613
+ React47.useEffect(() => {
9175
9614
  sheetOpenRef.current = sheetOpen;
9176
9615
  }, [sheetOpen]);
9177
- React46.useEffect(() => {
9616
+ React47.useEffect(() => {
9178
9617
  const poller = startStudioControlPolling((action) => {
9179
9618
  if (action === "show" && !sheetOpenRef.current) openSheet();
9180
9619
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -9182,17 +9621,17 @@ function StudioOverlay({
9182
9621
  }, studioControlOptions);
9183
9622
  return () => poller.stop();
9184
9623
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
9185
- React46.useEffect(() => {
9624
+ React47.useEffect(() => {
9186
9625
  void publishComergeStudioUIState(sheetOpen, studioControlOptions);
9187
9626
  }, [sheetOpen, studioControlOptions]);
9188
- return /* @__PURE__ */ jsxs40(Fragment7, { children: [
9189
- /* @__PURE__ */ jsx64(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
9190
- /* @__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(
9191
9630
  StudioSheetPager,
9192
9631
  {
9193
9632
  activePage,
9194
9633
  width,
9195
- preview: /* @__PURE__ */ jsx64(
9634
+ preview: /* @__PURE__ */ jsx65(
9196
9635
  PreviewPanel,
9197
9636
  {
9198
9637
  app,
@@ -9226,7 +9665,7 @@ function StudioOverlay({
9226
9665
  onSwitchRelatedApp
9227
9666
  }
9228
9667
  ),
9229
- chat: /* @__PURE__ */ jsx64(
9668
+ chat: /* @__PURE__ */ jsx65(
9230
9669
  ChatPanel,
9231
9670
  {
9232
9671
  messages: optimistic.messages,
@@ -9246,6 +9685,7 @@ function StudioOverlay({
9246
9685
  onSend: optimistic.onSend,
9247
9686
  onRetryMessage: optimistic.onRetry,
9248
9687
  isRetryingMessage: optimistic.isRetrying,
9688
+ onAttachmentLoadError: onChatAttachmentLoadError,
9249
9689
  queueItems: queueItemsForChat,
9250
9690
  onRemoveQueueItem,
9251
9691
  progress: chatProgress
@@ -9253,7 +9693,7 @@ function StudioOverlay({
9253
9693
  )
9254
9694
  }
9255
9695
  ) }),
9256
- showBubble && /* @__PURE__ */ jsx64(
9696
+ showBubble && /* @__PURE__ */ jsx65(
9257
9697
  Bubble,
9258
9698
  {
9259
9699
  visible: !sheetOpen && !drawing,
@@ -9262,10 +9702,10 @@ function StudioOverlay({
9262
9702
  onPress: toggleSheet,
9263
9703
  isLoading: isBubbleLoading,
9264
9704
  loadingBorderTone: isBaseBundleDownloading ? "warning" : "default",
9265
- 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 }) })
9266
9706
  }
9267
9707
  ),
9268
- /* @__PURE__ */ jsx64(
9708
+ /* @__PURE__ */ jsx65(
9269
9709
  DrawModeOverlay,
9270
9710
  {
9271
9711
  visible: drawing,
@@ -9274,7 +9714,7 @@ function StudioOverlay({
9274
9714
  onCapture: handleDrawCapture
9275
9715
  }
9276
9716
  ),
9277
- /* @__PURE__ */ jsx64(
9717
+ /* @__PURE__ */ jsx65(
9278
9718
  ConfirmMergeFlow,
9279
9719
  {
9280
9720
  visible: Boolean(confirmMr),
@@ -9288,7 +9728,7 @@ function StudioOverlay({
9288
9728
  onTestFirst: handleTestMr
9289
9729
  }
9290
9730
  ),
9291
- /* @__PURE__ */ jsx64(
9731
+ /* @__PURE__ */ jsx65(
9292
9732
  AppCommentsSheet,
9293
9733
  {
9294
9734
  appId: commentsAppId,
@@ -9301,7 +9741,7 @@ function StudioOverlay({
9301
9741
  }
9302
9742
 
9303
9743
  // src/studio/hooks/useEditQueue.ts
9304
- import * as React47 from "react";
9744
+ import * as React48 from "react";
9305
9745
 
9306
9746
  // src/data/apps/edit-queue/remote.ts
9307
9747
  var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
@@ -9410,17 +9850,17 @@ var editQueueRepository = new EditQueueRepositoryImpl(
9410
9850
 
9411
9851
  // src/studio/hooks/useEditQueue.ts
9412
9852
  function useEditQueue(appId) {
9413
- const [items, setItems] = React47.useState([]);
9414
- const [loading, setLoading] = React47.useState(false);
9415
- const [error, setError] = React47.useState(null);
9416
- 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);
9417
9857
  const foregroundSignal = useForegroundSignal(Boolean(appId));
9418
- const upsertSorted = React47.useCallback((prev, nextItem) => {
9858
+ const upsertSorted = React48.useCallback((prev, nextItem) => {
9419
9859
  const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
9420
9860
  next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
9421
9861
  return next;
9422
9862
  }, []);
9423
- const refetch = React47.useCallback(async () => {
9863
+ const refetch = React48.useCallback(async () => {
9424
9864
  if (!appId) {
9425
9865
  setItems([]);
9426
9866
  return;
@@ -9440,10 +9880,10 @@ function useEditQueue(appId) {
9440
9880
  if (activeRequestIdRef.current === requestId) setLoading(false);
9441
9881
  }
9442
9882
  }, [appId]);
9443
- React47.useEffect(() => {
9883
+ React48.useEffect(() => {
9444
9884
  void refetch();
9445
9885
  }, [refetch]);
9446
- React47.useEffect(() => {
9886
+ React48.useEffect(() => {
9447
9887
  if (!appId) return;
9448
9888
  const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
9449
9889
  onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
@@ -9452,7 +9892,7 @@ function useEditQueue(appId) {
9452
9892
  });
9453
9893
  return unsubscribe;
9454
9894
  }, [appId, upsertSorted, foregroundSignal]);
9455
- React47.useEffect(() => {
9895
+ React48.useEffect(() => {
9456
9896
  if (!appId) return;
9457
9897
  if (foregroundSignal <= 0) return;
9458
9898
  void refetch();
@@ -9461,16 +9901,16 @@ function useEditQueue(appId) {
9461
9901
  }
9462
9902
 
9463
9903
  // src/studio/hooks/useEditQueueActions.ts
9464
- import * as React48 from "react";
9904
+ import * as React49 from "react";
9465
9905
  function useEditQueueActions(appId) {
9466
- const update = React48.useCallback(
9906
+ const update = React49.useCallback(
9467
9907
  async (queueItemId, payload) => {
9468
9908
  if (!appId) return;
9469
9909
  await editQueueRepository.update(appId, queueItemId, payload);
9470
9910
  },
9471
9911
  [appId]
9472
9912
  );
9473
- const cancel = React48.useCallback(
9913
+ const cancel = React49.useCallback(
9474
9914
  async (queueItemId) => {
9475
9915
  if (!appId) return;
9476
9916
  await editQueueRepository.cancel(appId, queueItemId);
@@ -9481,7 +9921,7 @@ function useEditQueueActions(appId) {
9481
9921
  }
9482
9922
 
9483
9923
  // src/studio/hooks/useAgentRunProgress.ts
9484
- import * as React49 from "react";
9924
+ import * as React50 from "react";
9485
9925
 
9486
9926
  // src/data/agent-progress/repository.ts
9487
9927
  function mapRun(row) {
@@ -9747,23 +10187,23 @@ function deriveView(run, events, nowMs) {
9747
10187
  function useAgentRunProgress(threadId, opts) {
9748
10188
  var _a;
9749
10189
  const enabled = Boolean((opts == null ? void 0 : opts.enabled) ?? true);
9750
- const [run, setRun] = React49.useState(null);
9751
- const [events, setEvents] = React49.useState([]);
9752
- const [loading, setLoading] = React49.useState(false);
9753
- const [error, setError] = React49.useState(null);
9754
- const activeRequestIdRef = React49.useRef(0);
9755
- const lastSeqRef = React49.useRef(0);
9756
- 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);
9757
10197
  const foregroundSignal = useForegroundSignal(Boolean(threadId) && enabled);
9758
- const [bundleTick, setBundleTick] = React49.useState(0);
9759
- React49.useEffect(() => {
10198
+ const [bundleTick, setBundleTick] = React50.useState(0);
10199
+ React50.useEffect(() => {
9760
10200
  lastSeqRef.current = 0;
9761
10201
  runRef.current = null;
9762
10202
  }, [threadId]);
9763
- React49.useEffect(() => {
10203
+ React50.useEffect(() => {
9764
10204
  runRef.current = run;
9765
10205
  }, [run]);
9766
- const refetch = React49.useCallback(async () => {
10206
+ const refetch = React50.useCallback(async () => {
9767
10207
  if (!threadId || !enabled) {
9768
10208
  setRun(null);
9769
10209
  setEvents([]);
@@ -9799,15 +10239,15 @@ function useAgentRunProgress(threadId, opts) {
9799
10239
  if (activeRequestIdRef.current === requestId) setLoading(false);
9800
10240
  }
9801
10241
  }, [enabled, threadId]);
9802
- React49.useEffect(() => {
10242
+ React50.useEffect(() => {
9803
10243
  void refetch();
9804
10244
  }, [refetch]);
9805
- React49.useEffect(() => {
10245
+ React50.useEffect(() => {
9806
10246
  if (!threadId || !enabled) return;
9807
10247
  if (foregroundSignal <= 0) return;
9808
10248
  void refetch();
9809
10249
  }, [enabled, foregroundSignal, refetch, threadId]);
9810
- React49.useEffect(() => {
10250
+ React50.useEffect(() => {
9811
10251
  if (!threadId || !enabled) return;
9812
10252
  const unsubRuns = agentProgressRepository.subscribeThreadRuns(threadId, {
9813
10253
  onInsert: (nextRun) => {
@@ -9837,7 +10277,7 @@ function useAgentRunProgress(threadId, opts) {
9837
10277
  });
9838
10278
  return unsubRuns;
9839
10279
  }, [enabled, threadId, foregroundSignal]);
9840
- React49.useEffect(() => {
10280
+ React50.useEffect(() => {
9841
10281
  if (!enabled || !(run == null ? void 0 : run.id)) return;
9842
10282
  const runId = run.id;
9843
10283
  const processIncoming = (incoming) => {
@@ -9869,8 +10309,8 @@ function useAgentRunProgress(threadId, opts) {
9869
10309
  });
9870
10310
  return unsubscribe;
9871
10311
  }, [enabled, run == null ? void 0 : run.id, foregroundSignal]);
9872
- const view = React49.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
9873
- React49.useEffect(() => {
10312
+ const view = React50.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
10313
+ React50.useEffect(() => {
9874
10314
  var _a2;
9875
10315
  if (!((_a2 = view.bundle) == null ? void 0 : _a2.active)) return;
9876
10316
  const interval = setInterval(() => {
@@ -9883,13 +10323,13 @@ function useAgentRunProgress(threadId, opts) {
9883
10323
  }
9884
10324
 
9885
10325
  // src/studio/hooks/useRelatedApps.ts
9886
- import * as React50 from "react";
10326
+ import * as React51 from "react";
9887
10327
  function useRelatedApps(appId) {
9888
- const [relatedApps, setRelatedApps] = React50.useState(null);
9889
- const [loading, setLoading] = React50.useState(false);
9890
- const [error, setError] = React50.useState(null);
9891
- const requestSeqRef = React50.useRef(0);
9892
- 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 () => {
9893
10333
  if (!appId) {
9894
10334
  setRelatedApps(null);
9895
10335
  setError(null);
@@ -9915,7 +10355,7 @@ function useRelatedApps(appId) {
9915
10355
  }
9916
10356
  }
9917
10357
  }, [appId]);
9918
- React50.useEffect(() => {
10358
+ React51.useEffect(() => {
9919
10359
  void fetchRelatedApps();
9920
10360
  }, [fetchRelatedApps]);
9921
10361
  return {
@@ -9927,7 +10367,7 @@ function useRelatedApps(appId) {
9927
10367
  }
9928
10368
 
9929
10369
  // src/studio/ComergeStudio.tsx
9930
- import { jsx as jsx65, jsxs as jsxs41 } from "react/jsx-runtime";
10370
+ import { jsx as jsx66, jsxs as jsxs42 } from "react/jsx-runtime";
9931
10371
  function ComergeStudio({
9932
10372
  appId,
9933
10373
  clientKey: clientKey2,
@@ -9943,13 +10383,13 @@ function ComergeStudio({
9943
10383
  embeddedBaseBundles,
9944
10384
  onSystemEvent
9945
10385
  }) {
9946
- const [activeAppId, setActiveAppId] = React51.useState(appId);
9947
- const [runtimeAppId, setRuntimeAppId] = React51.useState(appId);
9948
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React51.useState(null);
9949
- const didSyncFromHostRef = React51.useRef(false);
9950
- const lastNotifiedRef = React51.useRef(null);
9951
- const platform = React51.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
9952
- 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(
9953
10393
  (nextAppId, source) => {
9954
10394
  if (!onActiveAppChanged) return;
9955
10395
  const trimmedAppId = nextAppId.trim();
@@ -9962,28 +10402,28 @@ function ComergeStudio({
9962
10402
  },
9963
10403
  [appKey, onActiveAppChanged]
9964
10404
  );
9965
- const setActiveAppIdWithSource = React51.useCallback(
10405
+ const setActiveAppIdWithSource = React52.useCallback(
9966
10406
  (nextAppId, source) => {
9967
10407
  setActiveAppId(nextAppId);
9968
10408
  notifyActiveAppChanged(nextAppId, source);
9969
10409
  },
9970
10410
  [notifyActiveAppChanged]
9971
10411
  );
9972
- React51.useEffect(() => {
10412
+ React52.useEffect(() => {
9973
10413
  const source = didSyncFromHostRef.current ? "host_route_sync" : "initial";
9974
10414
  didSyncFromHostRef.current = true;
9975
10415
  setActiveAppIdWithSource(appId, source);
9976
10416
  setRuntimeAppId(appId);
9977
10417
  setPendingRuntimeTargetAppId(null);
9978
10418
  }, [appId, setActiveAppIdWithSource]);
9979
- const captureTargetRef = React51.useRef(null);
9980
- return /* @__PURE__ */ jsx65(
10419
+ const captureTargetRef = React52.useRef(null);
10420
+ return /* @__PURE__ */ jsx66(
9981
10421
  StudioBootstrap,
9982
10422
  {
9983
10423
  clientKey: clientKey2,
9984
10424
  analyticsEnabled,
9985
- fallback: /* @__PURE__ */ jsx65(View49, { style: { flex: 1 } }),
9986
- 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(
9987
10427
  ComergeStudioInner,
9988
10428
  {
9989
10429
  userId,
@@ -10033,11 +10473,11 @@ function ComergeStudioInner({
10033
10473
  const { app, loading: appLoading } = useApp(activeAppId);
10034
10474
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
10035
10475
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
10036
- const sawEditingOnPendingTargetRef = React51.useRef(false);
10037
- React51.useEffect(() => {
10476
+ const sawEditingOnPendingTargetRef = React52.useRef(false);
10477
+ React52.useEffect(() => {
10038
10478
  sawEditingOnPendingTargetRef.current = false;
10039
10479
  }, [pendingRuntimeTargetAppId]);
10040
- React51.useEffect(() => {
10480
+ React52.useEffect(() => {
10041
10481
  if (!pendingRuntimeTargetAppId) return;
10042
10482
  if (activeAppId !== pendingRuntimeTargetAppId) return;
10043
10483
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -10055,13 +10495,13 @@ function ComergeStudioInner({
10055
10495
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
10056
10496
  embeddedBaseBundles
10057
10497
  });
10058
- const sawEditingOnActiveAppRef = React51.useRef(false);
10059
- const [showPostEditPreparing, setShowPostEditPreparing] = React51.useState(false);
10060
- React51.useEffect(() => {
10498
+ const sawEditingOnActiveAppRef = React52.useRef(false);
10499
+ const [showPostEditPreparing, setShowPostEditPreparing] = React52.useState(false);
10500
+ React52.useEffect(() => {
10061
10501
  sawEditingOnActiveAppRef.current = false;
10062
10502
  setShowPostEditPreparing(false);
10063
10503
  }, [activeAppId]);
10064
- React51.useEffect(() => {
10504
+ React52.useEffect(() => {
10065
10505
  if (!(app == null ? void 0 : app.id)) return;
10066
10506
  if (app.status === "editing") {
10067
10507
  sawEditingOnActiveAppRef.current = true;
@@ -10073,7 +10513,7 @@ function ComergeStudioInner({
10073
10513
  sawEditingOnActiveAppRef.current = false;
10074
10514
  }
10075
10515
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
10076
- React51.useEffect(() => {
10516
+ React52.useEffect(() => {
10077
10517
  if (!showPostEditPreparing) return;
10078
10518
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10079
10519
  if (!stillProcessingBaseBundle) {
@@ -10085,19 +10525,19 @@ function ComergeStudioInner({
10085
10525
  const editQueue = useEditQueue(activeAppId);
10086
10526
  const agentProgress = useAgentRunProgress(threadId, { enabled: enableAgentProgress });
10087
10527
  const editQueueActions = useEditQueueActions(activeAppId);
10088
- const [lastEditQueueInfo, setLastEditQueueInfo] = React51.useState(null);
10089
- const lastEditQueueInfoRef = React51.useRef(null);
10090
- 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);
10091
10531
  const mergeRequests = useMergeRequests({ appId: activeAppId });
10092
- const hasOpenOutgoingMr = React51.useMemo(() => {
10532
+ const hasOpenOutgoingMr = React52.useMemo(() => {
10093
10533
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
10094
10534
  }, [mergeRequests.lists.outgoing]);
10095
- const incomingReviewMrs = React51.useMemo(() => {
10535
+ const incomingReviewMrs = React52.useMemo(() => {
10096
10536
  if (!userId) return mergeRequests.lists.incoming;
10097
10537
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
10098
10538
  }, [mergeRequests.lists.incoming, userId]);
10099
10539
  const uploader = useAttachmentUpload();
10100
- const updateLastEditQueueInfo = React51.useCallback(
10540
+ const updateLastEditQueueInfo = React52.useCallback(
10101
10541
  (info) => {
10102
10542
  lastEditQueueInfoRef.current = info;
10103
10543
  setLastEditQueueInfo(info);
@@ -10139,13 +10579,13 @@ function ComergeStudioInner({
10139
10579
  }
10140
10580
  });
10141
10581
  const chatSendDisabled = false;
10142
- const [processingMrId, setProcessingMrId] = React51.useState(null);
10143
- const [testingMrId, setTestingMrId] = React51.useState(null);
10144
- const [syncingUpstream, setSyncingUpstream] = React51.useState(false);
10145
- 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);
10146
10586
  const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
10147
10587
  const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10148
- const runtimePreparingText = React51.useMemo(() => {
10588
+ const runtimePreparingText = React52.useMemo(() => {
10149
10589
  const status = app == null ? void 0 : app.status;
10150
10590
  if (status === "ready" && bundle.bundleStatus === "pending") {
10151
10591
  return "Bundling app\u2026 this may take a few minutes";
@@ -10165,7 +10605,7 @@ function ComergeStudioInner({
10165
10605
  return "Preparing app\u2026";
10166
10606
  }
10167
10607
  }, [app == null ? void 0 : app.status, bundle.bundleStatus]);
10168
- const chatShowTypingIndicator = React51.useMemo(() => {
10608
+ const chatShowTypingIndicator = React52.useMemo(() => {
10169
10609
  var _a2;
10170
10610
  if (agentProgress.hasLiveProgress) return false;
10171
10611
  if (!thread.raw || thread.raw.length === 0) return false;
@@ -10174,12 +10614,12 @@ function ComergeStudioInner({
10174
10614
  return payloadType !== "outcome";
10175
10615
  }, [agentProgress.hasLiveProgress, thread.raw]);
10176
10616
  const showChatProgress = agentProgress.hasLiveProgress || Boolean((_a = agentProgress.view.bundle) == null ? void 0 : _a.active);
10177
- React51.useEffect(() => {
10617
+ React52.useEffect(() => {
10178
10618
  updateLastEditQueueInfo(null);
10179
10619
  setSuppressQueueUntilResponse(false);
10180
10620
  setUpstreamSyncStatus(null);
10181
10621
  }, [activeAppId, updateLastEditQueueInfo]);
10182
- const handleSyncUpstream = React51.useCallback(async () => {
10622
+ const handleSyncUpstream = React52.useCallback(async () => {
10183
10623
  if (!(app == null ? void 0 : app.id)) {
10184
10624
  throw new Error("Missing app");
10185
10625
  }
@@ -10192,7 +10632,7 @@ function ComergeStudioInner({
10192
10632
  setSyncingUpstream(false);
10193
10633
  }
10194
10634
  }, [activeAppId, app == null ? void 0 : app.id]);
10195
- React51.useEffect(() => {
10635
+ React52.useEffect(() => {
10196
10636
  if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
10197
10637
  const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
10198
10638
  if (!stillPresent) {
@@ -10200,7 +10640,7 @@ function ComergeStudioInner({
10200
10640
  setSuppressQueueUntilResponse(false);
10201
10641
  }
10202
10642
  }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
10203
- const chatQueueItems = React51.useMemo(() => {
10643
+ const chatQueueItems = React52.useMemo(() => {
10204
10644
  var _a2;
10205
10645
  if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
10206
10646
  return [];
@@ -10214,8 +10654,8 @@ function ComergeStudioInner({
10214
10654
  return editQueue.items;
10215
10655
  }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
10216
10656
  const { relatedApps, loading: relatedAppsLoading } = useRelatedApps(activeAppId);
10217
- const [switchingRelatedAppId, setSwitchingRelatedAppId] = React51.useState(null);
10218
- const handleOpenRelatedApps = React51.useCallback(() => {
10657
+ const [switchingRelatedAppId, setSwitchingRelatedAppId] = React52.useState(null);
10658
+ const handleOpenRelatedApps = React52.useCallback(() => {
10219
10659
  var _a2;
10220
10660
  if (!relatedApps) return;
10221
10661
  const ids = /* @__PURE__ */ new Set();
@@ -10224,7 +10664,7 @@ function ComergeStudioInner({
10224
10664
  for (const remix of relatedApps.remixes) ids.add(remix.id);
10225
10665
  void trackRelatedAppsOpened({ appId: relatedApps.current.id, relatedCount: ids.size });
10226
10666
  }, [relatedApps]);
10227
- const handleSwitchRelatedApp = React51.useCallback(
10667
+ const handleSwitchRelatedApp = React52.useCallback(
10228
10668
  async (targetAppId) => {
10229
10669
  var _a2;
10230
10670
  if (!targetAppId || targetAppId === activeAppId) return;
@@ -10285,8 +10725,8 @@ function ComergeStudioInner({
10285
10725
  setRuntimeAppId
10286
10726
  ]
10287
10727
  );
10288
- return /* @__PURE__ */ jsx65(View49, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs41(View49, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
10289
- /* @__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(
10290
10730
  RuntimeRenderer,
10291
10731
  {
10292
10732
  appKey,
@@ -10310,7 +10750,7 @@ function ComergeStudioInner({
10310
10750
  }
10311
10751
  }
10312
10752
  ),
10313
- /* @__PURE__ */ jsx65(
10753
+ /* @__PURE__ */ jsx66(
10314
10754
  StudioOverlay,
10315
10755
  {
10316
10756
  captureTargetRef,
@@ -10367,6 +10807,9 @@ function ComergeStudioInner({
10367
10807
  chatSending: actions.sending,
10368
10808
  chatShowTypingIndicator,
10369
10809
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
10810
+ onChatAttachmentLoadError: () => {
10811
+ thread.recoverAttachmentUrls();
10812
+ },
10370
10813
  chatQueueItems,
10371
10814
  onRemoveQueueItem: (id) => editQueueActions.cancel(id),
10372
10815
  chatProgress: showChatProgress ? agentProgress.view : null,