@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.js CHANGED
@@ -516,8 +516,8 @@ __export(index_exports, {
516
516
  module.exports = __toCommonJS(index_exports);
517
517
 
518
518
  // src/studio/ComergeStudio.tsx
519
- var React51 = __toESM(require("react"));
520
- var import_react_native61 = require("react-native");
519
+ var React52 = __toESM(require("react"));
520
+ var import_react_native63 = require("react-native");
521
521
  var import_bottom_sheet6 = require("@gorhom/bottom-sheet");
522
522
  var import_runtime2 = require("@comergehq/runtime");
523
523
 
@@ -1635,6 +1635,34 @@ function compareMessages(a, b) {
1635
1635
  if (aMs !== bMs) return aMs - bMs;
1636
1636
  return String(a.createdAt).localeCompare(String(b.createdAt));
1637
1637
  }
1638
+ function parseAttachments(payload) {
1639
+ const raw = payload == null ? void 0 : payload.attachments;
1640
+ if (!Array.isArray(raw)) return [];
1641
+ const out = [];
1642
+ for (const item of raw) {
1643
+ if (!item || typeof item !== "object") continue;
1644
+ const id = typeof item.id === "string" ? String(item.id) : "";
1645
+ const name = typeof item.name === "string" ? String(item.name) : "";
1646
+ const mimeType = typeof item.mimeType === "string" ? String(item.mimeType) : "";
1647
+ const size = typeof item.size === "number" ? Number(item.size) : 0;
1648
+ if (!id || !name || !mimeType || !Number.isFinite(size) || size <= 0) continue;
1649
+ out.push({
1650
+ id,
1651
+ name,
1652
+ mimeType,
1653
+ size,
1654
+ uri: typeof item.downloadUrl === "string" ? String(item.downloadUrl) : void 0,
1655
+ width: typeof item.width === "number" ? Number(item.width) : void 0,
1656
+ height: typeof item.height === "number" ? Number(item.height) : void 0
1657
+ });
1658
+ }
1659
+ return out;
1660
+ }
1661
+ function hasAttachmentWithoutUrl(payload) {
1662
+ const attachments = parseAttachments(payload);
1663
+ if (attachments.length === 0) return false;
1664
+ return attachments.some((att) => !att.uri);
1665
+ }
1638
1666
  function mapMessageToChatMessage(m) {
1639
1667
  var _a, _b;
1640
1668
  const kind = typeof ((_a = m.payload) == null ? void 0 : _a.type) === "string" ? String(m.payload.type) : null;
@@ -1644,7 +1672,8 @@ function mapMessageToChatMessage(m) {
1644
1672
  content: typeof ((_b = m.payload) == null ? void 0 : _b.content) === "string" ? m.payload.content : "",
1645
1673
  createdAt: m.createdAt,
1646
1674
  kind,
1647
- meta: extractMeta(m.payload)
1675
+ meta: extractMeta(m.payload),
1676
+ attachments: parseAttachments(m.payload)
1648
1677
  };
1649
1678
  }
1650
1679
  function useThreadMessages(threadId) {
@@ -1655,9 +1684,19 @@ function useThreadMessages(threadId) {
1655
1684
  const activeRequestIdRef = React4.useRef(0);
1656
1685
  const foregroundSignal = useForegroundSignal(Boolean(threadId));
1657
1686
  const hasLoadedOnceRef = React4.useRef(false);
1687
+ const attachmentRecoveryTimerRef = React4.useRef(null);
1688
+ const lastAttachmentRecoveryAtRef = React4.useRef(0);
1658
1689
  React4.useEffect(() => {
1659
1690
  hasLoadedOnceRef.current = false;
1660
1691
  }, [threadId]);
1692
+ React4.useEffect(() => {
1693
+ return () => {
1694
+ if (attachmentRecoveryTimerRef.current) {
1695
+ clearTimeout(attachmentRecoveryTimerRef.current);
1696
+ attachmentRecoveryTimerRef.current = null;
1697
+ }
1698
+ };
1699
+ }, []);
1661
1700
  const upsertSorted = React4.useCallback((prev, m) => {
1662
1701
  const next = prev.filter((x) => x.id !== m.id);
1663
1702
  next.push(m);
@@ -1698,29 +1737,56 @@ function useThreadMessages(threadId) {
1698
1737
  }
1699
1738
  }
1700
1739
  }, [threadId]);
1740
+ const recoverAttachmentUrls = React4.useCallback(() => {
1741
+ if (!threadId) return;
1742
+ if (attachmentRecoveryTimerRef.current) return;
1743
+ const now = Date.now();
1744
+ if (now - lastAttachmentRecoveryAtRef.current < 2e3) return;
1745
+ attachmentRecoveryTimerRef.current = setTimeout(() => {
1746
+ attachmentRecoveryTimerRef.current = null;
1747
+ lastAttachmentRecoveryAtRef.current = Date.now();
1748
+ void refetch({ background: true });
1749
+ }, 250);
1750
+ }, [refetch, threadId]);
1701
1751
  React4.useEffect(() => {
1702
1752
  void refetch();
1703
1753
  }, [refetch]);
1704
1754
  React4.useEffect(() => {
1705
1755
  if (!threadId) return;
1706
1756
  const unsubscribe = messagesRepository.subscribeThread(threadId, {
1707
- onInsert: (m) => setRaw((prev) => upsertSorted(prev, m)),
1708
- onUpdate: (m) => setRaw((prev) => upsertSorted(prev, m)),
1757
+ onInsert: (m) => {
1758
+ setRaw((prev) => upsertSorted(prev, m));
1759
+ if (hasAttachmentWithoutUrl(m.payload)) {
1760
+ recoverAttachmentUrls();
1761
+ }
1762
+ },
1763
+ onUpdate: (m) => {
1764
+ setRaw((prev) => upsertSorted(prev, m));
1765
+ if (hasAttachmentWithoutUrl(m.payload)) {
1766
+ recoverAttachmentUrls();
1767
+ }
1768
+ },
1709
1769
  onDelete: (m) => setRaw((prev) => prev.filter((x) => x.id !== m.id))
1710
1770
  });
1711
1771
  return unsubscribe;
1712
- }, [threadId, upsertSorted, foregroundSignal]);
1772
+ }, [threadId, upsertSorted, foregroundSignal, recoverAttachmentUrls]);
1713
1773
  React4.useEffect(() => {
1714
1774
  if (!threadId) return;
1715
1775
  if (foregroundSignal <= 0) return;
1716
1776
  void refetch({ background: true });
1717
1777
  }, [foregroundSignal, refetch, threadId]);
1778
+ React4.useEffect(() => {
1779
+ if (!threadId) return;
1780
+ if (raw.length === 0) return;
1781
+ if (!raw.some((m) => hasAttachmentWithoutUrl(m.payload))) return;
1782
+ recoverAttachmentUrls();
1783
+ }, [raw, recoverAttachmentUrls, threadId]);
1718
1784
  const messages = React4.useMemo(() => {
1719
1785
  const visible = raw.filter((m) => !isQueuedHiddenMessage(m));
1720
1786
  const resolved = visible.length > 0 ? visible : raw;
1721
1787
  return resolved.map(mapMessageToChatMessage);
1722
1788
  }, [raw]);
1723
- return { raw, messages, loading, refreshing, error, refetch };
1789
+ return { raw, messages, loading, refreshing, error, refetch, recoverAttachmentUrls };
1724
1790
  }
1725
1791
 
1726
1792
  // src/studio/hooks/useBundleManager.ts
@@ -2983,6 +3049,23 @@ function getMimeTypeFromDataUrl(dataUrl) {
2983
3049
  const mimeMatch = header.match(/data:(.*?);base64/i);
2984
3050
  return (mimeMatch == null ? void 0 : mimeMatch[1]) ?? "image/png";
2985
3051
  }
3052
+ async function getImageDimensionsFromDataUrl(dataUrl) {
3053
+ try {
3054
+ const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
3055
+ const dims = await new Promise((resolve, reject) => {
3056
+ import_react_native6.Image.getSize(
3057
+ normalized,
3058
+ (width, height) => resolve({ width, height }),
3059
+ (err) => reject(err)
3060
+ );
3061
+ });
3062
+ if (dims.width > 0 && dims.height > 0) {
3063
+ return { width: Math.round(dims.width), height: Math.round(dims.height) };
3064
+ }
3065
+ } catch {
3066
+ }
3067
+ return {};
3068
+ }
2986
3069
  function useAttachmentUpload() {
2987
3070
  const [uploading, setUploading] = React7.useState(false);
2988
3071
  const [error, setError] = React7.useState(null);
@@ -2997,17 +3080,24 @@ function useAttachmentUpload() {
2997
3080
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
2998
3081
  const blob = import_react_native6.Platform.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
2999
3082
  const mimeType = getMimeTypeFromDataUrl(normalized);
3000
- return { blob, idx, mimeType };
3083
+ const dimensions = mimeType.startsWith("image/") ? await getImageDimensionsFromDataUrl(normalized) : {};
3084
+ return { blob, idx, mimeType, ...dimensions };
3001
3085
  })
3002
3086
  );
3003
- const files = blobs.map(({ blob, mimeType }, idx) => ({
3087
+ const files = blobs.map(({ blob, mimeType, width, height }, idx) => ({
3004
3088
  name: `attachment-${Date.now()}-${idx}.png`,
3005
3089
  size: blob.size,
3006
- mimeType
3090
+ mimeType,
3091
+ width,
3092
+ height
3007
3093
  }));
3008
3094
  const presign = await attachmentRepository.presign({ threadId, appId, files });
3009
3095
  await Promise.all(presign.uploads.map((u, index) => attachmentRepository.upload(u, blobs[index].blob)));
3010
- return presign.uploads.map((u) => u.attachment);
3096
+ return presign.uploads.map((u, index) => ({
3097
+ ...u.attachment,
3098
+ width: blobs[index].width,
3099
+ height: blobs[index].height
3100
+ }));
3011
3101
  } catch (e) {
3012
3102
  const err = e instanceof Error ? e : new Error(String(e));
3013
3103
  setError(err);
@@ -3026,13 +3116,16 @@ function useAttachmentUpload() {
3026
3116
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
3027
3117
  const blob = import_react_native6.Platform.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
3028
3118
  const mimeType = getMimeTypeFromDataUrl(normalized);
3029
- return { blob, mimeType };
3119
+ const dimensions = mimeType.startsWith("image/") ? await getImageDimensionsFromDataUrl(normalized) : {};
3120
+ return { blob, mimeType, ...dimensions };
3030
3121
  })
3031
3122
  );
3032
- const files = blobs.map(({ blob, mimeType }, idx) => ({
3123
+ const files = blobs.map(({ blob, mimeType, width, height }, idx) => ({
3033
3124
  name: `attachment-${Date.now()}-${idx}.png`,
3034
3125
  size: blob.size,
3035
- mimeType
3126
+ mimeType,
3127
+ width,
3128
+ height
3036
3129
  }));
3037
3130
  const presign = await attachmentRepository.stagePresign({ files });
3038
3131
  await Promise.all(presign.uploads.map((u, index) => attachmentRepository.uploadStaged(u, blobs[index].blob)));
@@ -3315,8 +3408,8 @@ function RuntimeRenderer({
3315
3408
  }
3316
3409
 
3317
3410
  // src/studio/ui/StudioOverlay.tsx
3318
- var React46 = __toESM(require("react"));
3319
- var import_react_native60 = require("react-native");
3411
+ var React47 = __toESM(require("react"));
3412
+ var import_react_native62 = require("react-native");
3320
3413
 
3321
3414
  // src/components/studio-sheet/StudioBottomSheet.tsx
3322
3415
  var React12 = __toESM(require("react"));
@@ -3856,6 +3949,7 @@ function EdgeGlowFrame({
3856
3949
  role = "accent",
3857
3950
  thickness = 40,
3858
3951
  intensity = 1,
3952
+ animationDurationMs = 300,
3859
3953
  style
3860
3954
  }) {
3861
3955
  const theme = useTheme();
@@ -3864,10 +3958,10 @@ function EdgeGlowFrame({
3864
3958
  React14.useEffect(() => {
3865
3959
  import_react_native13.Animated.timing(anim, {
3866
3960
  toValue: visible ? 1 : 0,
3867
- duration: 300,
3961
+ duration: animationDurationMs,
3868
3962
  useNativeDriver: true
3869
3963
  }).start();
3870
- }, [anim, visible]);
3964
+ }, [anim, visible, animationDurationMs]);
3871
3965
  const c = baseColor(role, theme);
3872
3966
  const strong = withAlpha(c, 0.6 * alpha);
3873
3967
  const soft = withAlpha(c, 0.22 * alpha);
@@ -4386,7 +4480,16 @@ function DrawModeOverlay({
4386
4480
  }, [captureTargetRef, capturing, onCapture]);
4387
4481
  if (!visible) return null;
4388
4482
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native17.View, { style: [import_react_native17.StyleSheet.absoluteFill, styles3.root, style], pointerEvents: "box-none", children: [
4389
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EdgeGlowFrame, { visible: !hideUi, role: "danger", thickness: 50, intensity: 1 }),
4483
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4484
+ EdgeGlowFrame,
4485
+ {
4486
+ visible: !hideUi,
4487
+ role: "danger",
4488
+ thickness: 50,
4489
+ intensity: 1,
4490
+ animationDurationMs: hideUi ? 0 : 300
4491
+ }
4492
+ ),
4390
4493
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4391
4494
  DrawSurface,
4392
4495
  {
@@ -5874,7 +5977,10 @@ function PreviewRelatedAppsSection({
5874
5977
  }) {
5875
5978
  var _a;
5876
5979
  const theme = useTheme();
5980
+ const { height: windowHeight } = (0, import_react_native35.useWindowDimensions)();
5877
5981
  const [relatedAppsOpen, setRelatedAppsOpen] = React27.useState(false);
5982
+ const modalMaxHeight = Math.max(240, windowHeight * 0.5);
5983
+ const modalScrollMaxHeight = Math.max(140, modalMaxHeight - 96);
5878
5984
  const relatedAppItems = React27.useMemo(() => {
5879
5985
  if (!relatedApps) return [];
5880
5986
  const items = [];
@@ -6007,10 +6113,7 @@ function PreviewRelatedAppsSection({
6007
6113
  }
6008
6114
  )
6009
6115
  ] }),
6010
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { style: { alignItems: "flex-end", gap: 6 }, children: [
6011
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native35.View, { style: { minHeight: 20, justifyContent: "center" }, children: item.app.status !== "ready" ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(PreviewStatusBadge, { status: item.app.status }) : null }),
6012
- isSwitching ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native35.ActivityIndicator, { size: "small", color: theme.colors.primary }) : null
6013
- ] })
6116
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native35.View, { style: { alignItems: "flex-end", gap: 6 }, children: isSwitching ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native35.ActivityIndicator, { size: "small", color: theme.colors.primary }) : null })
6014
6117
  ] })
