@volter-ai-dev/supercode-ui 0.1.21 → 0.1.23

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/embed.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { render } from "preact";
3
3
 
4
4
  // src/messenger.jsx
5
- import { useEffect as useEffect6, useId as useId2, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "preact/hooks";
5
+ import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
6
6
 
7
7
  // core.mjs
8
8
  var HARNESS_NAMES = Object.freeze({
@@ -761,7 +761,7 @@ function isSendKey(event) {
761
761
  }
762
762
 
763
763
  // src/composer.jsx
764
- import { useEffect, useRef, useState } from "preact/hooks";
764
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
765
765
 
766
766
  // src/memory.js
767
767
  var MEMORY_LIMIT = 100;
@@ -821,7 +821,8 @@ function UiIcon({ name, size = 16, class: className = "" }) {
821
821
  }
822
822
 
823
823
  // src/context.jsx
824
- import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
824
+ import { useEffect, useRef, useState } from "preact/hooks";
825
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
825
826
  var MAX_CONTEXT_ITEMS = 32;
826
827
  var MAX_IMAGE_ITEMS = 4;
827
828
  var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
@@ -920,12 +921,101 @@ function ImageTray({ items, onRemove }) {
920
921
  onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
921
922
  ] }, item.id ?? `${item.label}:${index}`)) });
922
923
  }
923
- function MessageImages({ items }) {
924
+ function imageFilename(label) {
925
+ const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
926
+ return value || "image";
927
+ }
928
+ function ImageViewer({ items, index, adapter, onChange, onClose }) {
929
+ const dialog = useRef(null);
930
+ const reset = useRef(null);
931
+ const [copyState, setCopyState2] = useState("idle");
932
+ const item = items[index];
933
+ const remote = item?.url?.startsWith("http://") || item?.url?.startsWith("https://");
934
+ useEffect(() => {
935
+ if (!dialog.current?.open) dialog.current?.showModal();
936
+ return () => clearTimeout(reset.current);
937
+ }, []);
938
+ useEffect(() => {
939
+ clearTimeout(reset.current);
940
+ setCopyState2("idle");
941
+ }, [index]);
942
+ if (!item?.url) return null;
943
+ const move = (amount) => onChange((index + amount + items.length) % items.length);
944
+ const copy = async () => {
945
+ try {
946
+ await adapter.copyText(item.url);
947
+ setCopyState2("copied");
948
+ } catch {
949
+ setCopyState2("failed");
950
+ }
951
+ clearTimeout(reset.current);
952
+ reset.current = setTimeout(() => setCopyState2("idle"), 1500);
953
+ };
954
+ const close = () => dialog.current?.close();
955
+ return /* @__PURE__ */ jsxs2(
956
+ "dialog",
957
+ {
958
+ ref: dialog,
959
+ class: "scui-image-viewer",
960
+ "aria-label": `Image preview: ${item.label}`,
961
+ onClose,
962
+ onClick: (event) => {
963
+ if (event.target === event.currentTarget) close();
964
+ },
965
+ onKeyDown: (event) => {
966
+ if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
967
+ event.preventDefault();
968
+ move(event.key === "ArrowLeft" ? -1 : 1);
969
+ },
970
+ children: [
971
+ /* @__PURE__ */ jsxs2("header", { children: [
972
+ /* @__PURE__ */ jsxs2("span", { children: [
973
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
974
+ items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
975
+ index + 1,
976
+ " of ",
977
+ items.length
978
+ ] }) : null
979
+ ] }),
980
+ /* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
981
+ remote && adapter?.copyText ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": copyState === "copied" ? "Image link copied" : copyState === "failed" ? "Could not copy image link" : "Copy image link", title: "Copy image link", "data-status": copyState, onClick: copy, children: /* @__PURE__ */ jsx2(UiIcon, { name: copyState === "copied" ? "check" : "copy", size: 16 }) }) : null,
982
+ remote ? /* @__PURE__ */ jsx2("a", { href: item.url, target: "_blank", rel: "noreferrer", "aria-label": "Open original image", title: "Open original image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 16 }) }) : /* @__PURE__ */ jsx2("a", { href: item.url, download: imageFilename(item.label), "aria-label": "Download image", title: "Download image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "down", size: 16 }) }),
983
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 17 }) })
984
+ ] })
985
+ ] }),
986
+ /* @__PURE__ */ jsxs2("figure", { children: [
987
+ /* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }),
988
+ items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
989
+ /* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) }),
990
+ /* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) })
991
+ ] }) : null
992
+ ] })
993
+ ]
994
+ }
995
+ );
996
+ }
997
+ function MessageImages({ items, adapter }) {
998
+ const [active, setActive] = useState(null);
999
+ const opener = useRef(null);
924
1000
  if (!items?.length) return null;
925
- return /* @__PURE__ */ jsx2("div", { class: "scui-message-images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { children: [
926
- /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
927
- item.label
928
- ] }, item.id ?? `${item.label}:${index}`)) });
1001
+ const viewable = items.filter((item) => item.url);
1002
+ const close = () => {
1003
+ setActive(null);
1004
+ requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
1005
+ };
1006
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1007
+ /* @__PURE__ */ jsx2("div", { class: "scui-message-images", "aria-label": "Message images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `View image ${item.label}`, onClick: (event) => {
1008
+ opener.current = event.currentTarget;
1009
+ setActive(viewable.indexOf(item));
1010
+ }, children: /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
1011
+ /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
1012
+ /* @__PURE__ */ jsxs2("span", { children: [
1013
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1014
+ /* @__PURE__ */ jsx2("small", { children: "Preview unavailable" })
1015
+ ] })
1016
+ ] }, item.id ?? `${item.label}:${index}`)) }),
1017
+ active !== null && viewable[active] ? /* @__PURE__ */ jsx2(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
1018
+ ] });
929
1019
  }
