@comergehq/studio 0.1.9 → 0.1.10

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 React39 from "react";
9
+ import * as React40 from "react";
10
10
  import { Platform as RNPlatform, View as View45 } from "react-native";
11
11
  import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
12
12
 
@@ -952,6 +952,39 @@ var BundlesRepositoryImpl = class extends BaseRepository {
952
952
  var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
953
953
 
954
954
  // src/studio/hooks/useBundleManager.ts
955
+ function sleep(ms) {
956
+ return new Promise((r) => setTimeout(r, ms));
957
+ }
958
+ function isRetryableNetworkError(e) {
959
+ var _a;
960
+ const err = e;
961
+ const code = typeof (err == null ? void 0 : err.code) === "string" ? err.code : "";
962
+ const message = typeof (err == null ? void 0 : err.message) === "string" ? err.message : "";
963
+ if (code === "ERR_NETWORK" || code === "ECONNABORTED") return true;
964
+ if (message.toLowerCase().includes("network error")) return true;
965
+ if (message.toLowerCase().includes("timeout")) return true;
966
+ const status = typeof ((_a = err == null ? void 0 : err.response) == null ? void 0 : _a.status) === "number" ? err.response.status : void 0;
967
+ if (status && (status === 429 || status >= 500)) return true;
968
+ return false;
969
+ }
970
+ async function withRetry(fn, opts) {
971
+ let lastErr = null;
972
+ for (let attempt = 1; attempt <= opts.attempts; attempt += 1) {
973
+ try {
974
+ return await fn();
975
+ } catch (e) {
976
+ lastErr = e;
977
+ const retryable = isRetryableNetworkError(e);
978
+ if (!retryable || attempt >= opts.attempts) {
979
+ throw e;
980
+ }
981
+ const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * Math.pow(2, attempt - 1));
982
+ const jitter = Math.floor(Math.random() * 250);
983
+ await sleep(exp + jitter);
984
+ }
985
+ }
986
+ throw lastErr;
987
+ }
955
988
  function safeName(s) {
956
989
  return s.replace(/[^a-zA-Z0-9._-]/g, "_");
957
990
  }