6015
6118
  },
6016
6119
  item.app.id
@@ -6053,17 +6156,35 @@ function PreviewRelatedAppsSection({
6053
6156
  children: inlineItems.map((item) => renderRelatedCard(item))
6054
6157
  }
6055
6158
  ),
6056
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Modal, { visible: relatedAppsOpen, onRequestClose: closeRelatedApps, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { style: { gap: theme.spacing.sm }, children: [
6057
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.text, fontSize: 18, fontWeight: theme.typography.fontWeight.semibold }, children: "Related apps" }),
6058
- sectionedRelatedApps.original.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { children: [
6059
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Original" }),
6060
- sectionedRelatedApps.original.map((item) => renderRelatedCard(item, { fullWidth: true }))
6061
- ] }) : null,
6062
- sectionedRelatedApps.remixes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { children: [
6063
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Remixes" }),
6064
- sectionedRelatedApps.remixes.map((item) => renderRelatedCard(item, { fullWidth: true }))
6065
- ] }) : null
6066
- ] }) })
6159
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6160
+ Modal,
6161
+ {
6162
+ visible: relatedAppsOpen,
6163
+ onRequestClose: closeRelatedApps,
6164
+ contentStyle: { maxHeight: modalMaxHeight, overflow: "hidden" },
6165
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { style: { gap: theme.spacing.sm, minHeight: 0 }, children: [
6166
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.text, fontSize: 18, fontWeight: theme.typography.fontWeight.semibold }, children: "Related apps" }),
6167
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
6168
+ import_react_native35.ScrollView,
6169
+ {
6170
+ style: { maxHeight: modalScrollMaxHeight },
6171
+ contentContainerStyle: { paddingBottom: theme.spacing.xs, gap: theme.spacing.sm },
6172
+ showsVerticalScrollIndicator: true,
6173
+ children: [
6174
+ sectionedRelatedApps.original.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { children: [
6175
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Original" }),
6176
+ sectionedRelatedApps.original.map((item) => renderRelatedCard(item, { fullWidth: true }))
6177
+ ] }) : null,
6178
+ sectionedRelatedApps.remixes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { children: [
6179
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Text, { style: { color: theme.colors.textMuted, marginBottom: theme.spacing.xs }, children: "Remixes" }),
6180
+ sectionedRelatedApps.remixes.map((item) => renderRelatedCard(item, { fullWidth: true }))
6181
+ ] }) : null
6182
+ ]
6183
+ }
6184
+ )
6185
+ ] })
6186
+ }
6187
+ )
6067
6188
  ] });
6068
6189
  }
6069
6190
 