930
1020
 
931
1021
  // src/textarea.js
@@ -967,17 +1057,18 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
967
1057
  }
968
1058
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
969
1059
  const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
970
- const [draft, setDraft] = useState(remembered.draft);
971
- const [context, setContext] = useState(remembered.context ?? []);
972
- const [images, setImages] = useState(remembered.images ?? []);
973
- const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
974
- const [dispatching, setDispatching] = useState(false);
975
- const [picking, setPicking] = useState(false);
976
- const [pickerError, setPickerError] = useState(null);
977
- const textarea = useRef(null);
1060
+ const [draft, setDraft] = useState2(remembered.draft);
1061
+ const [context, setContext] = useState2(remembered.context ?? []);
1062
+ const [images, setImages] = useState2(remembered.images ?? []);
1063
+ const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
1064
+ const [dispatching, setDispatching] = useState2(false);
1065
+ const [picking, setPicking] = useState2(false);
1066
+ const [dragging, setDragging] = useState2(false);
1067
+ const [pickerError, setPickerError] = useState2(null);
1068
+ const textarea = useRef2(null);
978
1069
  useAutosizeTextarea(textarea, draft);
979
1070
  const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
980
- useEffect(() => {
1071
+ useEffect2(() => {
981
1072
  remember(draft, context, images, queue);
982
1073
  }, [draft, context, images, memoryKey, queue]);
983
1074
  const updateQueue = (update) => setQueue((items) => {
@@ -987,7 +1078,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
987
1078
  });
988
1079
  const queueBlocked = state.busy || pendingStatus !== null || dispatching;
989
1080
  const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
990
- useEffect(() => {
1081
+ useEffect2(() => {
991
1082
  if (!queueBlocked && state.canSend && queue.length) {
992
1083
  const [next, ...rest] = queue;
993
1084
  setDispatching(true);
@@ -997,13 +1088,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
997
1088
  adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
998
1089
  }
999
1090
  }, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
1000
- useEffect(() => {
1091
+ useEffect2(() => {
1001
1092
  if (pendingStatus !== null || state.busy) setDispatching(false);
1002
1093
  }, [pendingStatus, state.busy]);
1003
- useEffect(() => {
1094
+ useEffect2(() => {
1004
1095
  textarea.current?.focus({ preventScroll: true });
1005
1096
  }, [memoryKey]);
1006
- useEffect(() => {
1097
+ useEffect2(() => {
1007
1098
  if (!restoreDraft) return;
1008
1099
  setDraft(restoreDraft.text);
1009
1100
  const restoredContext = normalizeContext(restoreDraft.context);
@@ -1014,7 +1105,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1014
1105
  textarea.current?.focus({ preventScroll: true });
1015
1106
  onDraftRestored?.(restoreDraft.id);
1016
1107
  }, [onDraftRestored, restoreDraft?.id]);
1017
- useEffect(() => {
1108
+ useEffect2(() => {
1018
1109
  const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
1019
1110
  return () => clearTimeout(timer);
1020
1111
  }, [adapter, draft]);
@@ -1038,13 +1129,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1038
1129
  });
1039
1130
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
1040
1131
  };
1041
- const pasteImages = (event) => {
1042
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
1043
- if (!files.length) return;
1044
- event.preventDefault();
1132
+ const addImageFiles = (value, source) => {
1133
+ const allFiles = Array.from(value ?? []);
1134
+ if (!allFiles.length) return false;
1135
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
1136
+ if (files.length !== allFiles.length) {
1137
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
1138
+ return true;
1139
+ }
1045
1140
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
1046
1141
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1047
- return;
1142
+ return true;
1048
1143
  }
1049
1144
  setPicking(true);
1050
1145
  setPickerError(null);
@@ -1052,11 +1147,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1052
1147
  const next = mergeImages(current, picked);
1053
1148
  remember(draft, context, next, queue);
1054
1149
  return next;
1055
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
1150
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
1151
+ return true;
1152
+ };
1153
+ const pasteImages = (event) => {
1154
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
1155
+ };
1156
+ const dropImages = (event) => {
1157
+ setDragging(false);
1158
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
1056
1159
  };
1057
1160
  const send = () => {
1058
1161
  const text = draft.trim();
1059
- if (!text) return;
1162
+ if (!text && !images.length) return;
1060
1163
  const message = { text, context, images };
1061
1164
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
1062
1165
  else if (state.canSend) {
@@ -1077,7 +1180,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1077
1180
  ] }),
1078
1181
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
1079
1182
  /* @__PURE__ */ jsxs3("span", { children: [
1080
- item.text,
1183
+ item.text || "Image attachment",
1081
1184
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
1082
1185
  item.context.length + item.images.length,
1083
1186
  " attached"
@@ -1097,7 +1200,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1097
1200
  return next;
1098
1201
  }) }),
1099
1202
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
1100
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
1203
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
1204
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
1205
+ event.preventDefault();
1206
+ setDragging(true);
1207
+ }
1208
+ }, onDragOver: (event) => {
1209
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1210
+ }, onDragLeave: (event) => {
1211
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1212
+ }, onDrop: dropImages, children: [
1101
1213
  adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1102
1214
  /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
1103
1215
  const value = event.currentTarget.value;
@@ -1111,19 +1223,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1111
1223
  } }),
1112
1224
  /* @__PURE__ */ jsxs3("span", { children: [
1113
1225
  state.busy ? /* @__PURE__ */ jsx3("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
1114
- /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
1226
+ /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
1115
1227
  ] })
1116
1228
  ] })
1117
1229
  ] });
