@comergehq/studio 0.1.15 → 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)));
@@ -1932,6 +1963,9 @@ function useStudioActions({
1932
1963
  userId,
1933
1964
  app,
1934
1965
  onForkedApp,
1966
+ onEditStart,
1967
+ onEditQueued,
1968
+ onEditFinished,
1935
1969
  uploadAttachments
1936
1970
  }) {
1937
1971
  const [forking, setForking] = React8.useState(false);
@@ -1947,6 +1981,7 @@ function useStudioActions({
1947
1981
  setSending(true);
1948
1982
  setError(null);
1949
1983
  try {
1984
+ onEditStart == null ? void 0 : onEditStart();
1950
1985
  let targetApp = app;
1951
1986
  if (shouldForkOnEdit) {
1952
1987
  setForking(true);
@@ -1962,12 +1997,16 @@ function useStudioActions({
1962
1997
  if (attachments && attachments.length > 0 && uploadAttachments) {
1963
1998
  attachmentMetas = await uploadAttachments({ threadId, appId: targetApp.id, dataUrls: attachments });
1964
1999
  }
1965
- await agentRepository.editApp({
2000
+ const editResult = await agentRepository.editApp({
1966
2001
  prompt,
1967
2002
  thread_id: threadId,
1968
2003
  app_id: targetApp.id,
1969
2004
  attachments: attachmentMetas && attachmentMetas.length > 0 ? attachmentMetas : void 0
1970
2005
  });
2006
+ onEditQueued == null ? void 0 : onEditQueued({
2007
+ queueItemId: editResult.queueItemId ?? null,
2008
+ queuePosition: editResult.queuePosition ?? null
2009
+ });
1971
2010
  } catch (e) {
1972
2011
  const err = e instanceof Error ? e : new Error(String(e));
1973
2012
  setError(err);
@@ -1975,32 +2014,14 @@ function useStudioActions({
1975
2014
  } finally {
1976
2015
  setForking(false);
1977
2016
  setSending(false);
2017
+ onEditFinished == null ? void 0 : onEditFinished();
1978
2018
  }
1979
2019
  },
1980
- [app, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
2020
+ [app, onEditFinished, onEditQueued, onEditStart, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
1981
2021
  );
1982
2022
  return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
1983
2023
  }
1984
2024
 
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
2025
  // src/studio/ui/RuntimeRenderer.tsx
2005
2026
  var React9 = __toESM(require("react"));
2006
2027
  var import_react_native6 = require("react-native");
@@ -2038,8 +2059,8 @@ function RuntimeRenderer({
2038
2059
  }
2039
2060
 
2040
2061
  // src/studio/ui/StudioOverlay.tsx
2041
- var React42 = __toESM(require("react"));
2042
- var import_react_native54 = require("react-native");
2062
+ var React43 = __toESM(require("react"));
2063
+ var import_react_native55 = require("react-native");
2043
2064
 
2044
2065
  // src/components/studio-sheet/StudioBottomSheet.tsx
2045
2066
  var React12 = __toESM(require("react"));
@@ -5843,8 +5864,8 @@ function PreviewPanel({
5843
5864
  }
5844
5865
 
5845
5866
  // src/studio/ui/ChatPanel.tsx
5846
- var React39 = __toESM(require("react"));
5847
- var import_react_native51 = require("react-native");
5867
+ var React40 = __toESM(require("react"));
5868
+ var import_react_native52 = require("react-native");
5848
5869
 
5849
5870
  // src/components/chat/ChatPage.tsx
5850
5871
  var React37 = __toESM(require("react"));
@@ -6041,6 +6062,7 @@ function ChatPage({
6041
6062
  showTypingIndicator,
6042
6063
  renderMessageContent,
6043
6064
  topBanner,
6065
+ composerTop,
6044
6066
  composer,
6045
6067
  overlay,
6046
6068
  style,
@@ -6051,6 +6073,7 @@ function ChatPage({
6051
6073
  const theme = useTheme();
6052
6074
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
6053
6075
  const [composerHeight, setComposerHeight] = React37.useState(0);
6076
+ const [composerTopHeight, setComposerTopHeight] = React37.useState(0);
6054
6077
  const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
6055
6078
  React37.useEffect(() => {
6056
6079
  if (import_react_native47.Platform.OS !== "ios") return;
@@ -6062,8 +6085,9 @@ function ChatPage({
6062
6085
  };
6063
6086
  }, []);
6064
6087
  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;
6088
+ const totalComposerHeight = composerHeight + composerTopHeight;
6089
+ const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
6090
+ const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
6067
6091
  const resolvedOverlay = React37.useMemo(() => {
6068
6092
  var _a;
6069
6093
  if (!overlay) return null;
@@ -6073,6 +6097,10 @@ function ChatPage({
6073
6097
  style: [prevStyle, { bottom: overlayBottom }]
6074
6098
  });
6075
6099
  }, [overlay, overlayBottom]);
6100
+ React37.useEffect(() => {
6101
+ if (composerTop) return;
6102
+ setComposerTopHeight(0);
6103
+ }, [composerTop]);
6076
6104
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native47.View, { style: [{ flex: 1 }, style], children: [
6077
6105
  header ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { children: header }) : null,
6078
6106
  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 +6125,7 @@ function ChatPage({
6097
6125
  ]
6098
6126
  }
6099
6127
  ),
6100
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6128
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
6101
6129
  import_react_native47.View,
6102
6130
  {
6103
6131
  style: {
@@ -6109,14 +6137,24 @@ function ChatPage({
6109
6137
  paddingTop: theme.spacing.sm,
6110
6138
  paddingBottom: footerBottomPadding
6111
6139
  },
6112
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6113
- ChatComposer,
6114
- {
6115
- ...composer,
6116
- attachments: composer.attachments ?? [],
6117
- onLayout: ({ height }) => setComposerHeight(height)
6118
- }
6119
- )
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
+ ]
6120
6158
  }
6121
6159
  )
6122
6160
  ] })
@@ -6266,8 +6304,154 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6266
6304
  );
6267
6305
  }
6268
6306
 
6269
- // 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");
6270
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");
6271
6455
  function ChatPanel({
6272
6456
  title = "Chat",
6273
6457
  autoFocusComposer = false,
@@ -6285,11 +6469,13 @@ function ChatPanel({
6285
6469
  onClose,
6286
6470
  onNavigateHome,
6287
6471
  onStartDraw,
6288
- onSend
6472
+ onSend,
6473
+ queueItems = [],
6474
+ onRemoveQueueItem
6289
6475
  }) {
6290
- const listRef = React39.useRef(null);
6291
- const [nearBottom, setNearBottom] = React39.useState(true);
6292
- const handleSend = React39.useCallback(
6476
+ const listRef = React40.useRef(null);
6477
+ const [nearBottom, setNearBottom] = React40.useState(true);
6478
+ const handleSend = React40.useCallback(
6293
6479
  async (text, composerAttachments) => {
6294
6480
  const all = composerAttachments ?? attachments;
6295
6481
  await onSend(text, all.length > 0 ? all : void 0);
@@ -6303,25 +6489,25 @@ function ChatPanel({
6303
6489
  },
6304
6490
  [attachments, nearBottom, onClearAttachments, onSend]
6305
6491
  );
6306
- const handleScrollToBottom = React39.useCallback(() => {
6492
+ const handleScrollToBottom = React40.useCallback(() => {
6307
6493
  var _a;
6308
6494
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
6309
6495
  }, []);
6310
- const header = /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6496
+ const header = /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6311
6497
  ChatHeader,
6312
6498
  {
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
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
6316
6502
  ] }),
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" }) })
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" }) })
6320
6506
  ] }),
6321
6507
  center: null
6322
6508
  }
6323
6509
  );
6324
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6510
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6325
6511
  ForkNoticeBanner,
6326
6512
  {
6327
6513
  isOwner: !shouldForkOnEdit,
@@ -6330,33 +6516,35 @@ function ChatPanel({
6330
6516
  ) : null;
6331
6517
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
6332
6518
  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" })
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" })
6340
6526
  ] })
6341
6527
  ] });
6342
6528
  }
6343
- 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)(
6344
6531
  ChatPage,
6345
6532
  {
6346
6533
  header,
6347
6534
  messages,
6348
6535
  showTypingIndicator,
6349
6536
  topBanner,
6537
+ composerTop: queueTop,
6350
6538
  composerHorizontalPadding: 0,
6351
6539
  listRef,
6352
6540
  onNearBottomChange: setNearBottom,
6353
- overlay: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6541
+ overlay: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6354
6542
  ScrollToBottomButton,
6355
6543
  {
6356
6544
  visible: !nearBottom,
6357
6545
  onPress: handleScrollToBottom,
6358
6546
  style: { bottom: 80 },
6359
- 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" })
6360
6548
  }
6361
6549
  ),
6362
6550
  composer: {
@@ -6377,12 +6565,12 @@ function ChatPanel({
6377
6565
  }
6378
6566
 
6379
6567
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6380
- var React40 = __toESM(require("react"));
6381
- var import_react_native53 = require("react-native");
6568
+ var React41 = __toESM(require("react"));
6569
+ var import_react_native54 = require("react-native");
6382
6570
 
6383
6571
  // src/components/primitives/Modal.tsx
6384
- var import_react_native52 = require("react-native");
6385
- 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");
6386
6574
  function Modal({
6387
6575
  visible,
6388
6576
  onRequestClose,
@@ -6391,30 +6579,30 @@ function Modal({
6391
6579
  contentStyle
6392
6580
  }) {
6393
6581
  const theme = useTheme();
6394
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6395
- import_react_native52.Modal,
6582
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6583
+ import_react_native53.Modal,
6396
6584
  {
6397
6585
  visible,
6398
6586
  transparent: true,
6399
6587
  animationType: "fade",
6400
6588
  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,
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,
6404
6592
  {
6405
6593
  accessibilityRole: "button",
6406
6594
  onPress: dismissOnBackdropPress ? onRequestClose : void 0,
6407
6595
  style: { position: "absolute", inset: 0 }
6408
6596
  }
6409
6597
  ),
6410
- /* @__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 })
6411
6599
  ] })
6412
6600
  }
6413
6601
  );
6414
6602
  }
6415
6603
 
6416
6604
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6417
- var import_jsx_runtime55 = require("react/jsx-runtime");
6605
+ var import_jsx_runtime56 = require("react/jsx-runtime");
6418
6606
  function ConfirmMergeRequestDialog({
6419
6607
  visible,
6420
6608
  onOpenChange,
@@ -6425,14 +6613,14 @@ function ConfirmMergeRequestDialog({
6425
6613
  onTestFirst
6426
6614
  }) {
6427
6615
  const theme = useTheme();
6428
- const close = React40.useCallback(() => onOpenChange(false), [onOpenChange]);
6616
+ const close = React41.useCallback(() => onOpenChange(false), [onOpenChange]);
6429
6617
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6430
- const handleConfirm = React40.useCallback(() => {
6618
+ const handleConfirm = React41.useCallback(() => {
6431
6619
  if (!mergeRequest) return;
6432
6620
  onOpenChange(false);
6433
6621
  void onConfirm();
6434
6622
  }, [mergeRequest, onConfirm, onOpenChange]);
6435
- const handleTestFirst = React40.useCallback(() => {
6623
+ const handleTestFirst = React41.useCallback(() => {
6436
6624
  if (!mergeRequest) return;
6437
6625
  onOpenChange(false);
6438
6626
  void onTestFirst(mergeRequest);
@@ -6444,7 +6632,7 @@ function ConfirmMergeRequestDialog({
6444
6632
  justifyContent: "center",
6445
6633
  alignSelf: "stretch"
6446
6634
  };
6447
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
6635
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
6448
6636
  Modal,
6449
6637
  {
6450
6638
  visible,
@@ -6455,7 +6643,7 @@ function ConfirmMergeRequestDialog({
6455
6643
  backgroundColor: theme.colors.background
6456
6644
  },
6457
6645
  children: [
6458
- /* @__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)(
6459
6647
  Text,
6460
6648
  {
6461
6649
  style: {
@@ -6467,9 +6655,9 @@ function ConfirmMergeRequestDialog({
6467
6655
  children: "Are you sure you want to approve this merge request?"
6468
6656
  }
6469
6657
  ) }),
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,
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,
6473
6661
  {
6474
6662
  style: [
6475
6663
  fullWidthButtonBase,
@@ -6478,22 +6666,22 @@ function ConfirmMergeRequestDialog({
6478
6666
  opacity: canConfirm ? 1 : 0.5
6479
6667
  }
6480
6668
  ],
6481
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6482
- import_react_native53.Pressable,
6669
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6670
+ import_react_native54.Pressable,
6483
6671
  {
6484
6672
  accessibilityRole: "button",
6485
6673
  accessibilityLabel: "Approve Merge",
6486
6674
  disabled: !canConfirm,
6487
6675
  onPress: handleConfirm,
6488
6676
  style: [fullWidthButtonBase, { flex: 1 }],
6489
- 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" })
6490
6678
  }
6491
6679
  )
6492
6680
  }
6493
6681
  ),
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,
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,
6497
6685
  {
6498
6686
  style: [
6499
6687
  fullWidthButtonBase,
@@ -6504,22 +6692,22 @@ function ConfirmMergeRequestDialog({
6504
6692
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
6505
6693
  }
6506
6694
  ],
6507
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6508
- import_react_native53.Pressable,
6695
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6696
+ import_react_native54.Pressable,
6509
6697
  {
6510
6698
  accessibilityRole: "button",
6511
6699
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
6512
6700
  disabled: isBuilding || !mergeRequest,
6513
6701
  onPress: handleTestFirst,
6514
6702
  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" })
6703
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
6516
6704
  }
6517
6705
  )
6518
6706
  }
6519
6707
  ),
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,
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,
6523
6711
  {
6524
6712
  style: [
6525
6713
  fullWidthButtonBase,
@@ -6529,14 +6717,14 @@ function ConfirmMergeRequestDialog({
6529
6717
  borderColor: theme.colors.border
6530
6718
  }
6531
6719
  ],
6532
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
6533
- import_react_native53.Pressable,
6720
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6721
+ import_react_native54.Pressable,
6534
6722
  {
6535
6723
  accessibilityRole: "button",
6536
6724
  accessibilityLabel: "Cancel",
6537
6725
  onPress: close,
6538
6726
  style: [fullWidthButtonBase, { flex: 1 }],
6539
- 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" })
6540
6728
  }
6541
6729
  )
6542
6730
  }
@@ -6548,7 +6736,7 @@ function ConfirmMergeRequestDialog({
6548
6736
  }
6549
6737
 
6550
6738
  // src/studio/ui/ConfirmMergeFlow.tsx
6551
- var import_jsx_runtime56 = require("react/jsx-runtime");
6739
+ var import_jsx_runtime57 = require("react/jsx-runtime");
6552
6740
  function ConfirmMergeFlow({
6553
6741
  visible,
6554
6742
  onOpenChange,
@@ -6559,7 +6747,7 @@ function ConfirmMergeFlow({
6559
6747
  onConfirm,
6560
6748
  onTestFirst
6561
6749
  }) {
6562
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
6750
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6563
6751
  ConfirmMergeRequestDialog,
6564
6752
  {
6565
6753
  visible,
@@ -6581,11 +6769,11 @@ function ConfirmMergeFlow({
6581
6769
  }
6582
6770
 
6583
6771
  // src/studio/hooks/useOptimisticChatMessages.ts
6584
- var React41 = __toESM(require("react"));
6772
+ var React42 = __toESM(require("react"));
6585
6773
  function makeOptimisticId() {
6586
6774
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6587
6775
  }
6588
- function toEpochMs(createdAt) {
6776
+ function toEpochMs2(createdAt) {
6589
6777
  if (createdAt == null) return 0;
6590
6778
  if (typeof createdAt === "number") return createdAt;
6591
6779
  if (createdAt instanceof Date) return createdAt.getTime();
@@ -6604,7 +6792,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6604
6792
  for (const m of candidates) {
6605
6793
  if (m.author !== "human") continue;
6606
6794
  if (normalize(m.content) !== target) continue;
6607
- const serverMs = toEpochMs(m.createdAt);
6795
+ const serverMs = toEpochMs2(m.createdAt);
6608
6796
  const optimisticMs = Date.parse(o.createdAtIso);
6609
6797
  if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
6610
6798
  if (serverMs + 12e4 < optimisticMs) continue;
@@ -6616,14 +6804,15 @@ function isOptimisticResolvedByServer(chatMessages, o) {
6616
6804
  function useOptimisticChatMessages({
6617
6805
  threadId,
6618
6806
  shouldForkOnEdit,
6807
+ disableOptimistic = false,
6619
6808
  chatMessages,
6620
6809
  onSendChat
6621
6810
  }) {
6622
- const [optimisticChat, setOptimisticChat] = React41.useState([]);
6623
- React41.useEffect(() => {
6811
+ const [optimisticChat, setOptimisticChat] = React42.useState([]);
6812
+ React42.useEffect(() => {
6624
6813
  setOptimisticChat([]);
6625
6814
  }, [threadId]);
6626
- const messages = React41.useMemo(() => {
6815
+ const messages = React42.useMemo(() => {
6627
6816
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6628
6817
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6629
6818
  if (unresolved.length === 0) return chatMessages;
@@ -6639,7 +6828,7 @@ function useOptimisticChatMessages({
6639
6828
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6640
6829
  return merged;
6641
6830
  }, [chatMessages, optimisticChat]);
6642
- React41.useEffect(() => {
6831
+ React42.useEffect(() => {
6643
6832
  if (optimisticChat.length === 0) return;
6644
6833
  setOptimisticChat((prev) => {
6645
6834
  if (prev.length === 0) return prev;
@@ -6647,9 +6836,9 @@ function useOptimisticChatMessages({
6647
6836
  return next.length === prev.length ? prev : next;
6648
6837
  });
6649
6838
  }, [chatMessages, optimisticChat.length]);
6650
- const onSend = React41.useCallback(
6839
+ const onSend = React42.useCallback(
6651
6840
  async (text, attachments) => {
6652
- if (shouldForkOnEdit) {
6841
+ if (shouldForkOnEdit || disableOptimistic) {
6653
6842
  await onSendChat(text, attachments);
6654
6843
  return;
6655
6844
  }
@@ -6661,14 +6850,14 @@ function useOptimisticChatMessages({
6661
6850
  setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
6662
6851
  });
6663
6852
  },
6664
- [chatMessages, onSendChat, shouldForkOnEdit]
6853
+ [chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
6665
6854
  );
6666
6855
  return { messages, onSend };
6667
6856
  }
6668
6857
 
6669
6858
  // src/studio/ui/StudioOverlay.tsx
6670
6859
  var import_studio_control = require("@comergehq/studio-control");
6671
- var import_jsx_runtime57 = require("react/jsx-runtime");
6860
+ var import_jsx_runtime58 = require("react/jsx-runtime");
6672
6861
  function StudioOverlay({
6673
6862
  captureTargetRef,
6674
6863
  app,
@@ -6695,46 +6884,50 @@ function StudioOverlay({
6695
6884
  chatSending,
6696
6885
  chatShowTypingIndicator,
6697
6886
  onSendChat,
6887
+ chatQueueItems,
6888
+ onRemoveQueueItem,
6698
6889
  onNavigateHome,
6699
6890
  showBubble,
6700
6891
  studioControlOptions
6701
6892
  }) {
6702
6893
  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);
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);
6711
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";
6712
6904
  const optimistic = useOptimisticChatMessages({
6713
6905
  threadId,
6714
6906
  shouldForkOnEdit,
6907
+ disableOptimistic,
6715
6908
  chatMessages,
6716
6909
  onSendChat
6717
6910
  });
6718
- const [confirmMrId, setConfirmMrId] = React42.useState(null);
6719
- const confirmMr = React42.useMemo(
6911
+ const [confirmMrId, setConfirmMrId] = React43.useState(null);
6912
+ const confirmMr = React43.useMemo(
6720
6913
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6721
6914
  [confirmMrId, incomingMergeRequests]
6722
6915
  );
6723
- const handleSheetOpenChange = React42.useCallback((open) => {
6916
+ const handleSheetOpenChange = React43.useCallback((open) => {
6724
6917
  setSheetOpen(open);
6725
- if (!open) import_react_native54.Keyboard.dismiss();
6918
+ if (!open) import_react_native55.Keyboard.dismiss();
6726
6919
  }, []);
6727
- const closeSheet = React42.useCallback(() => {
6920
+ const closeSheet = React43.useCallback(() => {
6728
6921
  handleSheetOpenChange(false);
6729
6922
  }, [handleSheetOpenChange]);
6730
- const openSheet = React42.useCallback(() => setSheetOpen(true), []);
6731
- const goToChat = React42.useCallback(() => {
6923
+ const openSheet = React43.useCallback(() => setSheetOpen(true), []);
6924
+ const goToChat = React43.useCallback(() => {
6732
6925
  setActivePage("chat");
6733
6926
  openSheet();
6734
6927
  }, [openSheet]);
6735
- const backToPreview = React42.useCallback(() => {
6736
- if (import_react_native54.Platform.OS !== "ios") {
6737
- import_react_native54.Keyboard.dismiss();
6928
+ const backToPreview = React43.useCallback(() => {
6929
+ if (import_react_native55.Platform.OS !== "ios") {
6930
+ import_react_native55.Keyboard.dismiss();
6738
6931
  setActivePage("preview");
6739
6932
  return;
6740
6933
  }
@@ -6746,15 +6939,15 @@ function StudioOverlay({
6746
6939
  clearTimeout(t);
6747
6940
  setActivePage("preview");
6748
6941
  };
6749
- const sub = import_react_native54.Keyboard.addListener("keyboardDidHide", finalize);
6942
+ const sub = import_react_native55.Keyboard.addListener("keyboardDidHide", finalize);
6750
6943
  const t = setTimeout(finalize, 350);
6751
- import_react_native54.Keyboard.dismiss();
6944
+ import_react_native55.Keyboard.dismiss();
6752
6945
  }, []);
6753
- const startDraw = React42.useCallback(() => {
6946
+ const startDraw = React43.useCallback(() => {
6754
6947
  setDrawing(true);
6755
6948
  closeSheet();
6756
6949
  }, [closeSheet]);
6757
- const handleDrawCapture = React42.useCallback(
6950
+ const handleDrawCapture = React43.useCallback(
6758
6951
  (dataUrl) => {
6759
6952
  setChatAttachments((prev) => [...prev, dataUrl]);
6760
6953
  setDrawing(false);
@@ -6763,7 +6956,7 @@ function StudioOverlay({
6763
6956
  },
6764
6957
  [openSheet]
6765
6958
  );
6766
- const toggleSheet = React42.useCallback(async () => {
6959
+ const toggleSheet = React43.useCallback(async () => {
6767
6960
  if (!sheetOpen) {
6768
6961
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6769
6962
  if (shouldExitTest) {
@@ -6775,7 +6968,7 @@ function StudioOverlay({
6775
6968
  closeSheet();
6776
6969
  }
6777
6970
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6778
- const handleTestMr = React42.useCallback(
6971
+ const handleTestMr = React43.useCallback(
6779
6972
  async (mr) => {
6780
6973
  if (!onTestMr) return;
6781
6974
  await onTestMr(mr);
@@ -6783,10 +6976,10 @@ function StudioOverlay({
6783
6976
  },
6784
6977
  [closeSheet, onTestMr]
6785
6978
  );
6786
- React42.useEffect(() => {
6979
+ React43.useEffect(() => {
6787
6980
  sheetOpenRef.current = sheetOpen;
6788
6981
  }, [sheetOpen]);
6789
- React42.useEffect(() => {
6982
+ React43.useEffect(() => {
6790
6983
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
6791
6984
  if (action === "show" && !sheetOpenRef.current) openSheet();
6792
6985
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -6794,17 +6987,17 @@ function StudioOverlay({
6794
6987
  }, studioControlOptions);
6795
6988
  return () => poller.stop();
6796
6989
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
6797
- React42.useEffect(() => {
6990
+ React43.useEffect(() => {
6798
6991
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
6799
6992
  }, [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)(
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)(
6803
6996
  StudioSheetPager,
6804
6997
  {
6805
6998
  activePage,
6806
6999
  width,
6807
- preview: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7000
+ preview: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6808
7001
  PreviewPanel,
6809
7002
  {
6810
7003
  app,
@@ -6830,7 +7023,7 @@ function StudioOverlay({
6830
7023
  commentCountOverride: commentsCount ?? void 0
6831
7024
  }
6832
7025
  ),
6833
- chat: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7026
+ chat: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6834
7027
  ChatPanel,
6835
7028
  {
6836
7029
  messages: optimistic.messages,
@@ -6848,12 +7041,14 @@ function StudioOverlay({
6848
7041
  onClose: closeSheet,
6849
7042
  onNavigateHome,
6850
7043
  onStartDraw: startDraw,
6851
- onSend: optimistic.onSend
7044
+ onSend: optimistic.onSend,
7045
+ queueItems: chatQueueItems,
7046
+ onRemoveQueueItem
6852
7047
  }
6853
7048
  )
6854
7049
  }
6855
7050
  ) }),
6856
- showBubble && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7051
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6857
7052
  Bubble,
6858
7053
  {
6859
7054
  visible: !sheetOpen && !drawing,
@@ -6861,10 +7056,10 @@ function StudioOverlay({
6861
7056
  badgeCount: incomingMergeRequests.length,
6862
7057
  onPress: toggleSheet,
6863
7058
  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 }) })
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 }) })
6865
7060
  }
6866
7061
  ),
6867
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7062
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6868
7063
  DrawModeOverlay,
6869
7064
  {
6870
7065
  visible: drawing,
@@ -6873,7 +7068,7 @@ function StudioOverlay({
6873
7068
  onCapture: handleDrawCapture
6874
7069
  }
6875
7070
  ),
6876
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7071
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6877
7072
  ConfirmMergeFlow,
6878
7073
  {
6879
7074
  visible: Boolean(confirmMr),
@@ -6886,7 +7081,7 @@ function StudioOverlay({
6886
7081
  onTestFirst: handleTestMr
6887
7082
  }
6888
7083
  ),
6889
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7084
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6890
7085
  AppCommentsSheet,
6891
7086
  {
6892
7087
  appId: commentsAppId,
@@ -6898,8 +7093,190 @@ function StudioOverlay({
6898
7093
  ] });
6899
7094
  }
6900
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
+
6901
7278
  // src/studio/ComergeStudio.tsx
6902
- var import_jsx_runtime58 = require("react/jsx-runtime");
7279
+ var import_jsx_runtime59 = require("react/jsx-runtime");
6903
7280
  function ComergeStudio({
6904
7281
  appId,
6905
7282
  clientKey: clientKey2,
@@ -6910,17 +7287,17 @@ function ComergeStudio({
6910
7287
  studioControlOptions,
6911
7288
  embeddedBaseBundles
6912
7289
  }) {
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(() => {
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(() => {
6918
7295
  setActiveAppId(appId);
6919
7296
  setRuntimeAppId(appId);
6920
7297
  setPendingRuntimeTargetAppId(null);
6921
7298
  }, [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)(
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)(
6924
7301
  ComergeStudioInner,
6925
7302
  {
6926
7303
  userId,
@@ -6961,11 +7338,11 @@ function ComergeStudioInner({
6961
7338
  const { app, loading: appLoading } = useApp(activeAppId);
6962
7339
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6963
7340
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6964
- const sawEditingOnPendingTargetRef = React43.useRef(false);
6965
- React43.useEffect(() => {
7341
+ const sawEditingOnPendingTargetRef = React46.useRef(false);
7342
+ React46.useEffect(() => {
6966
7343
  sawEditingOnPendingTargetRef.current = false;
6967
7344
  }, [pendingRuntimeTargetAppId]);
6968
- React43.useEffect(() => {
7345
+ React46.useEffect(() => {
6969
7346
  if (!pendingRuntimeTargetAppId) return;
6970
7347
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6971
7348
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6983,13 +7360,13 @@ function ComergeStudioInner({
6983
7360
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
6984
7361
  embeddedBaseBundles
6985
7362
  });
6986
- const sawEditingOnActiveAppRef = React43.useRef(false);
6987
- const [showPostEditPreparing, setShowPostEditPreparing] = React43.useState(false);
6988
- React43.useEffect(() => {
7363
+ const sawEditingOnActiveAppRef = React46.useRef(false);
7364
+ const [showPostEditPreparing, setShowPostEditPreparing] = React46.useState(false);
7365
+ React46.useEffect(() => {
6989
7366
  sawEditingOnActiveAppRef.current = false;
6990
7367
  setShowPostEditPreparing(false);
6991
7368
  }, [activeAppId]);
6992
- React43.useEffect(() => {
7369
+ React46.useEffect(() => {
6993
7370
  if (!(app == null ? void 0 : app.id)) return;
6994
7371
  if (app.status === "editing") {
6995
7372
  sawEditingOnActiveAppRef.current = true;
@@ -7001,7 +7378,7 @@ function ComergeStudioInner({
7001
7378
  sawEditingOnActiveAppRef.current = false;
7002
7379
  }
7003
7380
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
7004
- React43.useEffect(() => {
7381
+ React46.useEffect(() => {
7005
7382
  if (!showPostEditPreparing) return;
7006
7383
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
7007
7384
  if (!stillProcessingBaseBundle) {
@@ -7010,15 +7387,27 @@ function ComergeStudioInner({
7010
7387
  }, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
7011
7388
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
7012
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);
7013
7395
  const mergeRequests = useMergeRequests({ appId: activeAppId });
7014
- const hasOpenOutgoingMr = React43.useMemo(() => {
7396
+ const hasOpenOutgoingMr = React46.useMemo(() => {
7015
7397
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
7016
7398
  }, [mergeRequests.lists.outgoing]);
7017
- const incomingReviewMrs = React43.useMemo(() => {
7399
+ const incomingReviewMrs = React46.useMemo(() => {
7018
7400
  if (!userId) return mergeRequests.lists.incoming;
7019
7401
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
7020
7402
  }, [mergeRequests.lists.incoming, userId]);
7021
7403
  const uploader = useAttachmentUpload();
7404
+ const updateLastEditQueueInfo = React46.useCallback(
7405
+ (info) => {
7406
+ lastEditQueueInfoRef.current = info;
7407
+ setLastEditQueueInfo(info);
7408
+ },
7409
+ []
7410
+ );
7022
7411
  const actions = useStudioActions({
7023
7412
  userId,
7024
7413
  app,
@@ -7033,20 +7422,62 @@ function ComergeStudioInner({
7033
7422
  setPendingRuntimeTargetAppId(null);
7034
7423
  }
7035
7424
  },
7036
- 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
+ }
7037
7443
  });
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(() => {
7444
+ const chatSendDisabled = false;
7445
+ const [processingMrId, setProcessingMrId] = React46.useState(null);
7446
+ const [testingMrId, setTestingMrId] = React46.useState(null);
7447
+ const chatShowTypingIndicator = React46.useMemo(() => {
7042
7448
  var _a;
7043
7449
  if (!thread.raw || thread.raw.length === 0) return false;
7044
7450
  const last = thread.raw[thread.raw.length - 1];
7045
7451
  const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
7046
7452
  return payloadType !== "outcome";
7047
7453
  }, [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)(
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)(
7050
7481
  RuntimeRenderer,
7051
7482
  {
7052
7483
  appKey,
@@ -7056,7 +7487,7 @@ function ComergeStudioInner({
7056
7487
  allowInitialPreparing: !embeddedBaseBundles
7057
7488
  }
7058
7489
  ),
7059
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7490
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7060
7491
  StudioOverlay,
7061
7492
  {
7062
7493
  captureTargetRef,
@@ -7108,6 +7539,8 @@ function ComergeStudioInner({
7108
7539
  chatSending: actions.sending,
7109
7540
  chatShowTypingIndicator,
7110
7541
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
7542
+ chatQueueItems,
7543
+ onRemoveQueueItem: (id) => editQueueActions.cancel(id),
7111
7544
  onNavigateHome,
7112
7545
  showBubble,
7113
7546
  studioControlOptions