@@ -7636,22 +7757,22 @@ ${shareUrl}`;
7636
7757
  }
7637
7758
 
7638
7759
  // src/studio/ui/ChatPanel.tsx
7639
- var React43 = __toESM(require("react"));
7640
- var import_react_native58 = require("react-native");
7760
+ var React44 = __toESM(require("react"));
7761
+ var import_react_native59 = require("react-native");
7641
7762
 
7642
7763
  // src/components/chat/ChatPage.tsx
7643
- var React40 = __toESM(require("react"));
7644
- var import_react_native51 = require("react-native");
7764
+ var React41 = __toESM(require("react"));
7765
+ var import_react_native52 = require("react-native");
7645
7766
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
7646
7767
 
7647
7768
  // src/components/chat/ChatMessageList.tsx
7648
- var React39 = __toESM(require("react"));
7649
- var import_react_native50 = require("react-native");
7769
+ var React40 = __toESM(require("react"));
7770
+ var import_react_native51 = require("react-native");
7650
7771
  var import_bottom_sheet5 = require("@gorhom/bottom-sheet");
7651
7772
 
7652
7773
  // src/components/chat/ChatMessageBubble.tsx
7653
- var React37 = __toESM(require("react"));
7654
- var import_react_native48 = require("react-native");
7774
+ var React38 = __toESM(require("react"));
7775
+ var import_react_native49 = require("react-native");
7655
7776
  var import_lucide_react_native10 = require("lucide-react-native");
7656
7777
 
7657
7778
  // src/components/primitives/Button.tsx
@@ -7717,19 +7838,261 @@ function Button({
7717
7838
  );
7718
7839
  }
7719
7840
 
7720
- // src/components/chat/ChatMessageBubble.tsx
7841
+ // src/components/chat/ChatMessageAttachments.tsx
7842
+ var React37 = __toESM(require("react"));
7843
+ var import_react_native48 = require("react-native");
7844
+ var import_expo_image = require("expo-image");
7721
7845
  var import_jsx_runtime49 = require("react/jsx-runtime");
7846
+ function ChatMessageAttachments({
7847
+ messageId,
7848
+ attachments,
7849
+ align = "left",
7850
+ onAttachmentLoadError
7851
+ }) {
7852
+ const theme = useTheme();
7853
+ const [viewerVisible, setViewerVisible] = React37.useState(false);
7854
+ const [viewerIndex, setViewerIndex] = React37.useState(0);
7855
+ const failedKeysRef = React37.useRef(/* @__PURE__ */ new Set());
7856
+ const [loadingById, setLoadingById] = React37.useState({});
7857
+ const [modalLoadingById, setModalLoadingById] = React37.useState({});
7858
+ const pulse = React37.useRef(new import_react_native48.Animated.Value(0.45)).current;
7859
+ const imageAttachments = React37.useMemo(
7860
+ () => attachments.filter(
7861
+ (att) => att.mimeType.startsWith("image/") && typeof att.uri === "string" && att.uri.length > 0
7862
+ ),
7863
+ [attachments]
7864
+ );
7865
+ const itemHeight = imageAttachments.length === 1 ? 180 : 124;
7866
+ const maxItemWidth = imageAttachments.length === 1 ? 280 : 180;
7867
+ const getAspectRatio = (att) => {
7868
+ const width = typeof att.width === "number" ? att.width : 0;
7869
+ const height = typeof att.height === "number" ? att.height : 0;
7870
+ if (width > 0 && height > 0) {
7871
+ return Math.max(0.35, Math.min(2.4, width / height));
7872
+ }
7873
+ return 0.8;
7874
+ };
7875
+ React37.useEffect(() => {
7876
+ import_react_native48.Animated.loop(
7877
+ import_react_native48.Animated.sequence([
7878
+ import_react_native48.Animated.timing(pulse, { toValue: 0.85, duration: 650, useNativeDriver: true }),
7879
+ import_react_native48.Animated.timing(pulse, { toValue: 0.45, duration: 650, useNativeDriver: true })
7880
+ ])
7881
+ ).start();
7882
+ }, [pulse]);
7883
+ React37.useEffect(() => {
7884
+ if (imageAttachments.length === 0) {
7885
+ setLoadingById({});
7886
+ setModalLoadingById({});
7887
+ return;
7888
+ }
7889
+ setLoadingById((prev) => {
7890
+ const next = {};
7891
+ for (const att of imageAttachments) {
7892
+ next[att.id] = prev[att.id] ?? true;
7893
+ }
7894
+ return next;
7895
+ });
7896
+ }, [imageAttachments]);
7897
+ React37.useEffect(() => {
7898
+ if (!viewerVisible) return;
7899
+ if (imageAttachments.length === 0) {
7900
+ setModalLoadingById({});
7901
+ return;
7902
+ }
7903
+ setModalLoadingById(() => {
7904
+ const next = {};
7905
+ for (const att of imageAttachments) {
7906
+ next[att.id] = true;
7907
+ }
7908
+ return next;
7909
+ });
7910
+ }, [viewerVisible, imageAttachments]);
7911
+ if (imageAttachments.length === 0) return null;
7912
+ const handleError = (attachmentId) => {
7913
+ const key = `${messageId}:${attachmentId}`;
7914
+ if (failedKeysRef.current.has(key)) return;
7915
+ failedKeysRef.current.add(key);
7916
+ onAttachmentLoadError == null ? void 0 : onAttachmentLoadError(messageId, attachmentId);
7917
+ };
7918
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
7919
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7920
+ import_react_native48.View,
7921
+ {
7922
+ style: {
7923
+ flexDirection: "row",
7924
+ flexWrap: "wrap",
7925
+ justifyContent: align === "right" ? "flex-end" : "flex-start",
7926
+ alignSelf: align === "right" ? "flex-end" : "flex-start",
7927
+ gap: theme.spacing.sm,
7928
+ marginBottom: theme.spacing.sm
7929
+ },
7930
+ children: imageAttachments.map((att, index) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
7931
+ import_react_native48.Pressable,
7932
+ {
7933
+ onPress: () => {
7934
+ setViewerIndex(index);
7935
+ setViewerVisible(true);
7936
+ },
7937
+ accessibilityRole: "button",
7938
+ accessibilityLabel: `Attachment ${index + 1} of ${imageAttachments.length}`,
7939
+ style: {
7940
+ height: itemHeight,
7941
+ aspectRatio: getAspectRatio(att),
7942
+ maxWidth: maxItemWidth,
7943
+ borderRadius: theme.radii.lg,
7944
+ overflow: "hidden"
7945
+ },
7946
+ children: [
7947
+ loadingById[att.id] ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7948
+ import_react_native48.Animated.View,
7949
+ {
7950
+ style: {
7951
+ ...import_react_native48.StyleSheet.absoluteFillObject,
7952
+ opacity: pulse,
7953
+ backgroundColor: theme.colors.border
7954
+ }
7955
+ }
7956
+ ) : null,
7957
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7958
+ import_expo_image.Image,
7959
+ {
7960
+ source: { uri: att.uri },
7961
+ style: { width: "100%", height: "100%" },
7962
+ contentFit: "contain",
7963
+ transition: 140,
7964
+ cachePolicy: "memory-disk",
7965
+ onLoadStart: () => {
7966
+ setLoadingById((prev) => ({ ...prev, [att.id]: true }));
7967
+ },
7968
+ onLoadEnd: () => {
7969
+ setLoadingById((prev) => ({ ...prev, [att.id]: false }));
7970
+ },
7971
+ onError: () => handleError(att.id)
7972
+ }
7973
+ )
7974
+ ]
7975
+ },
7976
+ att.id
7977
+ ))
7978
+ }
7979
+ ),
7980
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.Modal, { visible: viewerVisible, transparent: true, animationType: "fade", onRequestClose: () => setViewerVisible(false), children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { style: { flex: 1, backgroundColor: "rgba(0,0,0,0.95)" }, children: [
7981
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7982
+ import_react_native48.View,
7983
+ {
7984
+ style: {
7985
+ position: "absolute",
7986
+ top: 56,
7987
+ right: 16,
7988
+ zIndex: 2
7989
+ },
7990
+ children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7991
+ import_react_native48.Pressable,
7992
+ {
7993
+ accessibilityRole: "button",
7994
+ accessibilityLabel: "Close attachment viewer",
7995
+ onPress: () => setViewerVisible(false),
7996
+ style: {
7997
+ width: 44,
7998
+ height: 44,
7999
+ borderRadius: 22,
8000
+ alignItems: "center",
8001
+ justifyContent: "center",
8002
+ backgroundColor: "rgba(255,255,255,0.15)"
8003
+ },
8004
+ children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(IconClose, { size: 18, colorToken: "onPrimary" })
8005
+ }
8006
+ )
8007
+ }
8008
+ ),
8009
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8010
+ import_react_native48.FlatList,
8011
+ {
8012
+ data: imageAttachments,
8013
+ horizontal: true,
8014
+ pagingEnabled: true,
8015
+ initialScrollIndex: viewerIndex,
8016
+ keyExtractor: (item) => item.id,
8017
+ showsHorizontalScrollIndicator: false,
8018
+ getItemLayout: (_, index) => {
8019
+ const width = import_react_native48.Dimensions.get("window").width;
8020
+ return { length: width, offset: width * index, index };
8021
+ },
8022
+ renderItem: ({ item, index }) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { style: { width: import_react_native48.Dimensions.get("window").width, height: "100%", justifyContent: "center" }, children: [
8023
+ modalLoadingById[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8024
+ import_react_native48.Animated.View,
8025
+ {
8026
+ style: {
8027
+ ...import_react_native48.StyleSheet.absoluteFillObject,
8028
+ opacity: pulse,
8029
+ backgroundColor: theme.colors.border
8030
+ }
8031
+ }
8032
+ ) : null,
8033
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8034
+ import_expo_image.Image,
8035
+ {
8036
+ source: { uri: item.uri },
8037
+ style: { width: "100%", height: "78%" },
8038
+ contentFit: "contain",
8039
+ transition: 140,
8040
+ cachePolicy: "memory-disk",
8041
+ onLoadStart: () => {
8042
+ setModalLoadingById((prev) => ({ ...prev, [item.id]: true }));
8043
+ },
8044
+ onLoadEnd: () => {
8045
+ setModalLoadingById((prev) => ({ ...prev, [item.id]: false }));
8046
+ },
8047
+ onError: () => handleError(item.id)
8048
+ }
8049
+ ),
8050
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
8051
+ Text,
8052
+ {
8053
+ variant: "caption",
8054
+ color: "#FFFFFF",
8055
+ style: { textAlign: "center", marginTop: theme.spacing.sm },
8056
+ children: [
8057
+ index + 1,
8058
+ " / ",
8059
+ imageAttachments.length
8060
+ ]
8061
+ }
8062
+ )
8063
+ ] })
8064
+ }
8065
+ )
8066
+ ] }) })
8067
+ ] });
8068
+ }
8069
+
8070
+ // src/components/chat/ChatMessageBubble.tsx
8071
+ var import_jsx_runtime50 = require("react/jsx-runtime");
7722
8072
  function areMessageMetaEqual(a, b) {
7723
8073
  if (a === b) return true;
7724
8074
  if (!a || !b) return a === b;
7725
8075
  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;
7726
8076
  }
8077
+ function areMessageAttachmentsEqual(a, b) {
8078
+ if (a === b) return true;
8079
+ const left = a ?? [];
8080
+ const right = b ?? [];
8081
+ if (left.length !== right.length) return false;
8082
+ for (let i = 0; i < left.length; i += 1) {
8083
+ 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) {
8084
+ return false;
8085
+ }
8086
+ }
8087
+ return true;
8088
+ }
7727
8089
  function ChatMessageBubbleInner({
7728
8090
  message,
7729
8091
  renderContent,
7730
8092
  isLast,
7731
8093
  retrying,
7732
8094
  onRetryMessage,
8095
+ onAttachmentLoadError,
7733
8096
  style
7734
8097
  }) {
7735
8098
  var _a, _b;
@@ -7748,11 +8111,36 @@ function ChatMessageBubbleInner({
7748
8111
  const bodyColor = metaStatus === "success" ? theme.colors.success : metaStatus === "error" ? theme.colors.danger : void 0;
7749
8112
  const showRetry = Boolean(onRetryMessage) && isLast && metaStatus === "error" && message.author === "human";
7750
8113
  const retryLabel = retrying ? "Retrying..." : "Retry";
7751
- const handleRetryPress = React37.useCallback(() => {
8114
+ const hasText = message.content.trim().length > 0;
8115
+ const attachments = message.attachments ?? [];
8116
+ const hasAttachments = attachments.length > 0;
8117
+ const hasStatusIcon = isMergeCompleted || isSyncCompleted || isMergeApproved || isSyncStarted;
8118
+ const shouldRenderBubble = hasText || hasStatusIcon;
8119
+ const handleRetryPress = React38.useCallback(() => {
7752
8120
  onRetryMessage == null ? void 0 : onRetryMessage(message.id);
7753
8121
  }, [message.id, onRetryMessage]);
7754
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { style: [align, style], children: [
7755
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8122
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native49.View, { style: [align, style], children: [
8123
+ hasAttachments ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
8124
+ import_react_native49.View,
8125
+ {
8126
+ style: {
8127
+ maxWidth: "85%",
8128
+ marginBottom: shouldRenderBubble ? theme.spacing.xs : 0,
8129
+ alignSelf: isHuman ? "flex-end" : "flex-start",
8130
+ alignItems: isHuman ? "flex-end" : "flex-start"
8131
+ },
8132
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
8133
+ ChatMessageAttachments,
8134
+ {
8135
+ messageId: message.id,
8136
+ attachments,
8137
+ align: isHuman ? "right" : "left",
8138
+ onAttachmentLoadError
8139
+ }
8140
+ )
8141
+ }
8142
+ ) : null,
8143
+ shouldRenderBubble ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
7756
8144
  Surface,
7757
8145
  {
7758
8146
  variant: bubbleVariant,
@@ -7767,14 +8155,14 @@ function ChatMessageBubbleInner({
7767
8155
  },
7768
8156
  cornerStyle
7769
8157
  ],
7770
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7771
- isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react_native10.CheckCheck, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
7772
- isMergeApproved || isSyncStarted ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react_native10.GitMerge, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
7773
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.View, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
8158
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native49.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8159
+ isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react_native10.CheckCheck, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
8160
+ isMergeApproved || isSyncStarted ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react_native10.GitMerge, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
8161
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native49.View, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
7774
8162
  ] })
7775
8163
  }
7776
- ),
7777
- showRetry ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.View, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8164
+ ) : null,
8165
+ showRetry ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native49.View, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
7778
8166
  Button,
7779
8167
  {
7780
8168
  variant: "ghost",
@@ -7783,9 +8171,9 @@ function ChatMessageBubbleInner({
7783
8171
  disabled: retrying,
7784
8172
  style: { borderColor: theme.colors.danger },
7785
8173
  accessibilityLabel: "Retry send",
7786
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7787
- !retrying ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react_native10.RotateCcw, { size: 14, color: theme.colors.danger }) : null,
7788
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
8174
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native49.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8175
+ !retrying ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react_native10.RotateCcw, { size: 14, color: theme.colors.danger }) : null,
8176
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
7789
8177
  Text,
7790
8178
  {
7791
8179
  variant: "caption",
@@ -7801,30 +8189,30 @@ function ChatMessageBubbleInner({
7801
8189
  ] });
7802
8190
  }
7803
8191
  function areEqual(prev, next) {
7804
- 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;
8192
+ 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;
7805
8193
  }
7806
- var ChatMessageBubble = React37.memo(ChatMessageBubbleInner, areEqual);
8194
+ var ChatMessageBubble = React38.memo(ChatMessageBubbleInner, areEqual);
7807
8195
  ChatMessageBubble.displayName = "ChatMessageBubble";
7808
8196
 
7809
8197
  // src/components/chat/TypingIndicator.tsx
7810
- var React38 = __toESM(require("react"));
7811
- var import_react_native49 = require("react-native");
7812
- var import_jsx_runtime50 = require("react/jsx-runtime");
8198
+ var React39 = __toESM(require("react"));
8199
+ var import_react_native50 = require("react-native");
8200
+ var import_jsx_runtime51 = require("react/jsx-runtime");
7813
8201
  function TypingIndicator({ style }) {
7814
8202
  const theme = useTheme();
7815
8203
  const dotColor = theme.colors.textSubtle;
7816
- const anims = React38.useMemo(
7817
- () => [new import_react_native49.Animated.Value(0.3), new import_react_native49.Animated.Value(0.3), new import_react_native49.Animated.Value(0.3)],
8204
+ const anims = React39.useMemo(
8205
+ () => [new import_react_native50.Animated.Value(0.3), new import_react_native50.Animated.Value(0.3), new import_react_native50.Animated.Value(0.3)],
7818
8206
  []
7819
8207
  );
7820
- React38.useEffect(() => {
8208
+ React39.useEffect(() => {
7821
8209
  const loops = [];
7822
8210
  anims.forEach((a, idx) => {
7823
- const seq = import_react_native49.Animated.sequence([
7824
- import_react_native49.Animated.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
7825
- import_react_native49.Animated.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
8211
+ const seq = import_react_native50.Animated.sequence([
8212
+ import_react_native50.Animated.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
8213
+ import_react_native50.Animated.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
7826
8214
  ]);
7827
- const loop = import_react_native49.Animated.loop(seq);
8215
+ const loop = import_react_native50.Animated.loop(seq);
7828
8216
  loops.push(loop);
7829
8217
  loop.start();
7830
8218
  });
@@ -7832,8 +8220,8 @@ function TypingIndicator({ style }) {
7832
8220
  loops.forEach((l) => l.stop());
7833
8221
  };
7834
8222
  }, [anims]);
7835
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native49.View, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
7836
- import_react_native49.Animated.View,
8223
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_native50.View, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
8224
+ import_react_native50.Animated.View,
7837
8225
  {
7838
8226
  style: {
7839
8227
  width: 8,
@@ -7842,7 +8230,7 @@ function TypingIndicator({ style }) {
7842
8230
  marginHorizontal: 3,
7843
8231
  backgroundColor: dotColor,
7844
8232
  opacity: a,
7845
- transform: [{ translateY: import_react_native49.Animated.multiply(import_react_native49.Animated.subtract(a, 0.3), 2) }]
8233
+ transform: [{ translateY: import_react_native50.Animated.multiply(import_react_native50.Animated.subtract(a, 0.3), 2) }]
7846
8234
  }
7847
8235
  },
7848
8236
  i
@@ -7850,36 +8238,37 @@ function TypingIndicator({ style }) {
7850
8238
  }
7851
8239
 
7852
8240
  // src/components/chat/ChatMessageList.tsx
7853
- var import_jsx_runtime51 = require("react/jsx-runtime");
7854
- var ChatMessageList = React39.forwardRef(
8241
+ var import_jsx_runtime52 = require("react/jsx-runtime");
8242
+ var ChatMessageList = React40.forwardRef(
7855
8243
  ({
7856
8244
  messages,
7857
8245
  showTypingIndicator = false,
7858
8246
  renderMessageContent,
7859
8247
  onRetryMessage,
7860
8248
  isRetryingMessage,
8249
+ onAttachmentLoadError,
7861
8250
  contentStyle,
7862
8251
  bottomInset = 0,
7863
8252
  onNearBottomChange,
7864
8253
  nearBottomThreshold = 200
7865
8254
  }, ref) => {
7866
8255
  const theme = useTheme();
7867
- const listRef = React39.useRef(null);
7868
- const nearBottomRef = React39.useRef(true);
7869
- const initialScrollDoneRef = React39.useRef(false);
7870
- const lastMessageIdRef = React39.useRef(null);
7871
- const data = React39.useMemo(() => {
8256
+ const listRef = React40.useRef(null);
8257
+ const nearBottomRef = React40.useRef(true);
8258
+ const initialScrollDoneRef = React40.useRef(false);
8259
+ const lastMessageIdRef = React40.useRef(null);
8260
+ const data = React40.useMemo(() => {
7872
8261
  return [...messages].reverse();
7873
8262
  }, [messages]);
7874
8263
  const lastMessageId = messages.length > 0 ? messages[messages.length - 1].id : null;
7875
- const keyExtractor = React39.useCallback((m) => m.id, []);
7876
- const scrollToBottom = React39.useCallback((options) => {
8264
+ const keyExtractor = React40.useCallback((m) => m.id, []);
8265
+ const scrollToBottom = React40.useCallback((options) => {
7877
8266
  var _a;
7878
8267
  const animated = (options == null ? void 0 : options.animated) ?? true;
7879
8268
  (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
7880
8269
  }, []);
7881
- React39.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
7882
- const handleScroll = React39.useCallback(
8270
+ React40.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
8271
+ const handleScroll = React40.useCallback(
7883
8272
  (e) => {
7884
8273
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
7885
8274
  const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
@@ -7891,7 +8280,7 @@ var ChatMessageList = React39.forwardRef(
7891
8280
  },
7892
8281
  [bottomInset, nearBottomThreshold, onNearBottomChange]
7893
8282
  );
7894
- React39.useEffect(() => {
8283
+ React40.useEffect(() => {
7895
8284
  if (!initialScrollDoneRef.current) return;
7896
8285
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
7897
8286
  const prevLastId = lastMessageIdRef.current;
@@ -7901,14 +8290,14 @@ var ChatMessageList = React39.forwardRef(
7901
8290
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
7902
8291
  return () => cancelAnimationFrame(id);
7903
8292
  }, [messages, scrollToBottom]);
7904
- React39.useEffect(() => {
8293
+ React40.useEffect(() => {
7905
8294
  if (showTypingIndicator && nearBottomRef.current) {
7906
8295
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
7907
8296
  return () => cancelAnimationFrame(id);
7908
8297
  }
7909
8298
  return void 0;
7910
8299
  }, [showTypingIndicator, scrollToBottom]);
7911
- const handleContentSizeChange = React39.useCallback(() => {
8300
+ const handleContentSizeChange = React40.useCallback(() => {
7912
8301
  if (initialScrollDoneRef.current) return;
7913
8302
  initialScrollDoneRef.current = true;
7914
8303
  lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
@@ -7916,7 +8305,7 @@ var ChatMessageList = React39.forwardRef(
7916
8305
  onNearBottomChange == null ? void 0 : onNearBottomChange(true);
7917
8306
  requestAnimationFrame(() => scrollToBottom({ animated: false }));
7918
8307
  }, [messages, onNearBottomChange, scrollToBottom]);
7919
- const contentContainerStyle = React39.useMemo(
8308
+ const contentContainerStyle = React40.useMemo(
7920
8309
  () => [
7921
8310
  {
7922
8311
  paddingHorizontal: theme.spacing.lg,
@@ -7926,28 +8315,29 @@ var ChatMessageList = React39.forwardRef(
7926
8315
  ],
7927
8316
  [contentStyle, theme.spacing.lg, theme.spacing.sm]
7928
8317
  );
7929
- const renderSeparator = React39.useCallback(() => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_native50.View, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
7930
- const listHeader = React39.useMemo(
7931
- () => /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_react_native50.View, { children: [
7932
- showTypingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_native50.View, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(TypingIndicator, {}) }) : null,
7933
- bottomInset > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_native50.View, { style: { height: bottomInset } }) : null
8318
+ const renderSeparator = React40.useCallback(() => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native51.View, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
8319
+ const listHeader = React40.useMemo(
8320
+ () => /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_react_native51.View, { children: [
8321
+ showTypingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native51.View, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(TypingIndicator, {}) }) : null,
8322
+ bottomInset > 0 ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native51.View, { style: { height: bottomInset } }) : null
7934
8323
  ] }),
7935
8324
  [bottomInset, showTypingIndicator, theme.spacing.lg, theme.spacing.sm]
7936
8325
  );
7937
- const renderItem = React39.useCallback(
7938
- ({ item }) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
8326
+ const renderItem = React40.useCallback(
8327
+ ({ item }) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
7939
8328
  ChatMessageBubble,
7940
8329
  {
7941
8330
  message: item,
7942
8331
  renderContent: renderMessageContent,
7943
8332
  isLast: Boolean(lastMessageId && item.id === lastMessageId),
7944
8333
  retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
7945
- onRetryMessage
8334
+ onRetryMessage,
8335
+ onAttachmentLoadError
7946
8336
  }
7947
8337
  ),
7948
- [isRetryingMessage, lastMessageId, onRetryMessage, renderMessageContent]
8338
+ [isRetryingMessage, lastMessageId, onAttachmentLoadError, onRetryMessage, renderMessageContent]
7949
8339
  );
7950
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
8340
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
7951
8341
  import_bottom_sheet5.BottomSheetFlatList,
7952
8342
  {
7953
8343
  ref: listRef,
@@ -7970,7 +8360,7 @@ var ChatMessageList = React39.forwardRef(
7970
8360
  ChatMessageList.displayName = "ChatMessageList";
7971
8361
 
7972
8362
  // src/components/chat/ChatPage.tsx
7973
- var import_jsx_runtime52 = require("react/jsx-runtime");
8363
+ var import_jsx_runtime53 = require("react/jsx-runtime");
7974
8364
  function ChatPage({
7975
8365
  header,
7976
8366
  messages,
@@ -7978,6 +8368,7 @@ function ChatPage({
7978
8368
  renderMessageContent,
7979
8369
  onRetryMessage,
7980
8370
  isRetryingMessage,
8371
+ onAttachmentLoadError,
7981
8372
  topBanner,
7982
8373
  composerTop,
7983
8374
  composer,
@@ -7989,35 +8380,35 @@ function ChatPage({
7989
8380
  }) {
7990
8381
  const theme = useTheme();
7991
8382
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
7992
- const [composerHeight, setComposerHeight] = React40.useState(0);
7993
- const [composerTopHeight, setComposerTopHeight] = React40.useState(0);
7994
- const footerBottomPadding = import_react_native51.Platform.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
8383
+ const [composerHeight, setComposerHeight] = React41.useState(0);
8384
+ const [composerTopHeight, setComposerTopHeight] = React41.useState(0);
8385
+ const footerBottomPadding = import_react_native52.Platform.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
7995
8386
  const totalComposerHeight = composerHeight + composerTopHeight;
7996
8387
  const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
7997
8388
  const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
7998
- const resolvedOverlay = React40.useMemo(() => {
8389
+ const resolvedOverlay = React41.useMemo(() => {
7999
8390
  var _a;
8000
8391
  if (!overlay) return null;
8001
- if (!React40.isValidElement(overlay)) return overlay;
8392
+ if (!React41.isValidElement(overlay)) return overlay;
8002
8393
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
8003
- return React40.cloneElement(overlay, {
8394
+ return React41.cloneElement(overlay, {
8004
8395
  style: [prevStyle, { bottom: overlayBottom }]
8005
8396
  });
8006
8397
  }, [overlay, overlayBottom]);
8007
- React40.useEffect(() => {
8398
+ React41.useEffect(() => {
8008
8399
  if (composerTop) return;
8009
8400
  setComposerTopHeight(0);
8010
8401
  }, [composerTop]);
8011
- return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_react_native51.View, { style: [{ flex: 1 }, style], children: [
8012
- header ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native51.View, { children: header }) : null,
8013
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native51.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
8014
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
8015
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
8016
- import_react_native51.View,
8402
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native52.View, { style: [{ flex: 1 }, style], children: [
8403
+ header ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native52.View, { children: header }) : null,
8404
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native52.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
8405
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native52.View, { style: { flex: 1 }, children: [
8406
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
8407
+ import_react_native52.View,
8017
8408
  {
8018
8409
  style: { flex: 1 },
8019
8410
  children: [
8020
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
8411
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8021
8412
  ChatMessageList,
8022
8413
  {
8023
8414
  ref: listRef,
@@ -8026,6 +8417,7 @@ function ChatPage({
8026
8417
  renderMessageContent,
8027
8418
  onRetryMessage,
8028
8419
  isRetryingMessage,
8420
+ onAttachmentLoadError,
8029
8421
  onNearBottomChange,
8030
8422
  bottomInset
8031
8423
  }
@@ -8034,8 +8426,8 @@ function ChatPage({
8034
8426
  ]
8035
8427
  }
8036
8428
  ),
8037
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
8038
- import_react_native51.View,
8429
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
8430
+ import_react_native52.View,
8039
8431
  {
8040
8432
  style: {
8041
8433
  position: "absolute",
@@ -8047,15 +8439,15 @@ function ChatPage({
8047
8439
  paddingBottom: footerBottomPadding
8048
8440
  },
8049
8441
  children: [
8050
- composerTop ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
8051
- import_react_native51.View,
8442
+ composerTop ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8443
+ import_react_native52.View,
8052
8444
  {
8053
8445
  style: { marginBottom: theme.spacing.sm },
8054
8446
  onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
8055
8447
  children: composerTop
8056
8448
  }
8057
8449
  ) : null,
8058
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
8450
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8059
8451
  ChatComposer,
8060
8452
  {
8061
8453
  ...composer,
@@ -8071,15 +8463,15 @@ function ChatPage({
8071
8463
  }
8072
8464
 
8073
8465
  // src/components/chat/ScrollToBottomButton.tsx
8074
- var React41 = __toESM(require("react"));
8075
- var import_react_native52 = require("react-native");
8466
+ var React42 = __toESM(require("react"));
8467
+ var import_react_native53 = require("react-native");
8076
8468
  var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
8077
- var import_jsx_runtime53 = require("react/jsx-runtime");
8469
+ var import_jsx_runtime54 = require("react/jsx-runtime");
8078
8470
  function ScrollToBottomButton({ visible, onPress, children, style }) {
8079
8471
  const theme = useTheme();
8080
8472
  const progress = (0, import_react_native_reanimated2.useSharedValue)(visible ? 1 : 0);
8081
- const [pressed, setPressed] = React41.useState(false);
8082
- React41.useEffect(() => {
8473
+ const [pressed, setPressed] = React42.useState(false);
8474
+ React42.useEffect(() => {
8083
8475
  progress.value = (0, import_react_native_reanimated2.withTiming)(visible ? 1 : 0, { duration: 200, easing: import_react_native_reanimated2.Easing.out(import_react_native_reanimated2.Easing.ease) });
8084
8476
  }, [progress, visible]);
8085
8477
  const animStyle = (0, import_react_native_reanimated2.useAnimatedStyle)(() => ({
@@ -8088,7 +8480,7 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8088
8480
  }));
8089
8481
  const bg = theme.scheme === "dark" ? "rgba(39,39,42,0.9)" : "rgba(244,244,245,0.95)";
8090
8482
  const border = theme.scheme === "dark" ? withAlpha("#FFFFFF", 0.12) : withAlpha("#000000", 0.08);
8091
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8483
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
8092
8484
  import_react_native_reanimated2.default.View,
8093
8485
  {
8094
8486
  pointerEvents: visible ? "auto" : "none",
@@ -8102,8 +8494,8 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8102
8494
  style,
8103
8495
  animStyle
8104
8496
  ],
8105
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8106
- import_react_native52.View,
8497
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
8498
+ import_react_native53.View,
8107
8499
  {
8108
8500
  style: {
8109
8501
  width: 44,
@@ -8121,8 +8513,8 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8121
8513
  elevation: 5,
8122
8514
  opacity: pressed ? 0.85 : 1
8123
8515
  },
8124
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
8125
- import_react_native52.Pressable,
8516
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
8517
+ import_react_native53.Pressable,
8126
8518
  {
8127
8519
  onPress,
8128
8520
  onPressIn: () => setPressed(true),
@@ -8139,16 +8531,16 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
8139
8531
  }
8140
8532
 
8141
8533
  // src/components/chat/ChatHeader.tsx
8142
- var import_react_native53 = require("react-native");
8143
- var import_jsx_runtime54 = require("react/jsx-runtime");
8534
+ var import_react_native54 = require("react-native");
8535
+ var import_jsx_runtime55 = require("react/jsx-runtime");
8144
8536
  function ChatHeader({ left, right, center, style }) {
8145
- const flattenedStyle = import_react_native53.StyleSheet.flatten([
8537
+ const flattenedStyle = import_react_native54.StyleSheet.flatten([
8146
8538
  {
8147
8539
  paddingTop: 0
8148
8540
  },
8149
8541
  style
8150
8542
  ]);
8151
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
8543
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
8152
8544
  StudioSheetHeader,
8153
8545
  {
8154
8546
  left,
@@ -8160,13 +8552,13 @@ function ChatHeader({ left, right, center, style }) {
8160
8552
  }
8161
8553
 
8162
8554
  // src/components/chat/ForkNoticeBanner.tsx
8163
- var import_react_native54 = require("react-native");
8164
- var import_jsx_runtime55 = require("react/jsx-runtime");
8555
+ var import_react_native55 = require("react-native");
8556
+ var import_jsx_runtime56 = require("react/jsx-runtime");
8165
8557
  function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8166
8558
  const theme = useTheme();
8167
8559
  const resolvedTitle = title ?? (isOwner ? "Remixed app" : "Remix app");
8168
8560
  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.");
8169
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
8561
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8170
8562
  Card,
8171
8563
  {
8172
8564
  variant: "surfaceRaised",
@@ -8181,8 +8573,8 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8181
8573
  },
8182
8574
  style
8183
8575
  ],
8184
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native54.View, { style: { minWidth: 0 }, children: [
8185
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
8576
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native55.View, { style: { minWidth: 0 }, children: [
8577
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8186
8578
  Text,
8187
8579
  {
8188
8580
  style: {
@@ -8196,7 +8588,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8196
8588
  children: resolvedTitle
8197
8589
  }
8198
8590
  ),
8199
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
8591
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8200
8592
  Text,
8201
8593
  {
8202
8594
  style: {
@@ -8214,16 +8606,16 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
8214
8606
  }
8215
8607
 
8216
8608
  // src/components/chat/ChatQueue.tsx
8217
- var React42 = __toESM(require("react"));
8218
- var import_react_native55 = require("react-native");
8219
- var import_jsx_runtime56 = require("react/jsx-runtime");
8609
+ var React43 = __toESM(require("react"));
8610
+ var import_react_native56 = require("react-native");
8611
+ var import_jsx_runtime57 = require("react/jsx-runtime");
8220
8612
  function ChatQueue({ items, onRemove }) {
8221
8613
  const theme = useTheme();
8222
- const [expanded, setExpanded] = React42.useState({});
8223
- const [canExpand, setCanExpand] = React42.useState({});
8224
- const [collapsedText, setCollapsedText] = React42.useState({});
8225
- const [removing, setRemoving] = React42.useState({});
8226
- const buildCollapsedText = React42.useCallback((lines) => {
8614
+ const [expanded, setExpanded] = React43.useState({});
8615
+ const [canExpand, setCanExpand] = React43.useState({});
8616
+ const [collapsedText, setCollapsedText] = React43.useState({});
8617
+ const [removing, setRemoving] = React43.useState({});
8618
+ const buildCollapsedText = React43.useCallback((lines) => {
8227
8619
  var _a, _b;
8228
8620
  const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
8229
8621
  const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
@@ -8239,7 +8631,7 @@ function ChatQueue({ items, onRemove }) {
8239
8631
  return `${line1}
