@comergehq/studio 0.1.7 → 0.1.8

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";
1684
+ import * as React38 from "react";
1640
1685
  import { Keyboard as Keyboard5, Platform as Platform7, 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, Platform as Platform2, 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,43 @@ 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(() => {
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;
1769
+ const sheet = resolvedSheetRef.current;
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
+ }
1777
+ });
1778
+ return () => sub.remove();
1779
+ }, [open, resolvedSheetRef]);
1780
+ React9.useEffect(() => {
1714
1781
  if (Platform2.OS !== "ios") return;
1715
1782
  const sub = Keyboard.addListener("keyboardDidHide", () => {
1716
1783
  const sheet = resolvedSheetRef.current;
1717
1784
  if (!sheet || !open) return;
1718
1785
  const targetIndex = snapPoints.length - 1;
1719
- setTimeout(() => sheet.snapToIndex(targetIndex), 10);
1786
+ if (currentIndexRef.current === targetIndex) {
1787
+ setTimeout(() => sheet.snapToIndex(targetIndex), 10);
1788
+ }
1720
1789
  });
1721
1790
  return () => sub.remove();
1722
1791
  }, [open, resolvedSheetRef, snapPoints.length]);
1723
- React8.useEffect(() => {
1792
+ React9.useEffect(() => {
1724
1793
  const sheet = resolvedSheetRef.current;
1725
1794
  if (!sheet) return;
1726
1795
  if (open) {
@@ -1729,8 +1798,9 @@ function StudioBottomSheet({
1729
1798
  sheet.close();
1730
1799
  }
1731
1800
  }, [open, resolvedSheetRef, snapPoints.length]);
1732
- const handleChange = React8.useCallback(
1801
+ const handleChange = React9.useCallback(
1733
1802
  (index) => {
1803
+ currentIndexRef.current = index;
1734
1804
  onOpenChange == null ? void 0 : onOpenChange(index >= 0);
1735
1805
  },
1736
1806
  [onOpenChange]
@@ -1742,7 +1812,7 @@ function StudioBottomSheet({
1742
1812
  index: open ? snapPoints.length - 1 : -1,
1743
1813
  snapPoints,
1744
1814
  enablePanDownToClose: true,
1745
- keyboardBehavior: "extend",
1815
+ keyboardBehavior: "interactive",
1746
1816
  keyboardBlurBehavior: "restore",
1747
1817
  android_keyboardInputMode: "adjustResize",
1748
1818
  backgroundComponent: (props) => /* @__PURE__ */ jsx5(StudioSheetBackground, { ...props, renderBackground: background == null ? void 0 : background.renderBackground }),
@@ -1757,12 +1827,12 @@ function StudioBottomSheet({
1757
1827
  }
1758
1828
 
1759
1829
  // src/components/studio-sheet/StudioSheetPager.tsx
1760
- import * as React9 from "react";
1830
+ import * as React10 from "react";
1761
1831
  import { Animated } from "react-native";
1762
1832
  import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1763
1833
  function StudioSheetPager({ activePage, width, preview, chat, style }) {
1764
- const anim = React9.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
1765
- React9.useEffect(() => {
1834
+ const anim = React10.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
1835
+ React10.useEffect(() => {
1766
1836
  Animated.spring(anim, {
1767
1837
  toValue: activePage === "chat" ? 1 : 0,
1768
1838
  useNativeDriver: true,
@@ -1811,7 +1881,7 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
1811
1881
  }
1812
1882
 
1813
1883
  // src/components/floating-draggable-button/FloatingDraggableButton.tsx
1814
- import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef4 } from "react";
1884
+ import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef6 } from "react";
1815
1885
  import {
1816
1886
  PanResponder,
1817
1887
  Pressable,
@@ -1890,8 +1960,8 @@ function FloatingDraggableButton({
1890
1960
  const theme = useTheme();
1891
1961
  const { width, height } = useWindowDimensions();
1892
1962
  const isDanger = variant === "danger";
1893
- const onPressRef = useRef4(onPress);
1894
- useEffect8(() => {
1963
+ const onPressRef = useRef6(onPress);
1964
+ useEffect9(() => {
1895
1965
  onPressRef.current = onPress;
1896
1966
  }, [onPress]);
1897
1967
  const fallbackBgColor = useMemo3(() => {
@@ -1905,8 +1975,8 @@ function FloatingDraggableButton({
1905
1975
  const rotation = useSharedValue(ENTER_ROTATION_FROM_DEG);
1906
1976
  const opacity = useSharedValue(1);
1907
1977
  const borderPulse = useSharedValue(0);
1908
- const startPos = useRef4({ x: 0, y: 0 });
1909
- const isAnimatingOut = useRef4(false);
1978
+ const startPos = useRef6({ x: 0, y: 0 });
1979
+ const isAnimatingOut = useRef6(false);
1910
1980
  const animateToHidden = useCallback8(
1911
1981
  (options) => {
1912
1982
  translateX.value = withSpring(getHiddenTranslateX(size), SPRING_POSITION);
@@ -1942,7 +2012,7 @@ function FloatingDraggableButton({
1942
2012
  }
1943
2013
  });
1944
2014
  }, [animateToHidden]);
1945
- useEffect8(() => {
2015
+ useEffect9(() => {
1946
2016
  if (isLoading) {
1947
2017
  borderPulse.value = withRepeat(
1948
2018
  withSequence(
@@ -1968,7 +2038,7 @@ function FloatingDraggableButton({
1968
2038
  rotation.value = withSpring(0, SPRING_ROTATION_IN);
1969
2039
  opacity.value = withTiming(1, TIMING_OPACITY_IN);
1970
2040
  }, [height, offset.bottom, offset.left, opacity, rotation, scale, size, translateX, translateY]);
1971
- useEffect8(() => {
2041
+ useEffect9(() => {
1972
2042
  const timer = setTimeout(() => {
1973
2043
  if (visible) {
1974
2044
  animateIn();
@@ -1976,7 +2046,7 @@ function FloatingDraggableButton({
1976
2046
  }, 100);
1977
2047
  return () => clearTimeout(timer);
1978
2048
  }, []);
1979
- useEffect8(() => {
2049
+ useEffect9(() => {
1980
2050
  if (visible && isAnimatingOut.current) {
1981
2051
  animateIn();
1982
2052
  } else if (!visible && !isAnimatingOut.current) {
@@ -1984,13 +2054,13 @@ function FloatingDraggableButton({
1984
2054
  isAnimatingOut.current = true;
1985
2055
  }
1986
2056
  }, [visible, animateIn, animateToHidden]);
1987
- useEffect8(() => {
2057
+ useEffect9(() => {
1988
2058
  if (forceShowTrigger > 0 && visible) {
1989
2059
  isAnimatingOut.current = false;
1990
2060
  animateIn();
1991
2061
  }
1992
2062
  }, [forceShowTrigger, visible, animateIn]);
1993
- const panResponder = useRef4(
2063
+ const panResponder = useRef6(
1994
2064
  PanResponder.create({
1995
2065
  onStartShouldSetPanResponder: () => true,
1996
2066
  onMoveShouldSetPanResponder: () => true,
@@ -2107,7 +2177,7 @@ var styles = StyleSheet.create({
2107
2177
  });
2108
2178
 
2109
2179
  // src/components/overlays/EdgeGlowFrame.tsx
2110
- import * as React10 from "react";
2180
+ import * as React11 from "react";
2111
2181
  import { Animated as Animated3, View as View6 } from "react-native";
2112
2182
  import { LinearGradient } from "expo-linear-gradient";
2113
2183
 
@@ -2150,8 +2220,8 @@ function EdgeGlowFrame({
2150
2220
  }) {
2151
2221
  const theme = useTheme();
2152
2222
  const alpha = Math.max(0, Math.min(1, intensity));
2153
- const anim = React10.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2154
- React10.useEffect(() => {
2223
+ const anim = React11.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2224
+ React11.useEffect(() => {
2155
2225
  Animated3.timing(anim, {
2156
2226
  toValue: visible ? 1 : 0,
2157
2227
  duration: 300,
@@ -2202,12 +2272,12 @@ function EdgeGlowFrame({
2202
2272
  }
2203
2273
 
2204
2274
  // src/components/draw/DrawModeOverlay.tsx
2205
- import * as React13 from "react";
2275
+ import * as React14 from "react";
2206
2276
  import { StyleSheet as StyleSheet3, View as View10 } from "react-native";
2207
2277
  import { captureRef } from "react-native-view-shot";
2208
2278
 
2209
2279
  // src/components/draw/DrawSurface.tsx
2210
- import * as React11 from "react";
2280
+ import * as React12 from "react";
2211
2281
  import { PanResponder as PanResponder2, StyleSheet as StyleSheet2, View as View7 } from "react-native";
2212
2282
  import Svg, { Path } from "react-native-svg";
2213
2283
 
@@ -2239,25 +2309,25 @@ function DrawSurface({
2239
2309
  style,
2240
2310
  minDistance = 1
2241
2311
  }) {
2242
- const [renderTick, setRenderTick] = React11.useState(0);
2243
- const currentPointsRef = React11.useRef([]);
2244
- const rafRef = React11.useRef(null);
2245
- const triggerRender = React11.useCallback(() => {
2312
+ const [renderTick, setRenderTick] = React12.useState(0);
2313
+ const currentPointsRef = React12.useRef([]);
2314
+ const rafRef = React12.useRef(null);
2315
+ const triggerRender = React12.useCallback(() => {
2246
2316
  if (rafRef.current !== null) return;
2247
2317
  rafRef.current = requestAnimationFrame(() => {
2248
2318
  rafRef.current = null;
2249
2319
  setRenderTick((n) => n + 1);
2250
2320
  });
2251
2321
  }, []);
2252
- React11.useEffect(() => () => {
2322
+ React12.useEffect(() => () => {
2253
2323
  if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
2254
2324
  }, []);
2255
- const onStart = React11.useCallback((e) => {
2325
+ const onStart = React12.useCallback((e) => {
2256
2326
  const { locationX, locationY } = e.nativeEvent;
2257
2327
  currentPointsRef.current = [{ x: locationX, y: locationY }];
2258
2328
  triggerRender();
2259
2329
  }, [triggerRender]);
2260
- const onMove = React11.useCallback((e, _g) => {
2330
+ const onMove = React12.useCallback((e, _g) => {
2261
2331
  const { locationX, locationY } = e.nativeEvent;
2262
2332
  const pts = currentPointsRef.current;
2263
2333
  if (pts.length > 0) {
@@ -2270,7 +2340,7 @@ function DrawSurface({
2270
2340
  currentPointsRef.current = [...pts, { x: locationX, y: locationY }];
2271
2341
  triggerRender();
2272
2342
  }, [minDistance, triggerRender]);
2273
- const onEnd = React11.useCallback(() => {
2343
+ const onEnd = React12.useCallback(() => {
2274
2344
  const points = currentPointsRef.current;
2275
2345
  if (points.length > 0) {
2276
2346
  onAddStroke({ points, color, width: strokeWidth });
@@ -2278,7 +2348,7 @@ function DrawSurface({
2278
2348
  currentPointsRef.current = [];
2279
2349
  triggerRender();
2280
2350
  }, [color, onAddStroke, strokeWidth, triggerRender]);
2281
- const panResponder = React11.useMemo(
2351
+ const panResponder = React12.useMemo(
2282
2352
  () => PanResponder2.create({
2283
2353
  onStartShouldSetPanResponder: () => true,
2284
2354
  onMoveShouldSetPanResponder: () => true,
@@ -2328,7 +2398,7 @@ var styles2 = StyleSheet2.create({
2328
2398
  });
2329
2399
 
2330
2400
  // src/components/draw/DrawToolbar.tsx
2331
- import * as React12 from "react";
2401
+ import * as React13 from "react";
2332
2402
  import {
2333
2403
  ActivityIndicator,
2334
2404
  Animated as Animated4,
@@ -2425,11 +2495,11 @@ function DrawToolbar({
2425
2495
  }) {
2426
2496
  const insets = useSafeAreaInsets2();
2427
2497
  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(() => {
2498
+ const [expanded, setExpanded] = React13.useState(false);
2499
+ const pos = React13.useRef(new Animated4.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2500
+ const start = React13.useRef({ x: 0, y: 0 });
2501
+ const currentPos = React13.useRef({ x: 0, y: 0 });
2502
+ React13.useEffect(() => {
2433
2503
  if (hidden) return;
2434
2504
  Animated4.spring(pos.y, {
2435
2505
  toValue: insets.top + 60,
@@ -2439,7 +2509,7 @@ function DrawToolbar({
2439
2509
  mass: 0.8
2440
2510
  }).start();
2441
2511
  }, [hidden, insets.top, pos.y]);
2442
- React12.useEffect(() => {
2512
+ React13.useEffect(() => {
2443
2513
  const id = pos.addListener((v) => {
2444
2514
  currentPos.current = { x: v.x ?? 0, y: v.y ?? 0 };
2445
2515
  });
@@ -2447,7 +2517,7 @@ function DrawToolbar({
2447
2517
  pos.removeListener(id);
2448
2518
  };
2449
2519
  }, [pos]);
2450
- const clamp2 = React12.useCallback(
2520
+ const clamp2 = React13.useCallback(
2451
2521
  (x, y) => {
2452
2522
  const minX = 10;
2453
2523
  const maxX = Math.max(10, screenWidth - 230);
@@ -2457,7 +2527,7 @@ function DrawToolbar({
2457
2527
  },
2458
2528
  [insets.top, screenHeight, screenWidth]
2459
2529
  );
2460
- const panResponder = React12.useMemo(
2530
+ const panResponder = React13.useMemo(
2461
2531
  () => PanResponder3.create({
2462
2532
  onStartShouldSetPanResponder: () => false,
2463
2533
  onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 5 || Math.abs(g.dy) > 5,
@@ -2485,7 +2555,7 @@ function DrawToolbar({
2485
2555
  children
2486
2556
  }) {
2487
2557
  const isDisabled = Boolean(disabled) || Boolean(capturingDisabled);
2488
- const [pressed, setPressed] = React12.useState(false);
2558
+ const [pressed, setPressed] = React13.useState(false);
2489
2559
  return /* @__PURE__ */ jsx11(
2490
2560
  View9,
2491
2561
  {
@@ -2623,7 +2693,7 @@ function DrawModeOverlay({
2623
2693
  renderDragHandle
2624
2694
  }) {
2625
2695
  const theme = useTheme();
2626
- const defaultPalette = React13.useMemo(
2696
+ const defaultPalette = React14.useMemo(
2627
2697
  () => [
2628
2698
  "#EF4444",
2629
2699
  // Red
@@ -2641,11 +2711,11 @@ function DrawModeOverlay({
2641
2711
  []
2642
2712
  );
2643
2713
  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(() => {
2714
+ const [selectedColor, setSelectedColor] = React14.useState(colors[0] ?? "#EF4444");
2715
+ const [strokes, setStrokes] = React14.useState([]);
2716
+ const [capturing, setCapturing] = React14.useState(false);
2717
+ const [hideUi, setHideUi] = React14.useState(false);
2718
+ React14.useEffect(() => {
2649
2719
  if (!visible) return;
2650
2720
  setStrokes([]);
2651
2721
  setSelectedColor(colors[0] ?? "#EF4444");
@@ -2653,14 +2723,14 @@ function DrawModeOverlay({
2653
2723
  setHideUi(false);
2654
2724
  }, [colors, visible]);
2655
2725
  const canUndo = strokes.length > 0;
2656
- const handleUndo = React13.useCallback(() => {
2726
+ const handleUndo = React14.useCallback(() => {
2657
2727
  setStrokes((prev) => prev.slice(0, -1));
2658
2728
  }, []);
2659
- const handleCancel = React13.useCallback(() => {
2729
+ const handleCancel = React14.useCallback(() => {
2660
2730
  setStrokes([]);
2661
2731
  onCancel();
2662
2732
  }, [onCancel]);
2663
- const handleDone = React13.useCallback(async () => {
2733
+ const handleDone = React14.useCallback(async () => {
2664
2734
  if (!captureTargetRef.current || capturing) return;
2665
2735
  try {
2666
2736
  setCapturing(true);
@@ -2720,7 +2790,7 @@ var styles3 = StyleSheet3.create({
2720
2790
  });
2721
2791
 
2722
2792
  // src/components/comments/AppCommentsSheet.tsx
2723
- import * as React20 from "react";
2793
+ import * as React21 from "react";
2724
2794
  import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform4, Pressable as Pressable5, View as View14 } from "react-native";
2725
2795
  import {
2726
2796
  BottomSheetBackdrop,
@@ -2732,7 +2802,7 @@ import { LiquidGlassView as LiquidGlassView4, isLiquidGlassSupported as isLiquid
2732
2802
  import { Play as Play2 } from "lucide-react-native";
2733
2803
 
2734
2804
  // src/components/chat/ChatComposer.tsx
2735
- import * as React15 from "react";
2805
+ import * as React16 from "react";
2736
2806
  import {
2737
2807
  ActivityIndicator as ActivityIndicator2,
2738
2808
  Animated as Animated5,
@@ -2746,11 +2816,11 @@ import { LiquidGlassView as LiquidGlassView3, isLiquidGlassSupported as isLiquid
2746
2816
  import { Plus } from "lucide-react-native";
2747
2817
 
2748
2818
  // src/components/chat/MultilineTextInput.tsx
2749
- import * as React14 from "react";
2819
+ import * as React15 from "react";
2750
2820
  import { TextInput } from "react-native";
2751
2821
  import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
2752
2822
  import { jsx as jsx13 } from "react/jsx-runtime";
2753
- var MultilineTextInput = React14.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2823
+ var MultilineTextInput = React15.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2754
2824
  const theme = useTheme();
2755
2825
  const baseStyle = {
2756
2826
  minHeight: 44,
@@ -2834,7 +2904,7 @@ function AspectRatioThumbnail({
2834
2904
  onRemove,
2835
2905
  renderRemoveIcon
2836
2906
  }) {
2837
- const [aspectRatio, setAspectRatio] = React15.useState(1);
2907
+ const [aspectRatio, setAspectRatio] = React16.useState(1);
2838
2908
  return /* @__PURE__ */ jsxs8(View11, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
2839
2909
  /* @__PURE__ */ jsx15(View11, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx15(
2840
2910
  Image,
@@ -2876,6 +2946,7 @@ function ChatComposer({
2876
2946
  onChangeValue,
2877
2947
  placeholder = "Describe the idea you want to build",
2878
2948
  disabled = false,
2949
+ sendDisabled = false,
2879
2950
  sending = false,
2880
2951
  autoFocus = false,
2881
2952
  onSend,
@@ -2890,19 +2961,19 @@ function ChatComposer({
2890
2961
  style
2891
2962
  }) {
2892
2963
  const theme = useTheme();
2893
- const [internal, setInternal] = React15.useState("");
2964
+ const [internal, setInternal] = React16.useState("");
2894
2965
  const text = value ?? internal;
2895
2966
  const setText = onChangeValue ?? setInternal;
2896
2967
  const hasAttachments = attachments.length > 0;
2897
2968
  const hasText = text.trim().length > 0;
2898
2969
  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(() => {
2970
+ const isButtonDisabled = sending || disabled || sendDisabled;
2971
+ const maxInputHeight = React16.useMemo(() => Dimensions.get("window").height * 0.5, []);
2972
+ const shakeAnim = React16.useRef(new Animated5.Value(0)).current;
2973
+ const [sendPressed, setSendPressed] = React16.useState(false);
2974
+ const inputRef = React16.useRef(null);
2975
+ const prevAutoFocusRef = React16.useRef(false);
2976
+ React16.useEffect(() => {
2906
2977
  const shouldFocus = autoFocus && !prevAutoFocusRef.current && !disabled && !sending;
2907
2978
  prevAutoFocusRef.current = autoFocus;
2908
2979
  if (!shouldFocus) return;
@@ -2912,7 +2983,7 @@ function ChatComposer({
2912
2983
  }, 75);
2913
2984
  return () => clearTimeout(t);
2914
2985
  }, [autoFocus, disabled, sending]);
2915
- const triggerShake = React15.useCallback(() => {
2986
+ const triggerShake = React16.useCallback(() => {
2916
2987
  shakeAnim.setValue(0);
2917
2988
  Animated5.sequence([
2918
2989
  Animated5.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
@@ -2922,7 +2993,7 @@ function ChatComposer({
2922
2993
  Animated5.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
2923
2994
  ]).start();
2924
2995
  }, [shakeAnim]);
2925
- const handleSend = React15.useCallback(async () => {
2996
+ const handleSend = React16.useCallback(async () => {
2926
2997
  if (isButtonDisabled) return;
2927
2998
  if (!hasText) {
2928
2999
  triggerShake();
@@ -3055,7 +3126,7 @@ function ChatComposer({
3055
3126
  }
3056
3127
 
3057
3128
  // src/components/comments/CommentRow.tsx
3058
- import * as React16 from "react";
3129
+ import * as React17 from "react";
3059
3130
  import { View as View13 } from "react-native";
3060
3131
 
3061
3132
  // src/components/primitives/Avatar.tsx
@@ -3127,9 +3198,9 @@ function formatTimeAgo(iso) {
3127
3198
  import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
3128
3199
  function CommentRow({ comment, showDivider }) {
3129
3200
  const theme = useTheme();
3130
- const [authorName, setAuthorName] = React16.useState(null);
3131
- const [authorAvatar, setAuthorAvatar] = React16.useState(null);
3132
- React16.useEffect(() => {
3201
+ const [authorName, setAuthorName] = React17.useState(null);
3202
+ const [authorAvatar, setAuthorAvatar] = React17.useState(null);
3203
+ React17.useEffect(() => {
3133
3204
  let cancelled = false;
3134
3205
  (async () => {
3135
3206
  try {
@@ -3169,7 +3240,7 @@ function CommentRow({ comment, showDivider }) {
3169
3240
  }
3170
3241
 
3171
3242
  // src/components/comments/useAppComments.ts
3172
- import * as React17 from "react";
3243
+ import * as React18 from "react";
3173
3244
 
3174
3245
  // src/data/comments/remote.ts
3175
3246
  var AppCommentsRemoteDataSourceImpl = class extends BaseRemote {
@@ -3241,18 +3312,18 @@ var appCommentsRepository = new AppCommentsRepositoryImpl(appCommentsRemoteDataS
3241
3312
 
3242
3313
  // src/components/comments/useAppComments.ts
3243
3314
  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) => {
3315
+ const [comments, setComments] = React18.useState([]);
3316
+ const [loading, setLoading] = React18.useState(false);
3317
+ const [sending, setSending] = React18.useState(false);
3318
+ const [error, setError] = React18.useState(null);
3319
+ const sortByCreatedAtAsc = React18.useCallback((items) => {
3249
3320
  return [...items].sort((a, b) => {
3250
3321
  const at = a.createdAt ? new Date(a.createdAt).getTime() : 0;
3251
3322
  const bt = b.createdAt ? new Date(b.createdAt).getTime() : 0;
3252
3323
  return at - bt;
3253
3324
  });
3254
3325
  }, []);
3255
- const refresh = React17.useCallback(async () => {
3326
+ const refresh = React18.useCallback(async () => {
3256
3327
  if (!appId) {
3257
3328
  setComments([]);
3258
3329
  return;
@@ -3269,10 +3340,10 @@ function useAppComments(appId) {
3269
3340
  setLoading(false);
3270
3341
  }
3271
3342
  }, [appId, sortByCreatedAtAsc]);
3272
- React17.useEffect(() => {
3343
+ React18.useEffect(() => {
3273
3344
  void refresh();
3274
3345
  }, [refresh]);
3275
- const create = React17.useCallback(
3346
+ const create = React18.useCallback(
3276
3347
  async (text) => {
3277
3348
  if (!appId) return;
3278
3349
  const trimmed = text.trim();
@@ -3295,11 +3366,11 @@ function useAppComments(appId) {
3295
3366
  }
3296
3367
 
3297
3368
  // src/components/comments/useAppDetails.ts
3298
- import * as React18 from "react";
3369
+ import * as React19 from "react";
3299
3370
  function useAppDetails(appId) {
3300
- const [app, setApp] = React18.useState(null);
3301
- const [loading, setLoading] = React18.useState(false);
3302
- React18.useEffect(() => {
3371
+ const [app, setApp] = React19.useState(null);
3372
+ const [loading, setLoading] = React19.useState(false);
3373
+ React19.useEffect(() => {
3303
3374
  if (!appId) {
3304
3375
  setApp(null);
3305
3376
  return;
@@ -3324,25 +3395,30 @@ function useAppDetails(appId) {
3324
3395
  }
3325
3396
 
3326
3397
  // src/components/comments/useIosKeyboardSnapFix.ts
3327
- import * as React19 from "react";
3398
+ import * as React20 from "react";
3328
3399
  import { Keyboard as Keyboard2, Platform as Platform3 } from "react-native";
3329
- function useIosKeyboardSnapFix(sheetRef) {
3330
- const [keyboardVisible, setKeyboardVisible] = React19.useState(false);
3331
- React19.useEffect(() => {
3400
+ function useIosKeyboardSnapFix(sheetRef, options) {
3401
+ const [keyboardVisible, setKeyboardVisible] = React20.useState(false);
3402
+ React20.useEffect(() => {
3332
3403
  if (Platform3.OS !== "ios") return;
3333
3404
  const show = Keyboard2.addListener("keyboardWillShow", () => setKeyboardVisible(true));
3334
3405
  const hide = Keyboard2.addListener("keyboardWillHide", () => {
3406
+ var _a;
3335
3407
  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);
3408
+ const target = (options == null ? void 0 : options.targetIndex) ?? 1;
3409
+ const current = ((_a = options == null ? void 0 : options.getCurrentIndex) == null ? void 0 : _a.call(options)) ?? null;
3410
+ if (current === target) {
3411
+ setTimeout(() => {
3412
+ var _a2, _b;
3413
+ return (_b = (_a2 = sheetRef.current) == null ? void 0 : _a2.snapToIndex) == null ? void 0 : _b.call(_a2, target);
3414
+ }, 10);
3415
+ }
3340
3416
  });
3341
3417
  return () => {
3342
3418
  show.remove();
3343
3419
  hide.remove();
3344
3420
  };
3345
- }, [sheetRef]);
3421
+ }, [options == null ? void 0 : options.getCurrentIndex, options == null ? void 0 : options.targetIndex, sheetRef]);
3346
3422
  return { keyboardVisible };
3347
3423
  }
3348
3424
 
@@ -3351,12 +3427,16 @@ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
3351
3427
  function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3352
3428
  const theme = useTheme();
3353
3429
  const insets = useSafeAreaInsets3();
3354
- const sheetRef = React20.useRef(null);
3355
- const snapPoints = React20.useMemo(() => ["50%", "90%"], []);
3430
+ const sheetRef = React21.useRef(null);
3431
+ const snapPoints = React21.useMemo(() => ["50%", "90%"], []);
3432
+ const currentIndexRef = React21.useRef(1);
3356
3433
  const { comments, loading, sending, error, create, refresh } = useAppComments(appId);
3357
3434
  const { app, loading: loadingApp } = useAppDetails(appId);
3358
- const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef);
3359
- React20.useEffect(() => {
3435
+ const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef, {
3436
+ getCurrentIndex: () => currentIndexRef.current,
3437
+ targetIndex: 1
3438
+ });
3439
+ React21.useEffect(() => {
3360
3440
  var _a, _b;
3361
3441
  if (appId) {
3362
3442
  (_a = sheetRef.current) == null ? void 0 : _a.present();
@@ -3365,21 +3445,22 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3365
3445
  (_b = sheetRef.current) == null ? void 0 : _b.dismiss();
3366
3446
  }
3367
3447
  }, [appId, refresh]);
3368
- React20.useEffect(() => {
3448
+ React21.useEffect(() => {
3369
3449
  if (!appId) return;
3370
3450
  onCountChange == null ? void 0 : onCountChange(comments.length);
3371
3451
  }, [appId, comments.length, onCountChange]);
3372
- const renderBackdrop = React20.useCallback(
3452
+ const renderBackdrop = React21.useCallback(
3373
3453
  (props) => /* @__PURE__ */ jsx18(BottomSheetBackdrop, { ...props, disappearsOnIndex: -1, appearsOnIndex: 0, opacity: 0.5 }),
3374
3454
  []
3375
3455
  );
3376
- const handleChange = React20.useCallback(
3456
+ const handleChange = React21.useCallback(
3377
3457
  (index) => {
3458
+ currentIndexRef.current = index;
3378
3459
  if (index === -1) onClose();
3379
3460
  },
3380
3461
  [onClose]
3381
3462
  );
3382
- const handlePlay = React20.useCallback(async () => {
3463
+ const handlePlay = React21.useCallback(async () => {
3383
3464
  var _a;
3384
3465
  if (!appId) return;
3385
3466
  (_a = sheetRef.current) == null ? void 0 : _a.dismiss();
@@ -3590,7 +3671,7 @@ function StudioSheetHeader({ left, center, right, style }) {
3590
3671
  }
3591
3672
 
3592
3673
  // src/components/studio-sheet/StudioSheetHeaderIconButton.tsx
3593
- import * as React21 from "react";
3674
+ import * as React22 from "react";
3594
3675
  import { Pressable as Pressable6, View as View17 } from "react-native";
3595
3676
  import { LiquidGlassView as LiquidGlassView5, isLiquidGlassSupported as isLiquidGlassSupported5 } from "@callstack/liquid-glass";
3596
3677
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -3605,7 +3686,7 @@ function StudioSheetHeaderIconButton({
3605
3686
  }) {
3606
3687
  const theme = useTheme();
3607
3688
  const size = 44;
3608
- const [pressed, setPressed] = React21.useState(false);
3689
+ const [pressed, setPressed] = React22.useState(false);
3609
3690
  const solidBg = intent === "danger" ? theme.colors.danger : intent === "primary" ? theme.colors.primary : theme.colors.neutral;
3610
3691
  const glassFallbackBg = theme.scheme === "dark" ? "#18181B" : "#F6F6F6";
3611
3692
  const glassInnerBg = intent === "danger" ? theme.colors.danger : theme.colors.primary;
@@ -3795,14 +3876,14 @@ function PreviewHeroCard({
3795
3876
  }
3796
3877
 
3797
3878
  // src/components/preview/PreviewPlaceholder.tsx
3798
- import * as React22 from "react";
3879
+ import * as React23 from "react";
3799
3880
  import { Animated as Animated6 } from "react-native";
3800
3881
  import { LinearGradient as LinearGradient2 } from "expo-linear-gradient";
3801
3882
  import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
3802
3883
  function PreviewPlaceholder({ visible, style }) {
3803
3884
  if (!visible) return null;
3804
- const opacityAnim = React22.useRef(new Animated6.Value(0)).current;
3805
- React22.useEffect(() => {
3885
+ const opacityAnim = React23.useRef(new Animated6.Value(0)).current;
3886
+ React23.useEffect(() => {
3806
3887
  if (!visible) return;
3807
3888
  const animation = Animated6.loop(
3808
3889
  Animated6.sequence([
@@ -4394,12 +4475,12 @@ function PreviewCustomizeSection({
4394
4475
  }
4395
4476
 
4396
4477
  // src/studio/ui/preview-panel/PreviewCollaborateSection.tsx
4397
- import * as React28 from "react";
4478
+ import * as React29 from "react";
4398
4479
  import { ActivityIndicator as ActivityIndicator6, Alert, View as View32 } from "react-native";
4399
4480
  import { Send as Send2 } from "lucide-react-native";
4400
4481
 
4401
4482
  // src/components/merge-requests/MergeRequestStatusCard.tsx
4402
- import * as React24 from "react";
4483
+ import * as React25 from "react";
4403
4484
  import { Animated as Animated7, Pressable as Pressable9, View as View28 } from "react-native";
4404
4485
  import { Ban, Check as Check3, CheckCheck, ChevronDown as ChevronDown2 } from "lucide-react-native";
4405
4486
 
@@ -4481,11 +4562,11 @@ function toIsoString(input) {
4481
4562
  }
4482
4563
 
4483
4564
  // src/components/merge-requests/useControlledExpansion.ts
4484
- import * as React23 from "react";
4565
+ import * as React24 from "react";
4485
4566
  function useControlledExpansion(props) {
4486
- const [uncontrolled, setUncontrolled] = React23.useState(false);
4567
+ const [uncontrolled, setUncontrolled] = React24.useState(false);
4487
4568
  const expanded = props.expanded ?? uncontrolled;
4488
- const setExpanded = React23.useCallback(
4569
+ const setExpanded = React24.useCallback(
4489
4570
  (next) => {
4490
4571
  var _a;
4491
4572
  (_a = props.onExpandedChange) == null ? void 0 : _a.call(props, next);
@@ -4510,8 +4591,8 @@ function MergeRequestStatusCard({
4510
4591
  const isDark = theme.scheme === "dark";
4511
4592
  const textColor = isDark ? "#FFFFFF" : "#000000";
4512
4593
  const subTextColor = isDark ? "#A1A1AA" : "#71717A";
4513
- const status = React24.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4514
- const { StatusIcon, iconColor, bgColor, statusText } = React24.useMemo(() => {
4594
+ const status = React25.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4595
+ const { StatusIcon, iconColor, bgColor, statusText } = React25.useMemo(() => {
4515
4596
  switch (mergeRequest.status) {
4516
4597
  case "approved":
4517
4598
  case "merged":
@@ -4542,8 +4623,8 @@ function MergeRequestStatusCard({
4542
4623
  const createdIso = toIsoString(mergeRequest.createdAt ?? null);
4543
4624
  const headerTimeAgo = updatedIso ? formatTimeAgo(updatedIso) : "";
4544
4625
  const createdTimeAgo = createdIso ? formatTimeAgo(createdIso) : "";
4545
- const rotate = React24.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4546
- React24.useEffect(() => {
4626
+ const rotate = React25.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4627
+ React25.useEffect(() => {
4547
4628
  Animated7.timing(rotate, {
4548
4629
  toValue: expanded ? 1 : 0,
4549
4630
  duration: 200,
@@ -4634,16 +4715,16 @@ function MergeRequestStatusCard({
4634
4715
  }
4635
4716
 
4636
4717
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
4637
- import * as React27 from "react";
4718
+ import * as React28 from "react";
4638
4719
  import { Animated as Animated9, FlatList, View as View31, useWindowDimensions as useWindowDimensions3 } from "react-native";
4639
4720
 
4640
4721
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
4641
- import * as React26 from "react";
4722
+ import * as React27 from "react";
4642
4723
  import { ActivityIndicator as ActivityIndicator5, Animated as Animated8, Pressable as Pressable11, View as View30 } from "react-native";
4643
4724
  import { Check as Check4, ChevronDown as ChevronDown3, Play as Play3, X as X3 } from "lucide-react-native";
4644
4725
 
4645
4726
  // src/components/merge-requests/ReviewMergeRequestActionButton.tsx
4646
- import * as React25 from "react";
4727
+ import * as React26 from "react";
4647
4728
  import { Pressable as Pressable10, View as View29 } from "react-native";
4648
4729
  import { jsx as jsx39 } from "react/jsx-runtime";
4649
4730
  function ReviewMergeRequestActionButton({
@@ -4654,7 +4735,7 @@ function ReviewMergeRequestActionButton({
4654
4735
  children,
4655
4736
  iconOnly
4656
4737
  }) {
4657
- const [pressed, setPressed] = React25.useState(false);
4738
+ const [pressed, setPressed] = React26.useState(false);
4658
4739
  const height = iconOnly ? 36 : 40;
4659
4740
  const width = iconOnly ? 36 : void 0;
4660
4741
  const paddingHorizontal = iconOnly ? 0 : 16;
@@ -4716,10 +4797,10 @@ function ReviewMergeRequestCard({
4716
4797
  onTest
4717
4798
  }) {
4718
4799
  const theme = useTheme();
4719
- const status = React26.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4800
+ const status = React27.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4720
4801
  const canAct = mr.status === "open";
4721
- const rotate = React26.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
4722
- React26.useEffect(() => {
4802
+ const rotate = React27.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
4803
+ React27.useEffect(() => {
4723
4804
  Animated8.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
4724
4805
  }, [isExpanded, rotate]);
4725
4806
  const position = total > 1 ? `${index + 1}/${total}` : "Merge request";
@@ -4851,11 +4932,11 @@ function ReviewMergeRequestCarousel({
4851
4932
  }) {
4852
4933
  const theme = useTheme();
4853
4934
  const { width } = useWindowDimensions3();
4854
- const [expanded, setExpanded] = React27.useState({});
4855
- const carouselScrollX = React27.useRef(new Animated9.Value(0)).current;
4935
+ const [expanded, setExpanded] = React28.useState({});
4936
+ const carouselScrollX = React28.useRef(new Animated9.Value(0)).current;
4856
4937
  const peekAmount = 24;
4857
4938
  const gap = 16;
4858
- const cardWidth = React27.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
4939
+ const cardWidth = React28.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
4859
4940
  const snapInterval = cardWidth + gap;
4860
4941
  const dotColor = theme.scheme === "dark" ? "#FFFFFF" : "#000000";
4861
4942
  if (mergeRequests.length === 0) return null;
@@ -4954,7 +5035,7 @@ function PreviewCollaborateSection({
4954
5035
  onTestMr
4955
5036
  }) {
4956
5037
  const theme = useTheme();
4957
- const [submittingMr, setSubmittingMr] = React28.useState(false);
5038
+ const [submittingMr, setSubmittingMr] = React29.useState(false);
4958
5039
  const hasSection = canSubmitMergeRequest || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
4959
5040
  if (!hasSection) return null;
4960
5041
  const showActionsSubtitle = canSubmitMergeRequest && onSubmitMergeRequest || onTestMr && incomingMergeRequests.length > 0;
@@ -5062,7 +5143,7 @@ function PreviewCollaborateSection({
5062
5143
  }
5063
5144
 
5064
5145
  // src/studio/ui/preview-panel/usePreviewPanelData.ts
5065
- import * as React30 from "react";
5146
+ import * as React31 from "react";
5066
5147
 
5067
5148
  // src/data/apps/images/remote.ts
5068
5149
  var AppImagesRemoteDataSourceImpl = class extends BaseRemote {
@@ -5113,7 +5194,7 @@ var AppImagesRepositoryImpl = class extends BaseRepository {
5113
5194
  var appImagesRepository = new AppImagesRepositoryImpl(appImagesRemoteDataSource);
5114
5195
 
5115
5196
  // src/studio/hooks/useAppStats.ts
5116
- import * as React29 from "react";
5197
+ import * as React30 from "react";
5117
5198
  import * as Haptics2 from "expo-haptics";
5118
5199
 
5119
5200
  // src/data/likes/remote.ts
@@ -5182,34 +5263,34 @@ function useAppStats({
5182
5263
  initialIsLiked = false,
5183
5264
  onOpenComments
5184
5265
  }) {
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(() => {
5266
+ const [likeCount, setLikeCount] = React30.useState(initialLikes);
5267
+ const [commentCount, setCommentCount] = React30.useState(initialComments);
5268
+ const [forkCount, setForkCount] = React30.useState(initialForks);
5269
+ const [isLiked, setIsLiked] = React30.useState(initialIsLiked);
5270
+ const didMutateRef = React30.useRef(false);
5271
+ const lastAppIdRef = React30.useRef("");
5272
+ React30.useEffect(() => {
5192
5273
  if (lastAppIdRef.current === appId) return;
5193
5274
  lastAppIdRef.current = appId;
5194
5275
  didMutateRef.current = false;
5195
5276
  }, [appId]);
5196
- React29.useEffect(() => {
5277
+ React30.useEffect(() => {
5197
5278
  if (didMutateRef.current) return;
5198
5279
  setLikeCount(initialLikes);
5199
5280
  }, [appId, initialLikes]);
5200
- React29.useEffect(() => {
5281
+ React30.useEffect(() => {
5201
5282
  if (didMutateRef.current) return;
5202
5283
  setCommentCount(initialComments);
5203
5284
  }, [appId, initialComments]);
5204
- React29.useEffect(() => {
5285
+ React30.useEffect(() => {
5205
5286
  if (didMutateRef.current) return;
5206
5287
  setForkCount(initialForks);
5207
5288
  }, [appId, initialForks]);
5208
- React29.useEffect(() => {
5289
+ React30.useEffect(() => {
5209
5290
  if (didMutateRef.current) return;
5210
5291
  setIsLiked(initialIsLiked);
5211
5292
  }, [appId, initialIsLiked]);
5212
- const handleLike = React29.useCallback(async () => {
5293
+ const handleLike = React30.useCallback(async () => {
5213
5294
  var _a, _b;
5214
5295
  if (!appId) return;
5215
5296
  didMutateRef.current = true;
@@ -5233,7 +5314,7 @@ function useAppStats({
5233
5314
  setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
5234
5315
  }
5235
5316
  }, [appId, isLiked, likeCount]);
5236
- const handleOpenComments = React29.useCallback(() => {
5317
+ const handleOpenComments = React30.useCallback(() => {
5237
5318
  if (!appId) return;
5238
5319
  try {
5239
5320
  void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
@@ -5248,11 +5329,11 @@ function useAppStats({
5248
5329
  var LIKE_DEBUG_PREFIX = "[COMERGE_LIKE_DEBUG]";
5249
5330
  function usePreviewPanelData(params) {
5250
5331
  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(() => {
5332
+ const [imageUrl, setImageUrl] = React31.useState(null);
5333
+ const [imageLoaded, setImageLoaded] = React31.useState(false);
5334
+ const [insights, setInsights] = React31.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5335
+ const [creator, setCreator] = React31.useState(null);
5336
+ React31.useEffect(() => {
5256
5337
  if (!(app == null ? void 0 : app.id)) return;
5257
5338
  let cancelled = false;
5258
5339
  (async () => {
@@ -5267,7 +5348,7 @@ function usePreviewPanelData(params) {
5267
5348
  cancelled = true;
5268
5349
  };
5269
5350
  }, [app == null ? void 0 : app.id]);
5270
- React30.useEffect(() => {
5351
+ React31.useEffect(() => {
5271
5352
  if (!(app == null ? void 0 : app.createdBy)) return;
5272
5353
  let cancelled = false;
5273
5354
  (async () => {
@@ -5283,10 +5364,10 @@ function usePreviewPanelData(params) {
5283
5364
  cancelled = true;
5284
5365
  };
5285
5366
  }, [app == null ? void 0 : app.createdBy]);
5286
- React30.useEffect(() => {
5367
+ React31.useEffect(() => {
5287
5368
  setImageLoaded(false);
5288
5369
  }, [app == null ? void 0 : app.id]);
5289
- React30.useEffect(() => {
5370
+ React31.useEffect(() => {
5290
5371
  if (!(app == null ? void 0 : app.id)) return;
5291
5372
  let cancelled = false;
5292
5373
  (async () => {
@@ -5311,7 +5392,7 @@ function usePreviewPanelData(params) {
5311
5392
  cancelled = true;
5312
5393
  };
5313
5394
  }, [app == null ? void 0 : app.id]);
5314
- React30.useEffect(() => {
5395
+ React31.useEffect(() => {
5315
5396
  if (!(app == null ? void 0 : app.id)) return;
5316
5397
  log.debug(
5317
5398
  `${LIKE_DEBUG_PREFIX} usePreviewPanelData.appChanged appId=${app.id} app.isLiked=${String(app.isLiked)}`
@@ -5325,7 +5406,7 @@ function usePreviewPanelData(params) {
5325
5406
  initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
5326
5407
  onOpenComments
5327
5408
  });
5328
- const canSubmitMergeRequest = React30.useMemo(() => {
5409
+ const canSubmitMergeRequest = React31.useMemo(() => {
5329
5410
  if (!isOwner) return false;
5330
5411
  if (!app) return false;
5331
5412
  if (!app.forkedFromAppId) return false;
@@ -5438,17 +5519,17 @@ function PreviewPanel({
5438
5519
  }
5439
5520
 
5440
5521
  // src/studio/ui/ChatPanel.tsx
5441
- import * as React35 from "react";
5522
+ import * as React36 from "react";
5442
5523
  import { ActivityIndicator as ActivityIndicator8, View as View41 } from "react-native";
5443
5524
 
5444
5525
  // src/components/chat/ChatPage.tsx
5445
- import * as React33 from "react";
5526
+ import * as React34 from "react";
5446
5527
  import { Keyboard as Keyboard4, Platform as Platform6, View as View37 } from "react-native";
5447
5528
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
5448
5529
  import Animated11, { useAnimatedKeyboard, useAnimatedStyle as useAnimatedStyle2 } from "react-native-reanimated";
5449
5530
 
5450
5531
  // src/components/chat/ChatMessageList.tsx
5451
- import * as React32 from "react";
5532
+ import * as React33 from "react";
5452
5533
  import { View as View36 } from "react-native";
5453
5534
  import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
5454
5535
 
@@ -5494,17 +5575,17 @@ function ChatMessageBubble({ message, renderContent, style }) {
5494
5575
  }
5495
5576
 
5496
5577
  // src/components/chat/TypingIndicator.tsx
5497
- import * as React31 from "react";
5578
+ import * as React32 from "react";
5498
5579
  import { Animated as Animated10, View as View35 } from "react-native";
5499
5580
  import { jsx as jsx45 } from "react/jsx-runtime";
5500
5581
  function TypingIndicator({ style }) {
5501
5582
  const theme = useTheme();
5502
5583
  const dotColor = theme.colors.textSubtle;
5503
- const anims = React31.useMemo(
5584
+ const anims = React32.useMemo(
5504
5585
  () => [new Animated10.Value(0.3), new Animated10.Value(0.3), new Animated10.Value(0.3)],
5505
5586
  []
5506
5587
  );
5507
- React31.useEffect(() => {
5588
+ React32.useEffect(() => {
5508
5589
  const loops = [];
5509
5590
  anims.forEach((a, idx) => {
5510
5591
  const seq = Animated10.sequence([
@@ -5537,40 +5618,44 @@ function TypingIndicator({ style }) {
5537
5618
  }
5538
5619
 
5539
5620
  // src/components/chat/ChatMessageList.tsx
5540
- import { jsx as jsx46 } from "react/jsx-runtime";
5541
- var ChatMessageList = React32.forwardRef(
5621
+ import { jsx as jsx46, jsxs as jsxs28 } from "react/jsx-runtime";
5622
+ var ChatMessageList = React33.forwardRef(
5542
5623
  ({
5543
5624
  messages,
5544
5625
  showTypingIndicator = false,
5545
5626
  renderMessageContent,
5546
5627
  contentStyle,
5628
+ bottomInset = 0,
5547
5629
  onNearBottomChange,
5548
5630
  nearBottomThreshold = 200
5549
5631
  }, ref) => {
5550
5632
  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) => {
5633
+ const listRef = React33.useRef(null);
5634
+ const nearBottomRef = React33.useRef(true);
5635
+ const initialScrollDoneRef = React33.useRef(false);
5636
+ const lastMessageIdRef = React33.useRef(null);
5637
+ const scrollToBottom = React33.useCallback((options) => {
5556
5638
  var _a;
5557
5639
  const animated = (options == null ? void 0 : options.animated) ?? true;
5558
5640
  (_a = listRef.current) == null ? void 0 : _a.scrollToEnd({ animated });
5559
5641
  }, []);
5560
- React32.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5561
- const handleScroll = React32.useCallback(
5642
+ React33.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5643
+ const handleScroll = React33.useCallback(
5562
5644
  (e) => {
5563
5645
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
5564
- const distanceFromBottom = Math.max(contentSize.height - (contentOffset.y + layoutMeasurement.height), 0);
5646
+ const distanceFromBottom = Math.max(
5647
+ contentSize.height - Math.max(bottomInset, 0) - (contentOffset.y + layoutMeasurement.height),
5648
+ 0
5649
+ );
5565
5650
  const isNear = distanceFromBottom <= nearBottomThreshold;
5566
5651
  if (nearBottomRef.current !== isNear) {
5567
5652
  nearBottomRef.current = isNear;
5568
5653
  onNearBottomChange == null ? void 0 : onNearBottomChange(isNear);
5569
5654
  }
5570
5655
  },
5571
- [nearBottomThreshold, onNearBottomChange]
5656
+ [bottomInset, nearBottomThreshold, onNearBottomChange]
5572
5657
  );
5573
- React32.useEffect(() => {
5658
+ React33.useEffect(() => {
5574
5659
  var _a;
5575
5660
  if (initialScrollDoneRef.current) return;
5576
5661
  if (messages.length === 0) return;
@@ -5579,7 +5664,7 @@ var ChatMessageList = React32.forwardRef(
5579
5664
  const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5580
5665
  return () => cancelAnimationFrame(id);
5581
5666
  }, [messages, scrollToBottom]);
5582
- React32.useEffect(() => {
5667
+ React33.useEffect(() => {
5583
5668
  if (!initialScrollDoneRef.current) return;
5584
5669
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
5585
5670
  const prevLastId = lastMessageIdRef.current;
@@ -5589,13 +5674,19 @@ var ChatMessageList = React32.forwardRef(
5589
5674
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5590
5675
  return () => cancelAnimationFrame(id);
5591
5676
  }, [messages, scrollToBottom]);
5592
- React32.useEffect(() => {
5677
+ React33.useEffect(() => {
5593
5678
  if (showTypingIndicator && nearBottomRef.current) {
5594
5679
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5595
5680
  return () => cancelAnimationFrame(id);
5596
5681
  }
5597
5682
  return void 0;
5598
5683
  }, [showTypingIndicator, scrollToBottom]);
5684
+ React33.useEffect(() => {
5685
+ if (!initialScrollDoneRef.current) return;
5686
+ if (!nearBottomRef.current) return;
5687
+ const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5688
+ return () => cancelAnimationFrame(id);
5689
+ }, [bottomInset, scrollToBottom]);
5599
5690
  return /* @__PURE__ */ jsx46(
5600
5691
  BottomSheetFlatList,
5601
5692
  {
@@ -5609,13 +5700,15 @@ var ChatMessageList = React32.forwardRef(
5609
5700
  {
5610
5701
  paddingHorizontal: theme.spacing.lg,
5611
5702
  paddingTop: theme.spacing.sm,
5612
- paddingBottom: theme.spacing.xl
5703
+ paddingBottom: theme.spacing.sm
5613
5704
  },
5614
5705
  contentStyle
5615
5706
  ],
5616
5707
  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 }
5708
+ ListFooterComponent: /* @__PURE__ */ jsxs28(View36, { children: [
5709
+ showTypingIndicator ? /* @__PURE__ */ jsx46(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx46(TypingIndicator, {}) }) : null,
5710
+ bottomInset > 0 ? /* @__PURE__ */ jsx46(View36, { style: { height: bottomInset } }) : null
5711
+ ] })
5619
5712
  }
5620
5713
  );
5621
5714
  }
@@ -5623,7 +5716,7 @@ var ChatMessageList = React32.forwardRef(
5623
5716
  ChatMessageList.displayName = "ChatMessageList";
5624
5717
 
5625
5718
  // src/components/chat/ChatPage.tsx
5626
- import { jsx as jsx47, jsxs as jsxs28 } from "react/jsx-runtime";
5719
+ import { jsx as jsx47, jsxs as jsxs29 } from "react/jsx-runtime";
5627
5720
  function ChatPage({
5628
5721
  header,
5629
5722
  messages,
@@ -5638,10 +5731,10 @@ function ChatPage({
5638
5731
  }) {
5639
5732
  const theme = useTheme();
5640
5733
  const insets = useSafeAreaInsets4();
5641
- const [composerHeight, setComposerHeight] = React33.useState(0);
5642
- const [keyboardVisible, setKeyboardVisible] = React33.useState(false);
5734
+ const [composerHeight, setComposerHeight] = React34.useState(0);
5735
+ const [keyboardVisible, setKeyboardVisible] = React34.useState(false);
5643
5736
  const animatedKeyboard = useAnimatedKeyboard();
5644
- React33.useEffect(() => {
5737
+ React34.useEffect(() => {
5645
5738
  if (Platform6.OS !== "ios") return;
5646
5739
  const show = Keyboard4.addListener("keyboardWillShow", () => setKeyboardVisible(true));
5647
5740
  const hide = Keyboard4.addListener("keyboardWillHide", () => setKeyboardVisible(false));
@@ -5656,19 +5749,20 @@ function ChatPage({
5656
5749
  return { paddingBottom: animatedKeyboard.height.value > 0 ? 0 : insets.bottom };
5657
5750
  });
5658
5751
  const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5659
- const resolvedOverlay = React33.useMemo(() => {
5752
+ const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
5753
+ const resolvedOverlay = React34.useMemo(() => {
5660
5754
  var _a;
5661
5755
  if (!overlay) return null;
5662
- if (!React33.isValidElement(overlay)) return overlay;
5756
+ if (!React34.isValidElement(overlay)) return overlay;
5663
5757
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
5664
- return React33.cloneElement(overlay, {
5758
+ return React34.cloneElement(overlay, {
5665
5759
  style: [prevStyle, { bottom: overlayBottom }]
5666
5760
  });
5667
5761
  }, [overlay, overlayBottom]);
5668
- return /* @__PURE__ */ jsxs28(View37, { style: [{ flex: 1 }, style], children: [
5762
+ return /* @__PURE__ */ jsxs29(View37, { style: [{ flex: 1 }, style], children: [
5669
5763
  header ? /* @__PURE__ */ jsx47(View37, { children: header }) : null,
5670
5764
  topBanner ? /* @__PURE__ */ jsx47(View37, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
5671
- /* @__PURE__ */ jsxs28(View37, { style: { flex: 1 }, children: [
5765
+ /* @__PURE__ */ jsxs29(View37, { style: { flex: 1 }, children: [
5672
5766
  /* @__PURE__ */ jsx47(
5673
5767
  ChatMessageList,
5674
5768
  {
@@ -5677,7 +5771,7 @@ function ChatPage({
5677
5771
  showTypingIndicator,
5678
5772
  renderMessageContent,
5679
5773
  onNearBottomChange,
5680
- contentStyle: { paddingBottom: theme.spacing.xl + composerHeight + footerBottomPadding }
5774
+ bottomInset
5681
5775
  }
5682
5776
  ),
5683
5777
  resolvedOverlay,
@@ -5710,15 +5804,15 @@ function ChatPage({
5710
5804
  }
5711
5805
 
5712
5806
  // src/components/chat/ScrollToBottomButton.tsx
5713
- import * as React34 from "react";
5807
+ import * as React35 from "react";
5714
5808
  import { Pressable as Pressable12, View as View38 } from "react-native";
5715
5809
  import Animated12, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle3, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
5716
5810
  import { jsx as jsx48 } from "react/jsx-runtime";
5717
5811
  function ScrollToBottomButton({ visible, onPress, children, style }) {
5718
5812
  const theme = useTheme();
5719
5813
  const progress = useSharedValue2(visible ? 1 : 0);
5720
- const [pressed, setPressed] = React34.useState(false);
5721
- React34.useEffect(() => {
5814
+ const [pressed, setPressed] = React35.useState(false);
5815
+ React35.useEffect(() => {
5722
5816
  progress.value = withTiming2(visible ? 1 : 0, { duration: 200, easing: Easing2.out(Easing2.ease) });
5723
5817
  }, [progress, visible]);
5724
5818
  const animStyle = useAnimatedStyle3(() => ({
@@ -5800,7 +5894,7 @@ function ChatHeader({ left, right, center, style }) {
5800
5894
 
5801
5895
  // src/components/chat/ForkNoticeBanner.tsx
5802
5896
  import { View as View40 } from "react-native";
5803
- import { jsx as jsx50, jsxs as jsxs29 } from "react/jsx-runtime";
5897
+ import { jsx as jsx50, jsxs as jsxs30 } from "react/jsx-runtime";
5804
5898
  function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5805
5899
  const theme = useTheme();
5806
5900
  const resolvedTitle = title ?? (isOwner ? "Remixed app" : "Remix app");
@@ -5820,7 +5914,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5820
5914
  },
5821
5915
  style
5822
5916
  ],
5823
- children: /* @__PURE__ */ jsxs29(View40, { style: { minWidth: 0 }, children: [
5917
+ children: /* @__PURE__ */ jsxs30(View40, { style: { minWidth: 0 }, children: [
5824
5918
  /* @__PURE__ */ jsx50(
5825
5919
  Text,
5826
5920
  {
@@ -5853,7 +5947,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
5853
5947
  }
5854
5948
 
5855
5949
  // src/studio/ui/ChatPanel.tsx
5856
- import { jsx as jsx51, jsxs as jsxs30 } from "react/jsx-runtime";
5950
+ import { jsx as jsx51, jsxs as jsxs31 } from "react/jsx-runtime";
5857
5951
  function ChatPanel({
5858
5952
  title = "Chat",
5859
5953
  autoFocusComposer = false,
@@ -5873,32 +5967,34 @@ function ChatPanel({
5873
5967
  onStartDraw,
5874
5968
  onSend
5875
5969
  }) {
5876
- const listRef = React35.useRef(null);
5877
- const [nearBottom, setNearBottom] = React35.useState(true);
5878
- const handleSend = React35.useCallback(
5970
+ const listRef = React36.useRef(null);
5971
+ const [nearBottom, setNearBottom] = React36.useState(true);
5972
+ const handleSend = React36.useCallback(
5879
5973
  async (text, composerAttachments) => {
5880
5974
  const all = composerAttachments ?? attachments;
5881
5975
  await onSend(text, all.length > 0 ? all : void 0);
5882
5976
  onClearAttachments == null ? void 0 : onClearAttachments();
5883
- requestAnimationFrame(() => {
5884
- var _a;
5885
- return (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5886
- });
5977
+ if (!nearBottom) {
5978
+ requestAnimationFrame(() => {
5979
+ var _a;
5980
+ return (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5981
+ });
5982
+ }
5887
5983
  },
5888
- [attachments, onClearAttachments, onSend]
5984
+ [attachments, nearBottom, onClearAttachments, onSend]
5889
5985
  );
5890
- const handleScrollToBottom = React35.useCallback(() => {
5986
+ const handleScrollToBottom = React36.useCallback(() => {
5891
5987
  var _a;
5892
5988
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
5893
5989
  }, []);
5894
5990
  const header = /* @__PURE__ */ jsx51(
5895
5991
  ChatHeader,
5896
5992
  {
5897
- left: /* @__PURE__ */ jsxs30(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5993
+ left: /* @__PURE__ */ jsxs31(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5898
5994
  /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx51(IconBack, { size: 20, colorToken: "floatingContent" }) }),
5899
5995
  onNavigateHome ? /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx51(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
5900
5996
  ] }),
5901
- right: /* @__PURE__ */ jsxs30(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5997
+ right: /* @__PURE__ */ jsxs31(View41, { style: { flexDirection: "row", alignItems: "center" }, children: [
5902
5998
  onStartDraw ? /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx51(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
5903
5999
  /* @__PURE__ */ jsx51(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx51(IconClose, { size: 20, colorToken: "floatingContent" }) })
5904
6000
  ] }),
@@ -5914,10 +6010,10 @@ function ChatPanel({
5914
6010
  ) : null;
5915
6011
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
5916
6012
  if (showMessagesLoading) {
5917
- return /* @__PURE__ */ jsxs30(View41, { style: { flex: 1 }, children: [
6013
+ return /* @__PURE__ */ jsxs31(View41, { style: { flex: 1 }, children: [
5918
6014
  /* @__PURE__ */ jsx51(View41, { children: header }),
5919
6015
  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: [
6016
+ /* @__PURE__ */ jsxs31(View41, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
5921
6017
  /* @__PURE__ */ jsx51(ActivityIndicator8, {}),
5922
6018
  /* @__PURE__ */ jsx51(View41, { style: { height: 12 } }),
5923
6019
  /* @__PURE__ */ jsx51(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
@@ -5943,7 +6039,10 @@ function ChatPanel({
5943
6039
  }
5944
6040
  ),
5945
6041
  composer: {
5946
- disabled: Boolean(loading) || Boolean(sendDisabled) || Boolean(forking),
6042
+ // Keep the input editable even when sending is disallowed (e.g. agent still working),
6043
+ // otherwise iOS will drop focus/keyboard and BottomSheet can get "stuck" with a keyboard-sized gap.
6044
+ disabled: Boolean(loading) || Boolean(forking),
6045
+ sendDisabled: Boolean(sendDisabled) || Boolean(loading) || Boolean(forking),
5947
6046
  sending: Boolean(sending),
5948
6047
  autoFocus: autoFocusComposer,
5949
6048
  onSend: handleSend,
@@ -5957,7 +6056,7 @@ function ChatPanel({
5957
6056
  }
5958
6057
 
5959
6058
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
5960
- import * as React36 from "react";
6059
+ import * as React37 from "react";
5961
6060
  import { Pressable as Pressable14, View as View43 } from "react-native";
5962
6061
 
5963
6062
  // src/components/primitives/Modal.tsx
@@ -5966,7 +6065,7 @@ import {
5966
6065
  Pressable as Pressable13,
5967
6066
  View as View42
5968
6067
  } from "react-native";
5969
- import { jsx as jsx52, jsxs as jsxs31 } from "react/jsx-runtime";
6068
+ import { jsx as jsx52, jsxs as jsxs32 } from "react/jsx-runtime";
5970
6069
  function Modal({
5971
6070
  visible,
5972
6071
  onRequestClose,
@@ -5982,7 +6081,7 @@ function Modal({
5982
6081
  transparent: true,
5983
6082
  animationType: "fade",
5984
6083
  onRequestClose,
5985
- children: /* @__PURE__ */ jsxs31(View42, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
6084
+ children: /* @__PURE__ */ jsxs32(View42, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
5986
6085
  /* @__PURE__ */ jsx52(
5987
6086
  Pressable13,
5988
6087
  {
@@ -5998,7 +6097,7 @@ function Modal({
5998
6097
  }
5999
6098
 
6000
6099
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6001
- import { jsx as jsx53, jsxs as jsxs32 } from "react/jsx-runtime";
6100
+ import { jsx as jsx53, jsxs as jsxs33 } from "react/jsx-runtime";
6002
6101
  function ConfirmMergeRequestDialog({
6003
6102
  visible,
6004
6103
  onOpenChange,
@@ -6009,14 +6108,14 @@ function ConfirmMergeRequestDialog({
6009
6108
  onTestFirst
6010
6109
  }) {
6011
6110
  const theme = useTheme();
6012
- const close = React36.useCallback(() => onOpenChange(false), [onOpenChange]);
6111
+ const close = React37.useCallback(() => onOpenChange(false), [onOpenChange]);
6013
6112
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6014
- const handleConfirm = React36.useCallback(() => {
6113
+ const handleConfirm = React37.useCallback(() => {
6015
6114
  if (!mergeRequest) return;
6016
6115
  onOpenChange(false);
6017
6116
  void onConfirm();
6018
6117
  }, [mergeRequest, onConfirm, onOpenChange]);
6019
- const handleTestFirst = React36.useCallback(() => {
6118
+ const handleTestFirst = React37.useCallback(() => {
6020
6119
  if (!mergeRequest) return;
6021
6120
  onOpenChange(false);
6022
6121
  void onTestFirst(mergeRequest);
@@ -6028,7 +6127,7 @@ function ConfirmMergeRequestDialog({
6028
6127
  justifyContent: "center",
6029
6128
  alignSelf: "stretch"
6030
6129
  };
6031
- return /* @__PURE__ */ jsxs32(
6130
+ return /* @__PURE__ */ jsxs33(
6032
6131
  Modal,
6033
6132
  {
6034
6133
  visible,
@@ -6051,7 +6150,7 @@ function ConfirmMergeRequestDialog({
6051
6150
  children: "Are you sure you want to approve this merge request?"
6052
6151
  }
6053
6152
  ) }),
6054
- /* @__PURE__ */ jsxs32(View43, { style: { marginTop: 16 }, children: [
6153
+ /* @__PURE__ */ jsxs33(View43, { style: { marginTop: 16 }, children: [
6055
6154
  /* @__PURE__ */ jsx53(
6056
6155
  View43,
6057
6156
  {
@@ -6165,7 +6264,7 @@ function ConfirmMergeFlow({
6165
6264
  }
6166
6265
 
6167
6266
  // src/studio/ui/StudioOverlay.tsx
6168
- import { Fragment as Fragment6, jsx as jsx55, jsxs as jsxs33 } from "react/jsx-runtime";
6267
+ import { Fragment as Fragment6, jsx as jsx55, jsxs as jsxs34 } from "react/jsx-runtime";
6169
6268
  function StudioOverlay({
6170
6269
  captureTargetRef,
6171
6270
  app,
@@ -6196,27 +6295,27 @@ function StudioOverlay({
6196
6295
  }) {
6197
6296
  const theme = useTheme();
6198
6297
  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(
6298
+ const [sheetOpen, setSheetOpen] = React38.useState(false);
6299
+ const [activePage, setActivePage] = React38.useState("preview");
6300
+ const [drawing, setDrawing] = React38.useState(false);
6301
+ const [chatAttachments, setChatAttachments] = React38.useState([]);
6302
+ const [commentsAppId, setCommentsAppId] = React38.useState(null);
6303
+ const [commentsCount, setCommentsCount] = React38.useState(null);
6304
+ const [confirmMrId, setConfirmMrId] = React38.useState(null);
6305
+ const confirmMr = React38.useMemo(
6207
6306
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6208
6307
  [confirmMrId, incomingMergeRequests]
6209
6308
  );
6210
- const closeSheet = React37.useCallback(() => {
6309
+ const closeSheet = React38.useCallback(() => {
6211
6310
  setSheetOpen(false);
6212
6311
  Keyboard5.dismiss();
6213
6312
  }, []);
6214
- const openSheet = React37.useCallback(() => setSheetOpen(true), []);
6215
- const goToChat = React37.useCallback(() => {
6313
+ const openSheet = React38.useCallback(() => setSheetOpen(true), []);
6314
+ const goToChat = React38.useCallback(() => {
6216
6315
  setActivePage("chat");
6217
6316
  openSheet();
6218
6317
  }, [openSheet]);
6219
- const backToPreview = React37.useCallback(() => {
6318
+ const backToPreview = React38.useCallback(() => {
6220
6319
  if (Platform7.OS !== "ios") {
6221
6320
  Keyboard5.dismiss();
6222
6321
  setActivePage("preview");
@@ -6234,11 +6333,11 @@ function StudioOverlay({
6234
6333
  const t = setTimeout(finalize, 350);
6235
6334
  Keyboard5.dismiss();
6236
6335
  }, []);
6237
- const startDraw = React37.useCallback(() => {
6336
+ const startDraw = React38.useCallback(() => {
6238
6337
  setDrawing(true);
6239
6338
  closeSheet();
6240
6339
  }, [closeSheet]);
6241
- const handleDrawCapture = React37.useCallback(
6340
+ const handleDrawCapture = React38.useCallback(
6242
6341
  (dataUrl) => {
6243
6342
  setChatAttachments((prev) => [...prev, dataUrl]);
6244
6343
  setDrawing(false);
@@ -6247,7 +6346,7 @@ function StudioOverlay({
6247
6346
  },
6248
6347
  [openSheet]
6249
6348
  );
6250
- const toggleSheet = React37.useCallback(async () => {
6349
+ const toggleSheet = React38.useCallback(async () => {
6251
6350
  if (!sheetOpen) {
6252
6351
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6253
6352
  if (shouldExitTest) {
@@ -6259,7 +6358,7 @@ function StudioOverlay({
6259
6358
  closeSheet();
6260
6359
  }
6261
6360
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6262
- const handleTestMr = React37.useCallback(
6361
+ const handleTestMr = React38.useCallback(
6263
6362
  async (mr) => {
6264
6363
  if (!onTestMr) return;
6265
6364
  await onTestMr(mr);
@@ -6267,7 +6366,7 @@ function StudioOverlay({
6267
6366
  },
6268
6367
  [closeSheet, onTestMr]
6269
6368
  );
6270
- return /* @__PURE__ */ jsxs33(Fragment6, { children: [
6369
+ return /* @__PURE__ */ jsxs34(Fragment6, { children: [
6271
6370
  /* @__PURE__ */ jsx55(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
6272
6371
  /* @__PURE__ */ jsx55(StudioBottomSheet, { open: sheetOpen, onOpenChange: setSheetOpen, children: /* @__PURE__ */ jsx55(
6273
6372
  StudioSheetPager,
@@ -6369,7 +6468,7 @@ function StudioOverlay({
6369
6468
  }
6370
6469
 
6371
6470
  // src/studio/ComergeStudio.tsx
6372
- import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
6471
+ import { jsx as jsx56, jsxs as jsxs35 } from "react/jsx-runtime";
6373
6472
  function ComergeStudio({
6374
6473
  appId,
6375
6474
  apiKey,
@@ -6377,16 +6476,16 @@ function ComergeStudio({
6377
6476
  onNavigateHome,
6378
6477
  style
6379
6478
  }) {
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(() => {
6479
+ const [activeAppId, setActiveAppId] = React39.useState(appId);
6480
+ const [runtimeAppId, setRuntimeAppId] = React39.useState(appId);
6481
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React39.useState(null);
6482
+ const platform = React39.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6483
+ React39.useEffect(() => {
6385
6484
  setActiveAppId(appId);
6386
6485
  setRuntimeAppId(appId);
6387
6486
  setPendingRuntimeTargetAppId(null);
6388
6487
  }, [appId]);
6389
- const captureTargetRef = React38.useRef(null);
6488
+ const captureTargetRef = React39.useRef(null);
6390
6489
  return /* @__PURE__ */ jsx56(StudioBootstrap, { apiKey, children: ({ userId }) => /* @__PURE__ */ jsx56(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx56(
6391
6490
  ComergeStudioInner,
6392
6491
  {
@@ -6422,11 +6521,11 @@ function ComergeStudioInner({
6422
6521
  const { app, loading: appLoading } = useApp(activeAppId);
6423
6522
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6424
6523
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6425
- const sawEditingOnPendingTargetRef = React38.useRef(false);
6426
- React38.useEffect(() => {
6524
+ const sawEditingOnPendingTargetRef = React39.useRef(false);
6525
+ React39.useEffect(() => {
6427
6526
  sawEditingOnPendingTargetRef.current = false;
6428
6527
  }, [pendingRuntimeTargetAppId]);
6429
- React38.useEffect(() => {
6528
+ React39.useEffect(() => {
6430
6529
  if (!pendingRuntimeTargetAppId) return;
6431
6530
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6432
6531
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6446,10 +6545,10 @@ function ComergeStudioInner({
6446
6545
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6447
6546
  const thread = useThreadMessages(threadId);
6448
6547
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6449
- const hasOpenOutgoingMr = React38.useMemo(() => {
6548
+ const hasOpenOutgoingMr = React39.useMemo(() => {
6450
6549
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6451
6550
  }, [mergeRequests.lists.outgoing]);
6452
- const incomingReviewMrs = React38.useMemo(() => {
6551
+ const incomingReviewMrs = React39.useMemo(() => {
6453
6552
  if (!userId) return mergeRequests.lists.incoming;
6454
6553
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6455
6554
  }, [mergeRequests.lists.incoming, userId]);
@@ -6471,16 +6570,16 @@ function ComergeStudioInner({
6471
6570
  uploadAttachments: uploader.uploadBase64Images
6472
6571
  });
6473
6572
  const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6474
- const [processingMrId, setProcessingMrId] = React38.useState(null);
6475
- const [testingMrId, setTestingMrId] = React38.useState(null);
6476
- const chatShowTypingIndicator = React38.useMemo(() => {
6573
+ const [processingMrId, setProcessingMrId] = React39.useState(null);
6574
+ const [testingMrId, setTestingMrId] = React39.useState(null);
6575
+ const chatShowTypingIndicator = React39.useMemo(() => {
6477
6576
  var _a;
6478
6577
  if (!thread.raw || thread.raw.length === 0) return false;
6479
6578
  const last = thread.raw[thread.raw.length - 1];
6480
6579
  const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
6481
6580
  return payloadType !== "outcome";
6482
6581
  }, [thread.raw]);
6483
- return /* @__PURE__ */ jsx56(View45, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs34(View45, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6582
+ return /* @__PURE__ */ jsx56(View45, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs35(View45, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6484
6583
  /* @__PURE__ */ jsx56(RuntimeRenderer, { appKey, bundlePath: bundle.bundlePath, renderToken: bundle.renderToken }),
6485
6584
  /* @__PURE__ */ jsx56(
6486
6585
  StudioOverlay,