@comergehq/studio 0.1.13 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,8 +36,8 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/studio/ComergeStudio.tsx
39
- var React43 = __toESM(require("react"));
40
- var import_react_native55 = require("react-native");
39
+ var React46 = __toESM(require("react"));
40
+ var import_react_native56 = require("react-native");
41
41
  var import_bottom_sheet6 = require("@gorhom/bottom-sheet");
42
42
 
43
43
  // src/studio/bootstrap/StudioBootstrap.tsx
@@ -846,6 +846,35 @@ function extractMeta(payload) {
846
846
  threadId: typeof obj.threadId === "string" ? obj.threadId : void 0
847
847
  };
848
848
  }
849
+ function getPayloadMeta(payload) {
850
+ const meta = payload == null ? void 0 : payload.meta;
851
+ if (!meta || typeof meta !== "object") return null;
852
+ return meta;
853
+ }
854
+ function isQueuedHiddenMessage(m) {
855
+ if (m.authorType !== "human") return false;
856
+ const meta = getPayloadMeta(m.payload);
857
+ return (meta == null ? void 0 : meta.visibility) === "queued";
858
+ }
859
+ function toEpochMs(value) {
860
+ if (value == null) return 0;
861
+ if (typeof value === "number") return value;
862
+ if (value instanceof Date) return value.getTime();
863
+ const parsed = Date.parse(String(value));
864
+ return Number.isFinite(parsed) ? parsed : 0;
865
+ }
866
+ function getEffectiveSortMs(m) {
867
+ const meta = getPayloadMeta(m.payload);
868
+ const runStartedAt = meta == null ? void 0 : meta.runStartedAt;
869
+ const runMs = toEpochMs(runStartedAt);
870
+ return runMs > 0 ? runMs : toEpochMs(m.createdAt);
871
+ }
872
+ function compareMessages(a, b) {
873
+ const aMs = getEffectiveSortMs(a);
874
+ const bMs = getEffectiveSortMs(b);
875
+ if (aMs !== bMs) return aMs - bMs;
876
+ return String(a.createdAt).localeCompare(String(b.createdAt));
877
+ }
849
878
  function mapMessageToChatMessage(m) {
850
879
  var _a, _b;
851
880
  const kind = typeof ((_a = m.payload) == null ? void 0 : _a.type) === "string" ? String(m.payload.type) : null;
@@ -865,8 +894,10 @@ function useThreadMessages(threadId) {
865
894
  const activeRequestIdRef = React4.useRef(0);
866
895
  const foregroundSignal = useForegroundSignal(Boolean(threadId));
867
896
  const upsertSorted = React4.useCallback((prev, m) => {
868
- const next = prev.some((x) => x.id === m.id) ? prev.map((x) => x.id === m.id ? m : x) : [...prev, m];
869
- next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
897
+ const include = !isQueuedHiddenMessage(m);
898
+ const next = prev.filter((x) => x.id !== m.id);
899
+ if (include) next.push(m);
900
+ next.sort(compareMessages);
870
901
  return next;
871
902
  }, []);
872
903
  const refetch = React4.useCallback(async () => {
@@ -880,7 +911,7 @@ function useThreadMessages(threadId) {
880
911
  try {
881
912
  const list = await messagesRepository.list(threadId);
882
913
  if (activeRequestIdRef.current !== requestId) return;
883
- setRaw([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
914
+ setRaw([...list].filter((m) => !isQueuedHiddenMessage(m)).sort(compareMessages));
884
915
  } catch (e) {
885
916
  if (activeRequestIdRef.current !== requestId) return;
886
917
  setError(e instanceof Error ? e : new Error(String(e)));
@@ -914,6 +945,7 @@ function useThreadMessages(threadId) {
914
945
  var React5 = __toESM(require("react"));
915
946
  var FileSystem = __toESM(require("expo-file-system/legacy"));
916
947
  var import_expo_asset = require("expo-asset");
948
+ var import_react_native_zip_archive = require("react-native-zip-archive");
917
949
 
918
950
  // src/data/apps/bundles/remote.ts
919
951
  var BundlesRemoteDataSourceImpl = class extends BaseRemote {
@@ -937,6 +969,13 @@ var BundlesRemoteDataSourceImpl = class extends BaseRemote {
937
969
  );
938
970
  return data;
939
971
  }
972
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
973
+ const { data } = await api.get(
974
+ `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
975
+ { params: { redirect: (options == null ? void 0 : options.redirect) ?? false, kind: options == null ? void 0 : options.kind } }
976
+ );
977
+ return data;
978
+ }
940
979
  };
941
980
  var bundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
942
981
 
@@ -958,6 +997,10 @@ var BundlesRepositoryImpl = class extends BaseRepository {
958
997
  const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
959
998
  return this.unwrapOrThrow(res);
960
999
  }
1000
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
1001
+ const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
1002
+ return this.unwrapOrThrow(res);
1003
+ }
961
1004
  };
962
1005
  var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
963
1006
 
@@ -1009,20 +1052,40 @@ async function ensureDir(path) {
1009
1052
  if (info.exists) return;
1010
1053
  await FileSystem.makeDirectoryAsync(path, { intermediates: true });
1011
1054
  }
1055
+ async function ensureBundleDir(key) {
1056
+ await ensureDir(bundlesCacheDir());
1057
+ await ensureDir(bundleDir(key));
1058
+ }
1012
1059
  function baseBundleKey(appId, platform) {
1013
1060
  return `base:${appId}:${platform}`;
1014
1061
  }
1015
1062
  function testBundleKey(appId, commitId, platform, bundleId) {
1016
1063
  return `test:${appId}:${commitId ?? "head"}:${platform}:${bundleId}`;
1017
1064
  }
1018
- function toBundleFileUri(key) {
1065
+ function legacyBundleFileUri(key) {
1019
1066
  const dir = bundlesCacheDir();
1020
1067
  return `${dir}${safeName(key)}.jsbundle`;
1021
1068
  }
1022
- function toBundleMetaFileUri(key) {
1069
+ function legacyBundleMetaFileUri(key) {
1023
1070
  const dir = bundlesCacheDir();
1024
1071
  return `${dir}${safeName(key)}.meta.json`;
1025
1072
  }
1073
+ function bundleDir(key) {
1074
+ const dir = bundlesCacheDir();
1075
+ return `${dir}${safeName(key)}/`;
1076
+ }
1077
+ function toBundleFileUri(key, platform) {
1078
+ return `${bundleDir(key)}index.${platform}.jsbundle`;
1079
+ }
1080
+ function toBundleMetaFileUri(key) {
1081
+ return `${bundleDir(key)}bundle.meta.json`;
1082
+ }
1083
+ function toAssetsMetaFileUri(key) {
1084
+ return `${bundleDir(key)}assets.meta.json`;
1085
+ }
1086
+ function toAssetsDir(key) {
1087
+ return `${bundleDir(key)}assets/`;
1088
+ }
1026
1089
  async function readJsonFile(fileUri) {
1027
1090
  try {
1028
1091
  const info = await FileSystem.getInfoAsync(fileUri);
@@ -1049,6 +1112,14 @@ async function getExistingNonEmptyFileUri(fileUri) {
1049
1112
  return null;
1050
1113
  }
1051
1114
  }
1115
+ async function getExistingBundleFileUri(key, platform) {
1116
+ const nextPath = toBundleFileUri(key, platform);
1117
+ const next = await getExistingNonEmptyFileUri(nextPath);
1118
+ if (next) return next;
1119
+ const legacyPath = legacyBundleFileUri(key);
1120
+ const legacy = await getExistingNonEmptyFileUri(legacyPath);
1121
+ return legacy;
1122
+ }
1052
1123
  async function downloadIfMissing(url, fileUri) {
1053
1124
  const existing = await getExistingNonEmptyFileUri(fileUri);
1054
1125
  if (existing) return existing;
@@ -1075,9 +1146,12 @@ async function deleteFileIfExists(fileUri) {
1075
1146
  async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
1076
1147
  if (!(embedded == null ? void 0 : embedded.module)) return null;
1077
1148
  const key = baseBundleKey(appId, platform);
1078
- const targetUri = toBundleFileUri(key);
1079
- const existing = await getExistingNonEmptyFileUri(targetUri);
1080
- if (existing) return { bundlePath: existing, meta: embedded.meta ?? null };
1149
+ const existing = await getExistingBundleFileUri(key, platform);
1150
+ if (existing) {
1151
+ return { bundlePath: existing, meta: embedded.meta ?? null };
1152
+ }
1153
+ await ensureBundleDir(key);
1154
+ const targetUri = toBundleFileUri(key, platform);
1081
1155
  const asset = import_expo_asset.Asset.fromModule(embedded.module);
1082
1156
  await asset.downloadAsync();
1083
1157
  const sourceUri = asset.localUri ?? asset.uri;
@@ -1090,8 +1164,50 @@ async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
1090
1164
  if (!finalUri) return null;
1091
1165
  return { bundlePath: finalUri, meta: embedded.meta ?? null };
1092
1166
  }
1093
- async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1094
- const tmpUri = toBundleFileUri(`tmp:${tmpKey}:${Date.now()}`);
1167
+ async function hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded) {
1168
+ var _a;
1169
+ const moduleId = embedded == null ? void 0 : embedded.assetsModule;
1170
+ if (!moduleId) return false;
1171
+ const assetsMeta = (embedded == null ? void 0 : embedded.assetsMeta) ?? null;
1172
+ const assetsDir = toAssetsDir(key);
1173
+ const metaUri = toAssetsMetaFileUri(key);
1174
+ const existingMeta = await readJsonFile(metaUri);
1175
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1176
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1177
+ const checksumMatches = Boolean(existingMeta == null ? void 0 : existingMeta.checksumSha256) && Boolean(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) && (existingMeta == null ? void 0 : existingMeta.checksumSha256) === (assetsMeta == null ? void 0 : assetsMeta.checksumSha256);
1178
+ const embeddedMetaMatches = (_a = existingMeta == null ? void 0 : existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:");
1179
+ if (assetsDirExists && checksumMatches && embeddedMetaMatches) {
1180
+ return true;
1181
+ }
1182
+ await ensureBundleDir(key);
1183
+ await ensureDir(assetsDir);
1184
+ const asset = import_expo_asset.Asset.fromModule(moduleId);
1185
+ await asset.downloadAsync();
1186
+ const sourceUri = asset.localUri ?? asset.uri;
1187
+ if (!sourceUri) return false;
1188
+ const info = await FileSystem.getInfoAsync(sourceUri);
1189
+ if (!info.exists) return false;
1190
+ const zipUri = `${bundleDir(key)}assets.zip`;
1191
+ await deleteFileIfExists(zipUri);
1192
+ await FileSystem.copyAsync({ from: sourceUri, to: zipUri });
1193
+ try {
1194
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1195
+ });
1196
+ } catch {
1197
+ }
1198
+ await ensureDir(assetsDir);
1199
+ await unzipArchive(zipUri, assetsDir);
1200
+ await writeJsonFile(metaUri, {
1201
+ checksumSha256: (assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? null,
1202
+ storageKey: `embedded:${(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? "unknown"}`,
1203
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1204
+ });
1205
+ return true;
1206
+ }
1207
+ async function safeReplaceFileFromUrl(url, targetUri, tmpKey, platform) {
1208
+ const tmpKeySafe = `tmp:${tmpKey}:${Date.now()}`;
1209
+ const tmpUri = toBundleFileUri(tmpKeySafe, platform);
1210
+ await ensureDir(bundleDir(tmpKeySafe));
1095
1211
  try {
1096
1212
  await withRetry(
1097
1213
  async () => {
@@ -1111,6 +1227,82 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1111
1227
  await deleteFileIfExists(tmpUri);
1112
1228
  }
1113
1229
  }
1230
+ async function safeReplaceFileFromUrlToPath(url, targetUri, tmpKey) {
1231
+ const tmpDir = `${bundlesCacheDir()}tmp/`;
1232
+ await ensureDir(tmpDir);
1233
+ const tmpUri = `${tmpDir}${safeName(tmpKey)}.tmp`;
1234
+ try {
1235
+ await withRetry(
1236
+ async () => {
1237
+ await deleteFileIfExists(tmpUri);
1238
+ await FileSystem.downloadAsync(url, tmpUri);
1239
+ const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
1240
+ if (!tmpOk) throw new Error("Downloaded file is empty.");
1241
+ },
1242
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1243
+ );
1244
+ await deleteFileIfExists(targetUri);
1245
+ await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
1246
+ const finalOk = await getExistingNonEmptyFileUri(targetUri);
1247
+ if (!finalOk) throw new Error("File replacement failed.");
1248
+ return targetUri;
1249
+ } finally {
1250
+ await deleteFileIfExists(tmpUri);
1251
+ }
1252
+ }
1253
+ function getMetroAssets(bundle) {
1254
+ const assets = bundle.assets ?? [];
1255
+ return assets.find((asset) => asset.kind === "metro-assets") ?? null;
1256
+ }
1257
+ async function ensureAssetsForBundle(appId, bundle, key, platform) {
1258
+ var _a;
1259
+ const asset = getMetroAssets(bundle);
1260
+ if (!(asset == null ? void 0 : asset.storageKey)) return;
1261
+ await ensureBundleDir(key);
1262
+ const assetsDir = toAssetsDir(key);
1263
+ await ensureDir(assetsDir);
1264
+ const metaUri = toAssetsMetaFileUri(key);
1265
+ const existingMeta = await readJsonFile(metaUri);
1266
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1267
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1268
+ if ((existingMeta == null ? void 0 : existingMeta.checksumSha256) && asset.checksumSha256 && existingMeta.checksumSha256 === asset.checksumSha256 && (existingMeta.storageKey === asset.storageKey || ((_a = existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:"))) && assetsDirExists) {
1269
+ return;
1270
+ }
1271
+ const signed = await withRetry(
1272
+ async () => {
1273
+ return await bundlesRepository.getSignedAssetsDownloadUrl(appId, bundle.id, {
1274
+ redirect: false,
1275
+ kind: asset.kind
1276
+ });
1277
+ },
1278
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1279
+ );
1280
+ const zipUri = `${bundleDir(key)}assets.zip`;
1281
+ await safeReplaceFileFromUrlToPath(signed.url, zipUri, `${appId}:${bundle.id}:${platform}:${asset.kind}`);
1282
+ try {
1283
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1284
+ });
1285
+ } catch {
1286
+ }
1287
+ await ensureDir(assetsDir);
1288
+ await unzipArchive(zipUri, assetsDir);
1289
+ await writeJsonFile(metaUri, {
1290
+ checksumSha256: asset.checksumSha256 ?? null,
1291
+ storageKey: asset.storageKey,
1292
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1293
+ });
1294
+ }
1295
+ async function unzipArchive(sourceUri, destDir) {
1296
+ try {
1297
+ await (0, import_react_native_zip_archive.unzip)(sourceUri, destDir);
1298
+ } catch (e) {
1299
+ throw new Error(
1300
+ `Failed to extract assets archive. Ensure 'react-native-zip-archive' is installed in the host app. ${String(
1301
+ (e == null ? void 0 : e.message) ?? e
1302
+ )}`
1303
+ );
1304
+ }
1305
+ }
1114
1306
  async function pollBundle(appId, bundleId, opts) {
1115
1307
  const start = Date.now();
1116
1308
  while (true) {
@@ -1146,18 +1338,33 @@ async function resolveBundlePath(src, platform, mode) {
1146
1338
  if (finalBundle.status === "failed") {
1147
1339
  throw new Error("Bundle build failed.");
1148
1340
  }
1341
+ let bundleWithAssets = finalBundle;
1342
+ if (finalBundle.status === "succeeded" && (!finalBundle.assets || finalBundle.assets.length === 0)) {
1343
+ try {
1344
+ bundleWithAssets = await bundlesRepository.getById(appId, finalBundle.id);
1345
+ } catch {
1346
+ bundleWithAssets = finalBundle;
1347
+ }
1348
+ }
1149
1349
  const signed = await withRetry(
1150
1350
  async () => {
1151
1351
  return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
1152
1352
  },
1153
1353
  { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1154
1354
  );
1355
+ const key = mode === "base" ? baseBundleKey(appId, platform) : testBundleKey(appId, commitId, platform, finalBundle.id);
1356
+ await ensureBundleDir(key);
1155
1357
  const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
1156
1358
  signed.url,
1157
- toBundleFileUri(baseBundleKey(appId, platform)),
1158
- `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`
1159
- ) : await downloadIfMissing(signed.url, toBundleFileUri(testBundleKey(appId, commitId, platform, finalBundle.id)));
1160
- return { bundlePath, label: "Ready", bundle: finalBundle };
1359
+ toBundleFileUri(key, platform),
1360
+ `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`,
1361
+ platform
1362
+ ) : await downloadIfMissing(signed.url, toBundleFileUri(key, platform));
1363
+ try {
1364
+ await ensureAssetsForBundle(appId, bundleWithAssets, key, platform);
1365
+ } catch {
1366
+ }
1367
+ return { bundlePath, label: "Ready", bundle: bundleWithAssets };
1161
1368
  }
1162
1369
  function useBundleManager({
1163
1370
  base,
@@ -1198,13 +1405,12 @@ function useBundleManager({
1198
1405
  const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
1199
1406
  const hydrateBaseFromDisk = React5.useCallback(
1200
1407
  async (appId, reason) => {
1201
- var _a;
1408
+ var _a, _b, _c;
1202
1409
  try {
1203
1410
  const dir = bundlesCacheDir();
1204
1411
  await ensureDir(dir);
1205
1412
  const key = baseBundleKey(appId, platform);
1206
- const uri = toBundleFileUri(key);
1207
- let existing = await getExistingNonEmptyFileUri(uri);
1413
+ let existing = await getExistingBundleFileUri(key, platform);
1208
1414
  let embeddedMeta = null;
1209
1415
  if (!existing) {
1210
1416
  const embedded = (_a = embeddedBaseBundlesRef.current) == null ? void 0 : _a[platform];
@@ -1213,14 +1419,25 @@ function useBundleManager({
1213
1419
  existing = hydrated.bundlePath;
1214
1420
  embeddedMeta = hydrated.meta ?? null;
1215
1421
  if (embeddedMeta) {
1422
+ await ensureBundleDir(key);
1216
1423
  await writeJsonFile(toBundleMetaFileUri(key), embeddedMeta);
1424
+ await writeJsonFile(legacyBundleMetaFileUri(key), embeddedMeta);
1217
1425
  }
1218
1426
  }
1219
1427
  }
1220
1428
  if (existing) {
1221
1429
  lastBaseBundlePathRef.current = existing;
1222
1430
  setBundlePath(existing);
1223
- const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key));
1431
+ const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key)) ?? await readJsonFile(legacyBundleMetaFileUri(key));
1432
+ const embedded = (_b = embeddedBaseBundlesRef.current) == null ? void 0 : _b[platform];
1433
+ const embeddedFingerprint = ((_c = embedded == null ? void 0 : embedded.meta) == null ? void 0 : _c.fingerprint) ?? null;
1434
+ const actualFingerprint = (meta == null ? void 0 : meta.fingerprint) ?? (embeddedMeta == null ? void 0 : embeddedMeta.fingerprint) ?? null;
1435
+ if ((embedded == null ? void 0 : embedded.assetsModule) && embeddedFingerprint && actualFingerprint === embeddedFingerprint) {
1436
+ try {
1437
+ await hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded);
1438
+ } catch {
1439
+ }
1440
+ }
1224
1441
  if (meta == null ? void 0 : meta.fingerprint) {
1225
1442
  lastBaseFingerprintRef.current = meta.fingerprint;
1226
1443
  }
@@ -1285,7 +1502,16 @@ function useBundleManager({
1285
1502
  lastBaseFingerprintRef.current = fingerprint;
1286
1503
  hasCompletedFirstNetworkBaseLoadRef.current = true;
1287
1504
  initialHydratedBaseFromDiskRef.current = false;
1288
- void writeJsonFile(toBundleMetaFileUri(baseBundleKey(src.appId, platform)), {
1505
+ const metaKey = baseBundleKey(src.appId, platform);
1506
+ await ensureBundleDir(metaKey);
1507
+ void writeJsonFile(toBundleMetaFileUri(metaKey), {
1508
+ fingerprint,
1509
+ bundleId: bundle.id,
1510
+ checksumSha256: bundle.checksumSha256 ?? null,
1511
+ size: bundle.size ?? null,
1512
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1513
+ });
1514
+ void writeJsonFile(legacyBundleMetaFileUri(metaKey), {
1289
1515
  fingerprint,
1290
1516
  bundleId: bundle.id,
1291
1517
  checksumSha256: bundle.checksumSha256 ?? null,
@@ -1737,6 +1963,9 @@ function useStudioActions({
1737
1963
  userId,
1738
1964
  app,
1739
1965
  onForkedApp,
1966
+ onEditStart,
1967
+ onEditQueued,
1968
+ onEditFinished,
1740
1969
  uploadAttachments
1741
1970
  }) {
1742
1971
  const [forking, setForking] = React8.useState(false);
@@ -1752,6 +1981,7 @@ function useStudioActions({
1752
1981
  setSending(true);
1753
1982
  setError(null);
1754
1983
  try {
1984
+ onEditStart == null ? void 0 : onEditStart();
1755
1985
  let targetApp = app;
1756
1986
  if (shouldForkOnEdit) {
1757
1987
  setForking(true);
@@ -1767,12 +1997,16 @@ function useStudioActions({
1767
1997
  if (attachments && attachments.length > 0 && uploadAttachments) {
1768
1998
  attachmentMetas = await uploadAttachments({ threadId, appId: targetApp.id, dataUrls: attachments });
1769
1999
  }
1770
- await agentRepository.editApp({
2000
+ const editResult = await agentRepository.editApp({
1771
2001
  prompt,
1772
2002
  thread_id: threadId,
1773
2003
  app_id: targetApp.id,
1774
2004
  attachments: attachmentMetas && attachmentMetas.length > 0 ? attachmentMetas : void 0
1775
2005
  });
2006
+ onEditQueued == null ? void 0 : onEditQueued({
2007
+ queueItemId: editResult.queueItemId ?? null,
2008
+ queuePosition: editResult.queuePosition ?? null
2009
+ });
1776
2010
  } catch (e) {
1777
2011
  const err = e instanceof Error ? e : new Error(String(e));
1778
2012
  setError(err);
@@ -1780,32 +2014,14 @@ function useStudioActions({
1780
2014
  } finally {
1781
2015
  setForking(false);
1782
2016
  setSending(false);
2017
+ onEditFinished == null ? void 0 : onEditFinished();
1783
2018
  }
1784
2019
  },
1785
- [app, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
2020
+ [app, onEditFinished, onEditQueued, onEditStart, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
1786
2021
  );
1787
2022
  return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
1788
2023
  }
1789
2024
 
1790
- // src/studio/lib/chat.ts
1791
- function hasNoOutcomeAfterLastHuman(messages) {
1792
- if (!messages || messages.length === 0) return false;
1793
- let lastHumanIndex = -1;
1794
- for (let i = messages.length - 1; i >= 0; i -= 1) {
1795
- if (messages[i].authorType === "human") {
1796
- lastHumanIndex = i;
1797
- break;
1798
- }
1799
- }
1800
- if (lastHumanIndex === -1) return false;
1801
- for (let i = lastHumanIndex + 1; i < messages.length; i += 1) {
1802
- const m = messages[i];
1803
- const payload = m.payload;
1804
- if (m.authorType === "ai" && (payload == null ? void 0 : payload.type) === "outcome") return false;
1805
- }
1806
- return true;
1807
- }
1808
-
1809
2025
  // src/studio/ui/RuntimeRenderer.tsx
1810
2026
  var React9 = __toESM(require("react"));
1811
2027
  var import_react_native6 = require("react-native");
@@ -1843,8 +2059,8 @@ function RuntimeRenderer({
1843
2059
  }
1844
2060
 
1845
2061
  // src/studio/ui/StudioOverlay.tsx
1846
- var React42 = __toESM(require("react"));
1847
- var import_react_native54 = require("react-native");
2062
+ var React43 = __toESM(require("react"));
2063
+ var import_react_native55 = require("react-native");
1848
2064
 
1849
2065
  // src/components/studio-sheet/StudioBottomSheet.tsx
1850
2066
  var React12 = __toESM(require("react"));
@@ -5648,8 +5864,8 @@ function PreviewPanel({
5648
5864
  }
5649
5865
 
5650
5866
  // src/studio/ui/ChatPanel.tsx
5651
- var React39 = __toESM(require("react"));
5652
- var import_react_native51 = require("react-native");
5867
+ var React40 = __toESM(require("react"));
5868
+ var import_react_native52 = require("react-native");
5653
5869
 
5654
5870
  // src/components/chat/ChatPage.tsx
5655
5871
  var React37 = __toESM(require("react"));
@@ -5846,6 +6062,7 @@ function ChatPage({
5846
6062
  showTypingIndicator,
5847
6063
  renderMessageContent,
5848
6064
  topBanner,
6065
+ composerTop,
5849
6066
  composer,
5850
6067
  overlay,
5851
6068
  style,
@@ -5856,6 +6073,7 @@ function ChatPage({
5856
6073
  const theme = useTheme();
5857
6074
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
5858
6075
  const [composerHeight, setComposerHeight] = React37.useState(0);
6076
+ const [composerTopHeight, setComposerTopHeight] = React37.useState(0);
5859
6077
  const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
5860
6078
  React37.useEffect(() => {
5861
6079
  if (import_react_native47.Platform.OS !== "ios") return;
@@ -5867,8 +6085,9 @@ function ChatPage({
5867
6085
  };
5868
6086
  }, []);
5869
6087
  const footerBottomPadding = import_react_native47.Platform.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5870
- const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5871
- const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
6088
+ const totalComposerHeight = composerHeight + composerTopHeight;
6089
+ const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
6090
+ const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
5872
6091
  const resolvedOverlay = React37.useMemo(() => {
5873
6092
  var _a;
5874
6093
  if (!overlay) return null;
@@ -5878,6 +6097,10 @@ function ChatPage({
5878
6097
  style: [prevStyle, { bottom: overlayBottom }]
5879
6098
  });
5880
6099
  }, [overlay, overlayBottom]);
6100
+ React37.useEffect(() => {
6101
+ if (composerTop) return;
6102
+ setComposerTopHeight(0);
6103
+ }, [composerTop]);
5881
6104
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native47.View, { style: [{ flex: 1 }, style], children: [
5882
6105
  header ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { children: header }) : null,
5883
6106
  topBanner ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
@@ -5902,7 +6125,7 @@ function ChatPage({
5902
6125
  ]
5903
6126
  }
5904
6127
  ),
5905
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6128
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
5906
6129
  import_react_native47.View,
5907
6130
  {
5908
6131
  style: {
@@ -5914,14 +6137,24 @@ function ChatPage({
5914
6137
  paddingTop: theme.spacing.sm,
5915
6138
  paddingBottom: footerBottomPadding
5916
6139
  },
5917
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
5918
- ChatComposer,
5919
- {
5920
- ...composer,
5921
- attachments: composer.attachments ?? [],
5922
- onLayout: ({ height }) => setComposerHeight(height)
5923
- }
5924
- )
6140
+ children: [
6141
+ composerTop ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6142
+ import_react_native47.View,
6143
+ {
6144
+ style: { marginBottom: theme.spacing.sm },
6145
+ onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
6146
+ children: composerTop
6147
+ }
6148
+ ) : null,
6149
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6150
+ ChatComposer,
6151
+ {
6152
+ ...composer,
6153
+ attachments: composer.attachments ?? [],
6154
+ onLayout: ({ height }) => setComposerHeight(height)
6155
+ }
6156
+ )
6157
+ ]
5925
6158
  }
5926
6159
  )
5927
6160
  ] })
@@ -6071,8 +6304,154 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6071
6304
  );
6072
6305
  }
6073
6306
 
6074
- // src/studio/ui/ChatPanel.tsx
6307
+ // src/components/chat/ChatQueue.tsx
6308
+ var React39 = __toESM(require("react"));
6309
+ var import_react_native51 = require("react-native");
6075
6310
  var import_jsx_runtime53 = require("react/jsx-runtime");
6311
+ function ChatQueue({ items, onRemove }) {
6312
+ const theme = useTheme();
6313
+ const [expanded, setExpanded] = React39.useState({});
6314
+ const [canExpand, setCanExpand] = React39.useState({});
6315
+ const [collapsedText, setCollapsedText] = React39.useState({});
6316
+ const [removing, setRemoving] = React39.useState({});
6317
+ const buildCollapsedText = React39.useCallback((lines) => {
6318
+ var _a, _b;
6319
+ const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
6320
+ const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
6321
+ const moreLabel = "more";
6322
+ const reserve = `\u2026 ${moreLabel}`.length;
6323
+ let trimmedLine2 = line2;
6324
+ if (trimmedLine2.length > reserve) {
6325
+ trimmedLine2 = trimmedLine2.slice(0, Math.max(0, trimmedLine2.length - reserve));
6326
+ } else {
6327
+ trimmedLine2 = "";
6328
+ }
6329
+ trimmedLine2 = trimmedLine2.replace(/\s+$/, "");
6330
+ return `${line1}
6331
+ ${trimmedLine2}\u2026 `;
6332
+ }, []);
6333
+ React39.useEffect(() => {
6334
+ if (items.length === 0) return;
6335
+ const ids = new Set(items.map((item) => item.id));
6336
+ setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6337
+ setCanExpand((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6338
+ setCollapsedText((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6339
+ setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6340
+ }, [items]);
6341
+ if (items.length === 0) return null;
6342
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6343
+ import_react_native51.View,
6344
+ {
6345
+ style: {
6346
+ borderWidth: 1,
6347
+ borderColor: theme.colors.border,
6348
+ borderRadius: theme.radii.lg,
6349
+ marginHorizontal: theme.spacing.md,
6350
+ padding: theme.spacing.md,
6351
+ backgroundColor: "transparent"
6352
+ },
6353
+ children: [
6354
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
6355
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
6356
+ const isExpanded = Boolean(expanded[item.id]);
6357
+ const showToggle = Boolean(canExpand[item.id]);
6358
+ const prompt = item.prompt ?? "";
6359
+ const moreLabel = "more";
6360
+ const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
6361
+ const isRemoving = Boolean(removing[item.id]);
6362
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6363
+ import_react_native51.View,
6364
+ {
6365
+ style: {
6366
+ flexDirection: "row",
6367
+ alignItems: "flex-start",
6368
+ gap: theme.spacing.sm,
6369
+ paddingHorizontal: theme.spacing.md,
6370
+ paddingVertical: theme.spacing.sm,
6371
+ borderRadius: theme.radii.md,
6372
+ backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
6373
+ },
6374
+ children: [
6375
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
6376
+ !canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6377
+ Text,
6378
+ {
6379
+ style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
6380
+ onTextLayout: (e) => {
6381
+ var _a;
6382
+ const lines = (_a = e.nativeEvent) == null ? void 0 : _a.lines;
6383
+ if (!lines) return;
6384
+ if (lines.length > 2) {
6385
+ setCanExpand((prev) => ({ ...prev, [item.id]: true }));
6386
+ setCollapsedText((prev) => ({
6387
+ ...prev,
6388
+ [item.id]: buildCollapsedText(lines)
6389
+ }));
6390
+ }
6391
+ },
6392
+ children: prompt
6393
+ }
6394
+ ) : null,
6395
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6396
+ Text,
6397
+ {
6398
+ variant: "bodyMuted",
6399
+ numberOfLines: isExpanded ? void 0 : 2,
6400
+ children: [
6401
+ displayPrompt,
6402
+ !isExpanded && showToggle ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6403
+ Text,
6404
+ {
6405
+ color: theme.colors.text,
6406
+ onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: true })),
6407
+ suppressHighlighting: true,
6408
+ children: moreLabel
6409
+ }
6410
+ ) : null
6411
+ ]
6412
+ }
6413
+ ),
6414
+ showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6415
+ import_react_native51.Pressable,
6416
+ {
6417
+ onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
6418
+ hitSlop: 6,
6419
+ style: { alignSelf: "flex-start", marginTop: 4 },
6420
+ children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
6421
+ }
6422
+ ) : null
6423
+ ] }),
6424
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6425
+ import_react_native51.Pressable,
6426
+ {
6427
+ onPress: () => {
6428
+ if (!onRemove || isRemoving) return;
6429
+ setRemoving((prev) => ({ ...prev, [item.id]: true }));
6430
+ Promise.resolve(onRemove(item.id)).finally(() => {
6431
+ setRemoving((prev) => {
6432
+ if (!prev[item.id]) return prev;
6433
+ const { [item.id]: _removed, ...rest } = prev;
6434
+ return rest;
6435
+ });
6436
+ });
6437
+ },
6438
+ hitSlop: 8,
6439
+ style: { alignSelf: "center" },
6440
+ children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconClose, { size: 14, colorToken: "text" })
6441
+ }
6442
+ )
6443
+ ]
6444
+ },
6445
+ item.id
6446
+ );
6447
+ }) })
6448
+ ]
6449
+ }
6450
+ );
6451
+ }
6452
+
6453
+ // src/studio/ui/ChatPanel.tsx
6454
+ var import_jsx_runtime54 = require("react/jsx-runtime");
6076
6455
  function ChatPanel({
6077
6456
  title = "Chat",
6078
6457
  autoFocusComposer = false,
@@ -6090,11 +6469,13 @@ function ChatPanel({
6090
6469
  onClose,
6091
6470
  onNavigateHome,
6092
6471
  onStartDraw,
6093
- onSend
6472
+ onSend,
6473
+ queueItems = [],
6474
+ onRemoveQueueItem
6094
6475
  }) {
6095
- const listRef = React39.useRef(null);
6096
- const [nearBottom, setNearBottom] = React39.useState(true);
6097
- const handleSend = React39.useCallback(
6476
+ const listRef = React40.useRef(null);
6477
+ const [nearBottom, setNearBottom] = React40.useState(true);
6478
+ const handleSend = React40.useCallback(
6098
6479
  async (text, composerAttachments) => {
6099
6480
  const all = composerAttachments ?? attachments;
6100
6481
  await onSend(text, all.length > 0 ? all : void 0);
@@ -6108,25 +6489,25 @@ function ChatPanel({
6108
6489
  },
6109
6490
  [attachments, nearBottom, onClearAttachments, onSend]
6110
6491
  );
6111
- const handleScrollToBottom = React39.useCallback(() => {
6492
+ const handleScrollToBottom = React40.useCallback(() => {
6112
6493
  var _a;
6113
6494
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
6114
6495
  }, []);
6115
- const header = /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6496
+ const header = /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6116
6497
  ChatHeader,
6117
6498
  {
6118
- left: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6119
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
6120
- onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
6499
+ left: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6500
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
6501
+ onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
6121
6502
  ] }),
6122
- right: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6123
- onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
6124
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
6503
+ right: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6504
+ onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
6505
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
6125
6506
  ] }),
6126
6507
  center: null
6127
6508
  }
6128
6509
  );
6129
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6510
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6130
6511
  ForkNoticeBanner,
6131
6512
  {
6132
6513
  isOwner: !shouldForkOnEdit,
@@ -6135,33 +6516,35 @@ function ChatPanel({
6135
6516
  ) : null;
6136
6517
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
6137
6518
  if (showMessagesLoading) {
6138
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
6139
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { children: header }),
6140
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
6141
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
6142
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.ActivityIndicator, {}),
6143
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { height: 12 } }),
6144
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
6519
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1 }, children: [
6520
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { children: header }),
6521
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
6522
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
6523
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.ActivityIndicator, {}),
6524
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { height: 12 } }),
6525
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
6145
6526
  ] })
6146
6527
  ] });
6147
6528
  }
6148
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6529
+ const queueTop = queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null;
6530
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6149
6531
  ChatPage,
6150
6532
  {
6151
6533
  header,
6152
6534
  messages,
6153
6535
  showTypingIndicator,
6154
6536
  topBanner,
6537
+ composerTop: queueTop,
6155
6538
  composerHorizontalPadding: 0,
6156
6539
  listRef,
6157
6540
  onNearBottomChange: setNearBottom,
6158
- overlay: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6541
+ overlay: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6159
6542
  ScrollToBottomButton,
6160
6543
  {
6161
6544
  visible: !nearBottom,
6162
6545
  onPress: handleScrollToBottom,
6163
6546
  style: { bottom: 80 },
6164
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
6547
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
6165
6548
  }
6166
6549
  ),
6167
6550
  composer: {
@@ -6182,12 +6565,12 @@ function ChatPanel({
6182
6565
  }
6183
6566
 
6184
6567
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6185
- var React40 = __toESM(require("react"));
6186
- var import_react_native53 = require("react-native");
6568
+ var React41 = __toESM(require("react"));
6569
+ var import_react_native54 = require("react-native");
6187
6570
 
6188
6571
  // src/components/primitives/Modal.tsx
6189
- var import_react_native52 = require("react-native");
6190
- var import_jsx_runtime54 = require("react/jsx-runtime");
6572
+ var import_react_native53 = require("react-native");
6573
+ var import_jsx_runtime55 = require("react/jsx-runtime");
6191
6574
  function Modal({
6192
6575
  visible,
6193
6576
  onRequestClose,
@@ -6196,30 +6579,30 @@ function Modal({
6196
6579
  contentStyle
6197
6580
  }) {
6198
6581
  const theme = useTheme();
6199
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6200
- import_react_native52.Modal,
6582
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6583
+ import_react_native53.Modal,
6201
6584
  {
6202
6585
  visible,
6203
6586
  transparent: true,
6204
6587
  animationType: "fade",
6205
6588
  onRequestClose,
6206
- children: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
6207
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6208
- import_react_native52.Pressable,
6589
+ children: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
6590
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6591
+ import_react_native53.Pressable,
6209
6592
  {
6210
6593
  accessibilityRole: "button",
6211
6594
  onPress: dismissOnBackdropPress ? onRequestClose : void 0,
6212
6595
  style: { position: "absolute", inset: 0 }
6213
6596
  }
6214
6597
  ),
6215
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
6598
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
6216
6599
  ] })
6217
6600
  }
6218
6601
  );
6219
6602
  }
6220
6603
 
6221
6604
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6222
- var import_jsx_runtime55 = require("react/jsx-runtime");
6605
+ var import_jsx_runtime56 = require("react/jsx-runtime");
6223
6606
  function ConfirmMergeRequestDialog({
6224
6607
  visible,
6225
6608
  onOpenChange,
@@ -6230,14 +6613,14 @@ function ConfirmMergeRequestDialog({
6230
6613
  onTestFirst
6231
6614
  }) {
6232
6615
  const theme = useTheme();
6233
- const close = React40.useCallback(() => onOpenChange(false), [onOpenChange]);
6616
+ const close = React41.useCallback(() => onOpenChange(false), [onOpenChange]);
6234
6617
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6235
- const handleConfirm = React40.useCallback(() => {
6618
+ const handleConfirm = React41.useCallback(() => {
6236
6619
  if (!mergeRequest) return;
6237
6620
  onOpenChange(false);
6238
6621
  void onConfirm();
6239
6622
  }, [mergeRequest, onConfirm, onOpenChange]);
6240
- const handleTestFirst = React40.useCallback(() => {
6623
+ const handleTestFirst = React41.useCallback(() => {
6241
6624
  if (!mergeRequest) return;
6242
6625
  onOpenChange(false);
6243
6626
  void onTestFirst(mergeRequest);
@@ -6249,7 +6632,7 @@ function ConfirmMergeRequestDialog({
6249
6632
  justifyContent: "center",
6250
6633
  alignSelf: "stretch"
6251
6634
  };
6252
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
6635
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
6253
6636
  Modal,
6254
6637
  {
6255
6638
  visible,
@@ -6260,7 +6643,7 @@ function ConfirmMergeRequestDialog({
6260
6643
  backgroundColor: theme.colors.background
6261
6644
  },
6262
6645
  children: [
6263
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6646
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6264
6647
  Text,
6265
6648
  {
6266
6649
  style: {
@@ -6272,9 +6655,9 @@ function ConfirmMergeRequestDialog({
6272
6655
  children: "Are you sure you want to approve this merge request?"
6273
6656
  }
6274
6657
  ) }),
6275
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { marginTop: 16 }, children: [
6276
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6277
- import_react_native53.View,
6658
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native54.View, { style: { marginTop: 16 }, children: [
6659
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6660
+ import_react_native54.View,
6278
6661
  {
6279
6662
  style: [
6280
6663
  fullWidthButtonBase,
@@ -6283,22 +6666,22 @@ function ConfirmMergeRequestDialog({
6283
6666
  opacity: canConfirm ? 1 : 0.5
6284
6667
  }
6285
6668
  ],
6286
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6287
- import_react_native53.Pressable,
6669
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6670
+ import_react_native54.Pressable,
6288
6671
  {
6289
6672
  accessibilityRole: "button",
6290
6673
  accessibilityLabel: "Approve Merge",
6291
6674
  disabled: !canConfirm,
6292
6675
  onPress: handleConfirm,
6293
6676
  style: [fullWidthButtonBase, { flex: 1 }],
6294
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
6677
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
6295
6678
  }
6296
6679
  )
6297
6680
  }
6298
6681
  ),
6299
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { height: 8 } }),
6300
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6301
- import_react_native53.View,
6682
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
6683
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6684
+ import_react_native54.View,
6302
6685
  {
6303
6686
  style: [
6304
6687
  fullWidthButtonBase,
@@ -6309,22 +6692,22 @@ function ConfirmMergeRequestDialog({
6309
6692
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
6310
6693
  }
6311
6694
  ],
6312
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6313
- import_react_native53.Pressable,
6695
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6696
+ import_react_native54.Pressable,
6314
6697
  {
6315
6698
  accessibilityRole: "button",
6316
6699
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
6317
6700
  disabled: isBuilding || !mergeRequest,
6318
6701
  onPress: handleTestFirst,
6319
6702
  style: [fullWidthButtonBase, { flex: 1 }],
6320
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
6703
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
6321
6704
  }
6322
6705
  )
6323
6706
  }
6324
6707
  ),
6325
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { height: 8 } }),
6326
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6327
- import_react_native53.View,
6708
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
6709
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6710
+ import_react_native54.View,
6328
6711
  {
6329
6712
  style: [
6330
6713
  fullWidthButtonBase,
@@ -6334,14 +6717,14 @@ function ConfirmMergeRequestDialog({
6334
6717
  borderColor: theme.colors.border
6335
6718
  }
6336
6719
  ],
6337
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6338
- import_react_native53.Pressable,
6720
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6721
+ import_react_native54.Pressable,
6339
6722
  {
6340
6723
  accessibilityRole: "button",
6341
6724
  accessibilityLabel: "Cancel",
6342
6725
  onPress: close,
6343
6726
  style: [fullWidthButtonBase, { flex: 1 }],
6344
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
6727
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
6345
6728
  }
6346
6729
  )
6347
6730
  }
@@ -6353,7 +6736,7 @@ function ConfirmMergeRequestDialog({
6353
6736
  }
6354
6737
 
6355
6738
  // src/studio/ui/ConfirmMergeFlow.tsx
6356
- var import_jsx_runtime56 = require("react/jsx-runtime");
6739
+ var import_jsx_runtime57 = require("react/jsx-runtime");
6357
6740
  function ConfirmMergeFlow({
6358
6741
  visible,
6359
6742
  onOpenChange,
@@ -6364,7 +6747,7 @@ function ConfirmMergeFlow({
6364
6747
  onConfirm,
6365
6748
  onTestFirst
6366
6749
  }) {
6367
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6750
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6368
6751
  ConfirmMergeRequestDialog,
6369
6752
  {
6370
6753
  visible,
@@ -6386,11 +6769,11 @@ function ConfirmMergeFlow({
6386
6769
  }
6387
6770
 
6388
6771
  // src/studio/hooks/useOptimisticChatMessages.ts
6389
- var React41 = __toESM(require("react"));
6772
+ var React42 = __toESM(require("react"));
6390
6773
  function makeOptimisticId() {
6391
6774
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6392
6775
  }
6393
- function toEpochMs(createdAt) {
6776
+ function toEpochMs2(createdAt) {
6394
6777
  if (createdAt == null) return 0;
6395
6778
  if (typeof createdAt === "number") return createdAt;
6396
6779
  if (createdAt instanceof Date) return createdAt.getTime();
@@ -6409,7 +6792,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6409
6792
  for (const m of candidates) {
6410
6793
  if (m.author !== "human") continue;
6411
6794
  if (normalize(m.content) !== target) continue;
6412
- const serverMs = toEpochMs(m.createdAt);
6795
+ const serverMs = toEpochMs2(m.createdAt);
6413
6796
  const optimisticMs = Date.parse(o.createdAtIso);
6414
6797
  if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
6415
6798
  if (serverMs + 12e4 < optimisticMs) continue;
@@ -6421,14 +6804,15 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6421
6804
  function useOptimisticChatMessages({
6422
6805
  threadId,
6423
6806
  shouldForkOnEdit,
6807
+ disableOptimistic = false,
6424
6808
  chatMessages,
6425
6809
  onSendChat
6426
6810
  }) {
6427
- const [optimisticChat, setOptimisticChat] = React41.useState([]);
6428
- React41.useEffect(() => {
6811
+ const [optimisticChat, setOptimisticChat] = React42.useState([]);
6812
+ React42.useEffect(() => {
6429
6813
  setOptimisticChat([]);
6430
6814
  }, [threadId]);
6431
- const messages = React41.useMemo(() => {
6815
+ const messages = React42.useMemo(() => {
6432
6816
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6433
6817
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6434
6818
  if (unresolved.length === 0) return chatMessages;
@@ -6444,7 +6828,7 @@ function useOptimisticChatMessages({
6444
6828
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6445
6829
  return merged;
6446
6830
  }, [chatMessages, optimisticChat]);
6447
- React41.useEffect(() => {
6831
+ React42.useEffect(() => {
6448
6832
  if (optimisticChat.length === 0) return;
6449
6833
  setOptimisticChat((prev) => {
6450
6834
  if (prev.length === 0) return prev;
@@ -6452,9 +6836,9 @@ function useOptimisticChatMessages({
6452
6836
  return next.length === prev.length ? prev : next;
6453
6837
  });
6454
6838
  }, [chatMessages, optimisticChat.length]);
6455
- const onSend = React41.useCallback(
6839
+ const onSend = React42.useCallback(
6456
6840
  async (text, attachments) => {
6457
- if (shouldForkOnEdit) {
6841
+ if (shouldForkOnEdit || disableOptimistic) {
6458
6842
  await onSendChat(text, attachments);
6459
6843
  return;
6460
6844
  }
@@ -6466,14 +6850,14 @@ function useOptimisticChatMessages({
6466
6850
  setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
6467
6851
  });
6468
6852
  },
6469
- [chatMessages, onSendChat, shouldForkOnEdit]
6853
+ [chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
6470
6854
  );
6471
6855
  return { messages, onSend };
6472
6856
  }
6473
6857
 
6474
6858
  // src/studio/ui/StudioOverlay.tsx
6475
6859
  var import_studio_control = require("@comergehq/studio-control");
6476
- var import_jsx_runtime57 = require("react/jsx-runtime");
6860
+ var import_jsx_runtime58 = require("react/jsx-runtime");
6477
6861
  function StudioOverlay({
6478
6862
  captureTargetRef,
6479
6863
  app,
@@ -6500,46 +6884,50 @@ function StudioOverlay({
6500
6884
  chatSending,
6501
6885
  chatShowTypingIndicator,
6502
6886
  onSendChat,
6887
+ chatQueueItems,
6888
+ onRemoveQueueItem,
6503
6889
  onNavigateHome,
6504
6890
  showBubble,
6505
6891
  studioControlOptions
6506
6892
  }) {
6507
6893
  const theme = useTheme();
6508
- const { width } = (0, import_react_native54.useWindowDimensions)();
6509
- const [sheetOpen, setSheetOpen] = React42.useState(false);
6510
- const sheetOpenRef = React42.useRef(sheetOpen);
6511
- const [activePage, setActivePage] = React42.useState("preview");
6512
- const [drawing, setDrawing] = React42.useState(false);
6513
- const [chatAttachments, setChatAttachments] = React42.useState([]);
6514
- const [commentsAppId, setCommentsAppId] = React42.useState(null);
6515
- const [commentsCount, setCommentsCount] = React42.useState(null);
6894
+ const { width } = (0, import_react_native55.useWindowDimensions)();
6895
+ const [sheetOpen, setSheetOpen] = React43.useState(false);
6896
+ const sheetOpenRef = React43.useRef(sheetOpen);
6897
+ const [activePage, setActivePage] = React43.useState("preview");
6898
+ const [drawing, setDrawing] = React43.useState(false);
6899
+ const [chatAttachments, setChatAttachments] = React43.useState([]);
6900
+ const [commentsAppId, setCommentsAppId] = React43.useState(null);
6901
+ const [commentsCount, setCommentsCount] = React43.useState(null);
6516
6902
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
6903
+ const disableOptimistic = Boolean(chatQueueItems && chatQueueItems.length > 0) || (app == null ? void 0 : app.status) === "editing";
6517
6904
  const optimistic = useOptimisticChatMessages({
6518
6905
  threadId,
6519
6906
  shouldForkOnEdit,
6907
+ disableOptimistic,
6520
6908
  chatMessages,
6521
6909
  onSendChat
6522
6910
  });
6523
- const [confirmMrId, setConfirmMrId] = React42.useState(null);
6524
- const confirmMr = React42.useMemo(
6911
+ const [confirmMrId, setConfirmMrId] = React43.useState(null);
6912
+ const confirmMr = React43.useMemo(
6525
6913
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6526
6914
  [confirmMrId, incomingMergeRequests]
6527
6915
  );
6528
- const handleSheetOpenChange = React42.useCallback((open) => {
6916
+ const handleSheetOpenChange = React43.useCallback((open) => {
6529
6917
  setSheetOpen(open);
6530
- if (!open) import_react_native54.Keyboard.dismiss();
6918
+ if (!open) import_react_native55.Keyboard.dismiss();
6531
6919
  }, []);
6532
- const closeSheet = React42.useCallback(() => {
6920
+ const closeSheet = React43.useCallback(() => {
6533
6921
  handleSheetOpenChange(false);
6534
6922
  }, [handleSheetOpenChange]);
6535
- const openSheet = React42.useCallback(() => setSheetOpen(true), []);
6536
- const goToChat = React42.useCallback(() => {
6923
+ const openSheet = React43.useCallback(() => setSheetOpen(true), []);
6924
+ const goToChat = React43.useCallback(() => {
6537
6925
  setActivePage("chat");
6538
6926
  openSheet();
6539
6927
  }, [openSheet]);
6540
- const backToPreview = React42.useCallback(() => {
6541
- if (import_react_native54.Platform.OS !== "ios") {
6542
- import_react_native54.Keyboard.dismiss();
6928
+ const backToPreview = React43.useCallback(() => {
6929
+ if (import_react_native55.Platform.OS !== "ios") {
6930
+ import_react_native55.Keyboard.dismiss();
6543
6931
  setActivePage("preview");
6544
6932
  return;
6545
6933
  }
@@ -6551,15 +6939,15 @@ function StudioOverlay({
6551
6939
  clearTimeout(t);
6552
6940
  setActivePage("preview");
6553
6941
  };
6554
- const sub = import_react_native54.Keyboard.addListener("keyboardDidHide", finalize);
6942
+ const sub = import_react_native55.Keyboard.addListener("keyboardDidHide", finalize);
6555
6943
  const t = setTimeout(finalize, 350);
6556
- import_react_native54.Keyboard.dismiss();
6944
+ import_react_native55.Keyboard.dismiss();
6557
6945
  }, []);
6558
- const startDraw = React42.useCallback(() => {
6946
+ const startDraw = React43.useCallback(() => {
6559
6947
  setDrawing(true);
6560
6948
  closeSheet();
6561
6949
  }, [closeSheet]);
6562
- const handleDrawCapture = React42.useCallback(
6950
+ const handleDrawCapture = React43.useCallback(
6563
6951
  (dataUrl) => {
6564
6952
  setChatAttachments((prev) => [...prev, dataUrl]);
6565
6953
  setDrawing(false);
@@ -6568,7 +6956,7 @@ function StudioOverlay({
6568
6956
  },
6569
6957
  [openSheet]
6570
6958
  );
6571
- const toggleSheet = React42.useCallback(async () => {
6959
+ const toggleSheet = React43.useCallback(async () => {
6572
6960
  if (!sheetOpen) {
6573
6961
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6574
6962
  if (shouldExitTest) {
@@ -6580,7 +6968,7 @@ function StudioOverlay({
6580
6968
  closeSheet();
6581
6969
  }
6582
6970
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6583
- const handleTestMr = React42.useCallback(
6971
+ const handleTestMr = React43.useCallback(
6584
6972
  async (mr) => {
6585
6973
  if (!onTestMr) return;
6586
6974
  await onTestMr(mr);
@@ -6588,10 +6976,10 @@ function StudioOverlay({
6588
6976
  },
6589
6977
  [closeSheet, onTestMr]
6590
6978
  );
6591
- React42.useEffect(() => {
6979
+ React43.useEffect(() => {
6592
6980
  sheetOpenRef.current = sheetOpen;
6593
6981
  }, [sheetOpen]);
6594
- React42.useEffect(() => {
6982
+ React43.useEffect(() => {
6595
6983
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
6596
6984
  if (action === "show" && !sheetOpenRef.current) openSheet();
6597
6985
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -6599,17 +6987,17 @@ function StudioOverlay({
6599
6987
  }, studioControlOptions);
6600
6988
  return () => poller.stop();
6601
6989
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
6602
- React42.useEffect(() => {
6990
+ React43.useEffect(() => {
6603
6991
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
6604
6992
  }, [sheetOpen, studioControlOptions]);
6605
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
6606
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
6607
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6993
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_jsx_runtime58.Fragment, { children: [
6994
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
6995
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6608
6996
  StudioSheetPager,
6609
6997
  {
6610
6998
  activePage,
6611
6999
  width,
6612
- preview: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7000
+ preview: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6613
7001
  PreviewPanel,
6614
7002
  {
6615
7003
  app,
@@ -6635,7 +7023,7 @@ function StudioOverlay({
6635
7023
  commentCountOverride: commentsCount ?? void 0
6636
7024
  }
6637
7025
  ),
6638
- chat: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7026
+ chat: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6639
7027
  ChatPanel,
6640
7028
  {
6641
7029
  messages: optimistic.messages,
@@ -6653,12 +7041,14 @@ function StudioOverlay({
6653
7041
  onClose: closeSheet,
6654
7042
  onNavigateHome,
6655
7043
  onStartDraw: startDraw,
6656
- onSend: optimistic.onSend
7044
+ onSend: optimistic.onSend,
7045
+ queueItems: chatQueueItems,
7046
+ onRemoveQueueItem
6657
7047
  }
6658
7048
  )
6659
7049
  }
6660
7050
  ) }),
6661
- showBubble && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7051
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6662
7052
  Bubble,
6663
7053
  {
6664
7054
  visible: !sheetOpen && !drawing,
@@ -6666,10 +7056,10 @@ function StudioOverlay({
6666
7056
  badgeCount: incomingMergeRequests.length,
6667
7057
  onPress: toggleSheet,
6668
7058
  isLoading: (app == null ? void 0 : app.status) === "editing",
6669
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native54.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
7059
+ children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native55.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
6670
7060
  }
6671
7061
  ),
6672
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7062
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6673
7063
  DrawModeOverlay,
6674
7064
  {
6675
7065
  visible: drawing,
@@ -6678,7 +7068,7 @@ function StudioOverlay({
6678
7068
  onCapture: handleDrawCapture
6679
7069
  }
6680
7070
  ),
6681
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7071
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6682
7072
  ConfirmMergeFlow,
6683
7073
  {
6684
7074
  visible: Boolean(confirmMr),
@@ -6691,7 +7081,7 @@ function StudioOverlay({
6691
7081
  onTestFirst: handleTestMr
6692
7082
  }
6693
7083
  ),
6694
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7084
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6695
7085
  AppCommentsSheet,
6696
7086
  {
6697
7087
  appId: commentsAppId,
@@ -6703,8 +7093,190 @@ function StudioOverlay({
6703
7093
  ] });
6704
7094
  }
6705
7095
 
7096
+ // src/studio/hooks/useEditQueue.ts
7097
+ var React44 = __toESM(require("react"));
7098
+
7099
+ // src/data/apps/edit-queue/remote.ts
7100
+ var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
7101
+ async list(appId) {
7102
+ const { data } = await api.get(
7103
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue`
7104
+ );
7105
+ return data;
7106
+ }
7107
+ async update(appId, queueItemId, payload) {
7108
+ const { data } = await api.patch(
7109
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`,
7110
+ payload
7111
+ );
7112
+ return data;
7113
+ }
7114
+ async cancel(appId, queueItemId) {
7115
+ const { data } = await api.delete(
7116
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`
7117
+ );
7118
+ return data;
7119
+ }
7120
+ };
7121
+ var editQueueRemoteDataSource = new EditQueueRemoteDataSourceImpl();
7122
+
7123
+ // src/data/apps/edit-queue/repository.ts
7124
+ var ACTIVE_STATUSES = ["pending"];
7125
+ function toString(value) {
7126
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
7127
+ }
7128
+ function toAttachments(value) {
7129
+ return Array.isArray(value) ? value : [];
7130
+ }
7131
+ function mapQueueItem(row) {
7132
+ const payload = row.payload ?? {};
7133
+ return {
7134
+ id: row.id,
7135
+ status: row.status,
7136
+ prompt: toString(payload.trimmedPrompt),
7137
+ messageId: toString(payload.messageId),
7138
+ attachments: toAttachments(payload.attachments),
7139
+ createdAt: row.created_at,
7140
+ updatedAt: row.updated_at,
7141
+ runAfter: row.run_after,
7142
+ priority: row.priority
7143
+ };
7144
+ }
7145
+ var EditQueueRepositoryImpl = class extends BaseRepository {
7146
+ constructor(remote) {
7147
+ super();
7148
+ this.remote = remote;
7149
+ }
7150
+ async list(appId) {
7151
+ const res = await this.remote.list(appId);
7152
+ const data = this.unwrapOrThrow(res);
7153
+ return data.items ?? [];
7154
+ }
7155
+ async update(appId, queueItemId, payload) {
7156
+ const res = await this.remote.update(appId, queueItemId, payload);
7157
+ return this.unwrapOrThrow(res);
7158
+ }
7159
+ async cancel(appId, queueItemId) {
7160
+ const res = await this.remote.cancel(appId, queueItemId);
7161
+ return this.unwrapOrThrow(res);
7162
+ }
7163
+ subscribeEditQueue(appId, handlers) {
7164
+ const supabase = getSupabaseClient();
7165
+ const channel = supabase.channel(`edit-queue:app:${appId}`).on(
7166
+ "postgres_changes",
7167
+ { event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7168
+ (payload) => {
7169
+ var _a;
7170
+ const row = payload.new;
7171
+ if (row.kind !== "edit") return;
7172
+ const item = mapQueueItem(row);
7173
+ if (!ACTIVE_STATUSES.includes(item.status)) return;
7174
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
7175
+ }
7176
+ ).on(
7177
+ "postgres_changes",
7178
+ { event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7179
+ (payload) => {
7180
+ var _a, _b;
7181
+ const row = payload.new;
7182
+ if (row.kind !== "edit") return;
7183
+ const item = mapQueueItem(row);
7184
+ if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
7185
+ else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
7186
+ }
7187
+ ).on(
7188
+ "postgres_changes",
7189
+ { event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7190
+ (payload) => {
7191
+ var _a;
7192
+ const row = payload.old;
7193
+ if (row.kind !== "edit") return;
7194
+ (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
7195
+ }
7196
+ ).subscribe();
7197
+ return () => {
7198
+ supabase.removeChannel(channel);
7199
+ };
7200
+ }
7201
+ };
7202
+ var editQueueRepository = new EditQueueRepositoryImpl(
7203
+ editQueueRemoteDataSource
7204
+ );
7205
+
7206
+ // src/studio/hooks/useEditQueue.ts
7207
+ function useEditQueue(appId) {
7208
+ const [items, setItems] = React44.useState([]);
7209
+ const [loading, setLoading] = React44.useState(false);
7210
+ const [error, setError] = React44.useState(null);
7211
+ const activeRequestIdRef = React44.useRef(0);
7212
+ const foregroundSignal = useForegroundSignal(Boolean(appId));
7213
+ const upsertSorted = React44.useCallback((prev, nextItem) => {
7214
+ const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
7215
+ next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
7216
+ return next;
7217
+ }, []);
7218
+ const refetch = React44.useCallback(async () => {
7219
+ if (!appId) {
7220
+ setItems([]);
7221
+ return;
7222
+ }
7223
+ const requestId = ++activeRequestIdRef.current;
7224
+ setLoading(true);
7225
+ setError(null);
7226
+ try {
7227
+ const list = await editQueueRepository.list(appId);
7228
+ if (activeRequestIdRef.current !== requestId) return;
7229
+ setItems([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
7230
+ } catch (e) {
7231
+ if (activeRequestIdRef.current !== requestId) return;
7232
+ setError(e instanceof Error ? e : new Error(String(e)));
7233
+ setItems([]);
7234
+ } finally {
7235
+ if (activeRequestIdRef.current === requestId) setLoading(false);
7236
+ }
7237
+ }, [appId]);
7238
+ React44.useEffect(() => {
7239
+ void refetch();
7240
+ }, [refetch]);
7241
+ React44.useEffect(() => {
7242
+ if (!appId) return;
7243
+ const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
7244
+ onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
7245
+ onUpdate: (item) => setItems((prev) => upsertSorted(prev, item)),
7246
+ onDelete: (item) => setItems((prev) => prev.filter((x) => x.id !== item.id))
7247
+ });
7248
+ return unsubscribe;
7249
+ }, [appId, upsertSorted, foregroundSignal]);
7250
+ React44.useEffect(() => {
7251
+ if (!appId) return;
7252
+ if (foregroundSignal <= 0) return;
7253
+ void refetch();
7254
+ }, [appId, foregroundSignal, refetch]);
7255
+ return { items, loading, error, refetch };
7256
+ }
7257
+
7258
+ // src/studio/hooks/useEditQueueActions.ts
7259
+ var React45 = __toESM(require("react"));
7260
+ function useEditQueueActions(appId) {
7261
+ const update = React45.useCallback(
7262
+ async (queueItemId, payload) => {
7263
+ if (!appId) return;
7264
+ await editQueueRepository.update(appId, queueItemId, payload);
7265
+ },
7266
+ [appId]
7267
+ );
7268
+ const cancel = React45.useCallback(
7269
+ async (queueItemId) => {
7270
+ if (!appId) return;
7271
+ await editQueueRepository.cancel(appId, queueItemId);
7272
+ },
7273
+ [appId]
7274
+ );
7275
+ return { update, cancel };
7276
+ }
7277
+
6706
7278
  // src/studio/ComergeStudio.tsx
6707
- var import_jsx_runtime58 = require("react/jsx-runtime");
7279
+ var import_jsx_runtime59 = require("react/jsx-runtime");
6708
7280
  function ComergeStudio({
6709
7281
  appId,
6710
7282
  clientKey: clientKey2,
@@ -6715,17 +7287,17 @@ function ComergeStudio({
6715
7287
  studioControlOptions,
6716
7288
  embeddedBaseBundles
6717
7289
  }) {
6718
- const [activeAppId, setActiveAppId] = React43.useState(appId);
6719
- const [runtimeAppId, setRuntimeAppId] = React43.useState(appId);
6720
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React43.useState(null);
6721
- const platform = React43.useMemo(() => import_react_native55.Platform.OS === "ios" ? "ios" : "android", []);
6722
- React43.useEffect(() => {
7290
+ const [activeAppId, setActiveAppId] = React46.useState(appId);
7291
+ const [runtimeAppId, setRuntimeAppId] = React46.useState(appId);
7292
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React46.useState(null);
7293
+ const platform = React46.useMemo(() => import_react_native56.Platform.OS === "ios" ? "ios" : "android", []);
7294
+ React46.useEffect(() => {
6723
7295
  setActiveAppId(appId);
6724
7296
  setRuntimeAppId(appId);
6725
7297
  setPendingRuntimeTargetAppId(null);
6726
7298
  }, [appId]);
6727
- const captureTargetRef = React43.useRef(null);
6728
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native55.View, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7299
+ const captureTargetRef = React46.useRef(null);
7300
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native56.View, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
6729
7301
  ComergeStudioInner,
6730
7302
  {
6731
7303
  userId,
@@ -6766,11 +7338,11 @@ function ComergeStudioInner({
6766
7338
  const { app, loading: appLoading } = useApp(activeAppId);
6767
7339
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6768
7340
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6769
- const sawEditingOnPendingTargetRef = React43.useRef(false);
6770
- React43.useEffect(() => {
7341
+ const sawEditingOnPendingTargetRef = React46.useRef(false);
7342
+ React46.useEffect(() => {
6771
7343
  sawEditingOnPendingTargetRef.current = false;
6772
7344
  }, [pendingRuntimeTargetAppId]);
6773
- React43.useEffect(() => {
7345
+ React46.useEffect(() => {
6774
7346
  if (!pendingRuntimeTargetAppId) return;
6775
7347
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6776
7348
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6788,13 +7360,13 @@ function ComergeStudioInner({
6788
7360
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
6789
7361
  embeddedBaseBundles
6790
7362
  });
6791
- const sawEditingOnActiveAppRef = React43.useRef(false);
6792
- const [showPostEditPreparing, setShowPostEditPreparing] = React43.useState(false);
6793
- React43.useEffect(() => {
7363
+ const sawEditingOnActiveAppRef = React46.useRef(false);
7364
+ const [showPostEditPreparing, setShowPostEditPreparing] = React46.useState(false);
7365
+ React46.useEffect(() => {
6794
7366
  sawEditingOnActiveAppRef.current = false;
6795
7367
  setShowPostEditPreparing(false);
6796
7368
  }, [activeAppId]);
6797
- React43.useEffect(() => {
7369
+ React46.useEffect(() => {
6798
7370
  if (!(app == null ? void 0 : app.id)) return;
6799
7371
  if (app.status === "editing") {
6800
7372
  sawEditingOnActiveAppRef.current = true;
@@ -6806,7 +7378,7 @@ function ComergeStudioInner({
6806
7378
  sawEditingOnActiveAppRef.current = false;
6807
7379
  }
6808
7380
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
6809
- React43.useEffect(() => {
7381
+ React46.useEffect(() => {
6810
7382
  if (!showPostEditPreparing) return;
6811
7383
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
6812
7384
  if (!stillProcessingBaseBundle) {
@@ -6815,15 +7387,27 @@ function ComergeStudioInner({
6815
7387
  }, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
6816
7388
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6817
7389
  const thread = useThreadMessages(threadId);
7390
+ const editQueue = useEditQueue(activeAppId);
7391
+ const editQueueActions = useEditQueueActions(activeAppId);
7392
+ const [lastEditQueueInfo, setLastEditQueueInfo] = React46.useState(null);
7393
+ const lastEditQueueInfoRef = React46.useRef(null);
7394
+ const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React46.useState(false);
6818
7395
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6819
- const hasOpenOutgoingMr = React43.useMemo(() => {
7396
+ const hasOpenOutgoingMr = React46.useMemo(() => {
6820
7397
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6821
7398
  }, [mergeRequests.lists.outgoing]);
6822
- const incomingReviewMrs = React43.useMemo(() => {
7399
+ const incomingReviewMrs = React46.useMemo(() => {
6823
7400
  if (!userId) return mergeRequests.lists.incoming;
6824
7401
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6825
7402
  }, [mergeRequests.lists.incoming, userId]);
6826
7403
  const uploader = useAttachmentUpload();
7404
+ const updateLastEditQueueInfo = React46.useCallback(
7405
+ (info) => {
7406
+ lastEditQueueInfoRef.current = info;
7407
+ setLastEditQueueInfo(info);
7408
+ },
7409
+ []
7410
+ );
6827
7411
  const actions = useStudioActions({
6828
7412
  userId,
6829
7413
  app,
@@ -6838,20 +7422,62 @@ function ComergeStudioInner({
6838
7422
  setPendingRuntimeTargetAppId(null);
6839
7423
  }
6840
7424
  },
6841
- uploadAttachments: uploader.uploadBase64Images
7425
+ uploadAttachments: uploader.uploadBase64Images,
7426
+ onEditStart: () => {
7427
+ if (editQueue.items.length === 0) {
7428
+ setSuppressQueueUntilResponse(true);
7429
+ }
7430
+ },
7431
+ onEditQueued: (info) => {
7432
+ updateLastEditQueueInfo(info);
7433
+ if (info.queuePosition !== 1) {
7434
+ setSuppressQueueUntilResponse(false);
7435
+ }
7436
+ },
7437
+ onEditFinished: () => {
7438
+ var _a;
7439
+ if (((_a = lastEditQueueInfoRef.current) == null ? void 0 : _a.queuePosition) !== 1) {
7440
+ setSuppressQueueUntilResponse(false);
7441
+ }
7442
+ }
6842
7443
  });
6843
- const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6844
- const [processingMrId, setProcessingMrId] = React43.useState(null);
6845
- const [testingMrId, setTestingMrId] = React43.useState(null);
6846
- const chatShowTypingIndicator = React43.useMemo(() => {
7444
+ const chatSendDisabled = false;
7445
+ const [processingMrId, setProcessingMrId] = React46.useState(null);
7446
+ const [testingMrId, setTestingMrId] = React46.useState(null);
7447
+ const chatShowTypingIndicator = React46.useMemo(() => {
6847
7448
  var _a;
6848
7449
  if (!thread.raw || thread.raw.length === 0) return false;
6849
7450
  const last = thread.raw[thread.raw.length - 1];
6850
7451
  const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
6851
7452
  return payloadType !== "outcome";
6852
7453
  }, [thread.raw]);
6853
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native55.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_react_native55.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
6854
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7454
+ React46.useEffect(() => {
7455
+ updateLastEditQueueInfo(null);
7456
+ setSuppressQueueUntilResponse(false);
7457
+ }, [activeAppId, updateLastEditQueueInfo]);
7458
+ React46.useEffect(() => {
7459
+ if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
7460
+ const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
7461
+ if (!stillPresent) {
7462
+ updateLastEditQueueInfo(null);
7463
+ setSuppressQueueUntilResponse(false);
7464
+ }
7465
+ }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
7466
+ const chatQueueItems = React46.useMemo(() => {
7467
+ var _a;
7468
+ if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
7469
+ return [];
7470
+ }
7471
+ if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
7472
+ return editQueue.items;
7473
+ }
7474
+ if (editQueue.items.length === 1 && ((_a = editQueue.items[0]) == null ? void 0 : _a.id) === lastEditQueueInfo.queueItemId) {
7475
+ return [];
7476
+ }
7477
+ return editQueue.items;
7478
+ }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
7479
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native56.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native56.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
7480
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
6855
7481
  RuntimeRenderer,
6856
7482
  {
6857
7483
  appKey,
@@ -6861,7 +7487,7 @@ function ComergeStudioInner({
6861
7487
  allowInitialPreparing: !embeddedBaseBundles
6862
7488
  }
6863
7489
  ),
6864
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7490
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
6865
7491
  StudioOverlay,
6866
7492
  {
6867
7493
  captureTargetRef,
@@ -6913,6 +7539,8 @@ function ComergeStudioInner({
6913
7539
  chatSending: actions.sending,
6914
7540
  chatShowTypingIndicator,
6915
7541
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
7542
+ chatQueueItems,
7543
+ onRemoveQueueItem: (id) => editQueueActions.cancel(id),
6916
7544
  onNavigateHome,
6917
7545
  showBubble,
6918
7546
  studioControlOptions