8240
8632
  ${trimmedLine2}\u2026 `;
8241
8633
  }, []);
8242
- React42.useEffect(() => {
8634
+ React43.useEffect(() => {
8243
8635
  if (items.length === 0) return;
8244
8636
  const ids = new Set(items.map((item) => item.id));
8245
8637
  setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
@@ -8248,8 +8640,8 @@ ${trimmedLine2}\u2026 `;
8248
8640
  setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
8249
8641
  }, [items]);
8250
8642
  if (items.length === 0) return null;
8251
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
8252
- import_react_native55.View,
8643
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
8644
+ import_react_native56.View,
8253
8645
  {
8254
8646
  style: {
8255
8647
  borderWidth: 1,
@@ -8260,16 +8652,16 @@ ${trimmedLine2}\u2026 `;
8260
8652
  backgroundColor: "transparent"
8261
8653
  },
8262
8654
  children: [
8263
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
8264
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native55.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
8655
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
8656
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
8265
8657
  const isExpanded = Boolean(expanded[item.id]);
8266
8658
  const showToggle = Boolean(canExpand[item.id]);
8267
8659
  const prompt = item.prompt ?? "";
8268
8660
  const moreLabel = "more";
8269
8661
  const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
8270
8662
  const isRemoving = Boolean(removing[item.id]);
8271
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
8272
- import_react_native55.View,
8663
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
8664
+ import_react_native56.View,
8273
8665
  {
8274
8666
  style: {
8275
8667
  flexDirection: "row",
@@ -8281,8 +8673,8 @@ ${trimmedLine2}\u2026 `;
8281
8673
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
8282
8674
  },
8283
8675
  children: [
8284
- /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native55.View, { style: { flex: 1 }, children: [
8285
- !canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8676
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { flex: 1 }, children: [
8677
+ !canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
8286
8678
  Text,
8287
8679
  {
8288
8680
  style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
@@ -8301,14 +8693,14 @@ ${trimmedLine2}\u2026 `;
8301
8693
  children: prompt
8302
8694
  }
8303
8695
  ) : null,
8304
- /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
8696
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
8305
8697
  Text,
8306
8698
  {
8307
8699
  variant: "bodyMuted",
8308
8700
  numberOfLines: isExpanded ? void 0 : 2,
8309
8701
  children: [
8310
8702
  displayPrompt,
8311
- !isExpanded && showToggle ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8703
+ !isExpanded && showToggle ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
8312
8704
  Text,
8313
8705
  {
8314
8706
  color: theme.colors.text,
@@ -8320,18 +8712,18 @@ ${trimmedLine2}\u2026 `;
8320
8712
  ]
8321
8713
  }
8322
8714
  ),
8323
- showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8324
- import_react_native55.Pressable,
8715
+ showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
8716
+ import_react_native56.Pressable,
8325
8717
  {
8326
8718
  onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
8327
8719
  hitSlop: 6,
8328
8720
  style: { alignSelf: "flex-start", marginTop: 4 },
8329
- children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
8721
+ children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
8330
8722
  }
8331
8723
  ) : null
8332
8724
  ] }),
8333
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
8334
- import_react_native55.Pressable,
8725
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
8726
+ import_react_native56.Pressable,
8335
8727
  {
8336
8728
  onPress: () => {
8337
8729
  if (!onRemove || isRemoving) return;
@@ -8346,7 +8738,7 @@ ${trimmedLine2}\u2026 `;
8346
8738
  },
8347
8739
  hitSlop: 8,
8348
8740
  style: { alignSelf: "center" },
8349
- children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native55.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(IconClose, { size: 14, colorToken: "text" })
8741
+ children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconClose, { size: 14, colorToken: "text" })
8350
8742
  }
8351
8743
  )
8352
8744
  ]
@@ -8360,15 +8752,15 @@ ${trimmedLine2}\u2026 `;
8360
8752
  }
8361
8753
 
8362
8754
  // src/components/chat/AgentProgressCard.tsx
8363
- var import_react_native56 = require("react-native");
8755
+ var import_react_native57 = require("react-native");
8364
8756
 
8365
8757
  // src/components/icons/RemixXLoopLottie.tsx
8366
8758
  var import_lottie_react_native = __toESM(require("lottie-react-native"));
8367
- var import_jsx_runtime57 = require("react/jsx-runtime");
8759
+ var import_jsx_runtime58 = require("react/jsx-runtime");
8368
8760
  var remixXLoopSource = require_remix_x_loop_lottie();
8369
8761
  var Lottie = import_lottie_react_native.default;
8370
8762
  function RemixXLoopLottie({ size = 24, style }) {
8371
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
8763
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
8372
8764
  Lottie,
8373
8765
  {
8374
8766
  source: remixXLoopSource,
@@ -8380,7 +8772,7 @@ function RemixXLoopLottie({ size = 24, style }) {
8380
8772
  }
8381
8773
 
8382
8774
  // src/components/chat/AgentProgressCard.tsx
8383
- var import_jsx_runtime58 = require("react/jsx-runtime");
8775
+ var import_jsx_runtime59 = require("react/jsx-runtime");
8384
8776
  function titleForPhase(phase) {
8385
8777
  if (phase === "planning") return "Planning";
8386
8778
  if (phase === "reasoning") return "Reasoning";
@@ -8405,8 +8797,8 @@ function AgentProgressCard({ progress }) {
8405
8797
  const showAnimatedStatusIcon = progress.status === "running";
8406
8798
  const subtitle = progress.latestMessage || `Agent is ${phaseLabel.toLowerCase()}...`;
8407
8799
  const todo = progress.todoSummary;
8408
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(
8409
- import_react_native56.View,
8800
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
8801
+ import_react_native57.View,
8410
8802
  {
8411
8803
  style: {
8412
8804
  borderWidth: 1,
@@ -8417,23 +8809,23 @@ function AgentProgressCard({ progress }) {
8417
8809
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8418
8810
  },
8419
8811
  children: [
8420
- /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_react_native56.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
8421
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Text, { variant: "caption", children: statusLabel }),
8422
- /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_react_native56.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8423
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Text, { variant: "captionMuted", children: phaseLabel }),
8424
- showAnimatedStatusIcon ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(RemixXLoopLottie, { size: 20, style: { marginLeft: 8 } }) : null
8812
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native57.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
8813
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { variant: "caption", children: statusLabel }),
8814
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native57.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8815
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { variant: "captionMuted", children: phaseLabel }),
8816
+ showAnimatedStatusIcon ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(RemixXLoopLottie, { size: 20, style: { marginLeft: 8 } }) : null
8425
8817
  ] })
8426
8818
  ] }),
