@comergehq/studio 0.1.15 → 0.1.17

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,9 @@ 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 next = prev.filter((x) => x.id !== m.id);
898
+ next.push(m);
899
+ next.sort(compareMessages);
870
900
  return next;
871
901
  }, []);
872
902
  const refetch = React4.useCallback(async () => {
@@ -880,7 +910,7 @@ function useThreadMessages(threadId) {
880
910
  try {
881
911
  const list = await messagesRepository.list(threadId);
882
912
  if (activeRequestIdRef.current !== requestId) return;
883
- setRaw([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
913
+ setRaw([...list].sort(compareMessages));
884
914
  } catch (e) {
885
915
  if (activeRequestIdRef.current !== requestId) return;
886
916
  setError(e instanceof Error ? e : new Error(String(e)));
@@ -906,7 +936,11 @@ function useThreadMessages(threadId) {
906
936
  if (foregroundSignal <= 0) return;
907
937
  void refetch();
908
938
  }, [foregroundSignal, refetch, threadId]);
909
- const messages = React4.useMemo(() => raw.map(mapMessageToChatMessage), [raw]);
939
+ const messages = React4.useMemo(() => {
940
+ const visible = raw.filter((m) => !isQueuedHiddenMessage(m));
941
+ const resolved = visible.length > 0 ? visible : raw;
942
+ return resolved.map(mapMessageToChatMessage);
943
+ }, [raw]);
910
944
  return { raw, messages, loading, error, refetch };
911
945
  }
912
946
 
@@ -1932,6 +1966,9 @@ function useStudioActions({
1932
1966
  userId,
1933
1967
  app,
1934
1968
  onForkedApp,
1969
+ onEditStart,
1970
+ onEditQueued,
1971
+ onEditFinished,
1935
1972
  uploadAttachments
1936
1973
  }) {
1937
1974
  const [forking, setForking] = React8.useState(false);
@@ -1958,16 +1995,21 @@ function useStudioActions({
1958
1995
  setForking(false);
1959
1996
  const threadId = targetApp.threadId;
1960
1997
  if (!threadId) throw new Error("No thread available for this app.");
1998
+ onEditStart == null ? void 0 : onEditStart();
1961
1999
  let attachmentMetas;
1962
2000
  if (attachments && attachments.length > 0 && uploadAttachments) {
1963
2001
  attachmentMetas = await uploadAttachments({ threadId, appId: targetApp.id, dataUrls: attachments });
1964
2002
  }
1965
- await agentRepository.editApp({
2003
+ const editResult = await agentRepository.editApp({
1966
2004
  prompt,
1967
2005
  thread_id: threadId,
1968
2006
  app_id: targetApp.id,
1969
2007
  attachments: attachmentMetas && attachmentMetas.length > 0 ? attachmentMetas : void 0
1970
2008
  });
2009
+ onEditQueued == null ? void 0 : onEditQueued({
2010
+ queueItemId: editResult.queueItemId ?? null,
2011
+ queuePosition: editResult.queuePosition ?? null
2012
+ });
1971
2013
  } catch (e) {
1972
2014
  const err = e instanceof Error ? e : new Error(String(e));
1973
2015
  setError(err);
@@ -1975,32 +2017,14 @@ function useStudioActions({
1975
2017
  } finally {
1976
2018
  setForking(false);
1977
2019
  setSending(false);
2020
+ onEditFinished == null ? void 0 : onEditFinished();
1978
2021
  }
1979
2022
  },
1980
- [app, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
2023
+ [app, onEditFinished, onEditQueued, onEditStart, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
1981
2024
  );
1982
2025
  return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
1983
2026
  }
1984
2027
 
1985
- // src/studio/lib/chat.ts
1986
- function hasNoOutcomeAfterLastHuman(messages) {
1987
- if (!messages || messages.length === 0) return false;
1988
- let lastHumanIndex = -1;
1989
- for (let i = messages.length - 1; i >= 0; i -= 1) {
1990
- if (messages[i].authorType === "human") {
1991
- lastHumanIndex = i;
1992
- break;
1993
- }
1994
- }
1995
- if (lastHumanIndex === -1) return false;
1996
- for (let i = lastHumanIndex + 1; i < messages.length; i += 1) {
1997
- const m = messages[i];
1998
- const payload = m.payload;
1999
- if (m.authorType === "ai" && (payload == null ? void 0 : payload.type) === "outcome") return false;
2000
- }
2001
- return true;
2002
- }
2003
-
2004
2028
  // src/studio/ui/RuntimeRenderer.tsx
2005
2029
  var React9 = __toESM(require("react"));
2006
2030
  var import_react_native6 = require("react-native");
@@ -2038,8 +2062,8 @@ function RuntimeRenderer({
2038
2062
  }
2039
2063
 
2040
2064
  // src/studio/ui/StudioOverlay.tsx
2041
- var React42 = __toESM(require("react"));
2042
- var import_react_native54 = require("react-native");
2065
+ var React43 = __toESM(require("react"));
2066
+ var import_react_native55 = require("react-native");
2043
2067
 
2044
2068
  // src/components/studio-sheet/StudioBottomSheet.tsx
2045
2069
  var React12 = __toESM(require("react"));
@@ -5843,8 +5867,8 @@ function PreviewPanel({
5843
5867
  }
5844
5868
 
5845
5869
  // src/studio/ui/ChatPanel.tsx
5846
- var React39 = __toESM(require("react"));
5847
- var import_react_native51 = require("react-native");
5870
+ var React40 = __toESM(require("react"));
5871
+ var import_react_native52 = require("react-native");
5848
5872
 
5849
5873
  // src/components/chat/ChatPage.tsx
5850
5874
  var React37 = __toESM(require("react"));
@@ -6041,6 +6065,7 @@ function ChatPage({
6041
6065
  showTypingIndicator,
6042
6066
  renderMessageContent,
6043
6067
  topBanner,
6068
+ composerTop,
6044
6069
  composer,
6045
6070
  overlay,
6046
6071
  style,
@@ -6051,6 +6076,7 @@ function ChatPage({
6051
6076
  const theme = useTheme();
6052
6077
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
6053
6078
  const [composerHeight, setComposerHeight] = React37.useState(0);
6079
+ const [composerTopHeight, setComposerTopHeight] = React37.useState(0);
6054
6080
  const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
6055
6081
  React37.useEffect(() => {
6056
6082
  if (import_react_native47.Platform.OS !== "ios") return;
@@ -6062,8 +6088,9 @@ function ChatPage({
6062
6088
  };
6063
6089
  }, []);
6064
6090
  const footerBottomPadding = import_react_native47.Platform.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
6065
- const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
6066
- const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
6091
+ const totalComposerHeight = composerHeight + composerTopHeight;
6092
+ const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
6093
+ const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
6067
6094
  const resolvedOverlay = React37.useMemo(() => {
6068
6095
  var _a;
6069
6096
  if (!overlay) return null;
@@ -6073,6 +6100,10 @@ function ChatPage({
6073
6100
  style: [prevStyle, { bottom: overlayBottom }]
6074
6101
  });
6075
6102
  }, [overlay, overlayBottom]);
6103
+ React37.useEffect(() => {
6104
+ if (composerTop) return;
6105
+ setComposerTopHeight(0);
6106
+ }, [composerTop]);
6076
6107
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native47.View, { style: [{ flex: 1 }, style], children: [
6077
6108
  header ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { children: header }) : null,
6078
6109
  topBanner ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
@@ -6097,7 +6128,7 @@ function ChatPage({
6097
6128
  ]
6098
6129
  }
6099
6130
  ),
6100
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6131
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
6101
6132
  import_react_native47.View,
6102
6133
  {
6103
6134
  style: {
@@ -6109,14 +6140,24 @@ function ChatPage({
6109
6140
  paddingTop: theme.spacing.sm,
6110
6141
  paddingBottom: footerBottomPadding
6111
6142
  },
6112
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6113
- ChatComposer,
6114
- {
6115
- ...composer,
6116
- attachments: composer.attachments ?? [],
6117
- onLayout: ({ height }) => setComposerHeight(height)
6118
- }
6119
- )
6143
+ children: [
6144
+ composerTop ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6145
+ import_react_native47.View,
6146
+ {
6147
+ style: { marginBottom: theme.spacing.sm },
6148
+ onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
6149
+ children: composerTop
6150
+ }
6151
+ ) : null,
6152
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6153
+ ChatComposer,
6154
+ {
6155
+ ...composer,
6156
+ attachments: composer.attachments ?? [],
6157
+ onLayout: ({ height }) => setComposerHeight(height)
6158
+ }
6159
+ )
6160
+ ]
6120
6161
  }
6121
6162
  )
6122
6163
  ] })
@@ -6266,8 +6307,154 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6266
6307
  );
6267
6308
  }
6268
6309
 
6269
- // src/studio/ui/ChatPanel.tsx
6310
+ // src/components/chat/ChatQueue.tsx
6311
+ var React39 = __toESM(require("react"));
6312
+ var import_react_native51 = require("react-native");
6270
6313
  var import_jsx_runtime53 = require("react/jsx-runtime");
6314
+ function ChatQueue({ items, onRemove }) {
6315
+ const theme = useTheme();
6316
+ const [expanded, setExpanded] = React39.useState({});
6317
+ const [canExpand, setCanExpand] = React39.useState({});
6318
+ const [collapsedText, setCollapsedText] = React39.useState({});
6319
+ const [removing, setRemoving] = React39.useState({});
6320
+ const buildCollapsedText = React39.useCallback((lines) => {
6321
+ var _a, _b;
6322
+ const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
6323
+ const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
6324
+ const moreLabel = "more";
6325
+ const reserve = `\u2026 ${moreLabel}`.length;
6326
+ let trimmedLine2 = line2;
6327
+ if (trimmedLine2.length > reserve) {
6328
+ trimmedLine2 = trimmedLine2.slice(0, Math.max(0, trimmedLine2.length - reserve));
6329
+ } else {
6330
+ trimmedLine2 = "";
6331
+ }
6332
+ trimmedLine2 = trimmedLine2.replace(/\s+$/, "");
6333
+ return `${line1}
6334
+ ${trimmedLine2}\u2026 `;
6335
+ }, []);
6336
+ React39.useEffect(() => {
6337
+ if (items.length === 0) return;
6338
+ const ids = new Set(items.map((item) => item.id));
6339
+ setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6340
+ setCanExpand((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6341
+ setCollapsedText((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6342
+ setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
6343
+ }, [items]);
6344
+ if (items.length === 0) return null;
6345
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6346
+ import_react_native51.View,
6347
+ {
6348
+ style: {
6349
+ borderWidth: 1,
6350
+ borderColor: theme.colors.border,
6351
+ borderRadius: theme.radii.lg,
6352
+ marginHorizontal: theme.spacing.md,
6353
+ padding: theme.spacing.md,
6354
+ backgroundColor: "transparent"
6355
+ },
6356
+ children: [
6357
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
6358
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
6359
+ const isExpanded = Boolean(expanded[item.id]);
6360
+ const showToggle = Boolean(canExpand[item.id]);
6361
+ const prompt = item.prompt ?? "";
6362
+ const moreLabel = "more";
6363
+ const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
6364
+ const isRemoving = Boolean(removing[item.id]);
6365
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6366
+ import_react_native51.View,
6367
+ {
6368
+ style: {
6369
+ flexDirection: "row",
6370
+ alignItems: "flex-start",
6371
+ gap: theme.spacing.sm,
6372
+ paddingHorizontal: theme.spacing.md,
6373
+ paddingVertical: theme.spacing.sm,
6374
+ borderRadius: theme.radii.md,
6375
+ backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
6376
+ },
6377
+ children: [
6378
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
6379
+ !canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6380
+ Text,
6381
+ {
6382
+ style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
6383
+ onTextLayout: (e) => {
6384
+ var _a;
6385
+ const lines = (_a = e.nativeEvent) == null ? void 0 : _a.lines;
6386
+ if (!lines) return;
6387
+ if (lines.length > 2) {
6388
+ setCanExpand((prev) => ({ ...prev, [item.id]: true }));
6389
+ setCollapsedText((prev) => ({
6390
+ ...prev,
6391
+ [item.id]: buildCollapsedText(lines)
6392
+ }));
6393
+ }
6394
+ },
6395
+ children: prompt
6396
+ }
6397
+ ) : null,
6398
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
6399
+ Text,
6400
+ {
6401
+ variant: "bodyMuted",
6402
+ numberOfLines: isExpanded ? void 0 : 2,
6403
+ children: [
6404
+ displayPrompt,
6405
+ !isExpanded && showToggle ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6406
+ Text,
6407
+ {
6408
+ color: theme.colors.text,
6409
+ onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: true })),
6410
+ suppressHighlighting: true,
6411
+ children: moreLabel
6412
+ }
6413
+ ) : null
6414
+ ]
6415
+ }
6416
+ ),
6417
+ showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6418
+ import_react_native51.Pressable,
6419
+ {
6420
+ onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
6421
+ hitSlop: 6,
6422
+ style: { alignSelf: "flex-start", marginTop: 4 },
6423
+ children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
6424
+ }
6425
+ ) : null
6426
+ ] }),
6427
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6428
+ import_react_native51.Pressable,
6429
+ {
6430
+ onPress: () => {
6431
+ if (!onRemove || isRemoving) return;
6432
+ setRemoving((prev) => ({ ...prev, [item.id]: true }));
6433
+ Promise.resolve(onRemove(item.id)).finally(() => {
6434
+ setRemoving((prev) => {
6435
+ if (!prev[item.id]) return prev;
6436
+ const { [item.id]: _removed, ...rest } = prev;
6437
+ return rest;
6438
+ });
6439
+ });
6440
+ },
6441
+ hitSlop: 8,
6442
+ style: { alignSelf: "center" },
6443
+ 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" })
6444
+ }
6445
+ )
6446
+ ]
6447
+ },
6448
+ item.id
6449
+ );
6450
+ }) })
6451
+ ]
6452
+ }
6453
+ );
6454
+ }
6455
+
6456
+ // src/studio/ui/ChatPanel.tsx
6457
+ var import_jsx_runtime54 = require("react/jsx-runtime");
6271
6458
  function ChatPanel({
6272
6459
  title = "Chat",
6273
6460
  autoFocusComposer = false,
@@ -6285,11 +6472,13 @@ function ChatPanel({
6285
6472
  onClose,
6286
6473
  onNavigateHome,
6287
6474
  onStartDraw,
6288
- onSend
6475
+ onSend,
6476
+ queueItems = [],
6477
+ onRemoveQueueItem
6289
6478
  }) {
6290
- const listRef = React39.useRef(null);
6291
- const [nearBottom, setNearBottom] = React39.useState(true);
6292
- const handleSend = React39.useCallback(
6479
+ const listRef = React40.useRef(null);
6480
+ const [nearBottom, setNearBottom] = React40.useState(true);
6481
+ const handleSend = React40.useCallback(
6293
6482
  async (text, composerAttachments) => {
6294
6483
  const all = composerAttachments ?? attachments;
6295
6484
  await onSend(text, all.length > 0 ? all : void 0);
@@ -6303,25 +6492,25 @@ function ChatPanel({
6303
6492
  },
6304
6493
  [attachments, nearBottom, onClearAttachments, onSend]
6305
6494
  );
6306
- const handleScrollToBottom = React39.useCallback(() => {
6495
+ const handleScrollToBottom = React40.useCallback(() => {
6307
6496
  var _a;
6308
6497
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
6309
6498
  }, []);
6310
- const header = /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6499
+ const header = /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6311
6500
  ChatHeader,
6312
6501
  {
6313
- left: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6314
- /* @__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" }) }),
6315
- 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
6502
+ left: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6503
+ /* @__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" }) }),
6504
+ 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
6316
6505
  ] }),
6317
- right: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6318
- 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,
6319
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
6506
+ right: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6507
+ 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,
6508
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
6320
6509
  ] }),
6321
6510
  center: null
6322
6511
  }
6323
6512
  );
6324
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6513
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6325
6514
  ForkNoticeBanner,
6326
6515
  {
6327
6516
  isOwner: !shouldForkOnEdit,
@@ -6330,33 +6519,35 @@ function ChatPanel({
6330
6519
  ) : null;
6331
6520
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
6332
6521
  if (showMessagesLoading) {
6333
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
6334
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { children: header }),
6335
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
6336
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
6337
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.ActivityIndicator, {}),
6338
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { height: 12 } }),
6339
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
6522
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1 }, children: [
6523
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { children: header }),
6524
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
6525
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
6526
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.ActivityIndicator, {}),
6527
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { height: 12 } }),
6528
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
6340
6529
  ] })
6341
6530
  ] });
6342
6531
  }
6343
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6532
+ const queueTop = queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null;
6533
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6344
6534
  ChatPage,
6345
6535
  {
6346
6536
  header,
6347
6537
  messages,
6348
6538
  showTypingIndicator,
6349
6539
  topBanner,
6540
+ composerTop: queueTop,
6350
6541
  composerHorizontalPadding: 0,
6351
6542
  listRef,
6352
6543
  onNearBottomChange: setNearBottom,
6353
- overlay: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6544
+ overlay: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6354
6545
  ScrollToBottomButton,
6355
6546
  {
6356
6547
  visible: !nearBottom,
6357
6548
  onPress: handleScrollToBottom,
6358
6549
  style: { bottom: 80 },
6359
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
6550
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
6360
6551
  }
6361
6552
  ),
6362
6553
  composer: {
@@ -6377,12 +6568,12 @@ function ChatPanel({
6377
6568
  }
6378
6569
 
6379
6570
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6380
- var React40 = __toESM(require("react"));
6381
- var import_react_native53 = require("react-native");
6571
+ var React41 = __toESM(require("react"));
6572
+ var import_react_native54 = require("react-native");
6382
6573
 
6383
6574
  // src/components/primitives/Modal.tsx
6384
- var import_react_native52 = require("react-native");
6385
- var import_jsx_runtime54 = require("react/jsx-runtime");
6575
+ var import_react_native53 = require("react-native");
6576
+ var import_jsx_runtime55 = require("react/jsx-runtime");
6386
6577
  function Modal({
6387
6578
  visible,
6388
6579
  onRequestClose,
@@ -6391,30 +6582,30 @@ function Modal({
6391
6582
  contentStyle
6392
6583
  }) {
6393
6584
  const theme = useTheme();
6394
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6395
- import_react_native52.Modal,
6585
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6586
+ import_react_native53.Modal,
6396
6587
  {
6397
6588
  visible,
6398
6589
  transparent: true,
6399
6590
  animationType: "fade",
6400
6591
  onRequestClose,
6401
- 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: [
6402
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6403
- import_react_native52.Pressable,
6592
+ 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: [
6593
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6594
+ import_react_native53.Pressable,
6404
6595
  {
6405
6596
  accessibilityRole: "button",
6406
6597
  onPress: dismissOnBackdropPress ? onRequestClose : void 0,
6407
6598
  style: { position: "absolute", inset: 0 }
6408
6599
  }
6409
6600
  ),
6410
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
6601
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
6411
6602
  ] })
6412
6603
  }
6413
6604
  );
6414
6605
  }
6415
6606
 
6416
6607
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6417
- var import_jsx_runtime55 = require("react/jsx-runtime");
6608
+ var import_jsx_runtime56 = require("react/jsx-runtime");
6418
6609
  function ConfirmMergeRequestDialog({
6419
6610
  visible,
6420
6611
  onOpenChange,
@@ -6425,14 +6616,14 @@ function ConfirmMergeRequestDialog({
6425
6616
  onTestFirst
6426
6617
  }) {
6427
6618
  const theme = useTheme();
6428
- const close = React40.useCallback(() => onOpenChange(false), [onOpenChange]);
6619
+ const close = React41.useCallback(() => onOpenChange(false), [onOpenChange]);
6429
6620
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6430
- const handleConfirm = React40.useCallback(() => {
6621
+ const handleConfirm = React41.useCallback(() => {
6431
6622
  if (!mergeRequest) return;
6432
6623
  onOpenChange(false);
6433
6624
  void onConfirm();
6434
6625
  }, [mergeRequest, onConfirm, onOpenChange]);
6435
- const handleTestFirst = React40.useCallback(() => {
6626
+ const handleTestFirst = React41.useCallback(() => {
6436
6627
  if (!mergeRequest) return;
6437
6628
  onOpenChange(false);
6438
6629
  void onTestFirst(mergeRequest);
@@ -6444,7 +6635,7 @@ function ConfirmMergeRequestDialog({
6444
6635
  justifyContent: "center",
6445
6636
  alignSelf: "stretch"
6446
6637
  };
6447
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
6638
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
6448
6639
  Modal,
6449
6640
  {
6450
6641
  visible,
@@ -6455,7 +6646,7 @@ function ConfirmMergeRequestDialog({
6455
6646
  backgroundColor: theme.colors.background
6456
6647
  },
6457
6648
  children: [
6458
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6649
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6459
6650
  Text,
6460
6651
  {
6461
6652
  style: {
@@ -6467,9 +6658,9 @@ function ConfirmMergeRequestDialog({
6467
6658
  children: "Are you sure you want to approve this merge request?"
6468
6659
  }
6469
6660
  ) }),
6470
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { marginTop: 16 }, children: [
6471
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6472
- import_react_native53.View,
6661
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native54.View, { style: { marginTop: 16 }, children: [
6662
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6663
+ import_react_native54.View,
6473
6664
  {
6474
6665
  style: [
6475
6666
  fullWidthButtonBase,
@@ -6478,22 +6669,22 @@ function ConfirmMergeRequestDialog({
6478
6669
  opacity: canConfirm ? 1 : 0.5
6479
6670
  }
6480
6671
  ],
6481
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6482
- import_react_native53.Pressable,
6672
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6673
+ import_react_native54.Pressable,
6483
6674
  {
6484
6675
  accessibilityRole: "button",
6485
6676
  accessibilityLabel: "Approve Merge",
6486
6677
  disabled: !canConfirm,
6487
6678
  onPress: handleConfirm,
6488
6679
  style: [fullWidthButtonBase, { flex: 1 }],
6489
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
6680
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
6490
6681
  }
6491
6682
  )
6492
6683
  }
6493
6684
  ),
6494
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { height: 8 } }),
6495
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6496
- import_react_native53.View,
6685
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
6686
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6687
+ import_react_native54.View,
6497
6688
  {
6498
6689
  style: [
6499
6690
  fullWidthButtonBase,
@@ -6504,22 +6695,22 @@ function ConfirmMergeRequestDialog({
6504
6695
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
6505
6696
  }
6506
6697
  ],
6507
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6508
- import_react_native53.Pressable,
6698
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6699
+ import_react_native54.Pressable,
6509
6700
  {
6510
6701
  accessibilityRole: "button",
6511
6702
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
6512
6703
  disabled: isBuilding || !mergeRequest,
6513
6704
  onPress: handleTestFirst,
6514
6705
  style: [fullWidthButtonBase, { flex: 1 }],
6515
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
6706
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
6516
6707
  }
6517
6708
  )
6518
6709
  }
6519
6710
  ),
6520
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { height: 8 } }),
6521
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6522
- import_react_native53.View,
6711
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
6712
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6713
+ import_react_native54.View,
6523
6714
  {
6524
6715
  style: [
6525
6716
  fullWidthButtonBase,
@@ -6529,14 +6720,14 @@ function ConfirmMergeRequestDialog({
6529
6720
  borderColor: theme.colors.border
6530
6721
  }
6531
6722
  ],
6532
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6533
- import_react_native53.Pressable,
6723
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6724
+ import_react_native54.Pressable,
6534
6725
  {
6535
6726
  accessibilityRole: "button",
6536
6727
  accessibilityLabel: "Cancel",
6537
6728
  onPress: close,
6538
6729
  style: [fullWidthButtonBase, { flex: 1 }],
6539
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
6730
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
6540
6731
  }
6541
6732
  )
6542
6733
  }
@@ -6548,7 +6739,7 @@ function ConfirmMergeRequestDialog({
6548
6739
  }
6549
6740
 
6550
6741
  // src/studio/ui/ConfirmMergeFlow.tsx
6551
- var import_jsx_runtime56 = require("react/jsx-runtime");
6742
+ var import_jsx_runtime57 = require("react/jsx-runtime");
6552
6743
  function ConfirmMergeFlow({
6553
6744
  visible,
6554
6745
  onOpenChange,
@@ -6559,7 +6750,7 @@ function ConfirmMergeFlow({
6559
6750
  onConfirm,
6560
6751
  onTestFirst
6561
6752
  }) {
6562
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6753
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6563
6754
  ConfirmMergeRequestDialog,
6564
6755
  {
6565
6756
  visible,
@@ -6581,11 +6772,11 @@ function ConfirmMergeFlow({
6581
6772
  }
6582
6773
 
6583
6774
  // src/studio/hooks/useOptimisticChatMessages.ts
6584
- var React41 = __toESM(require("react"));
6775
+ var React42 = __toESM(require("react"));
6585
6776
  function makeOptimisticId() {
6586
6777
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6587
6778
  }
6588
- function toEpochMs(createdAt) {
6779
+ function toEpochMs2(createdAt) {
6589
6780
  if (createdAt == null) return 0;
6590
6781
  if (typeof createdAt === "number") return createdAt;
6591
6782
  if (createdAt instanceof Date) return createdAt.getTime();
@@ -6604,7 +6795,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6604
6795
  for (const m of candidates) {
6605
6796
  if (m.author !== "human") continue;
6606
6797
  if (normalize(m.content) !== target) continue;
6607
- const serverMs = toEpochMs(m.createdAt);
6798
+ const serverMs = toEpochMs2(m.createdAt);
6608
6799
  const optimisticMs = Date.parse(o.createdAtIso);
6609
6800
  if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
6610
6801
  if (serverMs + 12e4 < optimisticMs) continue;
@@ -6616,14 +6807,15 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6616
6807
  function useOptimisticChatMessages({
6617
6808
  threadId,
6618
6809
  shouldForkOnEdit,
6810
+ disableOptimistic = false,
6619
6811
  chatMessages,
6620
6812
  onSendChat
6621
6813
  }) {
6622
- const [optimisticChat, setOptimisticChat] = React41.useState([]);
6623
- React41.useEffect(() => {
6814
+ const [optimisticChat, setOptimisticChat] = React42.useState([]);
6815
+ React42.useEffect(() => {
6624
6816
  setOptimisticChat([]);
6625
6817
  }, [threadId]);
6626
- const messages = React41.useMemo(() => {
6818
+ const messages = React42.useMemo(() => {
6627
6819
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6628
6820
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6629
6821
  if (unresolved.length === 0) return chatMessages;
@@ -6639,7 +6831,7 @@ function useOptimisticChatMessages({
6639
6831
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6640
6832
  return merged;
6641
6833
  }, [chatMessages, optimisticChat]);
6642
- React41.useEffect(() => {
6834
+ React42.useEffect(() => {
6643
6835
  if (optimisticChat.length === 0) return;
6644
6836
  setOptimisticChat((prev) => {
6645
6837
  if (prev.length === 0) return prev;
@@ -6647,9 +6839,9 @@ function useOptimisticChatMessages({
6647
6839
  return next.length === prev.length ? prev : next;
6648
6840
  });
6649
6841
  }, [chatMessages, optimisticChat.length]);
6650
- const onSend = React41.useCallback(
6842
+ const onSend = React42.useCallback(
6651
6843
  async (text, attachments) => {
6652
- if (shouldForkOnEdit) {
6844
+ if (shouldForkOnEdit || disableOptimistic) {
6653
6845
  await onSendChat(text, attachments);
6654
6846
  return;
6655
6847
  }
@@ -6661,14 +6853,14 @@ function useOptimisticChatMessages({
6661
6853
  setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
6662
6854
  });
6663
6855
  },
6664
- [chatMessages, onSendChat, shouldForkOnEdit]
6856
+ [chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
6665
6857
  );
6666
6858
  return { messages, onSend };
6667
6859
  }
6668
6860
 
6669
6861
  // src/studio/ui/StudioOverlay.tsx
6670
6862
  var import_studio_control = require("@comergehq/studio-control");
6671
- var import_jsx_runtime57 = require("react/jsx-runtime");
6863
+ var import_jsx_runtime58 = require("react/jsx-runtime");
6672
6864
  function StudioOverlay({
6673
6865
  captureTargetRef,
6674
6866
  app,
@@ -6695,46 +6887,52 @@ function StudioOverlay({
6695
6887
  chatSending,
6696
6888
  chatShowTypingIndicator,
6697
6889
  onSendChat,
6890
+ chatQueueItems,
6891
+ onRemoveQueueItem,
6698
6892
  onNavigateHome,
6699
6893
  showBubble,
6700
6894
  studioControlOptions
6701
6895
  }) {
6702
6896
  const theme = useTheme();
6703
- const { width } = (0, import_react_native54.useWindowDimensions)();
6704
- const [sheetOpen, setSheetOpen] = React42.useState(false);
6705
- const sheetOpenRef = React42.useRef(sheetOpen);
6706
- const [activePage, setActivePage] = React42.useState("preview");
6707
- const [drawing, setDrawing] = React42.useState(false);
6708
- const [chatAttachments, setChatAttachments] = React42.useState([]);
6709
- const [commentsAppId, setCommentsAppId] = React42.useState(null);
6710
- const [commentsCount, setCommentsCount] = React42.useState(null);
6897
+ const { width } = (0, import_react_native55.useWindowDimensions)();
6898
+ const [sheetOpen, setSheetOpen] = React43.useState(false);
6899
+ const sheetOpenRef = React43.useRef(sheetOpen);
6900
+ const [activePage, setActivePage] = React43.useState("preview");
6901
+ const [drawing, setDrawing] = React43.useState(false);
6902
+ const [chatAttachments, setChatAttachments] = React43.useState([]);
6903
+ const [commentsAppId, setCommentsAppId] = React43.useState(null);
6904
+ const [commentsCount, setCommentsCount] = React43.useState(null);
6711
6905
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
6906
+ const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
6907
+ const queueItemsForChat = isForking ? [] : chatQueueItems;
6908
+ const disableOptimistic = Boolean(queueItemsForChat && queueItemsForChat.length > 0) || (app == null ? void 0 : app.status) === "editing";
6712
6909
  const optimistic = useOptimisticChatMessages({
6713
6910
  threadId,
6714
6911
  shouldForkOnEdit,
6912
+ disableOptimistic,
6715
6913
  chatMessages,
6716
6914
  onSendChat
6717
6915
  });
6718
- const [confirmMrId, setConfirmMrId] = React42.useState(null);
6719
- const confirmMr = React42.useMemo(
6916
+ const [confirmMrId, setConfirmMrId] = React43.useState(null);
6917
+ const confirmMr = React43.useMemo(
6720
6918
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6721
6919
  [confirmMrId, incomingMergeRequests]
6722
6920
  );
6723
- const handleSheetOpenChange = React42.useCallback((open) => {
6921
+ const handleSheetOpenChange = React43.useCallback((open) => {
6724
6922
  setSheetOpen(open);
6725
- if (!open) import_react_native54.Keyboard.dismiss();
6923
+ if (!open) import_react_native55.Keyboard.dismiss();
6726
6924
  }, []);
6727
- const closeSheet = React42.useCallback(() => {
6925
+ const closeSheet = React43.useCallback(() => {
6728
6926
  handleSheetOpenChange(false);
6729
6927
  }, [handleSheetOpenChange]);
6730
- const openSheet = React42.useCallback(() => setSheetOpen(true), []);
6731
- const goToChat = React42.useCallback(() => {
6928
+ const openSheet = React43.useCallback(() => setSheetOpen(true), []);
6929
+ const goToChat = React43.useCallback(() => {
6732
6930
  setActivePage("chat");
6733
6931
  openSheet();
6734
6932
  }, [openSheet]);
6735
- const backToPreview = React42.useCallback(() => {
6736
- if (import_react_native54.Platform.OS !== "ios") {
6737
- import_react_native54.Keyboard.dismiss();
6933
+ const backToPreview = React43.useCallback(() => {
6934
+ if (import_react_native55.Platform.OS !== "ios") {
6935
+ import_react_native55.Keyboard.dismiss();
6738
6936
  setActivePage("preview");
6739
6937
  return;
6740
6938
  }
@@ -6746,15 +6944,15 @@ function StudioOverlay({
6746
6944
  clearTimeout(t);
6747
6945
  setActivePage("preview");
6748
6946
  };
6749
- const sub = import_react_native54.Keyboard.addListener("keyboardDidHide", finalize);
6947
+ const sub = import_react_native55.Keyboard.addListener("keyboardDidHide", finalize);
6750
6948
  const t = setTimeout(finalize, 350);
6751
- import_react_native54.Keyboard.dismiss();
6949
+ import_react_native55.Keyboard.dismiss();
6752
6950
  }, []);
6753
- const startDraw = React42.useCallback(() => {
6951
+ const startDraw = React43.useCallback(() => {
6754
6952
  setDrawing(true);
6755
6953
  closeSheet();
6756
6954
  }, [closeSheet]);
6757
- const handleDrawCapture = React42.useCallback(
6955
+ const handleDrawCapture = React43.useCallback(
6758
6956
  (dataUrl) => {
6759
6957
  setChatAttachments((prev) => [...prev, dataUrl]);
6760
6958
  setDrawing(false);
@@ -6763,7 +6961,7 @@ function StudioOverlay({
6763
6961
  },
6764
6962
  [openSheet]
6765
6963
  );
6766
- const toggleSheet = React42.useCallback(async () => {
6964
+ const toggleSheet = React43.useCallback(async () => {
6767
6965
  if (!sheetOpen) {
6768
6966
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6769
6967
  if (shouldExitTest) {
@@ -6775,7 +6973,7 @@ function StudioOverlay({
6775
6973
  closeSheet();
6776
6974
  }
6777
6975
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6778
- const handleTestMr = React42.useCallback(
6976
+ const handleTestMr = React43.useCallback(
6779
6977
  async (mr) => {
6780
6978
  if (!onTestMr) return;
6781
6979
  await onTestMr(mr);
@@ -6783,10 +6981,10 @@ function StudioOverlay({
6783
6981
  },
6784
6982
  [closeSheet, onTestMr]
6785
6983
  );
6786
- React42.useEffect(() => {
6984
+ React43.useEffect(() => {
6787
6985
  sheetOpenRef.current = sheetOpen;
6788
6986
  }, [sheetOpen]);
6789
- React42.useEffect(() => {
6987
+ React43.useEffect(() => {
6790
6988
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
6791
6989
  if (action === "show" && !sheetOpenRef.current) openSheet();
6792
6990
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -6794,17 +6992,17 @@ function StudioOverlay({
6794
6992
  }, studioControlOptions);
6795
6993
  return () => poller.stop();
6796
6994
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
6797
- React42.useEffect(() => {
6995
+ React43.useEffect(() => {
6798
6996
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
6799
6997
  }, [sheetOpen, studioControlOptions]);
6800
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
6801
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
6802
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6998
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_jsx_runtime58.Fragment, { children: [
6999
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
7000
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6803
7001
  StudioSheetPager,
6804
7002
  {
6805
7003
  activePage,
6806
7004
  width,
6807
- preview: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7005
+ preview: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6808
7006
  PreviewPanel,
6809
7007
  {
6810
7008
  app,
@@ -6830,7 +7028,7 @@ function StudioOverlay({
6830
7028
  commentCountOverride: commentsCount ?? void 0
6831
7029
  }
6832
7030
  ),
6833
- chat: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7031
+ chat: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6834
7032
  ChatPanel,
6835
7033
  {
6836
7034
  messages: optimistic.messages,
@@ -6848,12 +7046,14 @@ function StudioOverlay({
6848
7046
  onClose: closeSheet,
6849
7047
  onNavigateHome,
6850
7048
  onStartDraw: startDraw,
6851
- onSend: optimistic.onSend
7049
+ onSend: optimistic.onSend,
7050
+ queueItems: queueItemsForChat,
7051
+ onRemoveQueueItem
6852
7052
  }
6853
7053
  )
6854
7054
  }
6855
7055
  ) }),
6856
- showBubble && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7056
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6857
7057
  Bubble,
6858
7058
  {
6859
7059
  visible: !sheetOpen && !drawing,
@@ -6861,10 +7061,10 @@ function StudioOverlay({
6861
7061
  badgeCount: incomingMergeRequests.length,
6862
7062
  onPress: toggleSheet,
6863
7063
  isLoading: (app == null ? void 0 : app.status) === "editing",
6864
- 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 }) })
7064
+ 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 }) })
6865
7065
  }
6866
7066
  ),
6867
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7067
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6868
7068
  DrawModeOverlay,
6869
7069
  {
6870
7070
  visible: drawing,
@@ -6873,7 +7073,7 @@ function StudioOverlay({
6873
7073
  onCapture: handleDrawCapture
6874
7074
  }
6875
7075
  ),
6876
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7076
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6877
7077
  ConfirmMergeFlow,
6878
7078
  {
6879
7079
  visible: Boolean(confirmMr),
@@ -6886,7 +7086,7 @@ function StudioOverlay({
6886
7086
  onTestFirst: handleTestMr
6887
7087
  }
6888
7088
  ),
6889
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7089
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6890
7090
  AppCommentsSheet,
6891
7091
  {
6892
7092
  appId: commentsAppId,
@@ -6898,8 +7098,190 @@ function StudioOverlay({
6898
7098
  ] });
6899
7099
  }
6900
7100
 
7101
+ // src/studio/hooks/useEditQueue.ts
7102
+ var React44 = __toESM(require("react"));
7103
+
7104
+ // src/data/apps/edit-queue/remote.ts
7105
+ var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
7106
+ async list(appId) {
7107
+ const { data } = await api.get(
7108
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue`
7109
+ );
7110
+ return data;
7111
+ }
7112
+ async update(appId, queueItemId, payload) {
7113
+ const { data } = await api.patch(
7114
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`,
7115
+ payload
7116
+ );
7117
+ return data;
7118
+ }
7119
+ async cancel(appId, queueItemId) {
7120
+ const { data } = await api.delete(
7121
+ `/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`
7122
+ );
7123
+ return data;
7124
+ }
7125
+ };
7126
+ var editQueueRemoteDataSource = new EditQueueRemoteDataSourceImpl();
7127
+
7128
+ // src/data/apps/edit-queue/repository.ts
7129
+ var ACTIVE_STATUSES = ["pending"];
7130
+ function toString(value) {
7131
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
7132
+ }
7133
+ function toAttachments(value) {
7134
+ return Array.isArray(value) ? value : [];
7135
+ }
7136
+ function mapQueueItem(row) {
7137
+ const payload = row.payload ?? {};
7138
+ return {
7139
+ id: row.id,
7140
+ status: row.status,
7141
+ prompt: toString(payload.trimmedPrompt),
7142
+ messageId: toString(payload.messageId),
7143
+ attachments: toAttachments(payload.attachments),
7144
+ createdAt: row.created_at,
7145
+ updatedAt: row.updated_at,
7146
+ runAfter: row.run_after,
7147
+ priority: row.priority
7148
+ };
7149
+ }
7150
+ var EditQueueRepositoryImpl = class extends BaseRepository {
7151
+ constructor(remote) {
7152
+ super();
7153
+ this.remote = remote;
7154
+ }
7155
+ async list(appId) {
7156
+ const res = await this.remote.list(appId);
7157
+ const data = this.unwrapOrThrow(res);
7158
+ return data.items ?? [];
7159
+ }
7160
+ async update(appId, queueItemId, payload) {
7161
+ const res = await this.remote.update(appId, queueItemId, payload);
7162
+ return this.unwrapOrThrow(res);
7163
+ }
7164
+ async cancel(appId, queueItemId) {
7165
+ const res = await this.remote.cancel(appId, queueItemId);
7166
+ return this.unwrapOrThrow(res);
7167
+ }
7168
+ subscribeEditQueue(appId, handlers) {
7169
+ const supabase = getSupabaseClient();
7170
+ const channel = supabase.channel(`edit-queue:app:${appId}`).on(
7171
+ "postgres_changes",
7172
+ { event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7173
+ (payload) => {
7174
+ var _a;
7175
+ const row = payload.new;
7176
+ if (row.kind !== "edit") return;
7177
+ const item = mapQueueItem(row);
7178
+ if (!ACTIVE_STATUSES.includes(item.status)) return;
7179
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
7180
+ }
7181
+ ).on(
7182
+ "postgres_changes",
7183
+ { event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7184
+ (payload) => {
7185
+ var _a, _b;
7186
+ const row = payload.new;
7187
+ if (row.kind !== "edit") return;
7188
+ const item = mapQueueItem(row);
7189
+ if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
7190
+ else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
7191
+ }
7192
+ ).on(
7193
+ "postgres_changes",
7194
+ { event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7195
+ (payload) => {
7196
+ var _a;
7197
+ const row = payload.old;
7198
+ if (row.kind !== "edit") return;
7199
+ (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
7200
+ }
7201
+ ).subscribe();
7202
+ return () => {
7203
+ supabase.removeChannel(channel);
7204
+ };
7205
+ }
7206
+ };
7207
+ var editQueueRepository = new EditQueueRepositoryImpl(
7208
+ editQueueRemoteDataSource
7209
+ );
7210
+
7211
+ // src/studio/hooks/useEditQueue.ts
7212
+ function useEditQueue(appId) {
7213
+ const [items, setItems] = React44.useState([]);
7214
+ const [loading, setLoading] = React44.useState(false);
7215
+ const [error, setError] = React44.useState(null);
7216
+ const activeRequestIdRef = React44.useRef(0);
7217
+ const foregroundSignal = useForegroundSignal(Boolean(appId));
7218
+ const upsertSorted = React44.useCallback((prev, nextItem) => {
7219
+ const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
7220
+ next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
7221
+ return next;
7222
+ }, []);
7223
+ const refetch = React44.useCallback(async () => {
7224
+ if (!appId) {
7225
+ setItems([]);
7226
+ return;
7227
+ }
7228
+ const requestId = ++activeRequestIdRef.current;
7229
+ setLoading(true);
7230
+ setError(null);
7231
+ try {
7232
+ const list = await editQueueRepository.list(appId);
7233
+ if (activeRequestIdRef.current !== requestId) return;
7234
+ setItems([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
7235
+ } catch (e) {
7236
+ if (activeRequestIdRef.current !== requestId) return;
7237
+ setError(e instanceof Error ? e : new Error(String(e)));
7238
+ setItems([]);
7239
+ } finally {
7240
+ if (activeRequestIdRef.current === requestId) setLoading(false);
7241
+ }
7242
+ }, [appId]);
7243
+ React44.useEffect(() => {
7244
+ void refetch();
7245
+ }, [refetch]);
7246
+ React44.useEffect(() => {
7247
+ if (!appId) return;
7248
+ const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
7249
+ onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
7250
+ onUpdate: (item) => setItems((prev) => upsertSorted(prev, item)),
7251
+ onDelete: (item) => setItems((prev) => prev.filter((x) => x.id !== item.id))
7252
+ });
7253
+ return unsubscribe;
7254
+ }, [appId, upsertSorted, foregroundSignal]);
7255
+ React44.useEffect(() => {
7256
+ if (!appId) return;
7257
+ if (foregroundSignal <= 0) return;
7258
+ void refetch();
7259
+ }, [appId, foregroundSignal, refetch]);
7260
+ return { items, loading, error, refetch };
7261
+ }
7262
+
7263
+ // src/studio/hooks/useEditQueueActions.ts
7264
+ var React45 = __toESM(require("react"));
7265
+ function useEditQueueActions(appId) {
7266
+ const update = React45.useCallback(
7267
+ async (queueItemId, payload) => {
7268
+ if (!appId) return;
7269
+ await editQueueRepository.update(appId, queueItemId, payload);
7270
+ },
7271
+ [appId]
7272
+ );
7273
+ const cancel = React45.useCallback(
7274
+ async (queueItemId) => {
7275
+ if (!appId) return;
7276
+ await editQueueRepository.cancel(appId, queueItemId);
7277
+ },
7278
+ [appId]
7279
+ );
7280
+ return { update, cancel };
7281
+ }
7282
+
6901
7283
  // src/studio/ComergeStudio.tsx
6902
- var import_jsx_runtime58 = require("react/jsx-runtime");
7284
+ var import_jsx_runtime59 = require("react/jsx-runtime");
6903
7285
  function ComergeStudio({
6904
7286
  appId,
6905
7287
  clientKey: clientKey2,
@@ -6910,17 +7292,17 @@ function ComergeStudio({
6910
7292
  studioControlOptions,
6911
7293
  embeddedBaseBundles
6912
7294
  }) {
6913
- const [activeAppId, setActiveAppId] = React43.useState(appId);
6914
- const [runtimeAppId, setRuntimeAppId] = React43.useState(appId);
6915
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React43.useState(null);
6916
- const platform = React43.useMemo(() => import_react_native55.Platform.OS === "ios" ? "ios" : "android", []);
6917
- React43.useEffect(() => {
7295
+ const [activeAppId, setActiveAppId] = React46.useState(appId);
7296
+ const [runtimeAppId, setRuntimeAppId] = React46.useState(appId);
7297
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React46.useState(null);
7298
+ const platform = React46.useMemo(() => import_react_native56.Platform.OS === "ios" ? "ios" : "android", []);
7299
+ React46.useEffect(() => {
6918
7300
  setActiveAppId(appId);
6919
7301
  setRuntimeAppId(appId);
6920
7302
  setPendingRuntimeTargetAppId(null);
6921
7303
  }, [appId]);
6922
- const captureTargetRef = React43.useRef(null);
6923
- 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)(
7304
+ const captureTargetRef = React46.useRef(null);
7305
+ 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)(
6924
7306
  ComergeStudioInner,
6925
7307
  {
6926
7308
  userId,
@@ -6961,11 +7343,11 @@ function ComergeStudioInner({
6961
7343
  const { app, loading: appLoading } = useApp(activeAppId);
6962
7344
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6963
7345
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6964
- const sawEditingOnPendingTargetRef = React43.useRef(false);
6965
- React43.useEffect(() => {
7346
+ const sawEditingOnPendingTargetRef = React46.useRef(false);
7347
+ React46.useEffect(() => {
6966
7348
  sawEditingOnPendingTargetRef.current = false;
6967
7349
  }, [pendingRuntimeTargetAppId]);
6968
- React43.useEffect(() => {
7350
+ React46.useEffect(() => {
6969
7351
  if (!pendingRuntimeTargetAppId) return;
6970
7352
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6971
7353
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6983,13 +7365,13 @@ function ComergeStudioInner({
6983
7365
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
6984
7366
  embeddedBaseBundles
6985
7367
  });
6986
- const sawEditingOnActiveAppRef = React43.useRef(false);
6987
- const [showPostEditPreparing, setShowPostEditPreparing] = React43.useState(false);
6988
- React43.useEffect(() => {
7368
+ const sawEditingOnActiveAppRef = React46.useRef(false);
7369
+ const [showPostEditPreparing, setShowPostEditPreparing] = React46.useState(false);
7370
+ React46.useEffect(() => {
6989
7371
  sawEditingOnActiveAppRef.current = false;
6990
7372
  setShowPostEditPreparing(false);
6991
7373
  }, [activeAppId]);
6992
- React43.useEffect(() => {
7374
+ React46.useEffect(() => {
6993
7375
  if (!(app == null ? void 0 : app.id)) return;
6994
7376
  if (app.status === "editing") {
6995
7377
  sawEditingOnActiveAppRef.current = true;
@@ -7001,7 +7383,7 @@ function ComergeStudioInner({
7001
7383
  sawEditingOnActiveAppRef.current = false;
7002
7384
  }
7003
7385
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
7004
- React43.useEffect(() => {
7386
+ React46.useEffect(() => {
7005
7387
  if (!showPostEditPreparing) return;
7006
7388
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
7007
7389
  if (!stillProcessingBaseBundle) {
@@ -7010,15 +7392,27 @@ function ComergeStudioInner({
7010
7392
  }, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
7011
7393
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
7012
7394
  const thread = useThreadMessages(threadId);
7395
+ const editQueue = useEditQueue(activeAppId);
7396
+ const editQueueActions = useEditQueueActions(activeAppId);
7397
+ const [lastEditQueueInfo, setLastEditQueueInfo] = React46.useState(null);
7398
+ const lastEditQueueInfoRef = React46.useRef(null);
7399
+ const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React46.useState(false);
7013
7400
  const mergeRequests = useMergeRequests({ appId: activeAppId });
7014
- const hasOpenOutgoingMr = React43.useMemo(() => {
7401
+ const hasOpenOutgoingMr = React46.useMemo(() => {
7015
7402
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
7016
7403
  }, [mergeRequests.lists.outgoing]);
7017
- const incomingReviewMrs = React43.useMemo(() => {
7404
+ const incomingReviewMrs = React46.useMemo(() => {
7018
7405
  if (!userId) return mergeRequests.lists.incoming;
7019
7406
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
7020
7407
  }, [mergeRequests.lists.incoming, userId]);
7021
7408
  const uploader = useAttachmentUpload();
7409
+ const updateLastEditQueueInfo = React46.useCallback(
7410
+ (info) => {
7411
+ lastEditQueueInfoRef.current = info;
7412
+ setLastEditQueueInfo(info);
7413
+ },
7414
+ []
7415
+ );
7022
7416
  const actions = useStudioActions({
7023
7417
  userId,
7024
7418
  app,
@@ -7033,20 +7427,62 @@ function ComergeStudioInner({
7033
7427
  setPendingRuntimeTargetAppId(null);
7034
7428
  }
7035
7429
  },
7036
- uploadAttachments: uploader.uploadBase64Images
7430
+ uploadAttachments: uploader.uploadBase64Images,
7431
+ onEditStart: () => {
7432
+ if (editQueue.items.length === 0) {
7433
+ setSuppressQueueUntilResponse(true);
7434
+ }
7435
+ },
7436
+ onEditQueued: (info) => {
7437
+ updateLastEditQueueInfo(info);
7438
+ if (info.queuePosition !== 1) {
7439
+ setSuppressQueueUntilResponse(false);
7440
+ }
7441
+ },
7442
+ onEditFinished: () => {
7443
+ var _a;
7444
+ if (((_a = lastEditQueueInfoRef.current) == null ? void 0 : _a.queuePosition) !== 1) {
7445
+ setSuppressQueueUntilResponse(false);
7446
+ }
7447
+ }
7037
7448
  });
7038
- const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
7039
- const [processingMrId, setProcessingMrId] = React43.useState(null);
7040
- const [testingMrId, setTestingMrId] = React43.useState(null);
7041
- const chatShowTypingIndicator = React43.useMemo(() => {
7449
+ const chatSendDisabled = false;
7450
+ const [processingMrId, setProcessingMrId] = React46.useState(null);
7451
+ const [testingMrId, setTestingMrId] = React46.useState(null);
7452
+ const chatShowTypingIndicator = React46.useMemo(() => {
7042
7453
  var _a;
7043
7454
  if (!thread.raw || thread.raw.length === 0) return false;
7044
7455
  const last = thread.raw[thread.raw.length - 1];
7045
7456
  const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
7046
7457
  return payloadType !== "outcome";
7047
7458
  }, [thread.raw]);
7048
- 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: [
7049
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7459
+ React46.useEffect(() => {
7460
+ updateLastEditQueueInfo(null);
7461
+ setSuppressQueueUntilResponse(false);
7462
+ }, [activeAppId, updateLastEditQueueInfo]);
7463
+ React46.useEffect(() => {
7464
+ if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
7465
+ const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
7466
+ if (!stillPresent) {
7467
+ updateLastEditQueueInfo(null);
7468
+ setSuppressQueueUntilResponse(false);
7469
+ }
7470
+ }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
7471
+ const chatQueueItems = React46.useMemo(() => {
7472
+ var _a;
7473
+ if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
7474
+ return [];
7475
+ }
7476
+ if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
7477
+ return editQueue.items;
7478
+ }
7479
+ if (editQueue.items.length === 1 && ((_a = editQueue.items[0]) == null ? void 0 : _a.id) === lastEditQueueInfo.queueItemId) {
7480
+ return [];
7481
+ }
7482
+ return editQueue.items;
7483
+ }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
7484
+ 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: [
7485
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7050
7486
  RuntimeRenderer,
7051
7487
  {
7052
7488
  appKey,
@@ -7056,7 +7492,7 @@ function ComergeStudioInner({
7056
7492
  allowInitialPreparing: !embeddedBaseBundles
7057
7493
  }
7058
7494
  ),
7059
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7495
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7060
7496
  StudioOverlay,
7061
7497
  {
7062
7498
  captureTargetRef,
@@ -7108,6 +7544,8 @@ function ComergeStudioInner({
7108
7544
  chatSending: actions.sending,
7109
7545
  chatShowTypingIndicator,
7110
7546
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
7547
+ chatQueueItems,
7548
+ onRemoveQueueItem: (id) => editQueueActions.cancel(id),
7111
7549
  onNavigateHome,
7112
7550
  showBubble,
7113
7551
  studioControlOptions