@comergehq/studio 0.1.35 → 0.1.37

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