8427
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Text, { variant: "bodyMuted", children: subtitle }),
8428
- progress.changedFilesCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8819
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { variant: "bodyMuted", children: subtitle }),
8820
+ progress.changedFilesCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8429
8821
  "Updated files: ",
8430
8822
  progress.changedFilesCount
8431
8823
  ] }) : null,
8432
- progress.recentFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native56.View, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Text, { variant: "captionMuted", numberOfLines: 1, children: [
8824
+ progress.recentFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native57.View, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(Text, { variant: "captionMuted", numberOfLines: 1, children: [
8433
8825
  "\u2022 ",
8434
8826
  path
8435
8827
  ] }, path)) }) : null,
8436
- todo ? /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8828
+ todo ? /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
8437
8829
  "Todos: ",
8438
8830
  todo.completed,
8439
8831
  "/",
@@ -8447,8 +8839,8 @@ function AgentProgressCard({ progress }) {
8447
8839
  }
8448
8840
 
8449
8841
  // src/components/chat/BundleProgressCard.tsx
8450
- var import_react_native57 = require("react-native");
8451
- var import_jsx_runtime59 = require("react/jsx-runtime");
8842
+ var import_react_native58 = require("react-native");
8843
+ var import_jsx_runtime60 = require("react/jsx-runtime");
8452
8844
  function titleForStatus2(status) {
8453
8845
  if (status === "succeeded") return "Completed";
8454
8846
  if (status === "failed") return "Failed";
@@ -8460,8 +8852,8 @@ function BundleProgressCard({ progress }) {
8460
8852
  const percent = Math.round(Math.max(0, Math.min(1, progress.progressValue)) * 100);
8461
8853
  const fillColor = progress.status === "failed" ? theme.colors.danger : progress.status === "succeeded" ? theme.colors.success : theme.colors.warning;
8462
8854
  const detail = progress.errorMessage || progress.phaseLabel;
8463
- return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
8464
- import_react_native57.View,
8855
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(
8856
+ import_react_native58.View,
8465
8857
  {
8466
8858
  accessible: true,
8467
8859
  accessibilityRole: "progressbar",
@@ -8476,15 +8868,15 @@ function BundleProgressCard({ progress }) {
8476
8868
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
8477
8869
  },
8478
8870
  children: [
8479
- /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native57.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
8480
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { variant: "caption", children: statusLabel }),
8481
- /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(Text, { variant: "captionMuted", children: [
8871
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
8872
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Text, { variant: "caption", children: statusLabel }),
8873
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(Text, { variant: "captionMuted", children: [
8482
8874
  percent,
8483
8875
  "%"
8484
8876
  ] })
8485
8877
  ] }),
8486
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8487
- import_react_native57.View,
8878
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8879
+ import_react_native58.View,
8488
8880
  {
8489
8881
  style: {
8490
8882
  width: "100%",
@@ -8493,8 +8885,8 @@ function BundleProgressCard({ progress }) {
8493
8885
  backgroundColor: withAlpha(theme.colors.border, theme.scheme === "dark" ? 0.5 : 0.6),
8494
8886
  overflow: "hidden"
8495
8887
  },
8496
- children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8497
- import_react_native57.View,
8888
+ children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8889
+ import_react_native58.View,
8498
8890
  {
8499
8891
  style: {
8500
8892
  width: `${percent}%`,
@@ -8505,14 +8897,14 @@ function BundleProgressCard({ progress }) {
8505
8897
  )
8506
8898
  }
8507
8899
  ),
8508
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
8900
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
8509
8901
  ]
8510
8902
  }
8511
8903
  );
8512
8904
  }
8513
8905
 
8514
8906
  // src/studio/ui/ChatPanel.tsx
8515
- var import_jsx_runtime60 = require("react/jsx-runtime");
8907
+ var import_jsx_runtime61 = require("react/jsx-runtime");
8516
8908
  function ChatPanel({
8517
8909
  title = "Chat",
8518
8910
  messages,
@@ -8532,14 +8924,15 @@ function ChatPanel({
8532
8924
  onSend,
8533
8925
  onRetryMessage,
8534
8926
  isRetryingMessage,
8927
+ onAttachmentLoadError,
8535
8928
  queueItems = [],
8536
8929
  onRemoveQueueItem,
8537
8930
  progress = null
8538
8931
  }) {
8539
8932
  const theme = useTheme();
8540
- const listRef = React43.useRef(null);
8541
- const [nearBottom, setNearBottom] = React43.useState(true);
8542
- const handleSend = React43.useCallback(
8933
+ const listRef = React44.useRef(null);
8934
+ const [nearBottom, setNearBottom] = React44.useState(true);
8935
+ const handleSend = React44.useCallback(
8543
8936
  async (text, composerAttachments) => {
8544
8937
  const all = composerAttachments ?? attachments;
8545
8938
  await onSend(text, all.length > 0 ? all : void 0);
@@ -8553,25 +8946,25 @@ function ChatPanel({
8553
8946
  },
8554
8947
  [attachments, nearBottom, onClearAttachments, onSend]
8555
8948
  );
8556
- const handleScrollToBottom = React43.useCallback(() => {
8949
+ const handleScrollToBottom = React44.useCallback(() => {
8557
8950
  var _a;
8558
8951
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
8559
8952
  }, []);
8560
- const header = /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8953
+ const header = /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8561
8954
  ChatHeader,
8562
8955
  {
8563
- left: /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8564
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
8565
- onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
8956
+ left: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8957
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
8958
+ onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
8566
8959
  ] }),
8567
- right: /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8568
- onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
8569
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
8960
+ right: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
8961
+ onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
8962
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
8570
8963
  ] }),
8571
8964
  center: null
8572
8965
  }
8573
8966
  );
8574
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8967
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8575
8968
  ForkNoticeBanner,
8576
8969
  {
8577
8970
  isOwner: !shouldForkOnEdit,
@@ -8580,22 +8973,22 @@ function ChatPanel({
8580
8973
  ) : null;
8581
8974
  const showMessagesLoading = Boolean(loading) && messages.length === 0;
8582
8975
  if (showMessagesLoading) {
8583
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { flex: 1 }, children: [
8584
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native58.View, { children: header }),
8585
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native58.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
8586
- /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
8587
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native58.ActivityIndicator, {}),
8588
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native58.View, { style: { height: 12 } }),
8589
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Text, { variant: "bodyMuted", children: "Loading messages\u2026" })
8976
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { flex: 1 }, children: [
8977
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { children: header }),
8978
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
8979
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
8980
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.ActivityIndicator, {}),
8981
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { style: { height: 12 } }),
8982
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Text, { variant: "bodyMuted", children: "Loading messages\u2026" })
8590
8983
  ] })
8591
8984
  ] });
8592
8985
  }
8593
8986
  const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
8594
- const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native58.View, { style: { gap: theme.spacing.sm }, children: [
8595
- progress ? bundleProgress ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(AgentProgressCard, { progress }) : null,
8596
- !progress && queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
8987
+ const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { gap: theme.spacing.sm }, children: [
8988
+ progress ? bundleProgress ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(AgentProgressCard, { progress }) : null,
8989
+ !progress && queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
8597
8990
  ] }) : null;
8598
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8991
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8599
8992
  ChatPage,
