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

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/README.md CHANGED
@@ -126,8 +126,9 @@ The browser cannot supply locators, credentials, policy, environment variables,
126
126
  materialization paths. Session keys and target harnesses must be revalidated by the host.
127
127
  `pickContext` is optional and host-owned: the default composer shows one attachment control when
128
128
  this callback exists and accepts either typed text context (`detail`) or native image inputs
129
- (`url`). Images can also be pasted directly. The composer bounds images to four supported browser
130
- formats at 5 MB each, previews them locally, and keeps both attachment kinds associated with queued
129
+ (`url`). Images can also be pasted or dropped directly, and an image-only turn is sent without
130
+ inventing fallback prompt text. The composer bounds images to four supported browser formats at
131
+ 5 MB each, previews them locally, and keeps both attachment kinds associated with queued
131
132
  messages, retry, edit, and new-chat recovery. The host still chooses how explicit file selection
132
133
  is exposed and must revalidate every returned item.
133
134
 
package/components.mjs CHANGED
@@ -967,6 +967,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
967
967
  const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
968
968
  const [dispatching, setDispatching] = useState(false);
969
969
  const [picking, setPicking] = useState(false);
970
+ const [dragging, setDragging] = useState(false);
970
971
  const [pickerError, setPickerError] = useState(null);
971
972
  const textarea = useRef(null);
972
973
  useAutosizeTextarea(textarea, draft);
@@ -1032,13 +1033,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1032
1033
  });
1033
1034
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
1034
1035
  };
1035
- const pasteImages = (event) => {
1036
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
1037
- if (!files.length) return;
1038
- event.preventDefault();
1036
+ const addImageFiles = (value, source) => {
1037
+ const allFiles = Array.from(value ?? []);
1038
+ if (!allFiles.length) return false;
1039
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
1040
+ if (files.length !== allFiles.length) {
1041
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
1042
+ return true;
1043
+ }
1039
1044
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
1040
1045
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1041
- return;
1046
+ return true;
1042
1047
  }
1043
1048
  setPicking(true);
1044
1049
  setPickerError(null);
@@ -1046,11 +1051,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1046
1051
  const next = mergeImages(current, picked);
1047
1052
  remember(draft, context, next, queue);
1048
1053
  return next;
1049
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
1054
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
1055
+ return true;
1056
+ };
1057
+ const pasteImages = (event) => {
1058
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
1059
+ };
1060
+ const dropImages = (event) => {
1061
+ setDragging(false);
1062
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
1050
1063
  };
1051
1064
  const send = () => {
1052
1065
  const text = draft.trim();
1053
- if (!text) return;
1066
+ if (!text && !images.length) return;
1054
1067
  const message = { text, context, images };
1055
1068
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
1056
1069
  else if (state.canSend) {
@@ -1071,7 +1084,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1071
1084
  ] }),
1072
1085
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
1073
1086
  /* @__PURE__ */ jsxs3("span", { children: [
1074
- item.text,
1087
+ item.text || "Image attachment",
1075
1088
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
1076
1089
  item.context.length + item.images.length,
1077
1090
  " attached"
@@ -1091,7 +1104,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1091
1104
  return next;
1092
1105
  }) }),
