@comergehq/studio 0.1.7 → 0.1.9

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
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/studio/ComergeStudio.tsx
9
- import * as React38 from "react";
9
+ import * as React39 from "react";
10
10
  import { Platform as RNPlatform, View as View45 } from "react-native";
11
11
  import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
12
12
 
@@ -363,7 +363,7 @@ function StudioBootstrap({ children, fallback, renderError, apiKey }) {
363
363
  }
364
364
 
365
365
  // src/studio/hooks/useApp.ts
366
- import * as React2 from "react";
366
+ import * as React3 from "react";
367
367
 
368
368
  // src/core/services/http/index.ts
369
369
  import axios2 from "axios";
@@ -662,13 +662,40 @@ var AppsRepositoryImpl = class extends BaseRepository {
662
662
  };
663
663
  var appsRepository = new AppsRepositoryImpl(appsRemoteDataSource);
664
664
 
665
+ // src/studio/hooks/useForegroundSignal.ts
666
+ import * as React2 from "react";
667
+ import { AppState } from "react-native";
668
+ function useForegroundSignal(enabled = true) {
669
+ const [signal, setSignal] = React2.useState(0);
670
+ const lastStateRef = React2.useRef(AppState.currentState);
671
+ React2.useEffect(() => {
672
+ if (!enabled) return;
673
+ const sub = AppState.addEventListener("change", (nextState) => {
674
+ var _a, _b;
675
+ const prevState = lastStateRef.current;
676
+ lastStateRef.current = nextState;
677
+ const didResume = (prevState === "background" || prevState === "inactive") && nextState === "active";
678
+ if (!didResume) return;
679
+ try {
680
+ const supabase = getSupabaseClient();
681
+ (_b = (_a = supabase == null ? void 0 : supabase.realtime) == null ? void 0 : _a.connect) == null ? void 0 : _b.call(_a);
682
+ } catch {
683
+ }
684
+ setSignal((s) => s + 1);
685
+ });
686
+ return () => sub.remove();
687
+ }, [enabled]);
688
+ return signal;
689
+ }
690
+
665
691
  // src/studio/hooks/useApp.ts