8600
8993
  {
8601
8994
  header,
@@ -8603,18 +8996,19 @@ function ChatPanel({
8603
8996
  showTypingIndicator,
8604
8997
  onRetryMessage,
8605
8998
  isRetryingMessage,
8999
+ onAttachmentLoadError,
8606
9000
  topBanner,
8607
9001
  composerTop: queueTop,
8608
9002
  composerHorizontalPadding: 0,
8609
9003
  listRef,
8610
9004
  onNearBottomChange: setNearBottom,
8611
- overlay: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
9005
+ overlay: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8612
9006
  ScrollToBottomButton,
8613
9007
  {
8614
9008
  visible: !nearBottom,
8615
9009
  onPress: handleScrollToBottom,
8616
9010
  style: { bottom: 80 },
8617
- children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
9011
+ children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
8618
9012
  }
8619
9013
  ),
8620
9014
  composer: {
@@ -8634,9 +9028,9 @@ function ChatPanel({
8634
9028
  }
8635
9029
 
8636
9030
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
8637
- var React44 = __toESM(require("react"));
8638
- var import_react_native59 = require("react-native");
8639
- var import_jsx_runtime61 = require("react/jsx-runtime");
9031
+ var React45 = __toESM(require("react"));
9032
+ var import_react_native60 = require("react-native");
9033
+ var import_jsx_runtime62 = require("react/jsx-runtime");
8640
9034
  function ConfirmMergeRequestDialog({
8641
9035
  visible,
8642
9036
  onOpenChange,
@@ -8647,14 +9041,14 @@ function ConfirmMergeRequestDialog({
8647
9041
  onTestFirst
8648
9042
  }) {
8649
9043
  const theme = useTheme();
8650
- const close = React44.useCallback(() => onOpenChange(false), [onOpenChange]);
9044
+ const close = React45.useCallback(() => onOpenChange(false), [onOpenChange]);
8651
9045
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
8652
- const handleConfirm = React44.useCallback(() => {
9046
+ const handleConfirm = React45.useCallback(() => {
8653
9047
  if (!mergeRequest) return;
8654
9048
  onOpenChange(false);
8655
9049
  void onConfirm();
8656
9050
  }, [mergeRequest, onConfirm, onOpenChange]);
8657
- const handleTestFirst = React44.useCallback(() => {
9051
+ const handleTestFirst = React45.useCallback(() => {
8658
9052
  if (!mergeRequest) return;
8659
9053
  onOpenChange(false);
8660
9054
  void onTestFirst(mergeRequest);
@@ -8666,7 +9060,7 @@ function ConfirmMergeRequestDialog({
8666
9060
  justifyContent: "center",
8667
9061
  alignSelf: "stretch"
8668
9062
  };
8669
- return /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(
9063
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
8670
9064
  Modal,
8671
9065
  {
8672
9066
  visible,
@@ -8677,7 +9071,7 @@ function ConfirmMergeRequestDialog({
8677
9071
  backgroundColor: theme.colors.background
8678
9072
  },
8679
9073
  children: [
8680
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
9074
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_react_native60.View, { children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
8681
9075
  Text,
8682
9076
  {
8683
9077
  style: {
@@ -8689,9 +9083,9 @@ function ConfirmMergeRequestDialog({
8689
9083
  children: "Are you sure you want to approve this merge request?"
8690
9084
  }
8691
9085
  ) }),
8692
- /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_react_native59.View, { style: { marginTop: 16 }, children: [
8693
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8694
- import_react_native59.View,
9086
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(import_react_native60.View, { style: { marginTop: 16 }, children: [
9087
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9088
+ import_react_native60.View,
8695
9089
  {
8696
9090
  style: [
8697
9091
  fullWidthButtonBase,
@@ -8700,22 +9094,22 @@ function ConfirmMergeRequestDialog({
8700
9094
  opacity: canConfirm ? 1 : 0.5
8701
9095
  }
8702
9096
  ],
8703
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8704
- import_react_native59.Pressable,
9097
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9098
+ import_react_native60.Pressable,
8705
9099
  {
8706
9100
  accessibilityRole: "button",
8707
9101
  accessibilityLabel: "Approve Merge",
8708
9102
  disabled: !canConfirm,
8709
9103
  onPress: handleConfirm,
8710
9104
  style: [fullWidthButtonBase, { flex: 1 }],
8711
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
9105
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
8712
9106
  }
8713
9107
  )
8714
9108
  }
8715
9109
  ),
8716
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { style: { height: 8 } }),
8717
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8718
- import_react_native59.View,
9110
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_react_native60.View, { style: { height: 8 } }),
9111
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9112
+ import_react_native60.View,
8719
9113
  {
8720
9114
  style: [
8721
9115
  fullWidthButtonBase,
@@ -8726,22 +9120,22 @@ function ConfirmMergeRequestDialog({
8726
9120
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
8727
9121
  }
8728
9122
  ],
8729
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8730
- import_react_native59.Pressable,
9123
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9124
+ import_react_native60.Pressable,
8731
9125
  {
8732
9126
  accessibilityRole: "button",
8733
9127
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
8734
9128
  disabled: isBuilding || !mergeRequest,
8735
9129
  onPress: handleTestFirst,
8736
9130
  style: [fullWidthButtonBase, { flex: 1 }],
8737
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
9131
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
8738
9132
  }
8739
9133
  )
8740
9134
  }
8741
9135
  ),
8742
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { style: { height: 8 } }),
8743
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8744
- import_react_native59.View,
9136
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_react_native60.View, { style: { height: 8 } }),
9137
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9138
+ import_react_native60.View,
8745
9139
  {
8746
9140
  style: [
8747
9141
  fullWidthButtonBase,
@@ -8751,14 +9145,14 @@ function ConfirmMergeRequestDialog({
8751
9145
  borderColor: theme.colors.border
8752
9146
  }
8753
9147
  ],
8754
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8755
- import_react_native59.Pressable,
9148
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9149
+ import_react_native60.Pressable,
8756
9150
  {
8757
9151
  accessibilityRole: "button",
8758
9152
  accessibilityLabel: "Cancel",
8759
9153
  onPress: close,
8760
9154
  style: [fullWidthButtonBase, { flex: 1 }],
8761
- children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
9155
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
8762
9156
  }
8763
9157
  )
8764
9158
  }
@@ -8770,7 +9164,7 @@ function ConfirmMergeRequestDialog({
8770
9164
  }
8771
9165
 
8772
9166
  // src/studio/ui/ConfirmMergeFlow.tsx
8773
- var import_jsx_runtime62 = require("react/jsx-runtime");
9167
+ var import_jsx_runtime63 = require("react/jsx-runtime");
8774
9168
  function ConfirmMergeFlow({
8775
9169
  visible,
8776
9170
  onOpenChange,
@@ -8781,7 +9175,7 @@ function ConfirmMergeFlow({
8781
9175
  onConfirm,
8782
9176
  onTestFirst
8783
9177
  }) {
8784
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
9178
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
8785
9179
  ConfirmMergeRequestDialog,
8786
9180
  {
8787
9181
  visible,
@@ -8803,7 +9197,8 @@ function ConfirmMergeFlow({
8803
9197
  }
8804
9198
 
8805
9199
  // src/studio/hooks/useOptimisticChatMessages.ts
8806
- var React45 = __toESM(require("react"));
9200
+ var React46 = __toESM(require("react"));
9201
+ var import_react_native61 = require("react-native");
8807
9202
  function makeOptimisticId() {
8808
9203
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
8809
9204
  }
@@ -8814,6 +9209,28 @@ function toEpochMs2(createdAt) {
8814
9209
  const t = Date.parse(String(createdAt));
8815
9210
  return Number.isFinite(t) ? t : 0;
8816
9211
  }
9212
+ async function resolveAttachmentDimensions(uris) {
9213
+ return Promise.all(
9214
+ uris.map(
9215
+ async (uri) => {
9216
+ try {
9217
+ const { width, height } = await new Promise((resolve, reject) => {
9218
+ import_react_native61.Image.getSize(
9219
+ uri,
9220
+ (w, h) => resolve({ width: w, height: h }),
9221
+ (err) => reject(err)
9222
+ );
9223
+ });
9224
+ if (width > 0 && height > 0) {
9225
+ return { uri, width: Math.round(width), height: Math.round(height) };
9226
+ }
9227
+ } catch {
9228
+ }
9229
+ return { uri };
9230
+ }
9231
+ )
9232
+ );
9233
+ }
8817
9234
  function isOptimisticResolvedByServer(chatMessages, o) {
8818
9235
  if (o.failed) return false;
8819
9236
  const normalize = (s) => s.trim();
@@ -8842,11 +9259,11 @@ function useOptimisticChatMessages({
8842
9259
  chatMessages,
8843
9260
  onSendChat
8844
9261
  }) {
8845
- const [optimisticChat, setOptimisticChat] = React45.useState([]);
8846
- React45.useEffect(() => {
9262
+ const [optimisticChat, setOptimisticChat] = React46.useState([]);
9263
+ React46.useEffect(() => {
8847
9264
  setOptimisticChat([]);
8848
9265
  }, [threadId]);
8849
- const messages = React45.useMemo(() => {
9266
+ const messages = React46.useMemo(() => {
8850
9267
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
8851
9268
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
8852
9269
  if (unresolved.length === 0) return chatMessages;
@@ -8856,13 +9273,22 @@ function useOptimisticChatMessages({
8856
9273
  content: o.content,
8857
9274
  createdAt: o.createdAtIso,
8858
9275
  kind: "optimistic",
9276
+ attachments: (o.attachments ?? []).map((attachment, index) => ({
9277
+ id: `${o.id}:attachment:${index}`,
9278
+ name: `attachment-${index + 1}.png`,
9279
+ mimeType: "image/png",
9280
+ size: 1,
9281
+ uri: attachment.uri,
9282
+ width: attachment.width,
9283
+ height: attachment.height
9284
+ })),
8859
9285
  meta: o.failed ? { kind: "optimistic", event: "send.failed", status: "error" } : { kind: "optimistic", event: "send.pending", status: "info" }
8860
9286
  }));
8861
9287
  const merged = [...chatMessages, ...optimisticAsChat];
8862
9288
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
8863
9289
  return merged;
8864
9290
  }, [chatMessages, optimisticChat]);
8865
- React45.useEffect(() => {
9291
+ React46.useEffect(() => {
8866
9292
  if (optimisticChat.length === 0) return;
8867
9293
  setOptimisticChat((prev) => {
8868
9294
  if (prev.length === 0) return prev;
@@ -8870,7 +9296,7 @@ function useOptimisticChatMessages({
8870
9296
  return next.length === prev.length ? prev : next;
8871
9297
  });
8872
9298
  }, [chatMessages, optimisticChat.length]);
8873
- const onSend = React45.useCallback(
9299
+ const onSend = React46.useCallback(
8874
9300
  async (text, attachments) => {
8875
9301
  if (disableOptimistic) {
8876
9302
  await onSendChat(text, attachments);
@@ -8879,7 +9305,7 @@ function useOptimisticChatMessages({
8879
9305
  const createdAtIso = (/* @__PURE__ */ new Date()).toISOString();
8880
9306
  const baseServerLastId = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1].id : null;
8881
9307
  const id = makeOptimisticId();
8882
- const normalizedAttachments = attachments && attachments.length > 0 ? [...attachments] : void 0;
9308
+ const normalizedAttachments = attachments && attachments.length > 0 ? await resolveAttachmentDimensions(attachments) : void 0;
8883
9309
  setOptimisticChat((prev) => [
8884
9310
  ...prev,
8885
9311
  {
@@ -8898,8 +9324,9 @@ function useOptimisticChatMessages({
8898
9324
  },
8899
9325
  [chatMessages, disableOptimistic, onSendChat]
8900
9326
  );
8901
- const onRetry = React45.useCallback(
9327
+ const onRetry = React46.useCallback(
8902
9328
  async (messageId) => {
9329
+ var _a;
8903
9330
  if (disableOptimistic) return;
8904
9331
  const target = optimisticChat.find((m) => m.id === messageId);
8905
9332
  if (!target || target.retrying) return;
@@ -8910,7 +9337,10 @@ function useOptimisticChatMessages({
8910
9337
  )
8911
9338
  );
8912
9339
  try {
8913
- await onSendChat(target.content, target.attachments);
9340
+ await onSendChat(
9341
+ target.content,
9342
+ (_a = target.attachments) == null ? void 0 : _a.map((att) => att.uri)
9343
+ );
8914
9344
  setOptimisticChat(
8915
9345
  (prev) => prev.map((m) => m.id === messageId ? { ...m, retrying: false } : m)
8916
9346
  );
@@ -8922,7 +9352,7 @@ function useOptimisticChatMessages({
8922
9352
  },
8923
9353
  [chatMessages, disableOptimistic, onSendChat, optimisticChat]
8924
9354
  );
8925
- const isRetrying = React45.useCallback(
9355
+ const isRetrying = React46.useCallback(
8926
9356
  (messageId) => {
8927
9357
  return optimisticChat.some((m) => m.id === messageId && m.retrying);
8928
9358
  },
@@ -8936,24 +9366,24 @@ var import_studio_control = require("@comergehq/studio-control");
8936
9366
 
8937
9367
  // src/components/icons/RemixUpIcon.tsx
8938
9368
  var import_react_native_svg3 = __toESM(require("react-native-svg"));
8939
- var import_jsx_runtime63 = require("react/jsx-runtime");
9369
+ var import_jsx_runtime64 = require("react/jsx-runtime");
8940
9370
  function RemixUpIcon({ width = 24, height = 24, ...props }) {
8941
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_react_native_svg3.default, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
8942
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
9371
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(import_react_native_svg3.default, { viewBox: "0 0 70 49", width, height, fill: "none", ...props, children: [
9372
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
8943
9373
  import_react_native_svg3.Path,
8944
9374
  {
8945
9375
  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",
8946
9376
  fill: "#00CBC0"
8947
9377
  }
8948
9378
  ),
8949
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
9379
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
8950
9380
  import_react_native_svg3.Path,
8951
9381
  {
8952
9382
  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",
8953
9383
  fill: "#FF1820"
8954
9384
  }
8955
9385
  ),
8956
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
9386
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
8957
9387
  import_react_native_svg3.Path,
8958
9388
  {
8959
9389
  d: "M34.7656 2.28882e-05L21.44 13.2661L34.706 26.5321L47.972 13.266L34.7656 2.28882e-05Z",
@@ -8964,7 +9394,7 @@ function RemixUpIcon({ width = 24, height = 24, ...props }) {
8964
9394
  }
8965
9395
 
8966
9396
  // src/studio/ui/StudioOverlay.tsx
8967
- var import_jsx_runtime64 = require("react/jsx-runtime");
9397
+ var import_jsx_runtime65 = require("react/jsx-runtime");
8968
9398
  function StudioOverlay({
8969
9399
  captureTargetRef,
8970
9400
  app,
@@ -8995,6 +9425,7 @@ function StudioOverlay({
8995
9425
  chatSending,
8996
9426
  chatShowTypingIndicator,
8997
9427
  onSendChat,
9428
+ onChatAttachmentLoadError,
8998
9429
  chatQueueItems,
8999
9430
  onRemoveQueueItem,
9000
9431
  chatProgress,
@@ -9008,15 +9439,15 @@ function StudioOverlay({
9008
9439
  onSwitchRelatedApp
9009
9440
  }) {
9010
9441
  const theme = useTheme();
9011
- const { width } = (0, import_react_native60.useWindowDimensions)();
9012
- const [sheetOpen, setSheetOpen] = React46.useState(false);
9013
- const sheetOpenRef = React46.useRef(sheetOpen);
9014
- const pendingNavigateHomeRef = React46.useRef(false);
9015
- const [activePage, setActivePage] = React46.useState("preview");
9016
- const [drawing, setDrawing] = React46.useState(false);
9017
- const [chatAttachments, setChatAttachments] = React46.useState([]);
9018
- const [commentsAppId, setCommentsAppId] = React46.useState(null);
9019
- const [commentsCount, setCommentsCount] = React46.useState(null);
9442
+ const { width } = (0, import_react_native62.useWindowDimensions)();
9443
+ const [sheetOpen, setSheetOpen] = React47.useState(false);
9444
+ const sheetOpenRef = React47.useRef(sheetOpen);
9445
+ const pendingNavigateHomeRef = React47.useRef(false);
9446
+ const [activePage, setActivePage] = React47.useState("preview");
9447
+ const [drawing, setDrawing] = React47.useState(false);
9448
+ const [chatAttachments, setChatAttachments] = React47.useState([]);
9449
+ const [commentsAppId, setCommentsAppId] = React47.useState(null);
9450
+ const [commentsCount, setCommentsCount] = React47.useState(null);
9020
9451
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
9021
9452
  const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
9022
9453
  const isBubbleLoading = (app == null ? void 0 : app.status) === "editing" || isBaseBundleDownloading;
@@ -9029,26 +9460,26 @@ function StudioOverlay({
9029
9460
  chatMessages,
9030
9461
  onSendChat
9031
9462
  });
9032
- const [confirmMrId, setConfirmMrId] = React46.useState(null);
9033
- const confirmMr = React46.useMemo(
9463
+ const [confirmMrId, setConfirmMrId] = React47.useState(null);
9464
+ const confirmMr = React47.useMemo(
9034
9465
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
9035
9466
  [confirmMrId, incomingMergeRequests]
9036
9467
  );
9037
- const handleSheetOpenChange = React46.useCallback((open) => {
9468
+ const handleSheetOpenChange = React47.useCallback((open) => {
9038
9469
  setSheetOpen(open);
9039
- if (!open) import_react_native60.Keyboard.dismiss();
9470
+ if (!open) import_react_native62.Keyboard.dismiss();
9040
9471
  }, []);
9041
- const closeSheet = React46.useCallback(() => {
9472
+ const closeSheet = React47.useCallback(() => {
9042
9473
  handleSheetOpenChange(false);
9043
9474
  }, [handleSheetOpenChange]);
9044
- const openSheet = React46.useCallback(() => setSheetOpen(true), []);
9045
- const goToChat = React46.useCallback(() => {
9475
+ const openSheet = React47.useCallback(() => setSheetOpen(true), []);
9476
+ const goToChat = React47.useCallback(() => {
9046
9477
  setActivePage("chat");
9047
9478
  openSheet();
9048
9479
  }, [openSheet]);
9049
- const backToPreview = React46.useCallback(() => {
9050
- if (import_react_native60.Platform.OS !== "ios") {
9051
- import_react_native60.Keyboard.dismiss();
9480
+ const backToPreview = React47.useCallback(() => {
9481
+ if (import_react_native62.Platform.OS !== "ios") {
9482
+ import_react_native62.Keyboard.dismiss();
9052
9483
  setActivePage("preview");
9053
9484
  return;
9054
9485
  }
@@ -9060,15 +9491,15 @@ function StudioOverlay({
9060
9491
  clearTimeout(t);
9061
9492
  setActivePage("preview");
9062
9493
  };
9063
- const sub = import_react_native60.Keyboard.addListener("keyboardDidHide", finalize);
9494
+ const sub = import_react_native62.Keyboard.addListener("keyboardDidHide", finalize);
9064
9495
  const t = setTimeout(finalize, 350);
9065
- import_react_native60.Keyboard.dismiss();
9496
+ import_react_native62.Keyboard.dismiss();
9066
9497
  }, []);
9067
- const startDraw = React46.useCallback(() => {
9498
+ const startDraw = React47.useCallback(() => {
9068
9499
  setDrawing(true);
9069
9500
  closeSheet();
9070
9501
  }, [closeSheet]);
9071
- const handleDrawCapture = React46.useCallback(
9502
+ const handleDrawCapture = React47.useCallback(
9072
9503
  (dataUrl) => {
9073
9504
  setChatAttachments((prev) => [...prev, dataUrl]);
9074
9505
  setDrawing(false);
@@ -9077,7 +9508,7 @@ function StudioOverlay({
9077
9508
  },
9078
9509
  [openSheet]
9079
9510
  );
9080
- const toggleSheet = React46.useCallback(async () => {
9511
+ const toggleSheet = React47.useCallback(async () => {
9081
9512
  if (!sheetOpen) {
9082
9513
  const shouldExitTest = Boolean(testingMrId) || isTesting;
9083
9514
  if (shouldExitTest) {
@@ -9089,7 +9520,7 @@ function StudioOverlay({
9089
9520
  closeSheet();
9090
9521
  }
9091
9522
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
9092
- const handleTestMr = React46.useCallback(
9523
+ const handleTestMr = React47.useCallback(
9093
9524
  async (mr) => {
9094
9525
  if (!onTestMr) return;
9095
9526
  await onTestMr(mr);
@@ -9097,46 +9528,46 @@ function StudioOverlay({
9097
9528
  },
9098
9529
  [closeSheet, onTestMr]
9099
9530
  );
9100
- const handleNavigateHome = React46.useCallback(() => {
9531
+ const handleNavigateHome = React47.useCallback(() => {
9101
9532
  if (!onNavigateHome) return;
9102
- if (import_react_native60.Platform.OS !== "android") {
9533
+ if (import_react_native62.Platform.OS !== "android") {
9103
9534
  onNavigateHome();
9104
9535
  return;
9105
9536
  }
9106
9537
  if (!sheetOpenRef.current) {
9107
- import_react_native60.InteractionManager.runAfterInteractions(() => {
9538
+ import_react_native62.InteractionManager.runAfterInteractions(() => {
9108
9539
  onNavigateHome();
9109
9540
  });
9110
9541
  return;
9111
9542
  }
9112
9543
  pendingNavigateHomeRef.current = true;
9113
- import_react_native60.Keyboard.dismiss();
9544
+ import_react_native62.Keyboard.dismiss();
9114
9545
  setActivePage("preview");
9115
9546
  closeSheet();
9116
9547
  }, [closeSheet, onNavigateHome]);
9117
- const handleSheetDismiss = React46.useCallback(() => {
9118
- if (import_react_native60.Platform.OS !== "android") return;
9548
+ const handleSheetDismiss = React47.useCallback(() => {
9549
+ if (import_react_native62.Platform.OS !== "android") return;
9119
9550
  if (!pendingNavigateHomeRef.current) return;
9120
9551
  pendingNavigateHomeRef.current = false;
9121
- import_react_native60.InteractionManager.runAfterInteractions(() => {
9552
+ import_react_native62.InteractionManager.runAfterInteractions(() => {
9122
9553
  onNavigateHome == null ? void 0 : onNavigateHome();
9123
9554
  });
9124
9555
  }, [onNavigateHome]);
9125
- React46.useEffect(() => {
9556
+ React47.useEffect(() => {
9126
9557
  if (!sheetOpen) {
9127
9558
  return;
9128
9559
  }
9129
9560
  pendingNavigateHomeRef.current = false;
9130
9561
  }, [sheetOpen]);
9131
- React46.useEffect(() => {
9562
+ React47.useEffect(() => {
9132
9563
  return () => {
9133
9564
  pendingNavigateHomeRef.current = false;
9134
9565
  };
9135
9566
  }, []);
9136
- React46.useEffect(() => {
9567
+ React47.useEffect(() => {
9137
9568
  sheetOpenRef.current = sheetOpen;
9138
9569
  }, [sheetOpen]);
9139
- React46.useEffect(() => {
9570
+ React47.useEffect(() => {
9140
9571
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
9141
9572
  if (action === "show" && !sheetOpenRef.current) openSheet();
9142
9573
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -9144,17 +9575,17 @@ function StudioOverlay({
9144
9575
  }, studioControlOptions);
9145
9576
  return () => poller.stop();
9146
9577
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
9147
- React46.useEffect(() => {
9578
+ React47.useEffect(() => {
9148
9579
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
9149
9580
  }, [sheetOpen, studioControlOptions]);
9150
- return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(import_jsx_runtime64.Fragment, { children: [
9151
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
9152
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, onDismiss: handleSheetDismiss, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9581
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(import_jsx_runtime65.Fragment, { children: [
9582
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
9583
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, onDismiss: handleSheetDismiss, children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9153
9584
  StudioSheetPager,
9154
9585
  {
9155
9586
  activePage,
9156
9587
  width,
9157
- preview: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9588
+ preview: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9158
9589
  PreviewPanel,
9159
9590
  {
9160
9591
  app,
@@ -9188,7 +9619,7 @@ function StudioOverlay({
9188
9619
  onSwitchRelatedApp
9189
9620
  }
9190
9621
  ),
9191
- chat: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9622
+ chat: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9192
9623
  ChatPanel,
9193
9624
  {
9194
9625
  messages: optimistic.messages,
@@ -9208,6 +9639,7 @@ function StudioOverlay({
9208
9639
  onSend: optimistic.onSend,
9209
9640
  onRetryMessage: optimistic.onRetry,
9210
9641
  isRetryingMessage: optimistic.isRetrying,
9642
+ onAttachmentLoadError: onChatAttachmentLoadError,
9211
9643
  queueItems: queueItemsForChat,
9212
9644
  onRemoveQueueItem,
9213
9645
  progress: chatProgress
@@ -9215,7 +9647,7 @@ function StudioOverlay({
9215
9647
  )
9216
9648
  }
9217
9649
  ) }),
9218
- showBubble && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9650
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9219
9651
  Bubble,
9220
9652
  {
9221
9653
  visible: !sheetOpen && !drawing,
@@ -9224,10 +9656,10 @@ function StudioOverlay({
9224
9656
  onPress: toggleSheet,
9225
9657
  isLoading: isBubbleLoading,
9226
9658
  loadingBorderTone: isBaseBundleDownloading ? "warning" : "default",
9227
- children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_react_native60.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: isBubbleLoading ? /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RemixXLoopLottie, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RemixUpIcon, { width: 24, height: 24 }) })
9659
+ children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(import_react_native62.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: isBubbleLoading ? /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(RemixXLoopLottie, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(RemixUpIcon, { width: 24, height: 24 }) })
9228
9660
  }
9229
9661
  ),
9230
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9662
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9231
9663
  DrawModeOverlay,
9232
9664
  {
9233
9665
  visible: drawing,
@@ -9236,7 +9668,7 @@ function StudioOverlay({
9236
9668
  onCapture: handleDrawCapture
9237
9669
  }
9238
9670
  ),
9239
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9671
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9240
9672
  ConfirmMergeFlow,
9241
9673
  {
9242
9674
  visible: Boolean(confirmMr),
@@ -9250,7 +9682,7 @@ function StudioOverlay({
9250
9682
  onTestFirst: handleTestMr
9251
9683
  }
9252
9684
  ),
9253
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
9685
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
9254
9686
  AppCommentsSheet,
9255
9687
  {
9256
9688
  appId: commentsAppId,
@@ -9263,7 +9695,7 @@ function StudioOverlay({
9263
9695
  }
9264
9696
 
9265
9697
  // src/studio/hooks/useEditQueue.ts
9266
- var React47 = __toESM(require("react"));
9698
+ var React48 = __toESM(require("react"));
9267
9699
 
9268
9700
  // src/data/apps/edit-queue/remote.ts
9269
9701
  var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
@@ -9372,17 +9804,17 @@ var editQueueRepository = new EditQueueRepositoryImpl(
9372
9804
 
9373
9805
  // src/studio/hooks/useEditQueue.ts
9374
9806
  function useEditQueue(appId) {
9375
- const [items, setItems] = React47.useState([]);
9376
- const [loading, setLoading] = React47.useState(false);
9377
- const [error, setError] = React47.useState(null);
9378
- const activeRequestIdRef = React47.useRef(0);
9807
+ const [items, setItems] = React48.useState([]);
9808
+ const [loading, setLoading] = React48.useState(false);
9809
+ const [error, setError] = React48.useState(null);
9810
+ const activeRequestIdRef = React48.useRef(0);
9379
9811
  const foregroundSignal = useForegroundSignal(Boolean(appId));
9380
- const upsertSorted = React47.useCallback((prev, nextItem) => {
9812
+ const upsertSorted = React48.useCallback((prev, nextItem) => {
9381
9813
  const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
9382
9814
  next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
9383
9815
  return next;
9384
9816
  }, []);
9385
- const refetch = React47.useCallback(async () => {
9817
+ const refetch = React48.useCallback(async () => {
9386
9818
  if (!appId) {
9387
9819
  setItems([]);
9388
9820
  return;
@@ -9402,10 +9834,10 @@ function useEditQueue(appId) {
9402
9834
  if (activeRequestIdRef.current === requestId) setLoading(false);
9403
9835
  }
9404
9836
  }, [appId]);
9405
- React47.useEffect(() => {
9837
+ React48.useEffect(() => {
9406
9838
  void refetch();
9407
9839
  }, [refetch]);
9408
- React47.useEffect(() => {
9840
+ React48.useEffect(() => {
9409
9841
  if (!appId) return;
9410
9842
  const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
9411
9843
  onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
@@ -9414,7 +9846,7 @@ function useEditQueue(appId) {
9414
9846
  });
9415
9847
  return unsubscribe;
9416
9848
  }, [appId, upsertSorted, foregroundSignal]);
9417
- React47.useEffect(() => {
9849
+ React48.useEffect(() => {
9418
9850
  if (!appId) return;
9419
9851
  if (foregroundSignal <= 0) return;
9420
9852
  void refetch();
@@ -9423,16 +9855,16 @@ function useEditQueue(appId) {
9423
9855
  }
9424
9856
 
9425
9857
  // src/studio/hooks/useEditQueueActions.ts
9426
- var React48 = __toESM(require("react"));
9858
+ var React49 = __toESM(require("react"));
9427
9859
  function useEditQueueActions(appId) {
9428
- const update = React48.useCallback(
9860
+ const update = React49.useCallback(
9429
9861
  async (queueItemId, payload) => {
9430
9862
  if (!appId) return;
9431
9863
  await editQueueRepository.update(appId, queueItemId, payload);
9432
9864
  },
9433
9865
  [appId]
9434
9866
  );
9435
- const cancel = React48.useCallback(
9867
+ const cancel = React49.useCallback(
9436
9868
  async (queueItemId) => {
9437
9869
  if (!appId) return;
9438
9870
  await editQueueRepository.cancel(appId, queueItemId);
@@ -9443,7 +9875,7 @@ function useEditQueueActions(appId) {
9443
9875
  }
9444
9876
 
9445
9877
  // src/studio/hooks/useAgentRunProgress.ts
9446
- var React49 = __toESM(require("react"));
9878
+ var React50 = __toESM(require("react"));
9447
9879
 
9448
9880
  // src/data/agent-progress/repository.ts
9449
9881
  function mapRun(row) {
@@ -9709,23 +10141,23 @@ function deriveView(run, events, nowMs) {
9709
10141
  function useAgentRunProgress(threadId, opts) {
9710
10142
  var _a;
9711
10143
  const enabled = Boolean((opts == null ? void 0 : opts.enabled) ?? true);
9712
- const [run, setRun] = React49.useState(null);
9713
- const [events, setEvents] = React49.useState([]);
9714
- const [loading, setLoading] = React49.useState(false);
9715
- const [error, setError] = React49.useState(null);
9716
- const activeRequestIdRef = React49.useRef(0);
9717
- const lastSeqRef = React49.useRef(0);
9718
- const runRef = React49.useRef(null);
10144
+ const [run, setRun] = React50.useState(null);
10145
+ const [events, setEvents] = React50.useState([]);
10146
+ const [loading, setLoading] = React50.useState(false);
10147
+ const [error, setError] = React50.useState(null);
10148
+ const activeRequestIdRef = React50.useRef(0);
10149
+ const lastSeqRef = React50.useRef(0);
10150
+ const runRef = React50.useRef(null);
9719
10151
  const foregroundSignal = useForegroundSignal(Boolean(threadId) && enabled);
9720
- const [bundleTick, setBundleTick] = React49.useState(0);
9721
- React49.useEffect(() => {
10152
+ const [bundleTick, setBundleTick] = React50.useState(0);
10153
+ React50.useEffect(() => {
9722
10154
  lastSeqRef.current = 0;
9723
10155
  runRef.current = null;
9724
10156
  }, [threadId]);
9725
- React49.useEffect(() => {
10157
+ React50.useEffect(() => {
9726
10158
  runRef.current = run;
9727
10159
  }, [run]);
9728
- const refetch = React49.useCallback(async () => {
10160
+ const refetch = React50.useCallback(async () => {
9729
10161
  if (!threadId || !enabled) {
9730
10162
  setRun(null);
9731
10163
  setEvents([]);
@@ -9761,15 +10193,15 @@ function useAgentRunProgress(threadId, opts) {
9761
10193
  if (activeRequestIdRef.current === requestId) setLoading(false);
9762
10194
  }
9763
10195
  }, [enabled, threadId]);
9764
- React49.useEffect(() => {
10196
+ React50.useEffect(() => {
9765
10197
  void refetch();
9766
10198
  }, [refetch]);
9767
- React49.useEffect(() => {
10199
+ React50.useEffect(() => {
9768
10200
  if (!threadId || !enabled) return;
9769
10201
  if (foregroundSignal <= 0) return;
9770
10202
  void refetch();
9771
10203
  }, [enabled, foregroundSignal, refetch, threadId]);
9772
- React49.useEffect(() => {
10204
+ React50.useEffect(() => {
9773
10205
  if (!threadId || !enabled) return;
9774
10206
  const unsubRuns = agentProgressRepository.subscribeThreadRuns(threadId, {
9775
10207
  onInsert: (nextRun) => {
@@ -9799,7 +10231,7 @@ function useAgentRunProgress(threadId, opts) {
9799
10231
  });
9800
10232
  return unsubRuns;
9801
10233
  }, [enabled, threadId, foregroundSignal]);
9802
- React49.useEffect(() => {
10234
+ React50.useEffect(() => {
9803
10235
  if (!enabled || !(run == null ? void 0 : run.id)) return;
9804
10236
  const runId = run.id;
9805
10237
  const processIncoming = (incoming) => {
@@ -9831,8 +10263,8 @@ function useAgentRunProgress(threadId, opts) {
9831
10263
  });
9832
10264
  return unsubscribe;
9833
10265
  }, [enabled, run == null ? void 0 : run.id, foregroundSignal]);
9834
- const view = React49.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
9835
- React49.useEffect(() => {
10266
+ const view = React50.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
10267
+ React50.useEffect(() => {
9836
10268
  var _a2;
9837
10269
  if (!((_a2 = view.bundle) == null ? void 0 : _a2.active)) return;
9838
10270
  const interval = setInterval(() => {
@@ -9845,13 +10277,13 @@ function useAgentRunProgress(threadId, opts) {
9845
10277
  }
9846
10278
 
9847
10279
  // src/studio/hooks/useRelatedApps.ts
9848
- var React50 = __toESM(require("react"));
10280
+ var React51 = __toESM(require("react"));
9849
10281
  function useRelatedApps(appId) {
9850
- const [relatedApps, setRelatedApps] = React50.useState(null);
9851
- const [loading, setLoading] = React50.useState(false);
9852
- const [error, setError] = React50.useState(null);
9853
- const requestSeqRef = React50.useRef(0);
9854
- const fetchRelatedApps = React50.useCallback(async () => {
10282
+ const [relatedApps, setRelatedApps] = React51.useState(null);
10283
+ const [loading, setLoading] = React51.useState(false);
10284
+ const [error, setError] = React51.useState(null);
10285
+ const requestSeqRef = React51.useRef(0);
10286
+ const fetchRelatedApps = React51.useCallback(async () => {
9855
10287
  if (!appId) {
9856
10288
  setRelatedApps(null);
9857
10289
  setError(null);
@@ -9877,7 +10309,7 @@ function useRelatedApps(appId) {
9877
10309
  }
9878
10310
  }
9879
10311
  }, [appId]);
9880
- React50.useEffect(() => {
10312
+ React51.useEffect(() => {
9881
10313
  void fetchRelatedApps();
9882
10314
  }, [fetchRelatedApps]);
9883
10315
  return {
@@ -9889,7 +10321,7 @@ function useRelatedApps(appId) {
9889
10321
  }
9890
10322
 
9891
10323
  // src/studio/ComergeStudio.tsx
9892
- var import_jsx_runtime65 = require("react/jsx-runtime");
10324
+ var import_jsx_runtime66 = require("react/jsx-runtime");
9893
10325
  function ComergeStudio({
9894
10326
  appId,
9895
10327
  clientKey: clientKey2,
@@ -9905,13 +10337,13 @@ function ComergeStudio({
9905
10337
  embeddedBaseBundles,
9906
10338
  onSystemEvent
9907
10339
  }) {
9908
- const [activeAppId, setActiveAppId] = React51.useState(appId);
9909
- const [runtimeAppId, setRuntimeAppId] = React51.useState(appId);
9910
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React51.useState(null);
9911
- const didSyncFromHostRef = React51.useRef(false);
9912
- const lastNotifiedRef = React51.useRef(null);
9913
- const platform = React51.useMemo(() => import_react_native61.Platform.OS === "ios" ? "ios" : "android", []);
9914
- const notifyActiveAppChanged = React51.useCallback(
10340
+ const [activeAppId, setActiveAppId] = React52.useState(appId);
10341
+ const [runtimeAppId, setRuntimeAppId] = React52.useState(appId);
10342
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React52.useState(null);
10343
+ const didSyncFromHostRef = React52.useRef(false);
10344
+ const lastNotifiedRef = React52.useRef(null);
10345
+ const platform = React52.useMemo(() => import_react_native63.Platform.OS === "ios" ? "ios" : "android", []);
10346
+ const notifyActiveAppChanged = React52.useCallback(
9915
10347
  (nextAppId, source) => {
9916
10348
  if (!onActiveAppChanged) return;
9917
10349
  const trimmedAppId = nextAppId.trim();
@@ -9924,28 +10356,28 @@ function ComergeStudio({
9924
10356
  },
9925
10357
  [appKey, onActiveAppChanged]
9926
10358
  );
9927
- const setActiveAppIdWithSource = React51.useCallback(
10359
+ const setActiveAppIdWithSource = React52.useCallback(
9928
10360
  (nextAppId, source) => {
9929
10361
  setActiveAppId(nextAppId);
9930
10362
  notifyActiveAppChanged(nextAppId, source);
9931
10363
  },
9932
10364
  [notifyActiveAppChanged]
9933
10365
  );
9934
- React51.useEffect(() => {
10366
+ React52.useEffect(() => {
9935
10367
  const source = didSyncFromHostRef.current ? "host_route_sync" : "initial";
9936
10368
  didSyncFromHostRef.current = true;
9937
10369
  setActiveAppIdWithSource(appId, source);
9938
10370
  setRuntimeAppId(appId);
9939
10371
  setPendingRuntimeTargetAppId(null);
9940
10372
  }, [appId, setActiveAppIdWithSource]);
9941
- const captureTargetRef = React51.useRef(null);
9942
- return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
10373
+ const captureTargetRef = React52.useRef(null);
10374
+ return /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
9943
10375
  StudioBootstrap,
9944
10376
  {
9945
10377
  clientKey: clientKey2,
9946
10378
  analyticsEnabled,
9947
- fallback: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(import_react_native61.View, { style: { flex: 1 } }),
9948
- children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
10379
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_react_native63.View, { style: { flex: 1 } }),
10380
+ children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
9949
10381
  ComergeStudioInner,
9950
10382
  {
9951
10383
  userId,
@@ -9995,11 +10427,11 @@ function ComergeStudioInner({
9995
10427
  const { app, loading: appLoading } = useApp(activeAppId);
9996
10428
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
9997
10429
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
9998
- const sawEditingOnPendingTargetRef = React51.useRef(false);
9999
- React51.useEffect(() => {
10430
+ const sawEditingOnPendingTargetRef = React52.useRef(false);
10431
+ React52.useEffect(() => {
10000
10432
  sawEditingOnPendingTargetRef.current = false;
10001
10433
  }, [pendingRuntimeTargetAppId]);
10002
- React51.useEffect(() => {
10434
+ React52.useEffect(() => {
10003
10435
  if (!pendingRuntimeTargetAppId) return;
10004
10436
  if (activeAppId !== pendingRuntimeTargetAppId) return;
10005
10437
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -10017,13 +10449,13 @@ function ComergeStudioInner({
10017
10449
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
10018
10450
  embeddedBaseBundles
10019
10451
  });
10020
- const sawEditingOnActiveAppRef = React51.useRef(false);
10021
- const [showPostEditPreparing, setShowPostEditPreparing] = React51.useState(false);
10022
- React51.useEffect(() => {
10452
+ const sawEditingOnActiveAppRef = React52.useRef(false);
10453
+ const [showPostEditPreparing, setShowPostEditPreparing] = React52.useState(false);
10454
+ React52.useEffect(() => {
10023
10455
  sawEditingOnActiveAppRef.current = false;
10024
10456
  setShowPostEditPreparing(false);
10025
10457
  }, [activeAppId]);
10026
- React51.useEffect(() => {
10458
+ React52.useEffect(() => {
10027
10459
  if (!(app == null ? void 0 : app.id)) return;
10028
10460
  if (app.status === "editing") {
10029
10461
  sawEditingOnActiveAppRef.current = true;
@@ -10035,7 +10467,7 @@ function ComergeStudioInner({
10035
10467
  sawEditingOnActiveAppRef.current = false;
10036
10468
  }
10037
10469
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
10038
- React51.useEffect(() => {
10470
+ React52.useEffect(() => {
10039
10471
  if (!showPostEditPreparing) return;
10040
10472
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10041
10473
  if (!stillProcessingBaseBundle) {
@@ -10047,19 +10479,19 @@ function ComergeStudioInner({
10047
10479
  const editQueue = useEditQueue(activeAppId);
10048
10480
  const agentProgress = useAgentRunProgress(threadId, { enabled: enableAgentProgress });
10049
10481
  const editQueueActions = useEditQueueActions(activeAppId);
10050
- const [lastEditQueueInfo, setLastEditQueueInfo] = React51.useState(null);
10051
- const lastEditQueueInfoRef = React51.useRef(null);
10052
- const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React51.useState(false);
10482
+ const [lastEditQueueInfo, setLastEditQueueInfo] = React52.useState(null);
10483
+ const lastEditQueueInfoRef = React52.useRef(null);
10484
+ const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React52.useState(false);
10053
10485
  const mergeRequests = useMergeRequests({ appId: activeAppId });
10054
- const hasOpenOutgoingMr = React51.useMemo(() => {
10486
+ const hasOpenOutgoingMr = React52.useMemo(() => {
10055
10487
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
10056
10488
  }, [mergeRequests.lists.outgoing]);
10057
- const incomingReviewMrs = React51.useMemo(() => {
10489
+ const incomingReviewMrs = React52.useMemo(() => {
10058
10490
  if (!userId) return mergeRequests.lists.incoming;
10059
10491
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
10060
10492
  }, [mergeRequests.lists.incoming, userId]);
10061
10493
  const uploader = useAttachmentUpload();
10062
- const updateLastEditQueueInfo = React51.useCallback(
10494
+ const updateLastEditQueueInfo = React52.useCallback(
10063
10495
  (info) => {
10064
10496
  lastEditQueueInfoRef.current = info;
10065
10497
  setLastEditQueueInfo(info);
@@ -10101,13 +10533,13 @@ function ComergeStudioInner({
10101
10533
  }
10102
10534
  });
10103
10535
  const chatSendDisabled = false;
10104
- const [processingMrId, setProcessingMrId] = React51.useState(null);
10105
- const [testingMrId, setTestingMrId] = React51.useState(null);
10106
- const [syncingUpstream, setSyncingUpstream] = React51.useState(false);
10107
- const [upstreamSyncStatus, setUpstreamSyncStatus] = React51.useState(null);
10536
+ const [processingMrId, setProcessingMrId] = React52.useState(null);
10537
+ const [testingMrId, setTestingMrId] = React52.useState(null);
10538
+ const [syncingUpstream, setSyncingUpstream] = React52.useState(false);
10539
+ const [upstreamSyncStatus, setUpstreamSyncStatus] = React52.useState(null);
10108
10540
  const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
10109
10541
  const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
10110
- const runtimePreparingText = React51.useMemo(() => {
10542
+ const runtimePreparingText = React52.useMemo(() => {
10111
10543
  const status = app == null ? void 0 : app.status;
10112
10544
  if (status === "ready" && bundle.bundleStatus === "pending") {
10113
10545
  return "Bundling app\u2026 this may take a few minutes";
@@ -10127,7 +10559,7 @@ function ComergeStudioInner({
10127
10559
  return "Preparing app\u2026";
10128
10560
  }
10129
10561
  }, [app == null ? void 0 : app.status, bundle.bundleStatus]);
10130
- const chatShowTypingIndicator = React51.useMemo(() => {
10562
+ const chatShowTypingIndicator = React52.useMemo(() => {
10131
10563
  var _a2;
10132
10564
  if (agentProgress.hasLiveProgress) return false;
10133
10565
  if (!thread.raw || thread.raw.length === 0) return false;
@@ -10136,12 +10568,12 @@ function ComergeStudioInner({
10136
10568
  return payloadType !== "outcome";
10137
10569
  }, [agentProgress.hasLiveProgress, thread.raw]);
10138
10570
  const showChatProgress = agentProgress.hasLiveProgress || Boolean((_a = agentProgress.view.bundle) == null ? void 0 : _a.active);
10139
- React51.useEffect(() => {
10571
+ React52.useEffect(() => {
10140
10572
  updateLastEditQueueInfo(null);
10141
10573
  setSuppressQueueUntilResponse(false);
10142
10574
  setUpstreamSyncStatus(null);
10143
10575
  }, [activeAppId, updateLastEditQueueInfo]);
10144
- const handleSyncUpstream = React51.useCallback(async () => {
10576
+ const handleSyncUpstream = React52.useCallback(async () => {
10145
10577
  if (!(app == null ? void 0 : app.id)) {
10146
10578
  throw new Error("Missing app");
10147
10579
  }
@@ -10154,7 +10586,7 @@ function ComergeStudioInner({
10154
10586
  setSyncingUpstream(false);
10155
10587
  }
10156
10588
  }, [activeAppId, app == null ? void 0 : app.id]);
10157
- React51.useEffect(() => {
10589
+ React52.useEffect(() => {
10158
10590
  if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
10159
10591
  const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
10160
10592
  if (!stillPresent) {
@@ -10162,7 +10594,7 @@ function ComergeStudioInner({
10162
10594
  setSuppressQueueUntilResponse(false);
10163
10595
  }
10164
10596
  }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
10165
- const chatQueueItems = React51.useMemo(() => {
10597
+ const chatQueueItems = React52.useMemo(() => {
10166
10598
  var _a2;
10167
10599
  if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
10168
10600
  return [];
@@ -10176,8 +10608,8 @@ function ComergeStudioInner({
10176
10608
  return editQueue.items;
10177
10609
  }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
10178
10610
  const { relatedApps, loading: relatedAppsLoading } = useRelatedApps(activeAppId);
10179
- const [switchingRelatedAppId, setSwitchingRelatedAppId] = React51.useState(null);
10180
- const handleOpenRelatedApps = React51.useCallback(() => {
10611
+ const [switchingRelatedAppId, setSwitchingRelatedAppId] = React52.useState(null);
10612
+ const handleOpenRelatedApps = React52.useCallback(() => {
10181
10613
  var _a2;
10182
10614
  if (!relatedApps) return;
10183
10615
  const ids = /* @__PURE__ */ new Set();
@@ -10186,7 +10618,7 @@ function ComergeStudioInner({
10186
10618
  for (const remix of relatedApps.remixes) ids.add(remix.id);
10187
10619
  void trackRelatedAppsOpened({ appId: relatedApps.current.id, relatedCount: ids.size });
10188
10620
  }, [relatedApps]);
10189
- const handleSwitchRelatedApp = React51.useCallback(
10621
+ const handleSwitchRelatedApp = React52.useCallback(
10190
10622
  async (targetAppId) => {
10191
10623
  var _a2;
10192
10624
  if (!targetAppId || targetAppId === activeAppId) return;
@@ -10247,8 +10679,8 @@ function ComergeStudioInner({
10247
10679
  setRuntimeAppId
10248
10680
  ]
10249
10681
  );
10250
- return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(import_react_native61.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(import_react_native61.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
10251
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
10682
+ return /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_react_native63.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_react_native63.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
10683
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
10252
10684
  RuntimeRenderer,
10253
10685
  {
10254
10686
  appKey,
@@ -10272,7 +10704,7 @@ function ComergeStudioInner({
10272
10704
  }
10273
10705
  }
10274
10706
  ),
10275
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
10707
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
10276
10708
  StudioOverlay,
10277
10709
  {
10278
10710
  captureTargetRef,
@@ -10329,6 +10761,9 @@ function ComergeStudioInner({
10329
10761
  chatSending: actions.sending,
10330
10762
  chatShowTypingIndicator,
10331
10763
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
10764
+ onChatAttachmentLoadError: () => {
10765
+ thread.recoverAttachmentUrls();
10766
+ },
10332
10767
  chatQueueItems,
10333
10768
  onRemoveQueueItem: (id) => editQueueActions.cancel(id),
10334
10769
  chatProgress: showChatProgress ? agentProgress.view : null,