@@ -1009,8 +1042,16 @@ async function getExistingNonEmptyFileUri(fileUri) {
1009
1042
  async function downloadIfMissing(url, fileUri) {
1010
1043
  const existing = await getExistingNonEmptyFileUri(fileUri);
1011
1044
  if (existing) return existing;
1012
- const res = await FileSystem.downloadAsync(url, fileUri);
1013
- return res.uri;
1045
+ return await withRetry(
1046
+ async () => {
1047
+ await deleteFileIfExists(fileUri);
1048
+ const res = await FileSystem.downloadAsync(url, fileUri);
1049
+ const ok = await getExistingNonEmptyFileUri(res.uri);
1050
+ if (!ok) throw new Error("Downloaded bundle is empty.");
1051
+ return res.uri;
1052
+ },
1053
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1054
+ );
1014
1055
  }
1015
1056
  async function deleteFileIfExists(fileUri) {
1016
1057
  try {
@@ -1024,11 +1065,15 @@ async function deleteFileIfExists(fileUri) {
1024
1065
  async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1025
1066
  const tmpUri = toBundleFileUri(`tmp:${tmpKey}:${Date.now()}`);
1026
1067
  try {
1027
- await FileSystem.downloadAsync(url, tmpUri);
1028
- const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
1029
- if (!tmpOk) {
1030
- throw new Error("Downloaded bundle is empty.");
1031
- }
1068
+ await withRetry(
1069
+ async () => {
1070
+ await deleteFileIfExists(tmpUri);
1071
+ await FileSystem.downloadAsync(url, tmpUri);
1072
+ const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
1073
+ if (!tmpOk) throw new Error("Downloaded bundle is empty.");
1074
+ },
1075
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1076
+ );
1032
1077
  await deleteFileIfExists(targetUri);
1033
1078
  await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
1034
1079
  const finalOk = await getExistingNonEmptyFileUri(targetUri);
@@ -1041,28 +1086,44 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1041
1086
  async function pollBundle(appId, bundleId, opts) {
1042
1087
  const start = Date.now();
1043
1088
  while (true) {
1044
- const bundle = await bundlesRepository.getById(appId, bundleId);
1045
- if (bundle.status === "succeeded" || bundle.status === "failed") return bundle;
1089
+ try {
1090
+ const bundle = await bundlesRepository.getById(appId, bundleId);
1091
+ if (bundle.status === "succeeded" || bundle.status === "failed") return bundle;
1092
+ } catch (e) {
1093
+ if (!isRetryableNetworkError(e)) {
1094
+ throw e;
1095
+ }
1096
+ }
1046
1097
  if (Date.now() - start > opts.timeoutMs) {
1047
1098
  throw new Error("Bundle build timed out.");
1048
1099
  }
1049
- await new Promise((r) => setTimeout(r, opts.intervalMs));
1100
+ await sleep(opts.intervalMs);
1050
1101
  }
1051
1102
  }
1052
1103
  async function resolveBundlePath(src, platform, mode) {
1053
1104
  const { appId, commitId } = src;
1054
1105
  const dir = bundlesCacheDir();
1055
1106
  await ensureDir(dir);
1056
- const initiate = await bundlesRepository.initiate(appId, {
1057
- platform,
1058
- commitId: commitId ?? void 0,
1059
- idempotencyKey: `${appId}:${commitId ?? "head"}:${platform}`
1060
- });
1107
+ const initiate = await withRetry(
1108
+ async () => {
1109
+ return await bundlesRepository.initiate(appId, {
1110
+ platform,
1111
+ commitId: commitId ?? void 0,
1112
+ idempotencyKey: `${appId}:${commitId ?? "head"}:${platform}`
1113
+ });
1114
+ },
1115
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1116
+ );
1061
1117
  const finalBundle = initiate.status === "succeeded" || initiate.status === "failed" ? initiate : await pollBundle(appId, initiate.id, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1200 });
1062
1118
  if (finalBundle.status === "failed") {
1063
1119
  throw new Error("Bundle build failed.");
1064
1120
  }
1065
- const signed = await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
1121
+ const signed = await withRetry(
1122
+ async () => {
1123
+ return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
1124
+ },
1125
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1126
+ );
1066
1127
  const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
1067
1128
  signed.url,
1068
1129
  toBundleFileUri(baseBundleKey(appId, platform)),
@@ -1078,6 +1139,7 @@ function useBundleManager({
1078
1139
  const [bundlePath, setBundlePath] = React5.useState(null);
1079
1140
  const [renderToken, setRenderToken] = React5.useState(0);
1080
1141
  const [loading, setLoading] = React5.useState(false);
1142
+ const [loadingMode, setLoadingMode] = React5.useState(null);
1081
1143
  const [statusLabel, setStatusLabel] = React5.useState(null);
1082
1144
  const [error, setError] = React5.useState(null);
1083
1145
  const [isTesting, setIsTesting] = React5.useState(false);
@@ -1093,6 +1155,7 @@ function useBundleManager({
1093
1155
  baseOpIdRef.current += 1;
1094
1156
  if (activeLoadModeRef.current === "base") {
1095
1157
  setLoading(false);
1158
+ setLoadingMode(null);
1096
1159
  setStatusLabel(null);
1097
1160
  activeLoadModeRef.current = null;
1098
1161
  }
@@ -1157,6 +1220,7 @@ function useBundleManager({
1157
1220
  const opId = mode === "base" ? ++baseOpIdRef.current : ++testOpIdRef.current;
1158
1221
  activeLoadModeRef.current = mode;
1159
1222
  setLoading(true);
1223
+ setLoadingMode(mode);
1160
1224
  setError(null);
1161
1225
  setStatusLabel(mode === "test" ? "Loading test bundle\u2026" : "Loading latest build\u2026");
1162
1226
  if (mode === "base") {
@@ -1199,6 +1263,7 @@ function useBundleManager({
1199
1263
  if (mode === "base" && opId !== baseOpIdRef.current) return;
1200
1264
  if (mode === "test" && opId !== testOpIdRef.current) return;
1201
1265
  setLoading(false);
1266
+ setLoadingMode(null);
1202
1267
  if (activeLoadModeRef.current === mode) activeLoadModeRef.current = null;
1203
1268
  }
1204
1269
  }, [activateCachedBase, platform]);
@@ -1220,7 +1285,7 @@ function useBundleManager({
1220
1285
  if (!canRequestLatest) return;
1221
1286
  void loadBase();
1222
1287
  }, [base.appId, base.commitId, platform, canRequestLatest, loadBase]);
1223
- return { bundlePath, renderToken, loading, statusLabel, error, isTesting, loadBase, loadTest, restoreBase };
1288
+ return { bundlePath, renderToken, loading, loadingMode, statusLabel, error, isTesting, loadBase, loadTest, restoreBase };
1224
1289
  }
1225
1290
 
1226
1291
  // src/studio/hooks/useMergeRequests.ts
@@ -1479,6 +1544,8 @@ function useMergeRequests(params) {
1479
1544
 
1480
1545
  // src/studio/hooks/useAttachmentUpload.ts
1481
1546
  import * as React7 from "react";
1547
+ import { Platform } from "react-native";
1548
+ import * as FileSystem2 from "expo-file-system/legacy";
1482
1549
 
1483
1550
  // src/data/attachment/remote.ts
1484
1551
  var AttachmentRemoteDataSourceImpl = class extends BaseRemote {
@@ -1518,6 +1585,40 @@ var attachmentRepository = new AttachmentRepositoryImpl(
1518
1585
  );
1519
1586
 
1520
1587
  // src/studio/hooks/useAttachmentUpload.ts
1588
+ async function dataUrlToBlobAndroid(dataUrl) {
1589
+ const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
1590
+ const comma = normalized.indexOf(",");
1591
+ if (comma === -1) {
1592
+ throw new Error("Invalid data URL (missing comma separator)");
1593
+ }
1594
+ const header = normalized.slice(0, comma);
1595
+ const base64 = normalized.slice(comma + 1);
1596
+ const mimeMatch = header.match(/data:(.*?);base64/i);
1597
+ const mimeType = (mimeMatch == null ? void 0 : mimeMatch[1]) ?? "application/octet-stream";
1598
+ const cacheDir = FileSystem2.cacheDirectory;
1599
+ if (!cacheDir) {
1600
+ throw new Error("expo-file-system cacheDirectory is unavailable");
1601
+ }
1602
+ const fileUri = `${cacheDir}attachment-${Date.now()}-${Math.random().toString(16).slice(2)}.bin`;
1603
+ await FileSystem2.writeAsStringAsync(fileUri, base64, {
1604
+ encoding: FileSystem2.EncodingType.Base64
1605
+ });
1606
+ try {
1607
+ const resp = await fetch(fileUri);
1608
+ const blob = await resp.blob();
1609
+ return blob.type ? blob : new Blob([blob], { type: mimeType });
1610
+ } finally {
1611
+ void FileSystem2.deleteAsync(fileUri, { idempotent: true }).catch(() => {
1612
+ });
1613
+ }
1614
+ }
1615
+ function getMimeTypeFromDataUrl(dataUrl) {
1616
+ const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
1617
+ const comma = normalized.indexOf(",");
1618
+ const header = comma === -1 ? normalized : normalized.slice(0, comma);
1619
+ const mimeMatch = header.match(/data:(.*?);base64/i);
1620
+ return (mimeMatch == null ? void 0 : mimeMatch[1]) ?? "image/png";
1621
+ }
1521
1622
  function useAttachmentUpload() {
1522
1623
  const [uploading, setUploading] = React7.useState(false);
1523
1624
  const [error, setError] = React7.useState(null);
@@ -1530,15 +1631,15 @@ function useAttachmentUpload() {
1530
1631
  const blobs = await Promise.all(
1531
1632
  dataUrls.map(async (dataUrl, idx) => {
1532
1633
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
1533
- const resp = await fetch(normalized);
1534
- const blob = await resp.blob();
1535
- return { blob, idx };
1634
+ const blob = Platform.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
1635
+ const mimeType = getMimeTypeFromDataUrl(normalized);
1636
+ return { blob, idx, mimeType };
1536
1637
  })
1537
1638
  );
1538
- const files = blobs.map(({ blob }, idx) => ({
1639
+ const files = blobs.map(({ blob, mimeType }, idx) => ({
1539
1640
  name: `attachment-${Date.now()}-${idx}.png`,
1540
1641
  size: blob.size,
1541
- mimeType: blob.type || "image/png"
1642
+ mimeType
1542
1643
  }));
1543
1644
  const presign = await attachmentRepository.presign({ threadId, appId, files });
1544
1645
  await Promise.all(presign.uploads.map((u, index) => attachmentRepository.upload(u, blobs[index].blob)));
@@ -1665,8 +1766,8 @@ function hasNoOutcomeAfterLastHuman(messages) {
1665
1766
  import { View as View2 } from "react-native";
1666
1767
  import { ComergeRuntimeRenderer } from "@comergehq/runtime";
1667
1768
  import { jsx as jsx3 } from "react/jsx-runtime";
1668
- function RuntimeRenderer({ appKey, bundlePath, renderToken, style }) {
1669
- if (!bundlePath) {
1769
+ function RuntimeRenderer({ appKey, bundlePath, forcePreparing, renderToken, style }) {
1770
+ if (!bundlePath || forcePreparing) {
1670
1771
  return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ jsx3(Text, { variant: "bodyMuted", children: "Preparing app\u2026" }) });
1671
1772
  }
1672
1773
  return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsx3(
@@ -1681,8 +1782,8 @@ function RuntimeRenderer({ appKey, bundlePath, renderToken, style }) {
1681
1782
  }
1682
1783
 
1683
1784
  // src/studio/ui/StudioOverlay.tsx
1684
- import * as React38 from "react";
1685
- import { Keyboard as Keyboard5, Platform as Platform8, View as View44, useWindowDimensions as useWindowDimensions4 } from "react-native";
1785
+ import * as React39 from "react";
1786
+ import { Keyboard as Keyboard5, Platform as Platform9, View as View44, useWindowDimensions as useWindowDimensions4 } from "react-native";
1686
1787
 
1687
1788
  // src/components/studio-sheet/StudioBottomSheet.tsx
1688
1789
  import * as React9 from "react";
@@ -1691,7 +1792,7 @@ import BottomSheet from "@gorhom/bottom-sheet";
1691
1792
  import { useSafeAreaInsets } from "react-native-safe-area-context";
1692
1793
 
1693
1794
  // src/components/studio-sheet/StudioSheetBackground.tsx
1694
- import { Platform, View as View3 } from "react-native";
1795
+ import { Platform as Platform2, View as View3 } from "react-native";
1695
1796
  import { LiquidGlassView, isLiquidGlassSupported } from "@callstack/liquid-glass";
1696
1797
  import { Fragment as Fragment2, jsx as jsx4, jsxs } from "react/jsx-runtime";
1697
1798
  function StudioSheetBackground({
@@ -1699,7 +1800,7 @@ function StudioSheetBackground({
1699
1800
  renderBackground
1700
1801
  }) {
1701
1802
  const theme = useTheme();
1702
- const radius = Platform.OS === "ios" ? 39 : 16;
1803
+ const radius = Platform2.OS === "ios" ? 39 : 16;
1703
1804
  const fallbackBgColor = theme.scheme === "dark" ? "rgba(11, 8, 15, 0.85)" : "rgba(255, 255, 255, 0.85)";
1704
1805
  const secondaryBgBaseColor = theme.scheme === "dark" ? "rgb(24, 24, 27)" : "rgb(173, 173, 173)";
1705
1806
  const containerStyle = {
@@ -1745,7 +1846,7 @@ import { jsx as jsx5 } from "react/jsx-runtime";
1745
1846
  function StudioBottomSheet({
1746
1847
  open,
1747
1848
  onOpenChange,
1748
- snapPoints = ["80%", "100%"],
1849
+ snapPoints = ["100%"],
1749
1850
  sheetRef,
1750
1851
  background,
1751
1852
  children,
@@ -1799,9 +1900,9 @@ function StudioBottomSheet({
1799
1900
  ref: resolvedSheetRef,
1800
1901
  index: open ? snapPoints.length - 1 : -1,
1801
1902
  snapPoints,
1903
+ enableDynamicSizing: false,
1802
1904
  enablePanDownToClose: true,
1803
- keyboardBehavior: "interactive",
1804
- keyboardBlurBehavior: "restore",
1905
+ enableContentPanningGesture: false,
1805
1906
  android_keyboardInputMode: "adjustResize",
1806
1907
  backgroundComponent: (props) => /* @__PURE__ */ jsx5(StudioSheetBackground, { ...props, renderBackground: background == null ? void 0 : background.renderBackground }),
1807
1908
  topInset: insets.top,
@@ -2779,7 +2880,7 @@ var styles3 = StyleSheet3.create({
2779
2880
 
2780
2881
  // src/components/comments/AppCommentsSheet.tsx
2781
2882
  import * as React21 from "react";
2782
- import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform4, Pressable as Pressable5, View as View14 } from "react-native";
2883
+ import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform5, Pressable as Pressable5, View as View14 } from "react-native";
2783
2884
  import {
2784
2885
  BottomSheetBackdrop,
2785
2886
  BottomSheetModal,
@@ -3384,11 +3485,11 @@ function useAppDetails(appId) {
3384
3485
 
3385
3486
  // src/components/comments/useIosKeyboardSnapFix.ts
3386
3487
  import * as React20 from "react";
3387
- import { Keyboard as Keyboard2, Platform as Platform3 } from "react-native";
3488
+ import { Keyboard as Keyboard2, Platform as Platform4 } from "react-native";
3388
3489
  function useIosKeyboardSnapFix(sheetRef, options) {
3389
3490
  const [keyboardVisible, setKeyboardVisible] = React20.useState(false);
3390
3491
  React20.useEffect(() => {
3391
- if (Platform3.OS !== "ios") return;
3492
+ if (Platform4.OS !== "ios") return;
3392
3493
  const show = Keyboard2.addListener("keyboardWillShow", () => setKeyboardVisible(true));
3393
3494
  const hide = Keyboard2.addListener("keyboardWillHide", () => {
3394
3495
  var _a;
@@ -3466,8 +3567,8 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3466
3567
  onChange: handleChange,
3467
3568
  backgroundStyle: {
3468
3569
  backgroundColor: theme.scheme === "dark" ? "#0B080F" : "#FFFFFF",
3469
- borderTopLeftRadius: Platform4.OS === "ios" ? 39 : 16,
3470
- borderTopRightRadius: Platform4.OS === "ios" ? 39 : 16
3570
+ borderTopLeftRadius: Platform5.OS === "ios" ? 39 : 16,
3571
+ borderTopRightRadius: Platform5.OS === "ios" ? 39 : 16
3471
3572
  },
3472
3573
  handleIndicatorStyle: { backgroundColor: theme.colors.handleIndicator },
3473
3574
  keyboardBehavior: "interactive",
@@ -3574,7 +3675,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3574
3675
  bottom: 0,
3575
3676
  paddingHorizontal: theme.spacing.lg,
3576
3677
  paddingTop: theme.spacing.sm,
3577
- paddingBottom: Platform4.OS === "ios" ? keyboardVisible ? theme.spacing.lg : insets.bottom : insets.bottom + 10,
3678
+ paddingBottom: Platform5.OS === "ios" ? keyboardVisible ? theme.spacing.lg : insets.bottom : insets.bottom + 10,
3578
3679
  borderTopWidth: 1,
3579
3680
  borderTopColor: withAlpha(theme.colors.border, 0.1),
3580
3681
  backgroundColor: withAlpha(theme.colors.background, 0.8)
@@ -4473,7 +4574,7 @@ import { Animated as Animated7, Pressable as Pressable9, View as View28 } from "
4473
4574
  import { Ban, Check as Check3, CheckCheck, ChevronDown as ChevronDown2 } from "lucide-react-native";
4474
4575
 
4475
4576
  // src/components/primitives/MarkdownText.tsx
4476
- import { Platform as Platform5, View as View27 } from "react-native";
4577
+ import { Platform as Platform6, View as View27 } from "react-native";
4477
4578
  import Markdown from "react-native-markdown-display";
4478
4579
  import { jsx as jsx37 } from "react/jsx-runtime";
4479
4580
  function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
@@ -4499,7 +4600,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
4499
4600
  paddingHorizontal: variant === "mergeRequest" ? 6 : 4,
4500
4601
  paddingVertical: variant === "mergeRequest" ? 2 : 0,
4501
4602
  borderRadius: variant === "mergeRequest" ? 6 : 4,
4502
- fontFamily: Platform5.OS === "ios" ? "Menlo" : "monospace",
4603
+ fontFamily: Platform6.OS === "ios" ? "Menlo" : "monospace",
4503
4604
  fontSize: 13
4504
4605
  },
4505
4606
  code_block: {
@@ -5512,12 +5613,12 @@ import { ActivityIndicator as ActivityIndicator8, View as View41 } from "react-n
5512
5613
 
5513
5614
  // src/components/chat/ChatPage.tsx
5514
5615
  import * as React34 from "react";
5515
- import { Keyboard as Keyboard4, Platform as Platform7, View as View37 } from "react-native";
5616
+ import { Keyboard as Keyboard4, Platform as Platform8, View as View37 } from "react-native";
5516
5617
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
5517
5618
 
5518
5619
  // src/components/chat/ChatMessageList.tsx
5519
5620
  import * as React33 from "react";
5520
- import { Platform as Platform6, View as View36 } from "react-native";
5621
+ import { View as View36 } from "react-native";
5521
5622
  import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
5522
5623
 
5523
5624
  // src/components/chat/ChatMessageBubble.tsx
@@ -5621,19 +5722,19 @@ var ChatMessageList = React33.forwardRef(
5621
5722
  const nearBottomRef = React33.useRef(true);
5622
5723
  const initialScrollDoneRef = React33.useRef(false);
5623
5724
  const lastMessageIdRef = React33.useRef(null);
5725
+ const data = React33.useMemo(() => {
5726
+ return [...messages].reverse();
5727
+ }, [messages]);
5624
5728
  const scrollToBottom = React33.useCallback((options) => {
5625
5729
  var _a;
5626
5730
  const animated = (options == null ? void 0 : options.animated) ?? true;
5627
- (_a = listRef.current) == null ? void 0 : _a.scrollToEnd({ animated });
5731
+ (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
5628
5732
  }, []);
5629
5733
  React33.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5630
5734
  const handleScroll = React33.useCallback(
5631
5735
  (e) => {
5632
5736
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
5633
- const distanceFromBottom = Math.max(
5634
- contentSize.height - Math.max(bottomInset, 0) - (contentOffset.y + layoutMeasurement.height),
5635
- 0
5636
- );
5737
+ const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
5637
5738
  const isNear = distanceFromBottom <= nearBottomThreshold;
5638
5739
  if (nearBottomRef.current !== isNear) {
5639
5740
  nearBottomRef.current = isNear;
@@ -5642,15 +5743,6 @@ var ChatMessageList = React33.forwardRef(
5642
5743
  },
5643
5744
  [bottomInset, nearBottomThreshold, onNearBottomChange]
5644
5745
  );
5645
- React33.useEffect(() => {
5646
- var _a;
5647
- if (initialScrollDoneRef.current) return;
5648
- if (messages.length === 0) return;
5649
- initialScrollDoneRef.current = true;
5650
- lastMessageIdRef.current = ((_a = messages[messages.length - 1]) == null ? void 0 : _a.id) ?? null;
5651
- const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5652
- return () => cancelAnimationFrame(id);
5653
- }, [messages, scrollToBottom]);
5654
5746
  React33.useEffect(() => {
5655
5747
  if (!initialScrollDoneRef.current) return;
5656
5748
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
@@ -5668,33 +5760,35 @@ var ChatMessageList = React33.forwardRef(
5668
5760
  }
5669
5761
  return void 0;
5670
5762
  }, [showTypingIndicator, scrollToBottom]);
5671
- React33.useEffect(() => {
5672
- if (!initialScrollDoneRef.current) return;
5673
- if (!nearBottomRef.current) return;
5674
- const id = requestAnimationFrame(() => scrollToBottom({ animated: false }));
5675
- return () => cancelAnimationFrame(id);
5676
- }, [bottomInset, scrollToBottom]);
5677
5763
  return /* @__PURE__ */ jsx46(
5678
5764
  BottomSheetFlatList,
5679
5765
  {
5680
5766
  ref: listRef,
5681
- data: messages,
5767
+ inverted: true,
5768
+ data,
5682
5769
  keyExtractor: (m) => m.id,
5683
- keyboardDismissMode: Platform6.OS === "ios" ? "interactive" : "on-drag",
5684
5770
  keyboardShouldPersistTaps: "handled",
5685
5771
  onScroll: handleScroll,
5686
5772
  scrollEventThrottle: 16,
5687
5773
  showsVerticalScrollIndicator: false,
5774
+ onContentSizeChange: () => {
5775
+ if (initialScrollDoneRef.current) return;
5776
+ initialScrollDoneRef.current = true;
5777
+ lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
5778
+ nearBottomRef.current = true;
5779
+ onNearBottomChange == null ? void 0 : onNearBottomChange(true);
5780
+ requestAnimationFrame(() => scrollToBottom({ animated: false }));
5781
+ },
5688
5782
  contentContainerStyle: [
5689
5783
  {
5690
5784
  paddingHorizontal: theme.spacing.lg,
5691
- paddingTop: theme.spacing.sm,
5692
- paddingBottom: theme.spacing.sm
5785
+ paddingVertical: theme.spacing.sm
5693
5786
  },
5694
5787
  contentStyle
5695
5788
  ],
5696
- renderItem: ({ item, index }) => /* @__PURE__ */ jsx46(View36, { style: { marginTop: index === 0 ? 0 : theme.spacing.sm }, children: /* @__PURE__ */ jsx46(ChatMessageBubble, { message: item, renderContent: renderMessageContent }) }),
5697
- ListFooterComponent: /* @__PURE__ */ jsxs28(View36, { children: [
5789
+ ItemSeparatorComponent: () => /* @__PURE__ */ jsx46(View36, { style: { height: theme.spacing.sm } }),
5790
+ renderItem: ({ item }) => /* @__PURE__ */ jsx46(ChatMessageBubble, { message: item, renderContent: renderMessageContent }),
5791
+ ListHeaderComponent: /* @__PURE__ */ jsxs28(View36, { children: [
5698
5792
  showTypingIndicator ? /* @__PURE__ */ jsx46(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx46(TypingIndicator, {}) }) : null,
5699
5793
  bottomInset > 0 ? /* @__PURE__ */ jsx46(View36, { style: { height: bottomInset } }) : null
5700
5794
  ] })
@@ -5715,6 +5809,7 @@ function ChatPage({
5715
5809
  composer,
5716
5810
  overlay,
5717
5811
  style,
5812
+ composerHorizontalPadding,
5718
5813
  onNearBottomChange,
5719
5814
  listRef
5720
5815
  }) {
@@ -5723,7 +5818,7 @@ function ChatPage({
5723
5818
  const [composerHeight, setComposerHeight] = React34.useState(0);
5724
5819
  const [keyboardVisible, setKeyboardVisible] = React34.useState(false);
5725
5820
  React34.useEffect(() => {
5726
- if (Platform7.OS !== "ios") return;
5821
+ if (Platform8.OS !== "ios") return;
5727
5822
  const show = Keyboard4.addListener("keyboardWillShow", () => setKeyboardVisible(true));
5728
5823
  const hide = Keyboard4.addListener("keyboardWillHide", () => setKeyboardVisible(false));
5729
5824
  return () => {
@@ -5731,7 +5826,7 @@ function ChatPage({
5731
5826
  hide.remove();
5732
5827
  };
5733
5828
  }, []);
5734
- const footerBottomPadding = Platform7.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5829
+ const footerBottomPadding = Platform8.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5735
5830
  const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5736
5831
  const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
5737
5832
  const resolvedOverlay = React34.useMemo(() => {
@@ -5775,7 +5870,7 @@ function ChatPage({
5775
5870
  left: 0,
5776
5871
  right: 0,
5777
5872
  bottom: 0,
5778
- paddingHorizontal: theme.spacing.lg,
5873
+ paddingHorizontal: composerHorizontalPadding ?? theme.spacing.md,
5779
5874
  paddingTop: theme.spacing.sm,
5780
5875
  paddingBottom: footerBottomPadding
5781
5876
  },
@@ -6017,6 +6112,7 @@ function ChatPanel({
6017
6112
  messages,
6018
6113
  showTypingIndicator,
6019
6114
  topBanner,
6115
+ composerHorizontalPadding: 0,
6020
6116
  listRef,
6021
6117
  onNearBottomChange: setNearBottom,
6022
6118
  overlay: /* @__PURE__ */ jsx51(
@@ -6253,6 +6349,92 @@ function ConfirmMergeFlow({
6253
6349
  );
6254
6350
  }
6255
6351
 
6352
+ // src/studio/hooks/useOptimisticChatMessages.ts
6353
+ import * as React38 from "react";
6354
+ function makeOptimisticId() {
6355
+ return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6356
+ }
6357
+ function toEpochMs(createdAt) {
6358
+ if (createdAt == null) return 0;
6359
+ if (typeof createdAt === "number") return createdAt;
6360
+ if (createdAt instanceof Date) return createdAt.getTime();
6361
+ const t = Date.parse(String(createdAt));
6362
+ return Number.isFinite(t) ? t : 0;
6363
+ }
6364
+ function isOptimisticResolvedByServer(chatMessages, o) {
6365
+ if (o.failed) return false;
6366
+ const normalize = (s) => s.trim();
6367
+ let startIndex = -1;
6368
+ if (o.baseServerLastId) {
6369
+ startIndex = chatMessages.findIndex((m) => m.id === o.baseServerLastId);
6370
+ }
6371
+ const candidates = startIndex >= 0 ? chatMessages.slice(startIndex + 1) : chatMessages;
6372
+ const target = normalize(o.content);
6373
+ for (const m of candidates) {
6374
+ if (m.author !== "human") continue;
6375
+ if (normalize(m.content) !== target) continue;
6376
+ const serverMs = toEpochMs(m.createdAt);
6377
+ const optimisticMs = Date.parse(o.createdAtIso);
6378
+ if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
6379
+ if (serverMs + 12e4 < optimisticMs) continue;
6380
+ }
6381
+ return true;
6382
+ }
6383
+ return false;
6384
+ }
6385
+ function useOptimisticChatMessages({
6386
+ threadId,
6387
+ shouldForkOnEdit,
6388
+ chatMessages,
6389
+ onSendChat
6390
+ }) {
6391
+ const [optimisticChat, setOptimisticChat] = React38.useState([]);
6392
+ React38.useEffect(() => {
6393
+ setOptimisticChat([]);
6394
+ }, [threadId]);
6395
+ const messages = React38.useMemo(() => {
6396
+ if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6397
+ const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6398
+ if (unresolved.length === 0) return chatMessages;
6399
+ const optimisticAsChat = unresolved.map((o) => ({
6400
+ id: o.id,
6401
+ author: "human",
6402
+ content: o.content,
6403
+ createdAt: o.createdAtIso,
6404
+ kind: "optimistic",
6405
+ meta: o.failed ? { kind: "optimistic", event: "send.failed", status: "error" } : { kind: "optimistic", event: "send.pending", status: "info" }
6406
+ }));
6407
+ const merged = [...chatMessages, ...optimisticAsChat];
6408
+ merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6409
+ return merged;
6410
+ }, [chatMessages, optimisticChat]);
6411
+ React38.useEffect(() => {
6412
+ if (optimisticChat.length === 0) return;
6413
+ setOptimisticChat((prev) => {
6414
+ if (prev.length === 0) return prev;
6415
+ const next = prev.filter((o) => !isOptimisticResolvedByServer(chatMessages, o) || o.failed);
6416
+ return next.length === prev.length ? prev : next;
6417
+ });
6418
+ }, [chatMessages, optimisticChat.length]);
6419
+ const onSend = React38.useCallback(
6420
+ async (text, attachments) => {
6421
+ if (shouldForkOnEdit) {
6422
+ await onSendChat(text, attachments);
6423
+ return;
6424
+ }
6425
+ const createdAtIso = (/* @__PURE__ */ new Date()).toISOString();
6426
+ const baseServerLastId = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1].id : null;
6427
+ const id = makeOptimisticId();
6428
+ setOptimisticChat((prev) => [...prev, { id, content: text, createdAtIso, baseServerLastId, failed: false }]);
6429
+ void Promise.resolve(onSendChat(text, attachments)).catch(() => {
6430
+ setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
6431
+ });
6432
+ },
6433
+ [chatMessages, onSendChat, shouldForkOnEdit]
6434
+ );
6435
+ return { messages, onSend };
6436
+ }
6437
+
6256
6438
  // src/studio/ui/StudioOverlay.tsx
6257
6439
  import { Fragment as Fragment6, jsx as jsx55, jsxs as jsxs34 } from "react/jsx-runtime";
6258
6440
  function StudioOverlay({
@@ -6285,31 +6467,38 @@ function StudioOverlay({
6285
6467
  }) {
6286
6468
  const theme = useTheme();
6287
6469
  const { width } = useWindowDimensions4();
6288
- const [sheetOpen, setSheetOpen] = React38.useState(false);
6289
- const [activePage, setActivePage] = React38.useState("preview");
6290
- const [drawing, setDrawing] = React38.useState(false);
6291
- const [chatAttachments, setChatAttachments] = React38.useState([]);
6292
- const [commentsAppId, setCommentsAppId] = React38.useState(null);
6293
- const [commentsCount, setCommentsCount] = React38.useState(null);
6294
- const [confirmMrId, setConfirmMrId] = React38.useState(null);
6295
- const confirmMr = React38.useMemo(
6470
+ const [sheetOpen, setSheetOpen] = React39.useState(false);
6471
+ const [activePage, setActivePage] = React39.useState("preview");
6472
+ const [drawing, setDrawing] = React39.useState(false);
6473
+ const [chatAttachments, setChatAttachments] = React39.useState([]);
6474
+ const [commentsAppId, setCommentsAppId] = React39.useState(null);
6475
+ const [commentsCount, setCommentsCount] = React39.useState(null);
6476
+ const threadId = (app == null ? void 0 : app.threadId) ?? null;
6477
+ const optimistic = useOptimisticChatMessages({
6478
+ threadId,
6479
+ shouldForkOnEdit,
6480
+ chatMessages,
6481
+ onSendChat
6482
+ });
6483
+ const [confirmMrId, setConfirmMrId] = React39.useState(null);
6484
+ const confirmMr = React39.useMemo(
6296
6485
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6297
6486
  [confirmMrId, incomingMergeRequests]
6298
6487
  );
6299
- const handleSheetOpenChange = React38.useCallback((open) => {
6488
+ const handleSheetOpenChange = React39.useCallback((open) => {
6300
6489
  setSheetOpen(open);
6301
6490
  if (!open) Keyboard5.dismiss();
6302
6491
  }, []);
6303
- const closeSheet = React38.useCallback(() => {
6492
+ const closeSheet = React39.useCallback(() => {
6304
6493
  handleSheetOpenChange(false);
6305
6494
  }, [handleSheetOpenChange]);
6306
- const openSheet = React38.useCallback(() => setSheetOpen(true), []);
6307
- const goToChat = React38.useCallback(() => {
6495
+ const openSheet = React39.useCallback(() => setSheetOpen(true), []);
6496
+ const goToChat = React39.useCallback(() => {
6308
6497
  setActivePage("chat");
6309
6498
  openSheet();
6310
6499
  }, [openSheet]);
6311
- const backToPreview = React38.useCallback(() => {
6312
- if (Platform8.OS !== "ios") {
6500
+ const backToPreview = React39.useCallback(() => {
6501
+ if (Platform9.OS !== "ios") {
6313
6502
  Keyboard5.dismiss();
6314
6503
  setActivePage("preview");
6315
6504
  return;
@@ -6326,11 +6515,11 @@ function StudioOverlay({
6326
6515
  const t = setTimeout(finalize, 350);
6327
6516
  Keyboard5.dismiss();
6328
6517
  }, []);
6329
- const startDraw = React38.useCallback(() => {
6518
+ const startDraw = React39.useCallback(() => {
6330
6519
  setDrawing(true);
6331
6520
  closeSheet();
6332
6521
  }, [closeSheet]);
6333
- const handleDrawCapture = React38.useCallback(
6522
+ const handleDrawCapture = React39.useCallback(
6334
6523
  (dataUrl) => {
6335
6524
  setChatAttachments((prev) => [...prev, dataUrl]);
6336
6525
  setDrawing(false);
@@ -6339,7 +6528,7 @@ function StudioOverlay({
6339
6528
  },
6340
6529
  [openSheet]
6341
6530
  );
6342
- const toggleSheet = React38.useCallback(async () => {
6531
+ const toggleSheet = React39.useCallback(async () => {
6343
6532
  if (!sheetOpen) {
6344
6533
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6345
6534
  if (shouldExitTest) {
@@ -6351,7 +6540,7 @@ function StudioOverlay({
6351
6540
  closeSheet();
6352
6541
  }
6353
6542
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6354
- const handleTestMr = React38.useCallback(
6543
+ const handleTestMr = React39.useCallback(
6355
6544
  async (mr) => {
6356
6545
  if (!onTestMr) return;
6357
6546
  await onTestMr(mr);
@@ -6395,7 +6584,7 @@ function StudioOverlay({
6395
6584
  chat: /* @__PURE__ */ jsx55(
6396
6585
  ChatPanel,
6397
6586
  {
6398
- messages: chatMessages,
6587
+ messages: optimistic.messages,
6399
6588
  showTypingIndicator: chatShowTypingIndicator,
6400
6589
  loading: chatLoading,
6401
6590
  sendDisabled: chatSendDisabled,
@@ -6410,7 +6599,7 @@ function StudioOverlay({
6410
6599
  onClose: closeSheet,
6411
6600
  onNavigateHome,
6412
6601
  onStartDraw: startDraw,
6413
- onSend: onSendChat
6602
+ onSend: optimistic.onSend
6414
6603
  }
6415
6604
  )
6416
6605
  }
@@ -6469,16 +6658,16 @@ function ComergeStudio({
6469
6658
  onNavigateHome,
6470
6659
  style
6471
6660
  }) {
6472
- const [activeAppId, setActiveAppId] = React39.useState(appId);
6473
- const [runtimeAppId, setRuntimeAppId] = React39.useState(appId);
6474
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React39.useState(null);
6475
- const platform = React39.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6476
- React39.useEffect(() => {
6661
+ const [activeAppId, setActiveAppId] = React40.useState(appId);
6662
+ const [runtimeAppId, setRuntimeAppId] = React40.useState(appId);
6663
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React40.useState(null);
6664
+ const platform = React40.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6665
+ React40.useEffect(() => {
6477
6666
  setActiveAppId(appId);
6478
6667
  setRuntimeAppId(appId);
6479
6668
  setPendingRuntimeTargetAppId(null);
6480
6669
  }, [appId]);
6481
- const captureTargetRef = React39.useRef(null);
6670
+ const captureTargetRef = React40.useRef(null);
6482
6671
  return /* @__PURE__ */ jsx56(StudioBootstrap, { apiKey, children: ({ userId }) => /* @__PURE__ */ jsx56(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx56(
6483
6672
  ComergeStudioInner,
6484
6673
  {
@@ -6514,11 +6703,11 @@ function ComergeStudioInner({
6514
6703
  const { app, loading: appLoading } = useApp(activeAppId);
6515
6704
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6516
6705
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6517
- const sawEditingOnPendingTargetRef = React39.useRef(false);
6518
- React39.useEffect(() => {
6706
+ const sawEditingOnPendingTargetRef = React40.useRef(false);
6707
+ React40.useEffect(() => {
6519
6708
  sawEditingOnPendingTargetRef.current = false;
6520
6709
  }, [pendingRuntimeTargetAppId]);
6521
- React39.useEffect(() => {
6710
+ React40.useEffect(() => {
6522
6711
  if (!pendingRuntimeTargetAppId) return;
6523
6712
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6524
6713
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6535,13 +6724,38 @@ function ComergeStudioInner({
6535
6724
  platform,
6536
6725
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready"
6537
6726
  });
6727
+ const sawEditingOnActiveAppRef = React40.useRef(false);
6728
+ const [showPostEditPreparing, setShowPostEditPreparing] = React40.useState(false);
6729
+ React40.useEffect(() => {
6730
+ sawEditingOnActiveAppRef.current = false;
6731
+ setShowPostEditPreparing(false);
6732
+ }, [activeAppId]);
6733
+ React40.useEffect(() => {
6734
+ if (!(app == null ? void 0 : app.id)) return;
6735
+ if (app.status === "editing") {
6736
+ sawEditingOnActiveAppRef.current = true;
6737
+ setShowPostEditPreparing(false);
6738
+ return;
6739
+ }
6740
+ if (app.status === "ready" && sawEditingOnActiveAppRef.current) {
6741
+ setShowPostEditPreparing(true);
6742
+ sawEditingOnActiveAppRef.current = false;
6743
+ }
6744
+ }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
6745
+ React40.useEffect(() => {
6746
+ if (!showPostEditPreparing) return;
6747
+ const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
6748
+ if (!stillProcessingBaseBundle) {
6749
+ setShowPostEditPreparing(false);
6750
+ }
6751
+ }, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
6538
6752
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6539
6753
  const thread = useThreadMessages(threadId);
6540
6754
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6541
- const hasOpenOutgoingMr = React39.useMemo(() => {
6755
+ const hasOpenOutgoingMr = React40.useMemo(() => {
6542
6756
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6543
6757
  }, [mergeRequests.lists.outgoing]);
6544
- const incomingReviewMrs = React39.useMemo(() => {
6758
+ const incomingReviewMrs = React40.useMemo(() => {
6545
6759
  if (!userId) return mergeRequests.lists.incoming;
6546
6760
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6547
6761
  }, [mergeRequests.lists.incoming, userId]);
@@ -6563,9 +6777,9 @@ function ComergeStudioInner({
6563
6777
  uploadAttachments: uploader.uploadBase64Images
6564
6778
  });
6565
6779
  const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6566
- const [processingMrId, setProcessingMrId] = React39.useState(null);
6567
- const [testingMrId, setTestingMrId] = React39.useState(null);
6568
- const chatShowTypingIndicator = React39.useMemo(() => {
6780
+ const [processingMrId, setProcessingMrId] = React40.useState(null);
6781
+ const [testingMrId, setTestingMrId] = React40.useState(null);
6782
+ const chatShowTypingIndicator = React40.useMemo(() => {
6569
6783
  var _a;
6570
6784
  if (!thread.raw || thread.raw.length === 0) return false;
6571
6785
  const last = thread.raw[thread.raw.length - 1];
@@ -6573,7 +6787,15 @@ function ComergeStudioInner({
6573
6787
  return payloadType !== "outcome";
6574
6788
  }, [thread.raw]);
6575
6789
  return /* @__PURE__ */ jsx56(View45, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs35(View45, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6576
- /* @__PURE__ */ jsx56(RuntimeRenderer, { appKey, bundlePath: bundle.bundlePath, renderToken: bundle.renderToken }),
6790
+ /* @__PURE__ */ jsx56(
6791
+ RuntimeRenderer,
6792
+ {
6793
+ appKey,
6794
+ bundlePath: bundle.bundlePath,
6795
+ forcePreparing: showPostEditPreparing,
6796
+ renderToken: bundle.renderToken
6797
+ }
6798
+ ),
6577
6799
  /* @__PURE__ */ jsx56(
6578
6800
  StudioOverlay,
6579
6801
  {