1118
1230
  }
1119
1231
 
1120
1232
  // src/conversation.jsx
1121
- import { Fragment as Fragment2 } from "preact";
1122
- import { useEffect as useEffect3, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState2 } from "preact/hooks";
1233
+ import { Fragment as Fragment3 } from "preact";
1234
+ import { useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState3 } from "preact/hooks";
1123
1235
 
1124
1236
  // src/markdown.jsx
1125
1237
  import MarkdownIt from "markdown-it";
1126
- import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "preact/hooks";
1238
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "preact/hooks";
1127
1239
  import { jsx as jsx4 } from "preact/jsx-runtime";
1128
1240
  var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
1129
1241
  var LANGUAGE_LABELS = {
@@ -1185,8 +1297,8 @@ function setCopyState(button, status) {
1185
1297
  }
1186
1298
  function Markdown({ value, copyText }) {
1187
1299
  const html = useMemo(() => markdown.render(value), [value]);
1188
- const resets = useRef2(/* @__PURE__ */ new Map());
1189
- useEffect2(() => () => {
1300
+ const resets = useRef3(/* @__PURE__ */ new Map());
1301
+ useEffect3(() => () => {
1190
1302
  for (const timer of resets.current.values()) clearTimeout(timer);
1191
1303
  resets.current.clear();
1192
1304
  }, []);
@@ -1215,7 +1327,7 @@ function Markdown({ value, copyText }) {
1215
1327
  }
1216
1328
 
1217
1329
  // src/conversation.jsx
1218
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
1330
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
1219
1331
  function LoadingStatus({ state, compact = false }) {
1220
1332
  const copy = {
1221
1333
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -1264,9 +1376,9 @@ function ContextDisclosure({ context }) {
1264
1376
  ] });
1265
1377
  }
1266
1378
  function MessageMeta({ entry, adapter }) {
1267
- const [copyState, setCopyState2] = useState2("idle");
1268
- const reset = useRef3(null);
1269
- useEffect3(() => () => clearTimeout(reset.current), []);
1379
+ const [copyState, setCopyState2] = useState3("idle");
1380
+ const reset = useRef4(null);
1381
+ useEffect4(() => () => clearTimeout(reset.current), []);
1270
1382
  const date = entry.ts === null ? null : new Date(entry.ts);
1271
1383
  const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
1272
1384
  if (!validDate && (!adapter?.copyText || !entry.text)) return null;
@@ -1287,33 +1399,33 @@ function MessageMeta({ entry, adapter }) {
1287
1399
  ] });
1288
1400
  }
1289
1401
  var TOOL_ICONS = {
1290
- read: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1402
+ read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1291
1403
  /* @__PURE__ */ jsx5("path", { d: "M5 2.75h7.25L15 5.5v7.75A1.75 1.75 0 0 1 13.25 15h-8.5A1.75 1.75 0 0 1 3 13.25v-8.5A2 2 0 0 1 5 2.75Z" }),
1292
1404
  /* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
1293
1405
  ] }),
1294
- search: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1406
+ search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1295
1407
  /* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
1296
1408
  /* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
1297
1409
  ] }),
1298
- edit: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1410
+ edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1299
1411
  /* @__PURE__ */ jsx5("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
1300
1412
  /* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
1301
1413
  ] }),
1302
- command: () => /* @__PURE__ */ jsx5(Fragment3, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
1303
- test: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1414
+ command: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
1415
+ test: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1304
1416
  /* @__PURE__ */ jsx5("path", { d: "M6 2.5v3L3 12a2 2 0 0 0 1.8 3h8.4a2 2 0 0 0 1.8-3l-3-6.5v-3M5 9h8" }),
1305
1417
  /* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
1306
1418
  ] }),
1307
- web: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1419
+ web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1308
1420
  /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
1309
1421
  /* @__PURE__ */ jsx5("path", { d: "M2.75 9h12.5M9 2.5c2 1.8 3 4 3 6.5s-1 4.7-3 6.5c-2-1.8-3-4-3-6.5s1-4.7 3-6.5Z" })
1310
1422
  ] }),
1311
- agent: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1423
+ agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1312
1424
  /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
1313
1425
  /* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
1314
1426
  ] }),
1315
- plan: () => /* @__PURE__ */ jsx5(Fragment3, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
1316
- other: () => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1427
+ plan: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
1428
+ other: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1317
1429
  /* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
1318
1430
  /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
1319
1431
  ] })
@@ -1379,9 +1491,9 @@ function ToolMetrics({ presentation }) {
1379
1491
  }
1380
1492
  function PendingElapsed({ now }) {
1381
1493
  const clock = now ?? Date.now;
1382
- const started = useRef3(clock());
1383
- const [elapsed, setElapsed] = useState2(0);
1384
- useEffect3(() => {
1494
+ const started = useRef4(clock());
1495
+ const [elapsed, setElapsed] = useState3(0);
1496
+ useEffect4(() => {
1385
1497
  const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
1386
1498
  return () => clearInterval(timer);
1387
1499
  }, [clock]);
@@ -1422,7 +1534,7 @@ function SearchPreview({ presentation }) {
1422
1534
  ] }) : null,
1423
1535
  lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
1424
1536
  const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
1425
- return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1537
+ return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
1426
1538
  /* @__PURE__ */ jsx5("code", { children: match[1] }),
1427
1539
  /* @__PURE__ */ jsxs4("small", { children: [
1428
1540
  match[2],
@@ -1455,9 +1567,9 @@ function ToolPreview({ presentation, entry }) {
1455
1567
  return presentation.preview ? /* @__PURE__ */ jsx5("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
1456
1568
  }
1457
1569
  function ToolActions({ presentation, adapter }) {
1458
- const [copied, setCopied] = useState2(false);
1459
- const reset = useRef3(null);
1460
- useEffect3(() => () => clearTimeout(reset.current), []);
1570
+ const [copied, setCopied] = useState3(false);
1571
+ const reset = useRef4(null);
1572
+ useEffect4(() => () => clearTimeout(reset.current), []);
1461
1573
  if (!adapter?.copyText) return null;
1462
1574
  const action = presentation.command ? ["Copy command", presentation.command] : presentation.path ? ["Copy path", presentation.path] : presentation.url ? ["Copy URL", presentation.url] : presentation.query ? ["Copy query", presentation.query] : null;
1463
1575
  const copy = async () => {
@@ -1504,7 +1616,7 @@ function TranscriptEntry({ entry, state, adapter }) {
1504
1616
  }
1505
1617
  if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
1506
1618
  return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
1507
- /* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
1619
+ /* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
1508
1620
  /* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
1509
1621
  /* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
1510
1622
  entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
@@ -1513,14 +1625,14 @@ function TranscriptEntry({ entry, state, adapter }) {
1513
1625
  }
1514
1626
  function ToolRow({ entry, workspace, open = false, adapter }) {
1515
1627
  const presentation = entry.presentation ?? createToolPresentation(entry);
1516
- const [expanded, setExpanded] = useState2(open || entry.status === "pending");
1517
- useEffect3(() => {
1628
+ const [expanded, setExpanded] = useState3(open || entry.status === "pending");
1629
+ useEffect4(() => {
1518
1630
  if (entry.status === "pending") setExpanded(true);
1519
1631
  }, [entry.status]);
1520
1632
  const target = compactToolTarget(presentation.target, workspace);
1521
1633
  const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
1522
1634
  const category = presentation.category ?? toolCategory(entry);
1523
- const summary = /* @__PURE__ */ jsxs4(Fragment3, { children: [
1635
+ const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
1524
1636
  /* @__PURE__ */ jsx5(ToolIcon, { category }),
1525
1637
  /* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
1526
1638
  target ? /* @__PURE__ */ jsx5("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
@@ -1548,9 +1660,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
1548
1660
  function ActivityGroup({ entries, state, adapter }) {
1549
1661
  if (entries.length === 1) return /* @__PURE__ */ jsx5("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx5(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
1550
1662
  const active = entries.some((entry) => entry.status === "pending");
1551
- const [open, setOpen] = useState2(active);
1663
+ const [open, setOpen] = useState3(active);
1552
1664
  const id = useId();
1553
- useEffect3(() => {
1665
+ useEffect4(() => {
1554
1666
  if (active) setOpen(true);
1555
1667
  }, [active]);
1556
1668
  return /* @__PURE__ */ jsxs4("section", { class: "scui-activity", children: [
@@ -1626,9 +1738,9 @@ function SessionDetails({ semantics }) {
1626
1738
  }
1627
1739
  var conversationMemory = /* @__PURE__ */ new Map();
1628
1740
  function ConversationAnnouncements({ state }) {
1629
- const previousBusy = useRef3(state.busy);
1630
- const [announcement, setAnnouncement] = useState2("");
1631
- useEffect3(() => {
1741
+ const previousBusy = useRef4(state.busy);
1742
+ const [announcement, setAnnouncement] = useState3("");
1743
+ useEffect4(() => {
1632
1744
  if (previousBusy.current && !state.busy && !state.error) {
1633
1745
  setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
1634
1746
  }
@@ -1637,13 +1749,13 @@ function ConversationAnnouncements({ state }) {
1637
1749
  return /* @__PURE__ */ jsx5("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
1638
1750
  }
1639
1751
  function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
1640
- const scroller = useRef3(null);
1752
+ const scroller = useRef4(null);
1641
1753
  const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
1642
- const [atBottom, setAtBottom] = useState2(remembered.atBottom);
1643
- const earlierAnchor = useRef3(null);
1644
- const restored = useRef3(false);
1754
+ const [atBottom, setAtBottom] = useState3(remembered.atBottom);
1755
+ const earlierAnchor = useRef4(null);
1756
+ const restored = useRef4(false);
1645
1757
  const blocks = groupConversation(state.transcript);
1646
- const unreadBoundary = useRef3(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
1758
+ const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
1647
1759
  const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
1648
1760
  const entries = block.kind === "activity" ? block.entries : [block.entry];
1649
1761
  return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
@@ -1702,12 +1814,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1702
1814
  }, children: "Load earlier messages" }) : null,
1703
1815
  !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
1704
1816
  !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx5(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx5("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
1705
- blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment2, { children: [
1817
+ blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
1706
1818
  index === unreadBlock ? /* @__PURE__ */ jsx5("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
1707
1819
  block.kind === "activity" ? /* @__PURE__ */ jsx5(Group, { value: block.entries, entries: block.entries, state, adapter }) : /* @__PURE__ */ jsx5(Entry, { value: block.entry, entry: block.entry, state, adapter })
1708
1820
  ] }, block.id)),
1709
1821
  pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
1710
- /* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
1822
+ /* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
1711
1823
  /* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
1712
1824
  /* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
1713
1825
  /* @__PURE__ */ jsxs4("footer", { children: [
@@ -1738,7 +1850,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1738
1850
  }
1739
1851
 
1740
1852
  // src/logo.jsx
1741
- import { useEffect as useEffect4 } from "preact/hooks";
1853
+ import { useEffect as useEffect5 } from "preact/hooks";
1742
1854
  import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
1743
1855
  var LOGOS = {
1744
1856
  "claude-code": {
@@ -1786,7 +1898,7 @@ var LOGOS = {
1786
1898
  };
1787
1899
  function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
1788
1900
  const logo = LOGOS[id];
1789
- useEffect4(() => {
1901
+ useEffect5(() => {
1790
1902
  if (!logo) onMissingLogo?.(id);
1791
1903
  }, [id, logo, onMissingLogo]);
1792
1904
  if (!logo) return null;
@@ -1797,7 +1909,7 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
1797
1909
  }
1798
1910
 
1799
1911
  // src/sessions.jsx
1800
- import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useRef as useRef4, useState as useState3 } from "preact/hooks";
1912
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
1801
1913
  import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
1802
1914
  var sessionListMemory = /* @__PURE__ */ new Map();
1803
1915
  function SessionRow({ row, state, onOpen }) {
@@ -1819,21 +1931,21 @@ function SessionRow({ row, state, onOpen }) {
1819
1931
  }
1820
1932
  function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
1821
1933
  const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
1822
- const [query, setQuery] = useState3(remembered.query);
1823
- const [loadingMore, setLoadingMore] = useState3(false);
1824
- const root = useRef4(null);
1825
- const rowScroller = useRef4(null);
1934
+ const [query, setQuery] = useState4(remembered.query);
1935
+ const [loadingMore, setLoadingMore] = useState4(false);
1936
+ const root = useRef5(null);
1937
+ const rowScroller = useRef5(null);
1826
1938
  const rows = filterSessions(state.sessions, query);
1827
1939
  const Row = components.SessionRow ?? SessionRow;
1828
1940
  useLayoutEffect3(() => {
1829
1941
  if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
1830
1942
  }, [memoryKey]);
1831
- useEffect5(() => {
1943
+ useEffect6(() => {
1832
1944
  if (!focusKey || !root.current) return;
1833
1945
  const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
1834
1946
  (target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
1835
1947
  }, [focusKey, rows.length]);
1836
- useEffect5(() => {
1948
+ useEffect6(() => {
1837
1949
  if (loadingMore) setLoadingMore(false);
1838
1950
  }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
1839
1951
  const loadMore = () => {
@@ -1919,10 +2031,10 @@ function Receipt({ state, adapter }) {
1919
2031
  return null;
1920
2032
  }
1921
2033
  function ConversationActions({ state, adapter, actionPending }) {
1922
- const [open, setOpen] = useState4(false);
1923
- const root = useRef5(null);
1924
- const panel = useRef5(null);
1925
- const trigger = useRef5(null);
2034
+ const [open, setOpen] = useState5(false);
2035
+ const root = useRef6(null);
2036
+ const panel = useRef6(null);
2037
+ const trigger = useRef6(null);
1926
2038
  const menuId = useId2();
1927
2039
  const targets = state.harnesses.filter((item) => item.startable);
1928
2040
  const groups = [
@@ -1943,7 +2055,7 @@ function ConversationActions({ state, adapter, actionPending }) {
1943
2055
  items: state.canBranch ? targets.map((target) => ({ key: `branch:${target.id}`, label: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}`, intent: { action: "branch", targetHarness: target.id } })) : []
1944
2056
  }
1945
2057
  ].filter((group) => group.items.length);
1946
- useEffect6(() => {
2058
+ useEffect7(() => {
1947
2059
  if (!open) return;
1948
2060
  panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
1949
2061
  const dismiss = (event) => {
@@ -1993,12 +2105,12 @@ function ConversationActions({ state, adapter, actionPending }) {
1993
2105
  ] });
1994
2106
  }
1995
2107
  function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
1996
- const back = useRef5(null);
2108
+ const back = useRef6(null);
1997
2109
  const harness = state.attached?.harness ?? state.harness;
1998
2110
  const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
1999
2111
  const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
2000
2112
  const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
2001
- useEffect6(() => {
2113
+ useEffect7(() => {
2002
2114
  if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
2003
2115
  }, []);
2004
2116
  return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
@@ -2020,24 +2132,24 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2020
2132
  function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
2021
2133
  const Header = slots.header;
2022
2134
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
2023
- const [pending, setPendingState] = useState4(() => pendingMessageMemory.get(memoryKey) ?? null);
2024
- const [pendingAction, setPendingAction] = useState4(null);
2025
- const [restoreDraft, setRestoreDraft] = useState4(null);
2026
- const restoreSequence = useRef5(0);
2027
- const actionSequence = useRef5(0);
2028
- const acknowledged = useRef5(/* @__PURE__ */ new Set());
2135
+ const [pending, setPendingState] = useState5(() => pendingMessageMemory.get(memoryKey) ?? null);
2136
+ const [pendingAction, setPendingAction] = useState5(null);
2137
+ const [restoreDraft, setRestoreDraft] = useState5(null);
2138
+ const restoreSequence = useRef6(0);
2139
+ const actionSequence = useRef6(0);
2140
+ const acknowledged = useRef6(/* @__PURE__ */ new Set());
2029
2141
  const setPending = (update) => setPendingState((current) => {
2030
2142
  const next = typeof update === "function" ? update(current) : update;
2031
2143
  if (next) boundedSet(pendingMessageMemory, memoryKey, next);
2032
2144
  else pendingMessageMemory.delete(memoryKey);
2033
2145
  return next;
2034
2146
  });
2035
- useEffect6(() => {
2147
+ useEffect7(() => {
2036
2148
  setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
2037
2149
  setPendingAction(null);
2038
2150
  setRestoreDraft(null);
2039
2151
  }, [memoryKey]);
2040
- useEffect6(() => {
2152
+ useEffect7(() => {
2041
2153
  if (!pending) return;
2042
2154
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
2043
2155
  setPending(null);
@@ -2066,7 +2178,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2066
2178
  setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
2067
2179
  setPending({ ...pending, status: "editing" });
2068
2180
  };
2069
- useEffect6(() => {
2181
+ useEffect7(() => {
2070
2182
  const key = state.attached?.key;
2071
2183
  if (!key || !state.attention.some((item) => item.key === key)) {
2072
2184
  if (key) acknowledged.current.delete(key);
@@ -2101,7 +2213,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2101
2213
  return result;
2102
2214
  }
2103
2215
  }), [adapter, state.error]);
2104
- useEffect6(() => {
2216
+ useEffect7(() => {
2105
2217
  if (!pendingAction) return;
2106
2218
  if (state.operation && !pendingAction.seenOperation) {
2107
2219
  setPendingAction({ ...pendingAction, seenOperation: true });
@@ -2134,23 +2246,24 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2134
2246
  const startable = state.harnesses.filter((item) => item.startable);
2135
2247
  const startableKey = startable.map((item) => item.id).join("\0");
2136
2248
  const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
2137
- const [harness, setHarness] = useState4(remembered.harness);
2138
- const [draft, setDraft] = useState4(remembered.draft);
2139
- const [context, setContext] = useState4(remembered.context);
2140
- const [images, setImages] = useState4(remembered.images ?? []);
2141
- const [starting, setStarting] = useState4(null);
2142
- const [picking, setPicking] = useState4(false);
2143
- const [pickerError, setPickerError] = useState4(null);
2144
- const startSequence = useRef5(0);
2145
- const textarea = useRef5(null);
2249
+ const [harness, setHarness] = useState5(remembered.harness);
2250
+ const [draft, setDraft] = useState5(remembered.draft);
2251
+ const [context, setContext] = useState5(remembered.context);
2252
+ const [images, setImages] = useState5(remembered.images ?? []);
2253
+ const [starting, setStarting] = useState5(null);
2254
+ const [picking, setPicking] = useState5(false);
2255
+ const [dragging, setDragging] = useState5(false);
2256
+ const [pickerError, setPickerError] = useState5(null);
2257
+ const startSequence = useRef6(0);
2258
+ const textarea = useRef6(null);
2146
2259
  useAutosizeTextarea(textarea, draft);
2147
- useEffect6(() => {
2260
+ useEffect7(() => {
2148
2261
  if (startable.some((item) => item.id === harness)) return;
2149
2262
  const next = startable[0]?.id ?? "";
2150
2263
  setHarness(next);
2151
2264
  boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
2152
2265
  }, [harness, startableKey]);
2153
- useEffect6(() => {
2266
+ useEffect7(() => {
2154
2267
  if (!starting) return;
2155
2268
  const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
2156
2269
  const beganWorking = !starting.busy && state.busy;
@@ -2158,10 +2271,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2158
2271
  newChatMemory.delete(memoryKey);
2159
2272
  onStarted();
2160
2273
  }, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
2161
- useEffect6(() => {
2274
+ useEffect7(() => {
2162
2275
  if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
2163
2276
  }, [starting, state.busy, state.error, state.operation]);
2164
- useEffect6(() => {
2277
+ useEffect7(() => {
2165
2278
  textarea.current?.focus({ preventScroll: true });
2166
2279
  }, []);
2167
2280
  const pickContext = () => {
@@ -2184,13 +2297,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2184
2297
  });
2185
2298
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
2186
2299
  };
2187
- const pasteImages = (event) => {
2188
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
2189
- if (!files.length) return;
2190
- event.preventDefault();
2300
+ const addImageFiles = (value, source) => {
2301
+ const allFiles = Array.from(value ?? []);
2302
+ if (!allFiles.length) return false;
2303
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
2304
+ if (files.length !== allFiles.length) {
2305
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
2306
+ return true;
2307
+ }
2191
2308
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
2192
2309
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2193
- return;
2310
+ return true;
2194
2311
  }
2195
2312
  setPicking(true);
2196
2313
  setPickerError(null);
@@ -2198,11 +2315,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2198
2315
  const next = mergeImages(current, picked);
2199
2316
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2200
2317
  return next;
2201
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
2318
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
2319
+ return true;
2320
+ };
2321
+ const pasteImages = (event) => {
2322
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
2323
+ };
2324
+ const dropImages = (event) => {
2325
+ setDragging(false);
2326
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
2202
2327
  };
2203
2328
  const send = () => {
2204
2329
  const text = draft.trim();
2205
- if (!text || !harness || starting) return;
2330
+ if (!text && !images.length || !harness || starting) return;
2206
2331
  const id = startSequence.current + 1;
2207
2332
  startSequence.current = id;
2208
2333
  setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
@@ -2253,7 +2378,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2253
2378
  return next;
2254
2379
  }) }),
2255
2380
  pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2256
- /* @__PURE__ */ jsxs7("div", { class: "scui-envelope", children: [
2381
+ /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2382
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2383
+ event.preventDefault();
2384
+ setDragging(true);
2385
+ }
2386
+ }, onDragOver: (event) => {
2387
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
2388
+ }, onDragLeave: (event) => {
2389
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2390
+ }, onDrop: dropImages, children: [
2257
2391
  adapter.pickContext ? /* @__PURE__ */ jsx8("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "attach", size: 17 }) }) : null,
2258
2392
  /* @__PURE__ */ jsx8("textarea", { ref: textarea, rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || Boolean(starting), onPaste: pasteImages, onInput: (event) => {
2259
2393
  const value = event.currentTarget.value;
@@ -2265,7 +2399,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2265
2399
  send();
2266
2400
  }
2267
2401
  } }),
2268
- /* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "send", size: 17 }) }) })
2402
+ /* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "send", size: 17 }) }) })
2269
2403
  ] })
2270
2404
  ] })
2271
2405
  ] });
@@ -2274,14 +2408,14 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2274
2408
  const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
2275
2409
  const copy = { ...DEFAULT_LABELS, ...labels };
2276
2410
  const memoryKey = state.workspace || "@default";
2277
- const [view, setViewState] = useState4(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2278
- const [opening, setOpening] = useState4(null);
2279
- const [listFocus, setListFocus] = useState4(null);
2411
+ const [view, setViewState] = useState5(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2412
+ const [opening, setOpening] = useState5(null);
2413
+ const [listFocus, setListFocus] = useState5(null);
2280
2414
  const setView = (next) => {
2281
2415
  boundedSet(messengerViewMemory, memoryKey, next);
2282
2416
  setViewState(next);
2283
2417
  };
2284
- useEffect6(() => {
2418
+ useEffect7(() => {
2285
2419
  if (!opening) return;
2286
2420
  if (state.attached?.key === opening.key) {
2287
2421
  setOpening(null);