1093
1106
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
1094
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
1107
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
1108
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
1109
+ event.preventDefault();
1110
+ setDragging(true);
1111
+ }
1112
+ }, onDragOver: (event) => {
1113
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1114
+ }, onDragLeave: (event) => {
1115
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1116
+ }, onDrop: dropImages, children: [
1095
1117
  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,
1096
1118
  /* @__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) => {
1097
1119
  const value = event.currentTarget.value;
@@ -1105,7 +1127,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1105
1127
  } }),
1106
1128
  /* @__PURE__ */ jsxs3("span", { children: [
1107
1129
  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,
1108
- /* @__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 }) })
1130
+ /* @__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 }) })
1109
1131
  ] })
1110
1132
  ] })
1111
1133
  ] });
@@ -2140,6 +2162,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2140
2162
  const [images, setImages] = useState4(remembered.images ?? []);
2141
2163
  const [starting, setStarting] = useState4(null);
2142
2164
  const [picking, setPicking] = useState4(false);
2165
+ const [dragging, setDragging] = useState4(false);
2143
2166
  const [pickerError, setPickerError] = useState4(null);
2144
2167
  const startSequence = useRef5(0);
2145
2168
  const textarea = useRef5(null);
@@ -2184,13 +2207,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2184
2207
  });
2185
2208
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
2186
2209
  };
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();
2210
+ const addImageFiles = (value, source) => {
2211
+ const allFiles = Array.from(value ?? []);
2212
+ if (!allFiles.length) return false;
2213
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
2214
+ if (files.length !== allFiles.length) {
2215
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
2216
+ return true;
2217
+ }
2191
2218
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
2192
2219
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2193
- return;
2220
+ return true;
2194
2221
  }
2195
2222
  setPicking(true);
2196
2223
  setPickerError(null);
@@ -2198,11 +2225,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2198
2225
  const next = mergeImages(current, picked);
2199
2226
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2200
2227
  return next;
2201
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
2228
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
2229
+ return true;
2230
+ };
2231
+ const pasteImages = (event) => {
2232
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
2233
+ };
2234
+ const dropImages = (event) => {
2235
+ setDragging(false);
2236
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
2202
2237
  };
2203
2238
  const send = () => {
2204
2239
  const text = draft.trim();
2205
- if (!text || !harness || starting) return;
2240
+ if (!text && !images.length || !harness || starting) return;
2206
2241
  const id = startSequence.current + 1;
2207
2242
  startSequence.current = id;
2208
2243
  setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
@@ -2253,7 +2288,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2253
2288
  return next;
2254
2289
  }) }),
2255
2290
  pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2256
- /* @__PURE__ */ jsxs7("div", { class: "scui-envelope", children: [
2291
+ /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2292
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2293
+ event.preventDefault();
2294
+ setDragging(true);
2295
+ }
2296
+ }, onDragOver: (event) => {
2297
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
2298
+ }, onDragLeave: (event) => {
2299
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2300
+ }, onDrop: dropImages, children: [
2257
2301
  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
2302
  /* @__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
2303
  const value = event.currentTarget.value;
@@ -2265,7 +2309,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2265
2309
  send();
2266
2310
  }
2267
2311
  } }),
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 }) }) })
2312
+ /* @__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
2313
  ] })
2270
2314
  ] })
2271
2315
  ] });
package/composer.mjs CHANGED
@@ -274,6 +274,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
274
274
  const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
275
275
  const [dispatching, setDispatching] = useState(false);
276
276
  const [picking, setPicking] = useState(false);
277
+ const [dragging, setDragging] = useState(false);
277
278
  const [pickerError, setPickerError] = useState(null);
278
279
  const textarea = useRef(null);
279
280
  useAutosizeTextarea(textarea, draft);
@@ -339,13 +340,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
339
340
  });
340
341
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
341
342
  };
342
- const pasteImages = (event) => {
343
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
344
- if (!files.length) return;
345
- event.preventDefault();
343
+ const addImageFiles = (value, source) => {
344
+ const allFiles = Array.from(value ?? []);
345
+ if (!allFiles.length) return false;
346
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
347
+ if (files.length !== allFiles.length) {
348
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
349
+ return true;
350
+ }
346
351
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
347
352
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
348
- return;
353
+ return true;
349
354
  }
350
355
  setPicking(true);
351
356
  setPickerError(null);
@@ -353,11 +358,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
353
358
  const next = mergeImages(current, picked);
354
359
  remember(draft, context, next, queue);
355
360
  return next;
356
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
361
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
362
+ return true;
363
+ };
364
+ const pasteImages = (event) => {
365
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
366
+ };
367
+ const dropImages = (event) => {
368
+ setDragging(false);
369
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
357
370
  };
358
371
  const send = () => {
359
372
  const text = draft.trim();
360
- if (!text) return;
373
+ if (!text && !images.length) return;
361
374
  const message = { text, context, images };
362
375
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
363
376
  else if (state.canSend) {
@@ -378,7 +391,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
378
391
  ] }),
379
392
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
380
393
  /* @__PURE__ */ jsxs3("span", { children: [
381
- item.text,
394
+ item.text || "Image attachment",
382
395
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
383
396
  item.context.length + item.images.length,
384
397
  " attached"
@@ -398,7 +411,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
398
411
  return next;
399
412
  }) }),
400
413
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
401
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
414
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
415
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
416
+ event.preventDefault();
417
+ setDragging(true);
418
+ }
419
+ }, onDragOver: (event) => {
420
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
421
+ }, onDragLeave: (event) => {
422
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
423
+ }, onDrop: dropImages, children: [
402
424
  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,
403
425
  /* @__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) => {
404
426
  const value = event.currentTarget.value;
@@ -412,7 +434,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
412
434
  } }),
413
435
  /* @__PURE__ */ jsxs3("span", { children: [
414
436
  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,
415
- /* @__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 }) })
437
+ /* @__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 }) })
416
438
  ] })
417
439
  ] })
418
440
  ] });
package/embed.mjs CHANGED
@@ -973,6 +973,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
973
973
  const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
974
974
  const [dispatching, setDispatching] = useState(false);
975
975
  const [picking, setPicking] = useState(false);
976
+ const [dragging, setDragging] = useState(false);
976
977
  const [pickerError, setPickerError] = useState(null);
977
978
  const textarea = useRef(null);
978
979
  useAutosizeTextarea(textarea, draft);
@@ -1038,13 +1039,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1038
1039
  });
1039
1040
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
1040
1041
  };
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();
1042
+ const addImageFiles = (value, source) => {
1043
+ const allFiles = Array.from(value ?? []);
1044
+ if (!allFiles.length) return false;
1045
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
1046
+ if (files.length !== allFiles.length) {
1047
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
1048
+ return true;
1049
+ }
1045
1050
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
1046
1051
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1047
- return;
1052
+ return true;
1048
1053
  }
1049
1054
  setPicking(true);
1050
1055
  setPickerError(null);
@@ -1052,11 +1057,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1052
1057
  const next = mergeImages(current, picked);
1053
1058
  remember(draft, context, next, queue);
1054
1059
  return next;
1055
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
1060
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
1061
+ return true;
1062
+ };
1063
+ const pasteImages = (event) => {
1064
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
1065
+ };
1066
+ const dropImages = (event) => {
1067
+ setDragging(false);
1068
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
1056
1069
  };
1057
1070
  const send = () => {
1058
1071
  const text = draft.trim();
1059
- if (!text) return;
1072
+ if (!text && !images.length) return;
1060
1073
  const message = { text, context, images };
1061
1074
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
1062
1075
  else if (state.canSend) {
@@ -1077,7 +1090,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1077
1090
  ] }),
1078
1091
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
1079
1092
  /* @__PURE__ */ jsxs3("span", { children: [
1080
- item.text,
1093
+ item.text || "Image attachment",
1081
1094
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
1082
1095
  item.context.length + item.images.length,
1083
1096
  " attached"
@@ -1097,7 +1110,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1097
1110
  return next;
1098
1111
  }) }),
1099
1112
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
1100
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
1113
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
1114
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
1115
+ event.preventDefault();
1116
+ setDragging(true);
1117
+ }
1118
+ }, onDragOver: (event) => {
1119
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1120
+ }, onDragLeave: (event) => {
1121
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1122
+ }, onDrop: dropImages, children: [
1101
1123
  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
1124
  /* @__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
1125
  const value = event.currentTarget.value;
@@ -1111,7 +1133,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1111
1133
  } }),
1112
1134
  /* @__PURE__ */ jsxs3("span", { children: [
1113
1135
  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 }) })
1136
+ /* @__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
1137
  ] })
1116
1138
  ] })
1117
1139
  ] });
@@ -2140,6 +2162,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2140
2162
  const [images, setImages] = useState4(remembered.images ?? []);
2141
2163
  const [starting, setStarting] = useState4(null);
2142
2164
  const [picking, setPicking] = useState4(false);
2165
+ const [dragging, setDragging] = useState4(false);
2143
2166
  const [pickerError, setPickerError] = useState4(null);
2144
2167
  const startSequence = useRef5(0);
2145
2168
  const textarea = useRef5(null);
@@ -2184,13 +2207,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2184
2207
  });
2185
2208
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
2186
2209
  };
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();
2210
+ const addImageFiles = (value, source) => {
2211
+ const allFiles = Array.from(value ?? []);
2212
+ if (!allFiles.length) return false;
2213
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
2214
+ if (files.length !== allFiles.length) {
2215
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
2216
+ return true;
2217
+ }
2191
2218
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
2192
2219
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2193
- return;
2220
+ return true;
2194
2221
  }
2195
2222
  setPicking(true);
2196
2223
  setPickerError(null);
@@ -2198,11 +2225,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2198
2225
  const next = mergeImages(current, picked);
2199
2226
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2200
2227
  return next;
2201
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
2228
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
2229
+ return true;
2230
+ };
2231
+ const pasteImages = (event) => {
2232
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
2233
+ };
2234
+ const dropImages = (event) => {
2235
+ setDragging(false);
2236
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
2202
2237
  };
2203
2238
  const send = () => {
2204
2239
  const text = draft.trim();
2205
- if (!text || !harness || starting) return;
2240
+ if (!text && !images.length || !harness || starting) return;
2206
2241
  const id = startSequence.current + 1;
2207
2242
  startSequence.current = id;
2208
2243
  setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
@@ -2253,7 +2288,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2253
2288
  return next;
2254
2289
  }) }),
2255
2290
  pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2256
- /* @__PURE__ */ jsxs7("div", { class: "scui-envelope", children: [
2291
+ /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2292
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2293
+ event.preventDefault();
2294
+ setDragging(true);
2295
+ }
2296
+ }, onDragOver: (event) => {
2297
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
2298
+ }, onDragLeave: (event) => {
2299
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2300
+ }, onDrop: dropImages, children: [
2257
2301
  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
2302
  /* @__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
2303
  const value = event.currentTarget.value;
@@ -2265,7 +2309,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2265
2309
  send();
2266
2310
  }
2267
2311
  } }),
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 }) }) })
2312
+ /* @__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
2313
  ] })
2270
2314
  ] })
2271
2315
  ] });
package/messenger.mjs CHANGED
@@ -970,6 +970,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
970
970
  const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
971
971
  const [dispatching, setDispatching] = useState(false);
972
972
  const [picking, setPicking] = useState(false);
973
+ const [dragging, setDragging] = useState(false);
973
974
  const [pickerError, setPickerError] = useState(null);
974
975
  const textarea = useRef(null);
975
976
  useAutosizeTextarea(textarea, draft);
@@ -1035,13 +1036,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1035
1036
  });
1036
1037
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
1037
1038
  };
1038
- const pasteImages = (event) => {
1039
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
1040
- if (!files.length) return;
1041
- event.preventDefault();
1039
+ const addImageFiles = (value, source) => {
1040
+ const allFiles = Array.from(value ?? []);
1041
+ if (!allFiles.length) return false;
1042
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
1043
+ if (files.length !== allFiles.length) {
1044
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
1045
+ return true;
1046
+ }
1042
1047
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
1043
1048
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1044
- return;
1049
+ return true;
1045
1050
  }
1046
1051
  setPicking(true);
1047
1052
  setPickerError(null);
@@ -1049,11 +1054,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1049
1054
  const next = mergeImages(current, picked);
1050
1055
  remember(draft, context, next, queue);
1051
1056
  return next;
1052
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
1057
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
1058
+ return true;
1059
+ };
1060
+ const pasteImages = (event) => {
1061
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
1062
+ };
1063
+ const dropImages = (event) => {
1064
+ setDragging(false);
1065
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
1053
1066
  };
1054
1067
  const send = () => {
1055
1068
  const text = draft.trim();
1056
- if (!text) return;
1069
+ if (!text && !images.length) return;
1057
1070
  const message = { text, context, images };
1058
1071
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
1059
1072
  else if (state.canSend) {
@@ -1074,7 +1087,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1074
1087
  ] }),
1075
1088
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
1076
1089
  /* @__PURE__ */ jsxs3("span", { children: [
1077
- item.text,
1090
+ item.text || "Image attachment",
1078
1091
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
1079
1092
  item.context.length + item.images.length,
1080
1093
  " attached"
@@ -1094,7 +1107,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1094
1107
  return next;
1095
1108
  }) }),
1096
1109
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
1097
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
1110
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
1111
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
1112
+ event.preventDefault();
1113
+ setDragging(true);
1114
+ }
1115
+ }, onDragOver: (event) => {
1116
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1117
+ }, onDragLeave: (event) => {
1118
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1119
+ }, onDrop: dropImages, children: [
1098
1120
  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,
1099
1121
  /* @__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) => {
1100
1122
  const value = event.currentTarget.value;
@@ -1108,7 +1130,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1108
1130
  } }),
1109
1131
  /* @__PURE__ */ jsxs3("span", { children: [
1110
1132
  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,
1111
- /* @__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 }) })
1133
+ /* @__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 }) })
1112
1134
  ] })
1113
1135
  ] })
1114
1136
  ] });
@@ -2137,6 +2159,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2137
2159
  const [images, setImages] = useState4(remembered.images ?? []);
2138
2160
  const [starting, setStarting] = useState4(null);
2139
2161
  const [picking, setPicking] = useState4(false);
2162
+ const [dragging, setDragging] = useState4(false);
2140
2163
  const [pickerError, setPickerError] = useState4(null);
2141
2164
  const startSequence = useRef5(0);
2142
2165
  const textarea = useRef5(null);
@@ -2181,13 +2204,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2181
2204
  });
2182
2205
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
2183
2206
  };
2184
- const pasteImages = (event) => {
2185
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
2186
- if (!files.length) return;
2187
- event.preventDefault();
2207
+ const addImageFiles = (value, source) => {
2208
+ const allFiles = Array.from(value ?? []);
2209
+ if (!allFiles.length) return false;
2210
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
2211
+ if (files.length !== allFiles.length) {
2212
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
2213
+ return true;
2214
+ }
2188
2215
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
2189
2216
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2190
- return;
2217
+ return true;
2191
2218
  }
2192
2219
  setPicking(true);
2193
2220
  setPickerError(null);
@@ -2195,11 +2222,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2195
2222
  const next = mergeImages(current, picked);
2196
2223
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2197
2224
  return next;
2198
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
2225
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
2226
+ return true;
2227
+ };
2228
+ const pasteImages = (event) => {
2229
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
2230
+ };
2231
+ const dropImages = (event) => {
2232
+ setDragging(false);
2233
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
2199
2234
  };
2200
2235
  const send = () => {
2201
2236
  const text = draft.trim();
2202
- if (!text || !harness || starting) return;
2237
+ if (!text && !images.length || !harness || starting) return;
2203
2238
  const id = startSequence.current + 1;
2204
2239
  startSequence.current = id;
2205
2240
  setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
@@ -2250,7 +2285,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2250
2285
  return next;
2251
2286
  }) }),
2252
2287
  pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2253
- /* @__PURE__ */ jsxs7("div", { class: "scui-envelope", children: [
2288
+ /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2289
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2290
+ event.preventDefault();
2291
+ setDragging(true);
2292
+ }
2293
+ }, onDragOver: (event) => {
2294
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
2295
+ }, onDragLeave: (event) => {
2296
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2297
+ }, onDrop: dropImages, children: [
2254
2298
  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,
2255
2299
  /* @__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) => {
2256
2300
  const value = event.currentTarget.value;
@@ -2262,7 +2306,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2262
2306
  send();
2263
2307
  }
2264
2308
  } }),
2265
- /* @__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 }) }) })
2309
+ /* @__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 }) }) })
2266
2310
  ] })
2267
2311
  ] })
2268
2312
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
package/styles.css CHANGED
@@ -171,6 +171,7 @@
171
171
  .scui-continuation { display:flex; align-items:center; gap:8px; padding:7px 9px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }.scui-continuation > span { display:grid; flex:1 }.scui-continuation small { color:var(--scui-muted); font-size:10px }.scui-continuation button { padding:5px 8px; border:1px solid var(--scui-accent); border-radius:7px; background:transparent; color:var(--scui-accent); cursor:pointer }
172
172
  .scui-compose { flex:none; padding:8px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }
173
173
  .scui-envelope { display:flex; align-items:flex-end; gap:7px; padding:7px; border:1px solid var(--scui-border-strong); border-radius:16px; background:var(--scui-bg) }.scui-envelope textarea { flex:1; min-width:0; min-height:34px; max-height:150px; resize:none; overflow-y:hidden; border:0; outline:0; background:transparent; color:var(--scui-fg) }.scui-envelope > span { display:flex; gap:4px }
174
+ .scui-envelope.scui-drop-target { border-color:var(--scui-accent); background:color-mix(in srgb,var(--scui-accent) 7%,var(--scui-bg)); box-shadow:0 0 0 2px color-mix(in srgb,var(--scui-accent) 16%,transparent) }
174
175
  .scui-attach { display:grid; flex:0 0 34px; width:34px; height:34px; place-items:center; border:0; border-radius:50%; background:transparent; color:var(--scui-muted) }.scui-attach:hover:not(:disabled) { background:var(--scui-fill); color:var(--scui-fg) }
175
176
  .scui-compose-context { display:flex; gap:5px; margin-bottom:6px; overflow-x:auto; scrollbar-width:thin }.scui-compose-context > span { display:flex; flex:0 0 auto; max-width:210px; align-items:center; gap:5px; padding:4px 5px 4px 7px; border:1px solid var(--scui-border); border-radius:999px; background:var(--scui-fill); font-size:10px }.scui-compose-context strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }.scui-compose-context button { display:grid; width:18px; height:18px; padding:0; place-items:center; border:0; border-radius:50%; background:transparent }.scui-compose-context button:hover { background:color-mix(in srgb,var(--scui-fg) 9%,transparent) }.scui-context-error { display:block; margin:0 2px 6px; color:var(--scui-danger) }
176
177
  .scui-compose-images { display:flex; gap:6px; margin-bottom:6px; overflow-x:auto; scrollbar-width:thin }.scui-compose-images > span { position:relative; display:grid; flex:0 0 58px; width:58px; height:58px; overflow:hidden; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-fill); place-items:center }.scui-compose-images img { width:100%; height:100%; object-fit:cover }.scui-compose-images strong { position:absolute; right:2px; bottom:2px; left:2px; overflow:hidden; padding:2px 3px; border-radius:4px; background:rgba(0,0,0,.65); color:#fff; font-size:8px; font-weight:500; text-overflow:ellipsis; white-space:nowrap }.scui-compose-images button { position:absolute; z-index:1; top:2px; right:2px; display:grid; width:19px; height:19px; padding:0; border:1px solid rgba(255,255,255,.32); border-radius:50%; background:rgba(0,0,0,.68); color:#fff; place-items:center; cursor:pointer }