666
692
  function useApp(appId, options) {
667
693
  const enabled = (options == null ? void 0 : options.enabled) ?? true;
668
- const [app, setApp] = React2.useState(null);
669
- const [loading, setLoading] = React2.useState(false);
670
- const [error, setError] = React2.useState(null);
671
- const mergeApp = React2.useCallback((prev, next) => {
694
+ const [app, setApp] = React3.useState(null);
695
+ const [loading, setLoading] = React3.useState(false);
696
+ const [error, setError] = React3.useState(null);
697
+ const foregroundSignal = useForegroundSignal(enabled && Boolean(appId));
698
+ const mergeApp = React3.useCallback((prev, next) => {
672
699
  const merged = {
673
700
  ...prev ?? {},
674
701
  ...next,
@@ -677,7 +704,7 @@ function useApp(appId, options) {
677
704
  };
678
705
  return merged;
679
706
  }, []);
680
- const fetchOnce = React2.useCallback(async () => {
707
+ const fetchOnce = React3.useCallback(async () => {
681
708
  if (!enabled) return;
682
709
  if (!appId) return;
683
710
  setLoading(true);
@@ -692,34 +719,37 @@ function useApp(appId, options) {
692
719
  setLoading(false);
693
720
  }
694
721
  }, [appId, enabled]);
695
- React2.useEffect(() => {
722
+ React3.useEffect(() => {
696
723
  if (!enabled) return;
697
724
  void fetchOnce();
698
725
  }, [enabled, fetchOnce]);
699
- React2.useEffect(() => {
726
+ React3.useEffect(() => {
700
727
  if (!enabled) return;
701
728
  if (!appId) return;
702
729
  const unsubscribe = appsRepository.subscribeApp(appId, {
703
730
  onInsert: (a) => {
704
- console.log("[useApp] onInsert", a);
705
731
  setApp((prev) => mergeApp(prev, a));
706
732
  },
707
733
  onUpdate: (a) => {
708
- console.log("[useApp] onUpdate", a);
709
734
  setApp((prev) => mergeApp(prev, a));
710
735
  },
711
736
  onDelete: () => {
712
- console.log("[useApp] onDelete");
713
737
  setApp(null);
714
738
  }
715
739
  });
716
740
  return unsubscribe;
717
- }, [appId, enabled, mergeApp]);
741
+ }, [appId, enabled, mergeApp, foregroundSignal]);
742
+ React3.useEffect(() => {
743
+ if (!enabled) return;
744
+ if (!appId) return;
745
+ if (foregroundSignal <= 0) return;
746
+ void fetchOnce();
747
+ }, [appId, enabled, fetchOnce, foregroundSignal]);
718
748
  return { app, loading, error, refetch: fetchOnce };
719
749
  }
720
750
 
721
751
  // src/studio/hooks/useThreadMessages.ts
722
- import * as React3 from "react";
752
+ import * as React4 from "react";
723
753
 
724
754
  // src/data/messages/remote.ts
725
755
  var MessagesRemoteDataSourceImpl = class extends BaseRemote {
@@ -820,44 +850,59 @@ function mapMessageToChatMessage(m) {
820
850
  };
821
851
  }
822
852
  function useThreadMessages(threadId) {
823
- const [raw, setRaw] = React3.useState([]);
824
- const [loading, setLoading] = React3.useState(false);
825
- const [error, setError] = React3.useState(null);
826
- const refetch = React3.useCallback(async () => {
853
+ const [raw, setRaw] = React4.useState([]);
854
+ const [loading, setLoading] = React4.useState(false);
855
+ const [error, setError] = React4.useState(null);
856
+ const activeRequestIdRef = React4.useRef(0);
857
+ const foregroundSignal = useForegroundSignal(Boolean(threadId));
858
+ const upsertSorted = React4.useCallback((prev, m) => {
859
+ const next = prev.some((x) => x.id === m.id) ? prev.map((x) => x.id === m.id ? m : x) : [...prev, m];
860
+ next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
861
+ return next;
862
+ }, []);
863
+ const refetch = React4.useCallback(async () => {
827
864
  if (!threadId) {
828
865
  setRaw([]);
829
866
  return;
830
867
  }
868
+ const requestId = ++activeRequestIdRef.current;
831
869
  setLoading(true);
832
870
  setError(null);
833
871
  try {
834
872
  const list = await messagesRepository.list(threadId);
835
- setRaw(list);
873
+ if (activeRequestIdRef.current !== requestId) return;
874
+ setRaw([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
836
875
  } catch (e) {
876
+ if (activeRequestIdRef.current !== requestId) return;
837
877
  setError(e instanceof Error ? e : new Error(String(e)));
838
878
  setRaw([]);
839
879
  } finally {
840
- setLoading(false);
880
+ if (activeRequestIdRef.current === requestId) setLoading(false);
841
881
  }
842
882
  }, [threadId]);
843
- React3.useEffect(() => {
883
+ React4.useEffect(() => {
844
884
  void refetch();
845
885
  }, [refetch]);
846
- React3.useEffect(() => {
886
+ React4.useEffect(() => {
847
887
  if (!threadId) return;
848
888
  const unsubscribe = messagesRepository.subscribeThread(threadId, {
849
- onInsert: (m) => setRaw((prev) => [...prev, m]),
850
- onUpdate: (m) => setRaw((prev) => prev.map((x) => x.id === m.id ? m : x)),
889
+ onInsert: (m) => setRaw((prev) => upsertSorted(prev, m)),
890
+ onUpdate: (m) => setRaw((prev) => upsertSorted(prev, m)),
851
891
  onDelete: (m) => setRaw((prev) => prev.filter((x) => x.id !== m.id))
852
892
  });
853
893
  return unsubscribe;
854
- }, [threadId]);
855
- const messages = React3.useMemo(() => raw.map(mapMessageToChatMessage), [raw]);
894
+ }, [threadId, upsertSorted, foregroundSignal]);
895
+ React4.useEffect(() => {
896
+ if (!threadId) return;
897
+ if (foregroundSignal <= 0) return;
898
+ void refetch();
899
+ }, [foregroundSignal, refetch, threadId]);
900
+ const messages = React4.useMemo(() => raw.map(mapMessageToChatMessage), [raw]);
856
901
  return { raw, messages, loading, error, refetch };
857
902
  }
858
903
 
859
904
  // src/studio/hooks/useBundleManager.ts
860
- import * as React4 from "react";
905
+ import * as React5 from "react";
861
906
  import * as FileSystem from "expo-file-system/legacy";
862
907
 
863
908
  // src/data/apps/bundles/remote.ts
@@ -1030,19 +1075,19 @@ function useBundleManager({
1030
1075
  platform,
1031
1076
  canRequestLatest = true
1032
1077
  }) {
1033
- const [bundlePath, setBundlePath] = React4.useState(null);
1034
- const [renderToken, setRenderToken] = React4.useState(0);
1035
- const [loading, setLoading] = React4.useState(false);
1036
- const [statusLabel, setStatusLabel] = React4.useState(null);
1037
- const [error, setError] = React4.useState(null);
1038
- const [isTesting, setIsTesting] = React4.useState(false);
1039
- const baseRef = React4.useRef(base);
1078
+ const [bundlePath, setBundlePath] = React5.useState(null);
1079
+ const [renderToken, setRenderToken] = React5.useState(0);
1080
+ const [loading, setLoading] = React5.useState(false);
1081
+ const [statusLabel, setStatusLabel] = React5.useState(null);
1082
+ const [error, setError] = React5.useState(null);
1083
+ const [isTesting, setIsTesting] = React5.useState(false);
1084
+ const baseRef = React5.useRef(base);
1040
1085
  baseRef.current = base;
1041
- const baseOpIdRef = React4.useRef(0);
1042
- const testOpIdRef = React4.useRef(0);
1043
- const activeLoadModeRef = React4.useRef(null);
1044
- const canRequestLatestRef = React4.useRef(canRequestLatest);
1045
- React4.useEffect(() => {
1086
+ const baseOpIdRef = React5.useRef(0);
1087
+ const testOpIdRef = React5.useRef(0);
1088
+ const activeLoadModeRef = React5.useRef(null);
1089
+ const canRequestLatestRef = React5.useRef(canRequestLatest);
1090
+ React5.useEffect(() => {
1046
1091
  canRequestLatestRef.current = canRequestLatest;
1047
1092
  if (!canRequestLatest) {
1048
1093
  baseOpIdRef.current += 1;
@@ -1053,11 +1098,11 @@ function useBundleManager({
1053
1098
  }
1054
1099
  }
1055
1100
  }, [canRequestLatest]);
1056
- const lastBaseBundlePathRef = React4.useRef(null);
1057
- const lastBaseFingerprintRef = React4.useRef(null);
1058
- const initialHydratedBaseFromDiskRef = React4.useRef(false);
1059
- const hasCompletedFirstNetworkBaseLoadRef = React4.useRef(false);
1060
- const hydrateBaseFromDisk = React4.useCallback(
1101
+ const lastBaseBundlePathRef = React5.useRef(null);
1102
+ const lastBaseFingerprintRef = React5.useRef(null);
1103
+ const initialHydratedBaseFromDiskRef = React5.useRef(false);
1104
+ const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
1105
+ const hydrateBaseFromDisk = React5.useCallback(
1061
1106
  async (appId, reason) => {
1062
1107
  try {
1063
1108
  const dir = bundlesCacheDir();
@@ -1082,13 +1127,13 @@ function useBundleManager({
1082
1127
  },
1083
1128
  [platform]
1084
1129
  );
1085
- React4.useEffect(() => {
1130
+ React5.useEffect(() => {
1086
1131
  if (!base.appId) return;
1087
1132
  initialHydratedBaseFromDiskRef.current = false;
1088
1133
  hasCompletedFirstNetworkBaseLoadRef.current = false;
1089
1134
  void hydrateBaseFromDisk(base.appId, "initial");
1090
1135
  }, [base.appId, platform, hydrateBaseFromDisk]);
1091
- const activateCachedBase = React4.useCallback(
1136
+ const activateCachedBase = React5.useCallback(
1092
1137
  async (appId) => {
1093
1138
  setIsTesting(false);
1094
1139
  setStatusLabel(null);
@@ -1102,7 +1147,7 @@ function useBundleManager({
1102
1147
  },
1103
1148
  [hydrateBaseFromDisk]
1104
1149
  );
1105
- const load = React4.useCallback(async (src, mode) => {
1150
+ const load = React5.useCallback(async (src, mode) => {
1106
1151
  if (!src.appId) return;
1107
1152
  const canRequestLatest2 = canRequestLatestRef.current;
1108
1153
  if (mode === "base" && !canRequestLatest2) {
@@ -1157,13 +1202,13 @@ function useBundleManager({
1157
1202
  if (activeLoadModeRef.current === mode) activeLoadModeRef.current = null;
1158
1203
  }
1159
1204
  }, [activateCachedBase, platform]);
1160
- const loadBase = React4.useCallback(async () => {
1205
+ const loadBase = React5.useCallback(async () => {
1161
1206
  await load(baseRef.current, "base");
1162
1207
  }, [load]);
1163
- const loadTest = React4.useCallback(async (src) => {
1208
+ const loadTest = React5.useCallback(async (src) => {
1164
1209
  await load(src, "test");
1165
1210
  }, [load]);
1166
- const restoreBase = React4.useCallback(async () => {
1211
+ const restoreBase = React5.useCallback(async () => {
1167
1212
  const src = baseRef.current;
1168
1213
  if (!src.appId) return;
1169
1214
  await activateCachedBase(src.appId);
@@ -1171,7 +1216,7 @@ function useBundleManager({
1171
1216
  await load(src, "base");
1172
1217
  }
1173
1218
  }, [activateCachedBase, load]);
1174
- React4.useEffect(() => {
1219
+ React5.useEffect(() => {
1175
1220
  if (!canRequestLatest) return;
1176
1221
  void loadBase();
1177
1222
  }, [base.appId, base.commitId, platform, canRequestLatest, loadBase]);
@@ -1179,7 +1224,7 @@ function useBundleManager({
1179
1224
  }
1180
1225
 
1181
1226
  // src/studio/hooks/useMergeRequests.ts
1182
- import * as React5 from "react";
1227
+ import * as React6 from "react";
1183
1228
 
1184
1229
  // src/data/merge-requests/remote.ts
1185
1230
  var MergeRequestsRemoteDataSourceImpl = class extends BaseRemote {
@@ -1325,12 +1370,12 @@ function toUiStatus(status) {
1325
1370
  }
1326
1371
  function useMergeRequests(params) {
1327
1372
  const { appId } = params;
1328
- const [incoming, setIncoming] = React5.useState([]);
1329
- const [outgoing, setOutgoing] = React5.useState([]);
1330
- const [loading, setLoading] = React5.useState(false);
1331
- const [error, setError] = React5.useState(null);
1332
- const [creatorStatsById, setCreatorStatsById] = React5.useState({});
1333
- const pollUntilMerged = React5.useCallback(async (mrId) => {
1373
+ const [incoming, setIncoming] = React6.useState([]);
1374
+ const [outgoing, setOutgoing] = React6.useState([]);
1375
+ const [loading, setLoading] = React6.useState(false);
1376
+ const [error, setError] = React6.useState(null);
1377
+ const [creatorStatsById, setCreatorStatsById] = React6.useState({});
1378
+ const pollUntilMerged = React6.useCallback(async (mrId) => {
1334
1379
  const startedAt = Date.now();
1335
1380
  const timeoutMs = 2 * 60 * 1e3;
1336
1381
  for (; ; ) {
@@ -1340,7 +1385,7 @@ function useMergeRequests(params) {
1340
1385
  await new Promise((r) => setTimeout(r, 1500));
1341
1386
  }
1342
1387
  }, []);
1343
- const refresh = React5.useCallback(async () => {
1388
+ const refresh = React6.useCallback(async () => {
1344
1389
  if (!appId) {
1345
1390
  setIncoming([]);
1346
1391
  setOutgoing([]);
@@ -1379,27 +1424,27 @@ function useMergeRequests(params) {
1379
1424
  setLoading(false);
1380
1425
  }
1381
1426
  }, [appId]);
1382
- React5.useEffect(() => {
1427
+ React6.useEffect(() => {
1383
1428
  void refresh();
1384
1429
  }, [refresh]);
1385
- const openMergeRequest = React5.useCallback(async (sourceAppId) => {
1430
+ const openMergeRequest = React6.useCallback(async (sourceAppId) => {
1386
1431
  const mr = await mergeRequestsRepository.open({ sourceAppId });
1387
1432
  await refresh();
1388
1433
  return mr;
1389
1434
  }, [refresh]);
1390
- const approve = React5.useCallback(async (mrId) => {
1435
+ const approve = React6.useCallback(async (mrId) => {
1391
1436
  const mr = await mergeRequestsRepository.update(mrId, { status: "approved" });
1392
1437
  await refresh();
1393
1438
  const merged = await pollUntilMerged(mrId);
1394
1439
  await refresh();
1395
1440
  return merged ?? mr;
1396
1441
  }, [pollUntilMerged, refresh]);
1397
- const reject = React5.useCallback(async (mrId) => {
1442
+ const reject = React6.useCallback(async (mrId) => {
1398
1443
  const mr = await mergeRequestsRepository.update(mrId, { status: "rejected" });
1399
1444
  await refresh();
1400
1445
  return mr;
1401
1446
  }, [refresh]);
1402
- const toSummary = React5.useCallback((mr) => {
1447
+ const toSummary = React6.useCallback((mr) => {
1403
1448
  const stats = creatorStatsById[mr.createdBy];
1404
1449
  return {
1405
1450
  id: mr.id,
@@ -1415,7 +1460,7 @@ function useMergeRequests(params) {
1415
1460
  updatedAt: mr.updatedAt
1416
1461
  };
1417
1462
  }, [creatorStatsById]);
1418
- const byId = React5.useMemo(() => {
1463
+ const byId = React6.useMemo(() => {
1419
1464
  const all = [...incoming, ...outgoing];
1420
1465
  const map = {};
1421
1466
  for (const mr of all) map[mr.id] = mr;
@@ -1433,7 +1478,7 @@ function useMergeRequests(params) {
1433
1478
  }
1434
1479
 
1435
1480
  // src/studio/hooks/useAttachmentUpload.ts
1436
- import * as React6 from "react";
1481
+ import * as React7 from "react";
1437
1482
 
1438
1483
  // src/data/attachment/remote.ts
1439
1484
  var AttachmentRemoteDataSourceImpl = class extends BaseRemote {
@@ -1474,9 +1519,9 @@ var attachmentRepository = new AttachmentRepositoryImpl(
1474
1519
 
1475
1520
  // src/studio/hooks/useAttachmentUpload.ts
1476
1521
  function useAttachmentUpload() {
1477
- const [uploading, setUploading] = React6.useState(false);
1478
- const [error, setError] = React6.useState(null);
1479
- const uploadBase64Images = React6.useCallback(async ({ threadId, appId, dataUrls }) => {
1522
+ const [uploading, setUploading] = React7.useState(false);
1523
+ const [error, setError] = React7.useState(null);
1524
+ const uploadBase64Images = React7.useCallback(async ({ threadId, appId, dataUrls }) => {
1480
1525
  if (!threadId || !appId) return [];
1481
1526
  if (!dataUrls || dataUrls.length === 0) return [];
1482
1527
  setUploading(true);
@@ -1510,7 +1555,7 @@ function useAttachmentUpload() {
1510
1555
  }
1511
1556
 
1512
1557
  // src/studio/hooks/useStudioActions.ts
1513
- import * as React7 from "react";
1558
+ import * as React8 from "react";
1514
1559
 
1515
1560
  // src/data/agent/remote.ts
1516
1561
  var AgentRemoteDataSourceImpl = class extends BaseRemote {
@@ -1549,12 +1594,12 @@ function useStudioActions({
1549
1594
  onForkedApp,
1550
1595
  uploadAttachments
1551
1596
  }) {
1552
- const [forking, setForking] = React7.useState(false);
1553
- const [sending, setSending] = React7.useState(false);
1554
- const [error, setError] = React7.useState(null);
1597
+ const [forking, setForking] = React8.useState(false);
1598
+ const [sending, setSending] = React8.useState(false);
1599
+ const [error, setError] = React8.useState(null);
1555
1600
  const isOwner = Boolean(userId && (app == null ? void 0 : app.createdBy) && userId === app.createdBy);
1556
1601
  const shouldForkOnEdit = Boolean(userId && app && app.createdBy !== userId);
1557
- const sendEdit = React7.useCallback(
1602
+ const sendEdit = React8.useCallback(
1558
1603
  async ({ prompt, attachments }) => {
1559
1604
  if (!userId || !app) return;
1560
1605
  if (!prompt.trim()) return;
@@ -1636,12 +1681,12 @@ function RuntimeRenderer({ appKey, bundlePath, renderToken, style }) {
1636
1681
  }
1637
1682
 
1638
1683
  // src/studio/ui/StudioOverlay.tsx
1639
- import * as React37 from "react";
1640
- import { Keyboard as Keyboard5, Platform as Platform7, View as View44, useWindowDimensions as useWindowDimensions4 } from "react-native";
1684
+ import * as React38 from "react";
1685
+ import { Keyboard as Keyboard5, Platform as Platform8, View as View44, useWindowDimensions as useWindowDimensions4 } from "react-native";
1641
1686
 
1642
1687
  // src/components/studio-sheet/StudioBottomSheet.tsx
1643
- import * as React8 from "react";
1644
- import { Keyboard, Platform as Platform2, View as View4 } from "react-native";
1688
+ import * as React9 from "react";
1689
+ import { AppState as AppState2, Keyboard, View as View4 } from "react-native";
1645
1690
  import BottomSheet from "@gorhom/bottom-sheet";
1646
1691
  import { useSafeAreaInsets } from "react-native-safe-area-context";
1647
1692
 
@@ -1708,19 +1753,31 @@ function StudioBottomSheet({
1708
1753
  }) {
1709
1754
  const theme = useTheme();
1710
1755
  const insets = useSafeAreaInsets();
1711
- const internalSheetRef = React8.useRef(null);
1756
+ const internalSheetRef = React9.useRef(null);
1712
1757
  const resolvedSheetRef = sheetRef ?? internalSheetRef;
1713
- React8.useEffect(() => {
1714
- if (Platform2.OS !== "ios") return;
1715
- const sub = Keyboard.addListener("keyboardDidHide", () => {
1758
+ const currentIndexRef = React9.useRef(open ? snapPoints.length - 1 : -1);
1759
+ const lastAppStateRef = React9.useRef(AppState2.currentState);
1760
+ React9.useEffect(() => {
1761
+ const sub = AppState2.addEventListener("change", (state) => {
1762
+ const prev = lastAppStateRef.current;
1763
+ lastAppStateRef.current = state;
1764
+ if (state === "background" || state === "inactive") {
1765
+ Keyboard.dismiss();
1766
+ return;
1767
+ }
1768
+ if (state !== "active") return;
1716
1769
  const sheet = resolvedSheetRef.current;
1717
- if (!sheet || !open) return;
1718
- const targetIndex = snapPoints.length - 1;
1719
- setTimeout(() => sheet.snapToIndex(targetIndex), 10);
1770
+ if (!sheet) return;
1771
+ const idx = currentIndexRef.current;
1772
+ if (open && idx >= 0) {
1773
+ Keyboard.dismiss();
1774
+ requestAnimationFrame(() => sheet.snapToIndex(idx));
1775
+ setTimeout(() => sheet.snapToIndex(idx), 120);
1776
+ }
1720
1777
  });
1721
1778
  return () => sub.remove();
1722
- }, [open, resolvedSheetRef, snapPoints.length]);
1723
- React8.useEffect(() => {
1779
+ }, [open, resolvedSheetRef]);
1780
+ React9.useEffect(() => {
1724
1781
  const sheet = resolvedSheetRef.current;
1725
1782
  if (!sheet) return;
1726
1783
  if (open) {
@@ -1729,8 +1786,9 @@ function StudioBottomSheet({
1729
1786
  sheet.close();
1730
1787
  }
1731
1788
  }, [open, resolvedSheetRef, snapPoints.length]);
1732
- const handleChange = React8.useCallback(
1789
+ const handleChange = React9.useCallback(
1733
1790
  (index) => {
1791
+ currentIndexRef.current = index;
1734
1792
  onOpenChange == null ? void 0 : onOpenChange(index >= 0);
1735
1793
  },
1736
1794
  [onOpenChange]
@@ -1742,7 +1800,7 @@ function StudioBottomSheet({
1742
1800
  index: open ? snapPoints.length - 1 : -1,
1743
1801
  snapPoints,
1744
1802
  enablePanDownToClose: true,
1745
- keyboardBehavior: "extend",
1803
+ keyboardBehavior: "interactive",
1746
1804
  keyboardBlurBehavior: "restore",
1747
1805
  android_keyboardInputMode: "adjustResize",
1748
1806
  backgroundComponent: (props) => /* @__PURE__ */ jsx5(StudioSheetBackground, { ...props, renderBackground: background == null ? void 0 : background.renderBackground }),
@@ -1757,12 +1815,12 @@ function StudioBottomSheet({
1757
1815
  }
1758
1816
 
1759
1817
  // src/components/studio-sheet/StudioSheetPager.tsx
1760
- import * as React9 from "react";
1818
+ import * as React10 from "react";
1761
1819
  import { Animated } from "react-native";
1762
1820
  import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1763
1821
  function StudioSheetPager({ activePage, width, preview, chat, style }) {
1764
- const anim = React9.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
1765
- React9.useEffect(() => {
1822
+ const anim = React10.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
1823
+ React10.useEffect(() => {
1766
1824
  Animated.spring(anim, {
1767
1825
  toValue: activePage === "chat" ? 1 : 0,
1768
1826
  useNativeDriver: true,
@@ -1811,7 +1869,7 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
1811
1869
  }
1812
1870
 
1813
1871
  // src/components/floating-draggable-button/FloatingDraggableButton.tsx
1814
- import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef4 } from "react";
1872
+ import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef6 } from "react";
1815
1873
  import {
1816
1874
  PanResponder,
1817
1875
  Pressable,
@@ -1890,8 +1948,8 @@ function FloatingDraggableButton({
1890
1948
  const theme = useTheme();
1891
1949
  const { width, height } = useWindowDimensions();
1892
1950
  const isDanger = variant === "danger";
1893
- const onPressRef = useRef4(onPress);
1894
- useEffect8(() => {
1951
+ const onPressRef = useRef6(onPress);
1952
+ useEffect9(() => {
1895
1953
  onPressRef.current = onPress;
1896
1954
  }, [onPress]);
1897
1955
  const fallbackBgColor = useMemo3(() => {
@@ -1905,8 +1963,8 @@ function FloatingDraggableButton({
1905
1963
  const rotation = useSharedValue(ENTER_ROTATION_FROM_DEG);
1906
1964
  const opacity = useSharedValue(1);
1907
1965
  const borderPulse = useSharedValue(0);
1908
- const startPos = useRef4({ x: 0, y: 0 });
1909
- const isAnimatingOut = useRef4(false);
1966
+ const startPos = useRef6({ x: 0, y: 0 });
1967
+ const isAnimatingOut = useRef6(false);
1910
1968
  const animateToHidden = useCallback8(
1911
1969
  (options) => {
1912
1970
  translateX.value = withSpring(getHiddenTranslateX(size), SPRING_POSITION);
@@ -1942,7 +2000,7 @@ function FloatingDraggableButton({
1942
2000
  }
1943
2001
  });
1944
2002
  }, [animateToHidden]);
1945
- useEffect8(() => {
2003
+ useEffect9(() => {
1946
2004
  if (isLoading) {
1947
2005
  borderPulse.value = withRepeat(
1948
2006
  withSequence(
@@ -1968,7 +2026,7 @@ function FloatingDraggableButton({
1968
2026
  rotation.value = withSpring(0, SPRING_ROTATION_IN);
1969
2027
  opacity.value = withTiming(1, TIMING_OPACITY_IN);
1970
2028
  }, [height, offset.bottom, offset.left, opacity, rotation, scale, size, translateX, translateY]);
1971
- useEffect8(() => {
2029
+ useEffect9(() => {
1972
2030
  const timer = setTimeout(() => {
1973
2031
  if (visible) {
1974
2032
  animateIn();
@@ -1976,7 +2034,7 @@ function FloatingDraggableButton({
1976
2034
  }, 100);
1977
2035
  return () => clearTimeout(timer);
1978
2036
  }, []);
1979
- useEffect8(() => {
2037
+ useEffect9(() => {
1980
2038
  if (visible && isAnimatingOut.current) {
1981
2039
  animateIn();
1982
2040
  } else if (!visible && !isAnimatingOut.current) {
@@ -1984,13 +2042,13 @@ function FloatingDraggableButton({
1984
2042
  isAnimatingOut.current = true;
1985
2043
  }
1986
2044
  }, [visible, animateIn, animateToHidden]);
1987
- useEffect8(() => {
2045
+ useEffect9(() => {
1988
2046
  if (forceShowTrigger > 0 && visible) {
1989
2047
  isAnimatingOut.current = false;
1990
2048
  animateIn();
1991
2049
  }
1992
2050
  }, [forceShowTrigger, visible, animateIn]);
1993
- const panResponder = useRef4(
2051
+ const panResponder = useRef6(
1994
2052
  PanResponder.create({
1995
2053
  onStartShouldSetPanResponder: () => true,
1996
2054
  onMoveShouldSetPanResponder: () => true,
@@ -2107,7 +2165,7 @@ var styles = StyleSheet.create({
2107
2165
  });
2108
2166
 
2109
2167
  // src/components/overlays/EdgeGlowFrame.tsx
2110
- import * as React10 from "react";
2168
+ import * as React11 from "react";
2111
2169
  import { Animated as Animated3, View as View6 } from "react-native";
2112
2170
  import { LinearGradient } from "expo-linear-gradient";
2113
2171
 
@@ -2150,8 +2208,8 @@ function EdgeGlowFrame({
2150
2208
  }) {
2151
2209
  const theme = useTheme();
2152
2210
  const alpha = Math.max(0, Math.min(1, intensity));
2153
- const anim = React10.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2154
- React10.useEffect(() => {
2211
+ const anim = React11.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2212
+ React11.useEffect(() => {
2155
2213
  Animated3.timing(anim, {
2156
2214
  toValue: visible ? 1 : 0,
2157
2215
  duration: 300,
@@ -2202,12 +2260,12 @@ function EdgeGlowFrame({
2202
2260
  }
2203
2261
 
2204
2262
  // src/components/draw/DrawModeOverlay.tsx
2205
- import * as React13 from "react";
2263
+ import * as React14 from "react";
2206
2264
  import { StyleSheet as StyleSheet3, View as View10 } from "react-native";
2207
2265
  import { captureRef } from "react-native-view-shot";
2208
2266
 
2209
2267
  // src/components/draw/DrawSurface.tsx
2210
- import * as React11 from "react";
2268
+ import * as React12 from "react";
2211
2269
  import { PanResponder as PanResponder2, StyleSheet as StyleSheet2, View as View7 } from "react-native";
2212
2270
  import Svg, { Path } from "react-native-svg";
2213
2271
 
@@ -2239,25 +2297,25 @@ function DrawSurface({
2239
2297
  style,
2240
2298
  minDistance = 1
2241
2299
  }) {
2242
- const [renderTick, setRenderTick] = React11.useState(0);
2243
- const currentPointsRef = React11.useRef([]);
2244
- const rafRef = React11.useRef(null);
2245
- const triggerRender = React11.useCallback(() => {
2300
+ const [renderTick, setRenderTick] = React12.useState(0);
2301
+ const currentPointsRef = React12.useRef([]);
2302
+ const rafRef = React12.useRef(null);
2303
+ const triggerRender = React12.useCallback(() => {
2246
2304
  if (rafRef.current !== null) return;
2247
2305
  rafRef.current = requestAnimationFrame(() => {
2248
2306
  rafRef.current = null;
2249
2307
  setRenderTick((n) => n + 1);
2250
2308
  });
2251
2309
  }, []);
2252
- React11.useEffect(() => () => {
2310
+ React12.useEffect(() => () => {
2253
2311
  if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
2254
2312
  }, []);
2255
- const onStart = React11.useCallback((e) => {
2313
+ const onStart = React12.useCallback((e) => {
2256
2314
  const { locationX, locationY } = e.nativeEvent;
2257
2315
  currentPointsRef.current = [{ x: locationX, y: locationY }];
2258
2316
  triggerRender();
2259
2317
  }, [triggerRender]);
2260
- const onMove = React11.useCallback((e, _g) => {
2318
+ const onMove = React12.useCallback((e, _g) => {
2261
2319
  const { locationX, locationY } = e.nativeEvent;
2262
2320
  const pts = currentPointsRef.current;
2263
2321
  if (pts.length > 0) {
@@ -2270,7 +2328,7 @@ function DrawSurface({
2270
2328
  currentPointsRef.current = [...pts, { x: locationX, y: locationY }];
2271
2329
  triggerRender();
2272
2330
  }, [minDistance, triggerRender]);
2273
- const onEnd = React11.useCallback(() => {
2331
+ const onEnd = React12.useCallback(() => {
2274
2332
  const points = currentPointsRef.current;
2275
2333
  if (points.length > 0) {
2276
2334
  onAddStroke({ points, color, width: strokeWidth });
@@ -2278,7 +2336,7 @@ function DrawSurface({
2278
2336
  currentPointsRef.current = [];
2279
2337
  triggerRender();
2280
2338
  }, [color, onAddStroke, strokeWidth, triggerRender]);
2281
- const panResponder = React11.useMemo(
2339
+ const panResponder = React12.useMemo(
2282
2340
  () => PanResponder2.create({
2283
2341
  onStartShouldSetPanResponder: () => true,
2284
2342
  onMoveShouldSetPanResponder: () => true,
@@ -2328,7 +2386,7 @@ var styles2 = StyleSheet2.create({
2328
2386
  });
2329
2387
 
2330
2388
  // src/components/draw/DrawToolbar.tsx
2331
- import * as React12 from "react";
2389
+ import * as React13 from "react";
2332
2390
  import {
2333
2391
  ActivityIndicator,
2334
2392
  Animated as Animated4,
@@ -2425,11 +2483,11 @@ function DrawToolbar({
2425
2483
  }) {
2426
2484
  const insets = useSafeAreaInsets2();
2427
2485
  const { width: screenWidth, height: screenHeight } = useWindowDimensions2();
2428
- const [expanded, setExpanded] = React12.useState(false);
2429
- const pos = React12.useRef(new Animated4.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2430
- const start = React12.useRef({ x: 0, y: 0 });
2431
- const currentPos = React12.useRef({ x: 0, y: 0 });
2432
- React12.useEffect(() => {
2486
+ const [expanded, setExpanded] = React13.useState(false);
2487
+ const pos = React13.useRef(new Animated4.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2488
+ const start = React13.useRef({ x: 0, y: 0 });
2489
+ const currentPos = React13.useRef({ x: 0, y: 0 });
2490
+ React13.useEffect(() => {
2433
2491
  if (hidden) return;
2434
2492
  Animated4.spring(pos.y, {
2435
2493
  toValue: insets.top + 60,
@@ -2439,7 +2497,7 @@ function DrawToolbar({
2439
2497
  mass: 0.8
2440
2498
  }).start();
2441
2499
  }, [hidden, insets.top, pos.y]);
2442
- React12.useEffect(() => {
2500
+ React13.useEffect(() => {
2443
2501
  const id = pos.addListener((v) => {
2444
2502
  currentPos.current = { x: v.x ?? 0, y: v.y ?? 0 };
2445
2503
  });
@@ -2447,7 +2505,7 @@ function DrawToolbar({
2447
2505
  pos.removeListener(id);
2448
2506
  };
2449
2507
  }, [pos]);
2450
- const clamp2 = React12.useCallback(
2508
+ const clamp2 = React13.useCallback(
2451
2509
  (x, y) => {
2452
2510
  const minX = 10;
2453
2511
  const maxX = Math.max(10, screenWidth - 230);
@@ -2457,7 +2515,7 @@ function DrawToolbar({
2457
2515
  },
2458
2516
  [insets.top, screenHeight, screenWidth]
2459
2517
  );
2460
- const panResponder = React12.useMemo(
2518
+ const panResponder = React13.useMemo(
2461
2519
  () => PanResponder3.create({
2462
2520
  onStartShouldSetPanResponder: () => false,
2463
2521
  onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 5 || Math.abs(g.dy) > 5,
@@ -2485,7 +2543,7 @@ function DrawToolbar({
2485
2543
  children
2486
2544
  }) {
2487
2545
  const isDisabled = Boolean(disabled) || Boolean(capturingDisabled);
2488
- const [pressed, setPressed] = React12.useState(false);
2546
+ const [pressed, setPressed] = React13.useState(false);
2489
2547
  return /* @__PURE__ */ jsx11(
2490
2548
  View9,
2491
2549
  {
@@ -2623,7 +2681,7 @@ function DrawModeOverlay({
2623
2681
  renderDragHandle
2624
2682
  }) {
2625
2683
  const theme = useTheme();
2626
- const defaultPalette = React13.useMemo(
2684
+ const defaultPalette = React14.useMemo(
2627
2685
  () => [
2628
2686
  "#EF4444",
2629
2687
  // Red
@@ -2641,11 +2699,11 @@ function DrawModeOverlay({
2641
2699
  []
2642
2700
  );
2643
2701
  const colors = palette && palette.length > 0 ? palette : defaultPalette;
2644
- const [selectedColor, setSelectedColor] = React13.useState(colors[0] ?? "#EF4444");
2645
- const [strokes, setStrokes] = React13.useState([]);
2646
- const [capturing, setCapturing] = React13.useState(false);
2647
- const [hideUi, setHideUi] = React13.useState(false);
2648
- React13.useEffect(() => {
2702
+ const [selectedColor, setSelectedColor] = React14.useState(colors[0] ?? "#EF4444");
2703
+ const [strokes, setStrokes] = React14.useState([]);
2704
+ const [capturing, setCapturing] = React14.useState(false);
2705
+ const [hideUi, setHideUi] = React14.useState(false);
2706
+ React14.useEffect(() => {
2649
2707
  if (!visible) return;
2650
2708
  setStrokes([]);
2651
2709
  setSelectedColor(colors[0] ?? "#EF4444");
@@ -2653,14 +2711,14 @@ function DrawModeOverlay({
2653
2711
  setHideUi(false);
2654
2712
  }, [colors, visible]);
2655
2713
  const canUndo = strokes.length > 0;
2656
- const handleUndo = React13.useCallback(() => {
2714
+ const handleUndo = React14.useCallback(() => {
2657
2715
  setStrokes((prev) => prev.slice(0, -1));
2658
2716
  }, []);
2659
- const handleCancel = React13.useCallback(() => {
2717
+ const handleCancel = React14.useCallback(() => {
2660
2718
  setStrokes([]);
2661
2719
  onCancel();
2662
2720
  }, [onCancel]);
2663
- const handleDone = React13.useCallback(async () => {
2721
+ const handleDone = React14.useCallback(async () => {
2664
2722
  if (!captureTargetRef.current || capturing) return;
2665
2723
  try {
2666
2724
  setCapturing(true);
@@ -2720,7 +2778,7 @@ var styles3 = StyleSheet3.create({
2720
2778
  });
2721
2779
 
2722
2780
  // src/components/comments/AppCommentsSheet.tsx
2723
- import * as React20 from "react";
2781
+ import * as React21 from "react";
2724
2782
  import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform4, Pressable as Pressable5, View as View14 } from "react-native";
2725
2783
  import {
2726
2784
  BottomSheetBackdrop,
@@ -2732,7 +2790,7 @@ import { LiquidGlassView as LiquidGlassView4, isLiquidGlassSupported as isLiquid
2732
2790
  import { Play as Play2 } from "lucide-react-native";
2733
2791
 
2734
2792
  // src/components/chat/ChatComposer.tsx
2735
- import * as React15 from "react";
2793
+ import * as React16 from "react";
2736
2794
  import {
2737
2795
  ActivityIndicator as ActivityIndicator2,
2738
2796
  Animated as Animated5,
@@ -2746,11 +2804,11 @@ import { LiquidGlassView as LiquidGlassView3, isLiquidGlassSupported as isLiquid
2746
2804
  import { Plus } from "lucide-react-native";
2747
2805
 
2748
2806
  // src/components/chat/MultilineTextInput.tsx
2749
- import * as React14 from "react";
2807
+ import * as React15 from "react";
2750
2808
  import { TextInput } from "react-native";
2751
2809
  import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
2752
2810
  import { jsx as jsx13 } from "react/jsx-runtime";
2753
- var MultilineTextInput = React14.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2811
+ var MultilineTextInput = React15.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2754
2812
  const theme = useTheme();
2755
2813
  const baseStyle = {
2756
2814
  minHeight: 44,
@@ -2834,7 +2892,7 @@ function AspectRatioThumbnail({
2834
2892
  onRemove,
2835
2893
  renderRemoveIcon
2836
2894
  }) {
2837
- const [aspectRatio, setAspectRatio] = React15.useState(1);
2895
+ const [aspectRatio, setAspectRatio] = React16.useState(1);
2838
2896
  return /* @__PURE__ */ jsxs8(View11, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
2839
2897
  /* @__PURE__ */ jsx15(View11, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx15(
2840
2898
  Image,
@@ -2876,6 +2934,7 @@ function ChatComposer({
2876
2934
  onChangeValue,
2877
2935
  placeholder = "Describe the idea you want to build",
2878
2936
  disabled = false,
2937
+ sendDisabled = false,
2879
2938
  sending = false,
2880
2939
  autoFocus = false,
2881
2940
  onSend,
@@ -2890,19 +2949,19 @@ function ChatComposer({
2890
2949
  style
2891
2950
  }) {
2892
2951
  const theme = useTheme();
2893
- const [internal, setInternal] = React15.useState("");
2952
+ const [internal, setInternal] = React16.useState("");
2894
2953
  const text = value ?? internal;
2895
2954
  const setText = onChangeValue ?? setInternal;
2896
2955
  const hasAttachments = attachments.length > 0;
2897
2956
  const hasText = text.trim().length > 0;
2898
2957
  const composerMinHeight = hasAttachments ? THUMBNAIL_HEIGHT + 44 + 24 : 44;
2899
- const isButtonDisabled = sending || disabled;
2900
- const maxInputHeight = React15.useMemo(() => Dimensions.get("window").height * 0.5, []);
2901
- const shakeAnim = React15.useRef(new Animated5.Value(0)).current;
2902
- const [sendPressed, setSendPressed] = React15.useState(false);
2903
- const inputRef = React15.useRef(null);
2904
- const prevAutoFocusRef = React15.useRef(false);
2905
- React15.useEffect(() => {
2958
+ const isButtonDisabled = sending || disabled || sendDisabled;
2959
+ const maxInputHeight = React16.useMemo(() => Dimensions.get("window").height * 0.5, []);
2960
+ const shakeAnim = React16.useRef(new Animated5.Value(0)).current;
2961
+ const [sendPressed, setSendPressed] = React16.useState(false);
2962
+ const inputRef = React16.useRef(null);
2963
+ const prevAutoFocusRef = React16.useRef(false);
2964
+ React16.useEffect(() => {
2906
2965
  const shouldFocus = autoFocus && !prevAutoFocusRef.current && !disabled && !sending;
2907
2966
  prevAutoFocusRef.current = autoFocus;
2908
2967
  if (!shouldFocus) return;
@@ -2912,7 +2971,7 @@ function ChatComposer({
2912
2971
  }, 75);
2913
2972
  return () => clearTimeout(t);
2914
2973
  }, [autoFocus, disabled, sending]);
2915
- const triggerShake = React15.useCallback(() => {
2974
+ const triggerShake = React16.useCallback(() => {
2916
2975
  shakeAnim.setValue(0);
2917
2976
  Animated5.sequence([
2918
2977
  Animated5.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
@@ -2922,7 +2981,7 @@ function ChatComposer({
2922
2981
  Animated5.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
2923
2982
  ]).start();
2924
2983
  }, [shakeAnim]);
2925
- const handleSend = React15.useCallback(async () => {
2984
+ const handleSend = React16.useCallback(async () => {
2926
2985
  if (isButtonDisabled) return;
2927
2986
  if (!hasText) {
2928
2987
  triggerShake();
@@ -3055,7 +3114,7 @@ function ChatComposer({
3055
3114
  }
3056
3115
 
3057
3116
  // src/components/comments/CommentRow.tsx
3058
- import * as React16 from "react";
3117
+ import * as React17 from "react";
3059
3118
  import { View as View13 } from "react-native";
3060
3119
 
3061
3120
  // src/components/primitives/Avatar.tsx
@@ -3127,9 +3186,9 @@ function formatTimeAgo(iso) {
3127
3186
  import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
3128
3187
  function CommentRow({ comment, showDivider }) {
3129
3188
  const theme = useTheme();
3130
- const [authorName, setAuthorName] = React16.useState(null);
3131
- const [authorAvatar, setAuthorAvatar] = React16.useState(null);
3132
- React16.useEffect(() => {
3189
+ const [authorName, setAuthorName] = React17.useState(null);
3190
+ const [authorAvatar, setAuthorAvatar] = React17.useState(null);
3191
+ React17.useEffect(() => {
3133
3192
  let cancelled = false;
3134
3193
  (async () => {
3135
3194
  try {
@@ -3169,7 +3228,7 @@ function CommentRow({ comment, showDivider }) {
3169
3228
  }
3170
3229
 
3171
3230
  // src/components/comments/useAppComments.ts
3172
- import * as React17 from "react";
3231
+ import * as React18 from "react";
3173
3232
 
3174
3233
  // src/data/comments/remote.ts
3175
3234
  var AppCommentsRemoteDataSourceImpl = class extends BaseRemote {
@@ -3241,18 +3300,18 @@ var appCommentsRepository = new AppCommentsRepositoryImpl(appCommentsRemoteDataS
3241
3300
 
3242
3301
  // src/components/comments/useAppComments.ts
3243
3302
  function useAppComments(appId) {
3244
- const [comments, setComments] = React17.useState([]);
3245
- const [loading, setLoading] = React17.useState(false);
3246
- const [sending, setSending] = React17.useState(false);
3247
- const [error, setError] = React17.useState(null);
3248
- const sortByCreatedAtAsc = React17.useCallback((items) => {
3303
+ const [comments, setComments] = React18.useState([]);
3304
+ const [loading, setLoading] = React18.useState(false);
3305
+ const [sending, setSending] = React18.useState(false);
3306
+ const [error, setError] = React18.useState(null);
3307
+ const sortByCreatedAtAsc = React18.useCallback((items) => {
3249
3308
  return [...items].sort((a, b) => {
3250
3309
  const at = a.createdAt ? new Date(a.createdAt).getTime() : 0;
3251
3310
  const bt = b.createdAt ? new Date(b.createdAt).getTime() : 0;
3252
3311
  return at - bt;
3253
3312
  });
3254
3313
  }, []);
3255
- const refresh = React17.useCallback(async () => {
3314
+ const refresh = React18.useCallback(async () => {
3256
3315
  if (!appId) {
3257
3316
  setComments([]);
3258
3317
  return;
@@ -3269,10 +3328,10 @@ function useAppComments(appId) {
3269
3328
  setLoading(false);
3270
3329
  }
3271
3330
  }, [appId, sortByCreatedAtAsc]);
3272
- React17.useEffect(() => {
3331
+ React18.useEffect(() => {
3273
3332
  void refresh();
3274
3333
  }, [refresh]);
3275
- const create = React17.useCallback(
3334
+ const create = React18.useCallback(
3276
3335
  async (text) => {
3277
3336
  if (!appId) return;
3278
3337
  const trimmed = text.trim();
@@ -3295,11 +3354,11 @@ function useAppComments(appId) {
3295
3354
  }
3296
3355
 
3297
3356
  // src/components/comments/useAppDetails.ts
3298
- import * as React18 from "react";
3357
+ import * as React19 from "react";
3299
3358
  function useAppDetails(appId) {
3300
- const [app, setApp] = React18.useState(null);
3301
- const [loading, setLoading] = React18.useState(false);
3302
- React18.useEffect(() => {
3359
+ const [app, setApp] = React19.useState(null);
3360
+ const [loading, setLoading] = React19.useState(false);
3361
+ React19.useEffect(() => {
3303
3362
  if (!appId) {
3304
3363
  setApp(null);
3305
3364
  return;
@@ -3324,25 +3383,30 @@ function useAppDetails(appId) {
3324
3383
  }
3325
3384
 
3326
3385
  // src/components/comments/useIosKeyboardSnapFix.ts
3327
- import * as React19 from "react";
3386
+ import * as React20 from "react";
3328
3387
  import { Keyboard as Keyboard2, Platform as Platform3 } from "react-native";
3329
- function useIosKeyboardSnapFix(sheetRef) {
3330
- const [keyboardVisible, setKeyboardVisible] = React19.useState(false);
3331
- React19.useEffect(() => {
3388
+ function useIosKeyboardSnapFix(sheetRef, options) {
3389
+ const [keyboardVisible, setKeyboardVisible] = React20.useState(false);
3390
+ React20.useEffect(() => {
3332
3391
  if (Platform3.OS !== "ios") return;
3333
3392
  const show = Keyboard2.addListener("keyboardWillShow", () => setKeyboardVisible(true));
3334
3393
  const hide = Keyboard2.addListener("keyboardWillHide", () => {
3394
+ var _a;
3335
3395
  setKeyboardVisible(false);
3336
- setTimeout(() => {
3337
- var _a, _b;
3338
- return (_b = (_a = sheetRef.current) == null ? void 0 : _a.snapToIndex) == null ? void 0 : _b.call(_a, 1);
3339
- }, 10);
3396
+ const target = (options == null ? void 0 : options.targetIndex) ?? 1;
3397
+ const current = ((_a = options == null ? void 0 : options.getCurrentIndex) == null ? void 0 : _a.call(options)) ?? null;
3398
+ if (current === target) {
3399
+ setTimeout(() => {
3400
+ var _a2, _b;
3401
+ return (_b = (_a2 = sheetRef.current) == null ? void 0 : _a2.snapToIndex) == null ? void 0 : _b.call(_a2, target);
3402
+ }, 10);
3403
+ }
3340
3404
  });
3341
3405
  return () => {
3342
3406
  show.remove();
3343
3407
  hide.remove();
3344
3408
  };
3345
- }, [sheetRef]);
3409
+ }, [options == null ? void 0 : options.getCurrentIndex, options == null ? void 0 : options.targetIndex, sheetRef]);
3346
3410
  return { keyboardVisible };
3347
3411
  }
3348
3412
 
@@ -3351,12 +3415,16 @@ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
3351
3415
  function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3352
3416
  const theme = useTheme();
3353
3417
  const insets = useSafeAreaInsets3();
3354
- const sheetRef = React20.useRef(null);
3355
- const snapPoints = React20.useMemo(() => ["50%", "90%"], []);
3418
+ const sheetRef = React21.useRef(null);
3419
+ const snapPoints = React21.useMemo(() => ["50%", "90%"], []);
3420
+ const currentIndexRef = React21.useRef(1);
3356
3421
  const { comments, loading, sending, error, create, refresh } = useAppComments(appId);
3357
3422
  const { app, loading: loadingApp } = useAppDetails(appId);
3358
- const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef);
3359
- React20.useEffect(() => {
3423
+ const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef, {
3424
+ getCurrentIndex: () => currentIndexRef.current,
3425
+ targetIndex: 1
3426
+ });
3427
+ React21.useEffect(() => {
3360
3428
  var _a, _b;
3361
3429
  if (appId) {
3362
3430
  (_a = sheetRef.current) == null ? void 0 : _a.present();
@@ -3365,21 +3433,22 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3365
3433
  (_b = sheetRef.current) == null ? void 0 : _b.dismiss();
3366
3434
  }
3367
3435
  }, [appId, refresh]);
3368
- React20.useEffect(() => {
3436
+ React21.useEffect(() => {
3369
3437
  if (!appId) return;
3370
3438
  onCountChange == null ? void 0 : onCountChange(comments.length);
3371
3439
  }, [appId, comments.length, onCountChange]);
3372
- const renderBackdrop = React20.useCallback(
3440
+ const renderBackdrop = React21.useCallback(
3373
3441
  (props) => /* @__PURE__ */ jsx18(BottomSheetBackdrop, { ...props, disappearsOnIndex: -1, appearsOnIndex: 0, opacity: 0.5 }),
3374
3442
  []
3375
3443
  );
3376
- const handleChange = React20.useCallback(
3444
+ const handleChange = React21.useCallback(
3377
3445
  (index) => {
3446
+ currentIndexRef.current = index;
3378
3447
  if (index === -1) onClose();
3379
3448
  },
3380
3449
  [onClose]
3381
3450
  );
3382
- const handlePlay = React20.useCallback(async () => {
3451
+ const handlePlay = React21.useCallback(async () => {
3383
3452
  var _a;
3384
3453
  if (!appId) return;
3385
3454
  (_a = sheetRef.current) == null ? void 0 : _a.dismiss();
@@ -3590,7 +3659,7 @@ function StudioSheetHeader({ left, center, right, style }) {
3590
3659
  }
3591
3660
 
3592
3661
  // src/components/studio-sheet/StudioSheetHeaderIconButton.tsx
3593
- import * as React21 from "react";
3662
+ import * as React22 from "react";
3594
3663
  import { Pressable as Pressable6, View as View17 } from "react-native";
3595
3664
  import { LiquidGlassView as LiquidGlassView5, isLiquidGlassSupported as isLiquidGlassSupported5 } from "@callstack/liquid-glass";
3596
3665
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -3605,7 +3674,7 @@ function StudioSheetHeaderIconButton({
3605
3674
  }) {
3606
3675
  const theme = useTheme();
3607
3676
  const size = 44;
3608
- const [pressed, setPressed] = React21.useState(false);
3677
+ const [pressed, setPressed] = React22.useState(false);
3609
3678
  const solidBg = intent === "danger" ? theme.colors.danger : intent === "primary" ? theme.colors.primary : theme.colors.neutral;
3610
3679
  const glassFallbackBg = theme.scheme === "dark" ? "#18181B" : "#F6F6F6";
3611
3680
  const glassInnerBg = intent === "danger" ? theme.colors.danger : theme.colors.primary;
@@ -3795,14 +3864,14 @@ function PreviewHeroCard({
3795
3864
  }
3796
3865
 
3797
3866
  // src/components/preview/PreviewPlaceholder.tsx
3798
- import * as React22 from "react";
3867
+ import * as React23 from "react";
3799
3868
  import { Animated as Animated6 } from "react-native";
3800
3869
  import { LinearGradient as LinearGradient2 } from "expo-linear-gradient";
3801
3870
  import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
3802
3871
  function PreviewPlaceholder({ visible, style }) {
3803
3872
  if (!visible) return null;
3804
- const opacityAnim = React22.useRef(new Animated6.Value(0)).current;
3805
- React22.useEffect(() => {
3873
+ const opacityAnim = React23.useRef(new Animated6.Value(0)).current;
3874
+ React23.useEffect(() => {
3806
3875
  if (!visible) return;
3807
3876
  const animation = Animated6.loop(
3808
3877
  Animated6.sequence([
@@ -4394,12 +4463,12 @@ function PreviewCustomizeSection({
4394
4463
  }
4395
4464
 
4396
4465
  // src/studio/ui/preview-panel/PreviewCollaborateSection.tsx
4397
- import * as React28 from "react";
4466
+ import * as React29 from "react";
4398
4467
  import { ActivityIndicator as ActivityIndicator6, Alert, View as View32 } from "react-native";
4399
4468
  import { Send as Send2 } from "lucide-react-native";
4400
4469
 
4401
4470
  // src/components/merge-requests/MergeRequestStatusCard.tsx
4402
- import * as React24 from "react";
4471
+ import * as React25 from "react";
4403
4472
  import { Animated as Animated7, Pressable as Pressable9, View as View28 } from "react-native";
4404
4473
  import { Ban, Check as Check3, CheckCheck, ChevronDown as ChevronDown2 } from "lucide-react-native";
4405
4474
 
@@ -4481,11 +4550,11 @@ function toIsoString(input) {
4481
4550
  }
4482
4551
 
4483
4552
  // src/components/merge-requests/useControlledExpansion.ts
4484
- import * as React23 from "react";
4553
+ import * as React24 from "react";
4485
4554
  function useControlledExpansion(props) {
4486
- const [uncontrolled, setUncontrolled] = React23.useState(false);
4555
+ const [uncontrolled, setUncontrolled] = React24.useState(false);
4487
4556
  const expanded = props.expanded ?? uncontrolled;
4488
- const setExpanded = React23.useCallback(
4557
+ const setExpanded = React24.useCallback(
4489
4558
  (next) => {
4490
4559
  var _a;
4491
4560
  (_a = props.onExpandedChange) == null ? void 0 : _a.call(props, next);
@@ -4510,8 +4579,8 @@ function MergeRequestStatusCard({
4510
4579
  const isDark = theme.scheme === "dark";
4511
4580
  const textColor = isDark ? "#FFFFFF" : "#000000";
4512
4581
  const subTextColor = isDark ? "#A1A1AA" : "#71717A";
4513
- const status = React24.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4514
- const { StatusIcon, iconColor, bgColor, statusText } = React24.useMemo(() => {
4582
+ const status = React25.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4583
+ const { StatusIcon, iconColor, bgColor, statusText } = React25.useMemo(() => {
4515
4584
  switch (mergeRequest.status) {
4516
4585
  case "approved":
4517
4586
  case "merged":
@@ -4542,8 +4611,8 @@ function MergeRequestStatusCard({
4542
4611
  const createdIso = toIsoString(mergeRequest.createdAt ?? null);
4543
4612
  const headerTimeAgo = updatedIso ? formatTimeAgo(updatedIso) : "";
4544
4613
  const createdTimeAgo = createdIso ? formatTimeAgo(createdIso) : "";
4545
- const rotate = React24.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4546
- React24.useEffect(() => {
4614
+ const rotate = React25.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4615
+ React25.useEffect(() => {
4547
4616
  Animated7.timing(rotate, {
4548
4617
  toValue: expanded ? 1 : 0,
4549
4618
  duration: 200,
@@ -4634,16 +4703,16 @@ function MergeRequestStatusCard({
4634
4703
  }
4635
4704
 
4636
4705
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
4637
- import * as React27 from "react";
4706
+ import * as React28 from "react";
4638
4707
  import { Animated as Animated9, FlatList, View as View31, useWindowDimensions as useWindowDimensions3 } from "react-native";
4639
4708
 
4640
4709
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
4641
- import * as React26 from "react";
4710
+ import * as React27 from "react";
4642
4711
  import { ActivityIndicator as ActivityIndicator5, Animated as Animated8, Pressable as Pressable11, View as View30 } from "react-native";
4643
4712
  import { Check as Check4, ChevronDown as ChevronDown3, Play as Play3, X as X3 } from "lucide-react-native";
4644
4713
 
4645
4714
  // src/components/merge-requests/ReviewMergeRequestActionButton.tsx
4646
- import * as React25 from "react";
4715
+ import * as React26 from "react";
4647
4716
  import { Pressable as Pressable10, View as View29 } from "react-native";
4648
4717
  import { jsx as jsx39 } from "react/jsx-runtime";
4649
4718
  function ReviewMergeRequestActionButton({
@@ -4654,7 +4723,7 @@ function ReviewMergeRequestActionButton({
4654
4723
  children,
4655
4724
  iconOnly
4656
4725
  }) {
4657
- const [pressed, setPressed] = React25.useState(false);
4726
+ const [pressed, setPressed] = React26.useState(false);
4658
4727
  const height = iconOnly ? 36 : 40;
4659
4728
  const width = iconOnly ? 36 : void 0;
4660
4729
  const paddingHorizontal = iconOnly ? 0 : 16;
@@ -4716,10 +4785,10 @@ function ReviewMergeRequestCard({
4716
4785
  onTest
4717
4786
  }) {
4718
4787
  const theme = useTheme();
4719
- const status = React26.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4788
+ const status = React27.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4720
4789
  const canAct = mr.status === "open";
4721
- const rotate = React26.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
4722
- React26.useEffect(() => {
4790
+ const rotate = React27.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
4791
+ React27.useEffect(() => {
4723
4792
  Animated8.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
4724
4793
  }, [isExpanded, rotate]);
4725
4794
  const position = total > 1 ? `${index + 1}/${total}` : "Merge request";
@@ -4851,11 +4920,11 @@ function ReviewMergeRequestCarousel({
4851
4920
  }) {
4852
4921
  const theme = useTheme();
4853
4922
  const { width } = useWindowDimensions3();
4854
- const [expanded, setExpanded] = React27.useState({});
4855
- const carouselScrollX = React27.useRef(new Animated9.Value(0)).current;
4923
+ const [expanded, setExpanded] = React28.useState({});
4924
+ const carouselScrollX = React28.useRef(new Animated9.Value(0)).current;
4856
4925
  const peekAmount = 24;
4857
4926
  const gap = 16;
4858
- const cardWidth = React27.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
4927
+ const cardWidth = React28.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
4859
4928
  const snapInterval = cardWidth + gap;
4860
4929
  const dotColor = theme.scheme === "dark" ? "#FFFFFF" : "#000000";
4861
4930
  if (mergeRequests.length === 0) return null;
@@ -4954,7 +5023,7 @@ function PreviewCollaborateSection({
4954
5023
  onTestMr
4955
5024
  }) {
4956
5025
  const theme = useTheme();
4957
- const [submittingMr, setSubmittingMr] = React28.useState(false);
5026
+ const [submittingMr, setSubmittingMr] = React29.useState(false);
4958
5027
  const hasSection = canSubmitMergeRequest || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
4959
5028
  if (!hasSection) return null;
4960
5029
  const showActionsSubtitle = canSubmitMergeRequest && onSubmitMergeRequest || onTestMr && incomingMergeRequests.length > 0;
@@ -5062,7 +5131,7 @@ function PreviewCollaborateSection({
5062
5131
  }
5063
5132
 
5064
5133
  // src/studio/ui/preview-panel/usePreviewPanelData.ts
5065
- import * as React30 from "react";
5134
+ import * as React31 from "react";
5066
5135
 
5067
5136
  // src/data/apps/images/remote.ts
5068
5137
  var AppImagesRemoteDataSourceImpl = class extends BaseRemote {
@@ -5113,7 +5182,7 @@ var AppImagesRepositoryImpl = class extends BaseRepository {
5113
5182
  var appImagesRepository = new AppImagesRepositoryImpl(appImagesRemoteDataSource);
5114
5183
 
5115
5184
  // src/studio/hooks/useAppStats.ts
5116
- import * as React29 from "react";
5185
+ import * as React30 from "react";
5117
5186
  import * as Haptics2 from "expo-haptics";
5118
5187
 
5119
5188
  // src/data/likes/remote.ts
@@ -5182,34 +5251,34 @@ function useAppStats({
5182
5251
  initialIsLiked = false,
5183
5252
  onOpenComments
5184
5253
  }) {
5185
- const [likeCount, setLikeCount] = React29.useState(initialLikes);
5186
- const [commentCount, setCommentCount] = React29.useState(initialComments);
5187
- const [forkCount, setForkCount] = React29.useState(initialForks);
5188
- const [isLiked, setIsLiked] = React29.useState(initialIsLiked);
5189
- const didMutateRef = React29.useRef(false);
5190
- const lastAppIdRef = React29.useRef("");
5191
- React29.useEffect(() => {
5254
+ const [likeCount, setLikeCount] = React30.useState(initialLikes);
5255
+ const [commentCount, setCommentCount] = React30.useState(initialComments);
5256
+ const [forkCount, setForkCount] = React30.useState(initialForks);
5257
+ const [isLiked, setIsLiked] = React30.useState(initialIsLiked);
5258
+ const didMutateRef = React30.useRef(false);
5259
+ const lastAppIdRef = React30.useRef("");
5260
+ React30.useEffect(() => {
5192
5261
  if (lastAppIdRef.current === appId) return;
5193
5262
  lastAppIdRef.current = appId;
5194
5263
  didMutateRef.current = false;
5195
5264
  }, [appId]);
5196
- React29.useEffect(() => {
5265
+ React30.useEffect(() => {
5197
5266
  if (didMutateRef.current) return;
5198
5267
  setLikeCount(initialLikes);
5199
5268
  }, [appId, initialLikes]);
5200
- React29.useEffect(() => {
5269
+ React30.useEffect(() => {
5201
5270
  if (didMutateRef.current) return;
5202
5271
  setCommentCount(initialComments);
5203
5272
  }, [appId, initialComments]);
5204
- React29.useEffect(() => {
5273
+ React30.useEffect(() => {
5205
5274
  if (didMutateRef.current) return;
5206
5275
  setForkCount(initialForks);
5207
5276
  }, [appId, initialForks]);
5208
- React29.useEffect(() => {
5277
+ React30.useEffect(() => {
5209
5278
  if (didMutateRef.current) return;
5210
5279
  setIsLiked(initialIsLiked);
5211
5280
  }, [appId, initialIsLiked]);
5212
- const handleLike = React29.useCallback(async () => {
5281
+ const handleLike = React30.useCallback(async () => {
5213
5282
  var _a, _b;
5214
5283
  if (!appId) return;
5215
5284
  didMutateRef.current = true;
@@ -5233,7 +5302,7 @@ function useAppStats({
5233
5302
  setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
5234
5303
  }
5235
5304
  }, [appId, isLiked, likeCount]);
5236
- const handleOpenComments = React29.useCallback(() => {
5305
+ const handleOpenComments = React30.useCallback(() => {
5237
5306
  if (!appId) return;
5238
5307
  try {
5239
5308
  void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
@@ -5248,11 +5317,11 @@ function useAppStats({
5248
5317
  var LIKE_DEBUG_PREFIX = "[COMERGE_LIKE_DEBUG]";
5249
5318
  function usePreviewPanelData(params) {
5250
5319
  const { app, isOwner, outgoingMergeRequests, onOpenComments, commentCountOverride } = params;
5251
- const [imageUrl, setImageUrl] = React30.useState(null);
5252
- const [imageLoaded, setImageLoaded] = React30.useState(false);
5253
- const [insights, setInsights] = React30.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5254
- const [creator, setCreator] = React30.useState(null);
5255
- React30.useEffect(() => {
5320
+ const [imageUrl, setImageUrl] = React31.useState(null);
5321
+ const [imageLoaded, setImageLoaded] = React31.useState(false);
5322
+ const [insights, setInsights] = React31.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5323
+ const [creator, setCreator] = React31.useState(null);
5324
+ React31.useEffect(() => {
5256
5325
  if (!(app == null ? void 0 : app.id)) return;
5257
5326
  let cancelled = false;
5258
5327
  (async () => {
@@ -5267,7 +5336,7 @@ function usePreviewPanelData(params) {
5267
5336
  cancelled = true;
5268
5337
  };
5269
5338
  }, [app == null ? void 0 : app.id]);
5270
- React30.useEffect(() => {
5339
+ React31.useEffect(() => {
5271
5340
  if (!(app == null ? void 0 : app.createdBy)) return;
5272
5341
  let cancelled = false;
5273
5342
  (async () => {
@@ -5283,10 +5352,10 @@ function usePreviewPanelData(params) {
5283
5352
  cancelled = true;
5284
5353
  };
5285
5354
  }, [app == null ? void 0 : app.createdBy]);
5286
- React30.useEffect(() => {
5355
+ React31.useEffect(() => {
5287
5356
  setImageLoaded(false);
5288
5357
  }, [app == null ? void 0 : app.id]);
5289
- React30.useEffect(() => {
5358
+ React31.useEffect(() => {
5290
5359
  if (!(app == null ? void 0 : app.id)) return;
5291
5360
  let cancelled = false;
5292
5361
  (async () => {
@@ -5311,7 +5380,7 @@ function usePreviewPanelData(params) {
5311
5380
  cancelled = true;
5312
5381
  };
5313
5382
  }, [app == null ? void 0 : app.id]);
5314
- React30.useEffect(() => {
5383
+ React31.useEffect(() => {
5315
5384
  if (!(app == null ? void 0 : app.id)) return;
5316
5385
  log.debug(
5317
5386
  `${LIKE_DEBUG_PREFIX} usePreviewPanelData.appChanged appId=${app.id} app.isLiked=${String(app.isLiked)}`
@@ -5325,7 +5394,7 @@ function usePreviewPanelData(params) {
5325
5394
  initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
5326
5395
  onOpenComments
5327
5396
  });
5328
- const canSubmitMergeRequest = React30.useMemo(() => {
5397
+ const canSubmitMergeRequest = React31.useMemo(() => {
5329
5398
  if (!isOwner) return false;
5330
5399
  if (!app) return false;
5331
5400
  if (!app.forkedFromAppId) return false;
@@ -5438,18 +5507,17 @@ function PreviewPanel({
5438
5507
  }
5439
5508
 
5440
5509
  // src/studio/ui/ChatPanel.tsx
5441
- import * as React35 from "react";
5510
+ import * as React36 from "react";
5442
5511
  import { ActivityIndicator as ActivityIndicator8, View as View41 } from "react-native";
5443
5512
 
5444
5513
  // src/components/chat/ChatPage.tsx
5445
- import * as React33 from "react";
5446
- import { Keyboard as Keyboard4, Platform as Platform6, View as View37 } from "react-native";
5514
+ import * as React34 from "react";
5515
+ import { Keyboard as Keyboard4, Platform as Platform7, View as View37 } from "react-native";
5447
5516
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
5448
- import Animated11, { useAnimatedKeyboard, useAnimatedStyle as useAnimatedStyle2 } from "react-native-reanimated";
5449
5517
 
5450
5518
  // src/components/chat/ChatMessageList.tsx
5451
- import * as React32 from "react";
5452
- import { View as View36 } from "react-native";
5519
+ import * as React33 from "react";
5520
+ import { Platform as Platform6, View as View36 } from "react-native";
5453
5521
  import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
5454
5522
 
5455
5523
  // src/components/chat/ChatMessageBubble.tsx
@@ -5494,17 +5562,17 @@ function ChatMessageBubble({ message, renderContent, style }) {
5494
5562
  }
5495
5563
 
5496
5564
  // src/components/chat/TypingIndicator.tsx
5497
- import * as React31 from "react";
5565
+ import * as React32 from "react";
5498
5566
  import { Animated as Animated10, View as View35 } from "react-native";
5499
5567
  import { jsx as jsx45 } from "react/jsx-runtime";
5500
5568
  function TypingIndicator({ style }) {
5501
5569
  const theme = useTheme();
5502
5570
  const dotColor = theme.colors.textSubtle;
5503
- const anims = React31.useMemo(
5571
+ const anims = React32.useMemo(
5504
5572
  () => [new Animated10.Value(0.3), new Animated10.Value(0.3), new Animated10.Value(0.3)],
5505
5573
  []
5506
5574
  );
5507
- React31.useEffect(() => {
5575
+ React32.useEffect(() => {
5508
5576
  const loops = [];
5509
5577
  anims.forEach((a, idx) => {
5510
5578
  const seq = Animated10.sequence([
@@ -5537,40 +5605,44 @@ function TypingIndicator({ style }) {
5537
5605
  }
5538
5606
 
5539
5607
  // src/components/chat/ChatMessageList.tsx
5540
- import { jsx as jsx46 } from "react/jsx-runtime";
5541
- var ChatMessageList = React32.forwardRef(
5608
+ import { jsx as jsx46, jsxs as jsxs28 } from "react/jsx-runtime";
5609
+ var ChatMessageList = React33.forwardRef(
5542
5610
  ({
5543
5611
  messages,
5544
5612
  showTypingIndicator = false,
5545
5613
  renderMessageContent,
5546
5614
  contentStyle,
5615
+ bottomInset = 0,
5547
5616
  onNearBottomChange,
5548
5617
  nearBottomThreshold = 200
5549
5618
  }, ref) => {
5550
5619
  const theme = useTheme();
5551
- const listRef = React32.useRef(null);
5552
- const nearBottomRef = React32.useRef(true);
5553
- const initialScrollDoneRef = React32.useRef(false);
5554
- const lastMessageIdRef = React32.useRef(null);
5555
- const scrollToBottom = React32.useCallback((options) => {
5620
+ const listRef = React33.useRef(null);
5621
+ const nearBottomRef = React33.useRef(true);
5622
+ const initialScrollDoneRef = React33.useRef(false);
5623
+ const lastMessageIdRef = React33.useRef(null);
5624
+ const scrollToBottom = React33.useCallback((options) => {
5556
5625
  var _a;
5557
5626
  const animated = (options == null ? void 0 : options.animated) ?? true;
5558
5627
  (_a = listRef.current) == null ? void 0 : _a.scrollToEnd({ animated });
5559
5628
  }, []);
5560
- React32.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5561
- const handleScroll = React32.useCallback(
5629
+ React33.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5630
+ const handleScroll = React33.useCallback(
5562
5631
  (e) => {
5563
5632
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
5564
- const distanceFromBottom = Math.max(contentSize.height - (contentOffset.y + layoutMeasurement.height), 0);
5633
+ const distanceFromBottom = Math.max(
5634
+ contentSize.height - Math.max(bottomInset, 0) - (contentOffset.y + layoutMeasurement.height),
5635
+ 0
5636
+ );
5565
5637
  const isNear = distanceFromBottom <= nearBottomThreshold;
5566
5638
  if (nearBottomRef.current !== isNear) {
5567
5639
  nearBottomRef.current = isNear;
5568
5640
  onNearBottomChange == null ? void 0 : onNearBottomChange(isNear);
5569
5641
  }
5570
5642
  },
5571
- [nearBottomThreshold, onNearBottomChange]
5643
+ [bottomInset, nearBottomThreshold, onNearBottomChange]
5572
5644
  );
5573
- React32.useEffect(() => {
5645
+ React33.useEffect(() => {
5574
5646
  var _a;
5575
5647
  if (initialScrollDoneRef.current) return;
5576
5648
  if (messages.length === 0) return;
@@ -5579,7 +5651,7 @@ var ChatMessageList = React32.forwardRef(
5579
5651
  const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5580
5652
  return () => cancelAnimationFrame(id);
5581
5653
  }, [messages, scrollToBottom]);
5582
- React32.useEffect(() => {
5654
+ React33.useEffect(() => {
5583
5655
  if (!initialScrollDoneRef.current) return;
5584
5656
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
5585
5657
  const prevLastId = lastMessageIdRef.current;
@@ -5589,19 +5661,27 @@ var ChatMessageList = React32.forwardRef(
5589
5661
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5590
5662
  return () => cancelAnimationFrame(id);
5591
5663
  }, [messages, scrollToBottom]);
5592
- React32.useEffect(() => {
5664
+ React33.useEffect(() => {
5593
5665
  if (showTypingIndicator && nearBottomRef.current) {
5594
5666
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5595
5667
  return () => cancelAnimationFrame(id);
5596
5668
  }
5597
5669
  return void 0;
5598
5670
  }, [showTypingIndicator, scrollToBottom]);
5671
+ React33.useEffect(() => {
5672
+ if (!initialScrollDoneRef.current) return;
5673
+ if (!nearBottomRef.current) return;
5674
+ const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5675
+ return () => cancelAnimationFrame(id);
5676
+ }, [bottomInset, scrollToBottom]);
5599
5677
  return /* @__PURE__ */ jsx46(
5600
5678
  BottomSheetFlatList,
5601
5679
  {
5602
5680
  ref: listRef,
5603
5681
  data: messages,
5604
5682
  keyExtractor: (m) => m.id,
5683
+ keyboardDismissMode: Platform6.OS === "ios" ? "interactive" : "on-drag",
5684
+ keyboardShouldPersistTaps: "handled",
5605
5685
  onScroll: handleScroll,
5606
5686
  scrollEventThrottle: 16,
5607
5687
  showsVerticalScrollIndicator: false,
@@ -5609,13 +5689,15 @@ var ChatMessageList = React32.forwardRef(
5609
5689
  {
5610
5690
  paddingHorizontal: theme.spacing.lg,
5611
5691
  paddingTop: theme.spacing.sm,
5612
- paddingBottom: theme.spacing.xl
5692
+ paddingBottom: theme.spacing.sm
5613
5693
  },
5614
5694
  contentStyle
5615
5695
  ],
5616
5696
  renderItem: ({ item, index }) => /* @__PURE__ */ jsx46(View36, { style: { marginTop: index === 0 ? 0 : theme.spacing.sm }, children: /* @__PURE__ */ jsx46(ChatMessageBubble, { message: item, renderContent: renderMessageContent }) }),
5617
- ListFooterComponent: showTypingIndicator ? /* @__PURE__ */ jsx46(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx46(TypingIndicator, {}) }) : null,
5618
- maintainVisibleContentPosition: { minIndexForVisible: 0, autoscrollToTopThreshold: nearBottomThreshold }
5697
+ ListFooterComponent: /* @__PURE__ */ jsxs28(View36, { children: [
5698
+ showTypingIndicator ? /* @__PURE__ */ jsx46(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx46(TypingIndicator, {}) }) : null,
5699
+ bottomInset > 0 ? /* @__PURE__ */ jsx46(View36, { style: { height: bottomInset } }) : null
5700
+ ] })
5619
5701
  }
5620
5702
  );
5621
5703
  }
@@ -5623,7 +5705,7 @@ var ChatMessageList = React32.forwardRef(
5623
5705
  ChatMessageList.displayName = "ChatMessageList";
5624
5706
 
5625
5707
  // src/components/chat/ChatPage.tsx
5626
- import { jsx as jsx47, jsxs as jsxs28 } from "react/jsx-runtime";
5708
+ import { jsx as jsx47, jsxs as jsxs29 } from "react/jsx-runtime";
5627
5709
  function ChatPage({
5628
5710
  header,
5629
5711
  messages,
@@ -5638,11 +5720,10 @@ function ChatPage({
5638
5720
  }) {
5639
5721
  const theme = useTheme();
5640
5722
  const insets = useSafeAreaInsets4();
5641
- const [composerHeight, setComposerHeight] = React33.useState(0);
5642
- const [keyboardVisible, setKeyboardVisible] = React33.useState(false);
5643
- const animatedKeyboard = useAnimatedKeyboard();
5644
- React33.useEffect(() => {
5645
- if (Platform6.OS !== "ios") return;
5723
+ const [composerHeight, setComposerHeight] = React34.useState(0);
5724
+ const [keyboardVisible, setKeyboardVisible] = React34.useState(false);
5725
+ React34.useEffect(() => {
5726
+ if (Platform7.OS !== "ios") return;
5646
5727
  const show = Keyboard4.addListener("keyboardWillShow", () => setKeyboardVisible(true));
5647
5728
  const hide = Keyboard4.addListener("keyboardWillHide", () => setKeyboardVisible(false));
5648
5729
  return () => {
@@ -5650,51 +5731,54 @@ function ChatPage({
5650
5731
  hide.remove();
5651
5732
  };
5652
5733
  }, []);
5653
- const footerBottomPadding = Platform6.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5654
- const footerAnimatedStyle = useAnimatedStyle2(() => {
5655
- if (Platform6.OS !== "ios") return { paddingBottom: insets.bottom + 10 };
5656
- return { paddingBottom: animatedKeyboard.height.value > 0 ? 0 : insets.bottom };
5657
- });
5734
+ const footerBottomPadding = Platform7.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5658
5735
  const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5659
- const resolvedOverlay = React33.useMemo(() => {
5736
+ const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
5737
+ const resolvedOverlay = React34.useMemo(() => {
5660
5738
  var _a;
5661
5739
  if (!overlay) return null;
5662
- if (!React33.isValidElement(overlay)) return overlay;
5740
+ if (!React34.isValidElement(overlay)) return overlay;
5663
5741
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
5664
- return React33.cloneElement(overlay, {
5742
+ return React34.cloneElement(overlay, {
5665
5743
  style: [prevStyle, { bottom: overlayBottom }]
5666
5744
  });
5667
5745
  }, [overlay, overlayBottom]);
5668
- return /* @__PURE__ */ jsxs28(View37, { style: [{ flex: 1 }, style], children: [
5746
+ return /* @__PURE__ */ jsxs29(View37, { style: [{ flex: 1 }, style], children: [
5669
5747
  header ? /* @__PURE__ */ jsx47(View37, { children: header }) : null,
5670
5748
  topBanner ? /* @__PURE__ */ jsx47(View37, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
5671
- /* @__PURE__ */ jsxs28(View37, { style: { flex: 1 }, children: [
5672
- /* @__PURE__ */ jsx47(
5673
- ChatMessageList,
5749
+ /* @__PURE__ */ jsxs29(View37, { style: { flex: 1 }, children: [
5750
+ /* @__PURE__ */ jsxs29(
5751
+ View37,
5674
5752
  {
5675
- ref: listRef,
5676
- messages,
5677
- showTypingIndicator,
5678
- renderMessageContent,
5679
- onNearBottomChange,
5680
- contentStyle: { paddingBottom: theme.spacing.xl + composerHeight + footerBottomPadding }
5753
+ style: { flex: 1 },
5754
+ children: [
5755
+ /* @__PURE__ */ jsx47(
5756
+ ChatMessageList,
5757
+ {
5758
+ ref: listRef,
5759
+ messages,
5760
+ showTypingIndicator,
5761
+ renderMessageContent,
5762
+ onNearBottomChange,
5763
+ bottomInset
5764
+ }
5765
+ ),
5766
+ resolvedOverlay
5767
+ ]
5681
5768
  }
5682
5769
  ),
5683
- resolvedOverlay,
5684
5770
  /* @__PURE__ */ jsx47(
5685
- Animated11.View,
5771
+ View37,
5686
5772
  {
5687
- style: [
5688
- {
5689
- position: "absolute",
5690
- left: 0,
5691
- right: 0,
5692
- bottom: 0,
5693
- paddingHorizontal: theme.spacing.lg,
5694
- paddingTop: theme.spacing.sm
5695
- },
5696
- footerAnimatedStyle
5697
- ],
5773
+ style: {
5774
+ position: "absolute",
5775
+ left: 0,
5776
+ right: 0,
5777
+ bottom: 0,
5778
+ paddingHorizontal: theme.spacing.lg,
5779
+ paddingTop: theme.spacing.sm,
5780
+ paddingBottom: footerBottomPadding
5781
+ },
5698
5782
  children: /* @__PURE__ */ jsx47(
5699
5783
  ChatComposer,
5700
5784
  {
@@ -5710,25 +5794,25 @@ function ChatPage({
5710
5794
  }
5711
5795
 
5712
5796
  // src/components/chat/ScrollToBottomButton.tsx
5713
- import * as React34 from "react";
5797
+ import * as React35 from "react";
5714
5798
  import { Pressable as Pressable12, View as View38 } from "react-native";
5715
- import Animated12, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle3, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
5799
+ import Animated11, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle2, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
5716
5800
  import { jsx as jsx48 } from "react/jsx-runtime";
5717
5801
  function ScrollToBottomButton({ visible, onPress, children, style }) {
5718
5802
  const theme = useTheme();
5719
5803
  const progress = useSharedValue2(visible ? 1 : 0);
5720
- const [pressed, setPressed] = React34.useState(false);
5721
- React34.useEffect(() => {
5804
+ const [pressed, setPressed] = React35.useState(false);
5805
+ React35.useEffect(() => {
5722
5806
  progress.value = withTiming2(visible ? 1 : 0, { duration: 200, easing: Easing2.out(Easing2.ease) });
5723
5807
  }, [progress, visible]);
5724
- const animStyle = useAnimatedStyle3(() => ({
5808
+ const animStyle = useAnimatedStyle2(() => ({
5725
5809
  opacity: progress.value,
5726
5810
  transform: [{ translateY: (1 - progress.value) * 20 }]
5727
5811
  }));
5728
5812
  const bg = theme.scheme === "dark" ? "rgba(39,39,42,0.9)" : "rgba(244,244,245,0.95)";
5729
5813
  const border = theme.scheme === "dark" ? withAlpha("#FFFFFF", 0.12) : withAlpha("#000000", 0.08);
5730
5814
  return /* @__PURE__ */ jsx48(
5731
- Animated12.View,
5815
+ Animated11.View,
5732
5816
  {
5733
5817
  pointerEvents: visible ? "auto" : "none",
5734
5818
  style: [
@@ -5800,7 +5884,7 @@ function ChatHeader({ left, right, center, style }) {
5800
5884
 
5801
5885
  // src/components/chat/ForkNoticeBanner.tsx
5802
5886
  import { View as View40 } from "react-native";
5803
- import { jsx as jsx50, jsxs as jsxs29 } from "react/jsx-runtime";
5887
+ import { jsx as jsx50, jsxs as jsxs30 } from "react/jsx-runtime";
5804
5888
  function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5805
5889
  const theme = useTheme();
5806
5890
  const resolvedTitle = title ?? (isOwner ? "Remixed app" : "Remix app");
@@ -5820,7 +5904,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5820
5904
  },
5821
5905
  style
5822
5906
  ],
5823
- children: /* @__PURE__ */ jsxs29(View40, { style: { minWidth: 0 }, children: [
5907
+ children: /* @__PURE__ */ jsxs30(View40, { style: { minWidth: 0 }, children: [
5824
5908
  /* @__PURE__ */ jsx50(
5825
5909
  Text,
5826
5910
  {
@@ -5853,7 +5937,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5853
5937
  }
5854
5938
 
5855
5939
  // src/studio/ui/ChatPanel.tsx
5856
- import { jsx as jsx51, jsxs as jsxs30 } from "react/jsx-runtime";
5940
+ import { jsx as jsx51, jsxs as jsxs31 } from "react/jsx-runtime";
5857
5941
  function ChatPanel({
5858
5942
  title = "Chat",
5859
5943
  autoFocusComposer = false,
@@ -5873,32 +5957,34 @@ function ChatPanel({
5873
5957
  onStartDraw,
5874
5958
  onSend
5875
5959
  }) {
5876
- const listRef = React35.useRef(null);
5877
- const [nearBottom, setNearBottom] = React35.useState(true);
5878
- const handleSend = React35.useCallback(
5960
+ const listRef = React36.useRef(null);
5961
+ const [nearBottom, setNearBottom] = React36.useState(true);
5962
+ const handleSend = React36.useCallback(
5879
5963
  async (text, composerAttachments) => {
5880
5964
  const all = composerAttachments ?? attachments;
5881
5965
  await onSend(text, all.length > 0 ? all : void 0);
5882
5966
  onClearAttachments == null ? void 0 : onClearAttachments();
5883
- requestAnimationFrame(() => {
5884
- var _a;
5885
- return (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5886
- });
5967
+ if (!nearBottom) {
5968
+ requestAnimationFrame(() => {
5969
+ var _a;
5970
+ return (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5971
+ });
5972
+ }
5887
5973
  },
5888
- [attachments, onClearAttachments, onSend]
5974
+ [attachments, nearBottom, onClearAttachments, onSend]
5889
5975
  );
5890
- const handleScrollToBottom = React35.useCallback(() => {
5976
+ const handleScrollToBottom = React36.useCallback(() => {
5891
5977
  var _a;
5892
5978
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5893
5979
  }, []);
5894
5980
  const header = /* @__PURE__ */ jsx51(
5895
5981
  ChatHeader,
5896
5982
  {
5897
- left: /* @__PURE__ */ jsxs30(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5983
+ left: /* @__PURE__ */ jsxs31(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5898
5984
  /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx51(IconBack, { size: 20, colorToken: "floatingContent" }) }),
5899
5985
  onNavigateHome ? /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx51(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
5900
5986
  ] }),
5901
- right: /* @__PURE__ */ jsxs30(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5987
+ right: /* @__PURE__ */ jsxs31(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5902
5988
  onStartDraw ? /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx51(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
5903
5989
  /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx51(IconClose, { size: 20, colorToken: "floatingContent" }) })
5904
5990
  ] }),
@@ -5914,10 +6000,10 @@ function ChatPanel({
5914
6000
  ) : null;
5915
6001
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
5916
6002
  if (showMessagesLoading) {
5917
- return /* @__PURE__ */ jsxs30(View41, { style: { flex: 1 }, children: [
6003
+ return /* @__PURE__ */ jsxs31(View41, { style: { flex: 1 }, children: [
5918
6004
  /* @__PURE__ */ jsx51(View41, { children: header }),
5919
6005
  topBanner ? /* @__PURE__ */ jsx51(View41, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
5920
- /* @__PURE__ */ jsxs30(View41, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
6006
+ /* @__PURE__ */ jsxs31(View41, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
5921
6007
  /* @__PURE__ */ jsx51(ActivityIndicator8, {}),
5922
6008
  /* @__PURE__ */ jsx51(View41, { style: { height: 12 } }),
5923
6009
  /* @__PURE__ */ jsx51(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
@@ -5943,7 +6029,10 @@ function ChatPanel({
5943
6029
  }
5944
6030
  ),
5945
6031
  composer: {
5946
- disabled: Boolean(loading) || Boolean(sendDisabled) || Boolean(forking),
6032
+ // Keep the input editable even when sending is disallowed (e.g. agent still working),
6033
+ // otherwise iOS will drop focus/keyboard and BottomSheet can get "stuck" with a keyboard-sized gap.
6034
+ disabled: Boolean(loading) || Boolean(forking),
6035
+ sendDisabled: Boolean(sendDisabled) || Boolean(loading) || Boolean(forking),
5947
6036
  sending: Boolean(sending),
5948
6037
  autoFocus: autoFocusComposer,
5949
6038
  onSend: handleSend,
@@ -5957,7 +6046,7 @@ function ChatPanel({
5957
6046
  }
5958
6047
 
5959
6048
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
5960
- import * as React36 from "react";
6049
+ import * as React37 from "react";
5961
6050
  import { Pressable as Pressable14, View as View43 } from "react-native";
5962
6051
 
5963
6052
  // src/components/primitives/Modal.tsx
@@ -5966,7 +6055,7 @@ import {
5966
6055
  Pressable as Pressable13,
5967
6056
  View as View42
5968
6057
  } from "react-native";
5969
- import { jsx as jsx52, jsxs as jsxs31 } from "react/jsx-runtime";
6058
+ import { jsx as jsx52, jsxs as jsxs32 } from "react/jsx-runtime";
5970
6059
  function Modal({
5971
6060
  visible,
5972
6061
  onRequestClose,
@@ -5982,7 +6071,7 @@ function Modal({
5982
6071
  transparent: true,
5983
6072
  animationType: "fade",
5984
6073
  onRequestClose,
5985
- children: /* @__PURE__ */ jsxs31(View42, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
6074
+ children: /* @__PURE__ */ jsxs32(View42, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
5986
6075
  /* @__PURE__ */ jsx52(
5987
6076
  Pressable13,
5988
6077
  {
@@ -5998,7 +6087,7 @@ function Modal({
5998
6087
  }
5999
6088
 
6000
6089
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6001
- import { jsx as jsx53, jsxs as jsxs32 } from "react/jsx-runtime";
6090
+ import { jsx as jsx53, jsxs as jsxs33 } from "react/jsx-runtime";
6002
6091
  function ConfirmMergeRequestDialog({
6003
6092
  visible,
6004
6093
  onOpenChange,
@@ -6009,14 +6098,14 @@ function ConfirmMergeRequestDialog({
6009
6098
  onTestFirst
6010
6099
  }) {
6011
6100
  const theme = useTheme();
6012
- const close = React36.useCallback(() => onOpenChange(false), [onOpenChange]);
6101
+ const close = React37.useCallback(() => onOpenChange(false), [onOpenChange]);
6013
6102
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6014
- const handleConfirm = React36.useCallback(() => {
6103
+ const handleConfirm = React37.useCallback(() => {
6015
6104
  if (!mergeRequest) return;
6016
6105
  onOpenChange(false);
6017
6106
  void onConfirm();
6018
6107
  }, [mergeRequest, onConfirm, onOpenChange]);
6019
- const handleTestFirst = React36.useCallback(() => {
6108
+ const handleTestFirst = React37.useCallback(() => {
6020
6109
  if (!mergeRequest) return;
6021
6110
  onOpenChange(false);
6022
6111
  void onTestFirst(mergeRequest);
@@ -6028,7 +6117,7 @@ function ConfirmMergeRequestDialog({
6028
6117
  justifyContent: "center",
6029
6118
  alignSelf: "stretch"
6030
6119
  };
6031
- return /* @__PURE__ */ jsxs32(
6120
+ return /* @__PURE__ */ jsxs33(
6032
6121
  Modal,
6033
6122
  {
6034
6123
  visible,
@@ -6051,7 +6140,7 @@ function ConfirmMergeRequestDialog({
6051
6140
  children: "Are you sure you want to approve this merge request?"
6052
6141
  }
6053
6142
  ) }),
6054
- /* @__PURE__ */ jsxs32(View43, { style: { marginTop: 16 }, children: [
6143
+ /* @__PURE__ */ jsxs33(View43, { style: { marginTop: 16 }, children: [
6055
6144
  /* @__PURE__ */ jsx53(
6056
6145
  View43,
6057
6146
  {
@@ -6165,7 +6254,7 @@ function ConfirmMergeFlow({
6165
6254
  }
6166
6255
 
6167
6256
  // src/studio/ui/StudioOverlay.tsx
6168
- import { Fragment as Fragment6, jsx as jsx55, jsxs as jsxs33 } from "react/jsx-runtime";
6257
+ import { Fragment as Fragment6, jsx as jsx55, jsxs as jsxs34 } from "react/jsx-runtime";
6169
6258
  function StudioOverlay({
6170
6259
  captureTargetRef,
6171
6260
  app,
@@ -6196,28 +6285,31 @@ function StudioOverlay({
6196
6285
  }) {
6197
6286
  const theme = useTheme();
6198
6287
  const { width } = useWindowDimensions4();
6199
- const [sheetOpen, setSheetOpen] = React37.useState(false);
6200
- const [activePage, setActivePage] = React37.useState("preview");
6201
- const [drawing, setDrawing] = React37.useState(false);
6202
- const [chatAttachments, setChatAttachments] = React37.useState([]);
6203
- const [commentsAppId, setCommentsAppId] = React37.useState(null);
6204
- const [commentsCount, setCommentsCount] = React37.useState(null);
6205
- const [confirmMrId, setConfirmMrId] = React37.useState(null);
6206
- const confirmMr = React37.useMemo(
6288
+ const [sheetOpen, setSheetOpen] = React38.useState(false);
6289
+ const [activePage, setActivePage] = React38.useState("preview");
6290
+ const [drawing, setDrawing] = React38.useState(false);
6291
+ const [chatAttachments, setChatAttachments] = React38.useState([]);
6292
+ const [commentsAppId, setCommentsAppId] = React38.useState(null);
6293
+ const [commentsCount, setCommentsCount] = React38.useState(null);
6294
+ const [confirmMrId, setConfirmMrId] = React38.useState(null);
6295
+ const confirmMr = React38.useMemo(
6207
6296
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6208
6297
  [confirmMrId, incomingMergeRequests]
6209
6298
  );
6210
- const closeSheet = React37.useCallback(() => {
6211
- setSheetOpen(false);
6212
- Keyboard5.dismiss();
6299
+ const handleSheetOpenChange = React38.useCallback((open) => {
6300
+ setSheetOpen(open);
6301
+ if (!open) Keyboard5.dismiss();
6213
6302
  }, []);
6214
- const openSheet = React37.useCallback(() => setSheetOpen(true), []);
6215
- const goToChat = React37.useCallback(() => {
6303
+ const closeSheet = React38.useCallback(() => {
6304
+ handleSheetOpenChange(false);
6305
+ }, [handleSheetOpenChange]);
6306
+ const openSheet = React38.useCallback(() => setSheetOpen(true), []);
6307
+ const goToChat = React38.useCallback(() => {
6216
6308
  setActivePage("chat");
6217
6309
  openSheet();
6218
6310
  }, [openSheet]);
6219
- const backToPreview = React37.useCallback(() => {
6220
- if (Platform7.OS !== "ios") {
6311
+ const backToPreview = React38.useCallback(() => {
6312
+ if (Platform8.OS !== "ios") {
6221
6313
  Keyboard5.dismiss();
6222
6314
  setActivePage("preview");
6223
6315
  return;
@@ -6234,11 +6326,11 @@ function StudioOverlay({
6234
6326
  const t = setTimeout(finalize, 350);
6235
6327
  Keyboard5.dismiss();
6236
6328
  }, []);
6237
- const startDraw = React37.useCallback(() => {
6329
+ const startDraw = React38.useCallback(() => {
6238
6330
  setDrawing(true);
6239
6331
  closeSheet();
6240
6332
  }, [closeSheet]);
6241
- const handleDrawCapture = React37.useCallback(
6333
+ const handleDrawCapture = React38.useCallback(
6242
6334
  (dataUrl) => {
6243
6335
  setChatAttachments((prev) => [...prev, dataUrl]);
6244
6336
  setDrawing(false);
@@ -6247,7 +6339,7 @@ function StudioOverlay({
6247
6339
  },
6248
6340
  [openSheet]
6249
6341
  );
6250
- const toggleSheet = React37.useCallback(async () => {
6342
+ const toggleSheet = React38.useCallback(async () => {
6251
6343
  if (!sheetOpen) {
6252
6344
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6253
6345
  if (shouldExitTest) {
@@ -6259,7 +6351,7 @@ function StudioOverlay({
6259
6351
  closeSheet();
6260
6352
  }
6261
6353
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6262
- const handleTestMr = React37.useCallback(
6354
+ const handleTestMr = React38.useCallback(
6263
6355
  async (mr) => {
6264
6356
  if (!onTestMr) return;
6265
6357
  await onTestMr(mr);
@@ -6267,9 +6359,9 @@ function StudioOverlay({
6267
6359
  },
6268
6360
  [closeSheet, onTestMr]
6269
6361
  );
6270
- return /* @__PURE__ */ jsxs33(Fragment6, { children: [
6362
+ return /* @__PURE__ */ jsxs34(Fragment6, { children: [
6271
6363
  /* @__PURE__ */ jsx55(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
6272
- /* @__PURE__ */ jsx55(StudioBottomSheet, { open: sheetOpen, onOpenChange: setSheetOpen, children: /* @__PURE__ */ jsx55(
6364
+ /* @__PURE__ */ jsx55(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ jsx55(
6273
6365
  StudioSheetPager,
6274
6366
  {
6275
6367
  activePage,
@@ -6369,7 +6461,7 @@ function StudioOverlay({
6369
6461
  }
6370
6462
 
6371
6463
  // src/studio/ComergeStudio.tsx
6372
- import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
6464
+ import { jsx as jsx56, jsxs as jsxs35 } from "react/jsx-runtime";
6373
6465
  function ComergeStudio({
6374
6466
  appId,
6375
6467
  apiKey,
@@ -6377,16 +6469,16 @@ function ComergeStudio({
6377
6469
  onNavigateHome,
6378
6470
  style
6379
6471
  }) {
6380
- const [activeAppId, setActiveAppId] = React38.useState(appId);
6381
- const [runtimeAppId, setRuntimeAppId] = React38.useState(appId);
6382
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React38.useState(null);
6383
- const platform = React38.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6384
- React38.useEffect(() => {
6472
+ const [activeAppId, setActiveAppId] = React39.useState(appId);
6473
+ const [runtimeAppId, setRuntimeAppId] = React39.useState(appId);
6474
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React39.useState(null);
6475
+ const platform = React39.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6476
+ React39.useEffect(() => {
6385
6477
  setActiveAppId(appId);
6386
6478
  setRuntimeAppId(appId);
6387
6479
  setPendingRuntimeTargetAppId(null);
6388
6480
  }, [appId]);
6389
- const captureTargetRef = React38.useRef(null);
6481
+ const captureTargetRef = React39.useRef(null);
6390
6482
  return /* @__PURE__ */ jsx56(StudioBootstrap, { apiKey, children: ({ userId }) => /* @__PURE__ */ jsx56(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx56(
6391
6483
  ComergeStudioInner,
6392
6484
  {
@@ -6422,11 +6514,11 @@ function ComergeStudioInner({
6422
6514
  const { app, loading: appLoading } = useApp(activeAppId);
6423
6515
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6424
6516
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6425
- const sawEditingOnPendingTargetRef = React38.useRef(false);
6426
- React38.useEffect(() => {
6517
+ const sawEditingOnPendingTargetRef = React39.useRef(false);
6518
+ React39.useEffect(() => {
6427
6519
  sawEditingOnPendingTargetRef.current = false;
6428
6520
  }, [pendingRuntimeTargetAppId]);
6429
- React38.useEffect(() => {
6521
+ React39.useEffect(() => {
6430
6522
  if (!pendingRuntimeTargetAppId) return;
6431
6523
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6432
6524
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6446,10 +6538,10 @@ function ComergeStudioInner({
6446
6538
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6447
6539
  const thread = useThreadMessages(threadId);
6448
6540
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6449
- const hasOpenOutgoingMr = React38.useMemo(() => {
6541
+ const hasOpenOutgoingMr = React39.useMemo(() => {
6450
6542
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6451
6543
  }, [mergeRequests.lists.outgoing]);
6452
- const incomingReviewMrs = React38.useMemo(() => {
6544
+ const incomingReviewMrs = React39.useMemo(() => {
6453
6545
  if (!userId) return mergeRequests.lists.incoming;
6454
6546
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6455
6547
  }, [mergeRequests.lists.incoming, userId]);
@@ -6471,16 +6563,16 @@ function ComergeStudioInner({
6471
6563
  uploadAttachments: uploader.uploadBase64Images
6472
6564
  });
6473
6565
  const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6474
- const [processingMrId, setProcessingMrId] = React38.useState(null);
6475
- const [testingMrId, setTestingMrId] = React38.useState(null);
6476
- const chatShowTypingIndicator = React38.useMemo(() => {
6566
+ const [processingMrId, setProcessingMrId] = React39.useState(null);
6567
+ const [testingMrId, setTestingMrId] = React39.useState(null);
6568
+ const chatShowTypingIndicator = React39.useMemo(() => {
6477
6569
  var _a;
6478
6570
  if (!thread.raw || thread.raw.length === 0) return false;
6479
6571
  const last = thread.raw[thread.raw.length - 1];
6480
6572
  const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
6481
6573
  return payloadType !== "outcome";
6482
6574
  }, [thread.raw]);
6483
- return /* @__PURE__ */ jsx56(View45, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs34(View45, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6575
+ return /* @__PURE__ */ jsx56(View45, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs35(View45, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6484
6576
  /* @__PURE__ */ jsx56(RuntimeRenderer, { appKey, bundlePath: bundle.bundlePath, renderToken: bundle.renderToken }),
6485
6577
  /* @__PURE__ */ jsx56(
6486
6578
  StudioOverlay,