@nextclaw/agent-chat-ui 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -102,6 +102,7 @@ type ChatSkillPickerProps = {
102
102
  };
103
103
  type ChatInputBarActionsProps = {
104
104
  sendError?: string | null;
105
+ sendErrorDetailsLabel?: string;
105
106
  isSending: boolean;
106
107
  canStopGeneration: boolean;
107
108
  sendDisabled: boolean;
package/dist/index.js CHANGED
@@ -341,6 +341,12 @@ const ChatButton = React.forwardRef(({ className, variant, size, ...props }, ref
341
341
  ChatButton.displayName = "ChatButton";
342
342
  //#endregion
343
343
  //#region src/components/chat/ui/chat-input-bar/chat-input-bar-actions.tsx
344
+ const SEND_ERROR_PREVIEW_MAX_CHARS = 120;
345
+ function buildSendErrorPreview(value) {
346
+ const compact = value.replace(/\s+/g, " ").trim();
347
+ if (compact.length <= SEND_ERROR_PREVIEW_MAX_CHARS) return compact;
348
+ return `${compact.slice(0, SEND_ERROR_PREVIEW_MAX_CHARS - 1)}…`;
349
+ }
344
350
  function StopIcon() {
345
351
  return /* @__PURE__ */ jsx("span", {
346
352
  "aria-hidden": "true",
@@ -348,22 +354,49 @@ function StopIcon() {
348
354
  className: "block h-3 w-3 rounded-[2px] bg-gray-700 shadow-[inset_0_0_0_1px_rgba(17,24,39,0.06)]"
349
355
  });
350
356
  }
351
- function ChatInputBarActions(props) {
352
- const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
357
+ function ChatInputBarActions({ sendError, sendErrorDetailsLabel, isSending, canStopGeneration, sendDisabled, stopDisabled, stopHint, sendButtonLabel, stopButtonLabel, onSend, onStop }) {
358
+ const { Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
359
+ const normalizedSendError = sendError?.trim() ?? "";
360
+ const sendErrorPreview = normalizedSendError ? buildSendErrorPreview(normalizedSendError) : "";
361
+ const resolvedSendErrorDetailsLabel = sendErrorDetailsLabel?.trim() || "Details";
353
362
  return /* @__PURE__ */ jsxs("div", {
354
363
  className: "flex flex-col items-end gap-1",
355
- children: [props.sendError?.trim() ? /* @__PURE__ */ jsx("div", {
356
- className: "max-w-[420px] text-right text-[11px] text-red-600",
357
- children: props.sendError
364
+ children: [normalizedSendError ? /* @__PURE__ */ jsxs("div", {
365
+ className: "flex max-w-[420px] items-start justify-end gap-2 text-right",
366
+ children: [/* @__PURE__ */ jsx("div", {
367
+ className: "min-w-0 flex-1 text-[11px] text-red-600",
368
+ children: /* @__PURE__ */ jsx("span", {
369
+ className: "block truncate",
370
+ title: normalizedSendError,
371
+ children: sendErrorPreview
372
+ })
373
+ }), /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
374
+ asChild: true,
375
+ children: /* @__PURE__ */ jsx("button", {
376
+ type: "button",
377
+ className: "shrink-0 rounded-full border border-red-200/80 bg-red-50 px-2 py-0.5 text-[10px] font-semibold text-red-700 transition-colors hover:bg-red-100",
378
+ children: resolvedSendErrorDetailsLabel
379
+ })
380
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
381
+ align: "end",
382
+ className: "w-[min(32rem,calc(100vw-1.5rem))] border-red-100/80 p-0",
383
+ children: [/* @__PURE__ */ jsx("div", {
384
+ className: "border-b border-red-100 bg-red-50/80 px-4 py-2 text-xs font-semibold text-red-700",
385
+ children: resolvedSendErrorDetailsLabel
386
+ }), /* @__PURE__ */ jsx("pre", {
387
+ className: "max-h-80 overflow-auto whitespace-pre-wrap break-words px-4 py-3 text-xs leading-relaxed text-red-700",
388
+ children: normalizedSendError
389
+ })]
390
+ })] })]
358
391
  }) : null, /* @__PURE__ */ jsx("div", {
359
392
  className: "flex items-center gap-2",
360
- children: props.isSending ? props.canStopGeneration ? /* @__PURE__ */ jsx(ChatButton, {
393
+ children: isSending ? canStopGeneration ? /* @__PURE__ */ jsx(ChatButton, {
361
394
  size: "icon",
362
395
  variant: "outline",
363
396
  className: "h-8 w-8 rounded-full",
364
- "aria-label": props.stopButtonLabel,
365
- onClick: () => void props.onStop(),
366
- disabled: props.stopDisabled,
397
+ "aria-label": stopButtonLabel,
398
+ onClick: () => void onStop(),
399
+ disabled: stopDisabled,
367
400
  children: /* @__PURE__ */ jsx(StopIcon, {})
368
401
  }) : /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
369
402
  asChild: true,
@@ -371,7 +404,7 @@ function ChatInputBarActions(props) {
371
404
  size: "icon",
372
405
  variant: "outline",
373
406
  className: "h-8 w-8 rounded-full",
374
- "aria-label": props.stopButtonLabel,
407
+ "aria-label": stopButtonLabel,
375
408
  disabled: true,
376
409
  children: /* @__PURE__ */ jsx(StopIcon, {})
377
410
  }) })
@@ -379,14 +412,14 @@ function ChatInputBarActions(props) {
379
412
  side: "top",
380
413
  children: /* @__PURE__ */ jsx("p", {
381
414
  className: "text-xs",
382
- children: props.stopHint
415
+ children: stopHint
383
416
  })
384
417
  })] }) }) : /* @__PURE__ */ jsx(ChatButton, {
385
418
  size: "icon",
386
419
  className: "h-8 w-8 rounded-full",
387
- "aria-label": props.sendButtonLabel,
388
- onClick: () => void props.onSend(),
389
- disabled: props.sendDisabled,
420
+ "aria-label": sendButtonLabel,
421
+ onClick: () => void onSend(),
422
+ disabled: sendDisabled,
390
423
  children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-5 w-5" })
391
424
  })
392
425
  })]
@@ -2270,6 +2303,25 @@ const IMAGE_EXTENSIONS = new Set([
2270
2303
  "tiff",
2271
2304
  "webp"
2272
2305
  ]);
2306
+ const AUDIO_EXTENSIONS = new Set([
2307
+ "aac",
2308
+ "flac",
2309
+ "m4a",
2310
+ "mp3",
2311
+ "ogg",
2312
+ "opus",
2313
+ "wav",
2314
+ "weba"
2315
+ ]);
2316
+ const VIDEO_EXTENSIONS = new Set([
2317
+ "avi",
2318
+ "m4v",
2319
+ "mov",
2320
+ "mp4",
2321
+ "mkv",
2322
+ "webm",
2323
+ "wmv"
2324
+ ]);
2273
2325
  const DOCUMENT_EXTENSIONS = new Set([
2274
2326
  "doc",
2275
2327
  "docx",
@@ -2327,6 +2379,36 @@ function readFileExtension(label) {
2327
2379
  function isImageDataUrl(dataUrl) {
2328
2380
  return typeof dataUrl === "string" && /^data:image\//i.test(dataUrl.trim());
2329
2381
  }
2382
+ function inferMimeTypeFromExtension(extension) {
2383
+ if (IMAGE_EXTENSIONS.has(extension)) {
2384
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
2385
+ if (extension === "svg") return "image/svg+xml";
2386
+ return `image/${extension}`;
2387
+ }
2388
+ if (AUDIO_EXTENSIONS.has(extension)) {
2389
+ if (extension === "mp3") return "audio/mpeg";
2390
+ if (extension === "m4a") return "audio/mp4";
2391
+ if (extension === "weba") return "audio/webm";
2392
+ return `audio/${extension}`;
2393
+ }
2394
+ if (VIDEO_EXTENSIONS.has(extension)) {
2395
+ if (extension === "mov") return "video/quicktime";
2396
+ if (extension === "m4v") return "video/mp4";
2397
+ return `video/${extension}`;
2398
+ }
2399
+ if (extension === "pdf") return "application/pdf";
2400
+ return null;
2401
+ }
2402
+ function inferMimeTypeFromFileName(fileName) {
2403
+ const extension = readFileExtension(fileName);
2404
+ if (!extension) return null;
2405
+ return inferMimeTypeFromExtension(extension);
2406
+ }
2407
+ function resolveRenderableMimeType(file) {
2408
+ const normalizedMimeType = file.mimeType.trim().toLowerCase();
2409
+ if (normalizedMimeType.length > 0 && normalizedMimeType !== "application/octet-stream") return normalizedMimeType;
2410
+ return inferMimeTypeFromFileName(file.label);
2411
+ }
2330
2412
  function isImageFileLike(file) {
2331
2413
  const normalizedMimeType = file.mimeType.trim().toLowerCase();
2332
2414
  const extension = readFileExtension(file.label);
@@ -2336,8 +2418,8 @@ function resolveFileCategory(label, mimeType) {
2336
2418
  const extension = readFileExtension(label);
2337
2419
  const normalizedMimeType = mimeType.toLowerCase();
2338
2420
  if (normalizedMimeType.startsWith("image/") || IMAGE_EXTENSIONS.has(extension)) return "image";
2339
- if (normalizedMimeType.startsWith("audio/")) return "audio";
2340
- if (normalizedMimeType.startsWith("video/")) return "video";
2421
+ if (normalizedMimeType.startsWith("audio/") || AUDIO_EXTENSIONS.has(extension)) return "audio";
2422
+ if (normalizedMimeType.startsWith("video/") || VIDEO_EXTENSIONS.has(extension)) return "video";
2341
2423
  if (normalizedMimeType.includes("pdf") || extension === "pdf") return "pdf";
2342
2424
  if (ARCHIVE_EXTENSIONS.has(extension) || /(zip|tar|gzip|rar|compressed|archive)/.test(normalizedMimeType)) return "archive";
2343
2425
  if (SHEET_EXTENSIONS.has(extension) || /(spreadsheet|sheet|excel|csv)/.test(normalizedMimeType)) return "sheet";
@@ -2369,6 +2451,18 @@ const DEFAULT_FILE_CATEGORY_LABELS = {
2369
2451
  sheet: "Spreadsheet",
2370
2452
  video: "Video"
2371
2453
  };
2454
+ const FILE_CATEGORY_ICONS = {
2455
+ archive: FileArchive,
2456
+ audio: FileAudio2,
2457
+ code: FileCode2,
2458
+ data: FileJson2,
2459
+ document: FileText,
2460
+ generic: File,
2461
+ image: FileImage,
2462
+ pdf: FileText,
2463
+ sheet: FileSpreadsheet,
2464
+ video: FileVideo2
2465
+ };
2372
2466
  function readFileCategoryLabel(category, texts) {
2373
2467
  return texts?.attachmentCategoryLabels?.[category] ?? DEFAULT_FILE_CATEGORY_LABELS[category];
2374
2468
  }
@@ -2384,20 +2478,17 @@ function renderActionPill(label, isUser, isInteractive) {
2384
2478
  children: label
2385
2479
  });
2386
2480
  }
2387
- function readFileCategoryIcon(category) {
2388
- if (category === "archive") return FileArchive;
2389
- if (category === "audio") return FileAudio2;
2390
- if (category === "code") return FileCode2;
2391
- if (category === "data") return FileJson2;
2392
- if (category === "pdf") return FileText;
2393
- if (category === "sheet") return FileSpreadsheet;
2394
- if (category === "image") return FileImage;
2395
- if (category === "video") return FileVideo2;
2396
- if (category === "document") return FileText;
2397
- return File;
2481
+ function renderActionLink(label, href, isUser) {
2482
+ return /* @__PURE__ */ jsx("a", {
2483
+ href,
2484
+ target: "_blank",
2485
+ rel: "noreferrer",
2486
+ className: cn("inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-medium transition duration-200", isUser ? "border-white/14 bg-white/12 text-white hover:border-white/20 hover:bg-white/16" : "border-slate-200/80 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50"),
2487
+ children: label
2488
+ });
2398
2489
  }
2399
2490
  function FileCategoryGlyph({ category, isUser }) {
2400
- const Icon = readFileCategoryIcon(category);
2491
+ const Icon = FILE_CATEGORY_ICONS[category];
2401
2492
  return /* @__PURE__ */ jsx("div", {
2402
2493
  className: cn("flex h-14 w-14 shrink-0 items-center justify-center rounded-[1rem] border", isUser ? "border-white/12 bg-white/10 text-white" : FILE_CATEGORY_TILE_CLASSES[category]),
2403
2494
  children: /* @__PURE__ */ jsx(Icon, {
@@ -2406,14 +2497,10 @@ function FileCategoryGlyph({ category, isUser }) {
2406
2497
  })
2407
2498
  });
2408
2499
  }
2409
- function ChatMessageFile({ file, isUser = false, texts }) {
2410
- const { category, extension, sizeLabel } = buildChatMessageFileMeta(file);
2411
- const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
2412
- const isInteractive = Boolean(file.dataUrl);
2413
- const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
2414
- const categoryLabel = readFileCategoryLabel(category, texts);
2415
- const shellClasses = cn("block overflow-hidden rounded-[1.25rem] border transition duration-200", isUser ? "border-white/12 bg-white/10 text-white" : "border-slate-200/80 bg-white/95 text-slate-900", isInteractive && (isUser ? "hover:border-white/18 hover:bg-white/13" : "hover:border-slate-300 hover:bg-white"));
2416
- if (renderAsImage && file.dataUrl) return /* @__PURE__ */ jsx("a", {
2500
+ function renderImagePreview(params) {
2501
+ const { file, categoryLabel, sizeLabel, isUser } = params;
2502
+ if (!file.dataUrl) return null;
2503
+ return /* @__PURE__ */ jsx("a", {
2417
2504
  href: file.dataUrl,
2418
2505
  target: "_blank",
2419
2506
  rel: "noreferrer",
@@ -2436,7 +2523,10 @@ function ChatMessageFile({ file, isUser = false, texts }) {
2436
2523
  })]
2437
2524
  })
2438
2525
  });
2439
- const content = /* @__PURE__ */ jsxs("div", {
2526
+ }
2527
+ function renderFileCardHeader(params) {
2528
+ const { category, file, categoryLabel, sizeLabel, isUser, action } = params;
2529
+ return /* @__PURE__ */ jsxs("div", {
2440
2530
  className: "flex items-center gap-3 p-3.5",
2441
2531
  children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
2442
2532
  category,
@@ -2451,11 +2541,85 @@ function ChatMessageFile({ file, isUser = false, texts }) {
2451
2541
  className: "truncate text-[15px] font-semibold leading-5",
2452
2542
  children: file.label
2453
2543
  }), renderMetaLine(categoryLabel, sizeLabel, isUser)]
2454
- }), renderActionPill(actionLabel, isUser, isInteractive)]
2544
+ }), action]
2455
2545
  })
2456
2546
  })]
2457
2547
  });
2458
- if (!isInteractive) return /* @__PURE__ */ jsx("div", {
2548
+ }
2549
+ function renderInlineMediaCard(params) {
2550
+ const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel } = params;
2551
+ if (!file.dataUrl || category !== "audio" && category !== "video") return null;
2552
+ const mediaMimeType = resolveRenderableMimeType(file);
2553
+ return /* @__PURE__ */ jsxs("div", {
2554
+ className: shellClasses,
2555
+ children: [renderFileCardHeader({
2556
+ category,
2557
+ file,
2558
+ categoryLabel,
2559
+ sizeLabel,
2560
+ isUser,
2561
+ action: renderActionLink(actionLabel, file.dataUrl, isUser)
2562
+ }), /* @__PURE__ */ jsx("div", {
2563
+ className: "px-3.5 pb-3.5",
2564
+ children: category === "audio" ? /* @__PURE__ */ jsx("audio", {
2565
+ controls: true,
2566
+ preload: "metadata",
2567
+ "aria-label": file.label,
2568
+ className: "block w-full",
2569
+ children: /* @__PURE__ */ jsx("source", {
2570
+ src: file.dataUrl,
2571
+ ...mediaMimeType ? { type: mediaMimeType } : {}
2572
+ })
2573
+ }) : /* @__PURE__ */ jsx("div", {
2574
+ className: cn("overflow-hidden rounded-[1rem] border", isUser ? "border-white/10 bg-slate-950/26" : "border-slate-200/80 bg-slate-100/80"),
2575
+ children: /* @__PURE__ */ jsx("video", {
2576
+ controls: true,
2577
+ preload: "metadata",
2578
+ playsInline: true,
2579
+ "aria-label": file.label,
2580
+ className: "block max-h-[28rem] w-full bg-black",
2581
+ children: /* @__PURE__ */ jsx("source", {
2582
+ src: file.dataUrl,
2583
+ ...mediaMimeType ? { type: mediaMimeType } : {}
2584
+ })
2585
+ })
2586
+ })
2587
+ })]
2588
+ });
2589
+ }
2590
+ function ChatMessageFile({ file, isUser = false, texts }) {
2591
+ const { category, sizeLabel } = buildChatMessageFileMeta(file);
2592
+ const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
2593
+ const renderAsAudio = category === "audio" && Boolean(file.dataUrl);
2594
+ const renderAsVideo = category === "video" && Boolean(file.dataUrl);
2595
+ const isInteractive = Boolean(file.dataUrl);
2596
+ const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
2597
+ const categoryLabel = readFileCategoryLabel(category, texts);
2598
+ const shellClasses = cn("block overflow-hidden rounded-[1.25rem] border transition duration-200", isUser ? "border-white/12 bg-white/10 text-white" : "border-slate-200/80 bg-white/95 text-slate-900", isInteractive && (isUser ? "hover:border-white/18 hover:bg-white/13" : "hover:border-slate-300 hover:bg-white"));
2599
+ if (renderAsImage) return renderImagePreview({
2600
+ file,
2601
+ categoryLabel,
2602
+ sizeLabel,
2603
+ isUser
2604
+ });
2605
+ if (renderAsAudio || renderAsVideo) return renderInlineMediaCard({
2606
+ category,
2607
+ file,
2608
+ categoryLabel,
2609
+ sizeLabel,
2610
+ isUser,
2611
+ shellClasses,
2612
+ actionLabel
2613
+ });
2614
+ const content = renderFileCardHeader({
2615
+ category,
2616
+ file,
2617
+ categoryLabel,
2618
+ sizeLabel,
2619
+ isUser,
2620
+ action: renderActionPill(actionLabel, isUser, isInteractive)
2621
+ });
2622
+ if (!isInteractive || !file.dataUrl) return /* @__PURE__ */ jsx("div", {
2459
2623
  className: shellClasses,
2460
2624
  children: content
2461
2625
  });
@@ -2726,13 +2890,11 @@ function ToolCardHeaderSessionAction({ action, onAction }) {
2726
2890
  //#endregion
2727
2891
  //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
2728
2892
  const FILE_TEXT_CLASS_NAME = "font-mono text-[11px] leading-5";
2729
- const FILE_ROW_CLASS_NAME = `h-5 ${FILE_TEXT_CLASS_NAME}`;
2893
+ const FILE_ROW_CLASS_NAME = `flex h-5 w-full ${FILE_TEXT_CLASS_NAME}`;
2730
2894
  const FILE_LINE_NUMBER_CELL_CLASS_NAME = "flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
2731
- function formatLineNumber(value) {
2732
- return typeof value === "number" ? String(value) : "";
2733
- }
2734
2895
  function readVisibleLineNumber(line) {
2735
- return formatLineNumber(line.newLineNumber ?? line.oldLineNumber);
2896
+ const value = line.newLineNumber ?? line.oldLineNumber;
2897
+ return typeof value === "number" ? String(value) : "";
2736
2898
  }
2737
2899
  function readLineKey(prefix, line, index) {
2738
2900
  return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
@@ -2750,9 +2912,8 @@ function readLineNumberColumnWidth(block) {
2750
2912
  }, 0);
2751
2913
  return `${Math.max(6.5, Math.min(8, maxDigits + 3.5))}ch`;
2752
2914
  }
2753
- function buildRowTemplateColumns(params) {
2754
- if (!params.showLineNumbers) return;
2755
- return { gridTemplateColumns: `${params.lineNumberColumnWidth} minmax(0, 1fr)` };
2915
+ function readCodeColumnWidth(block) {
2916
+ return `calc(${block.lines.reduce((currentMax, line) => Math.max(currentMax, Math.max(1, line.text.length)), 1)}ch + 1.25rem)`;
2756
2917
  }
2757
2918
  function getLineNumberTone(line) {
2758
2919
  if (line.kind === "remove") return "border-r border-rose-200 bg-rose-50 text-rose-700";
@@ -2765,29 +2926,27 @@ function getCodeRowTone(line) {
2765
2926
  return "bg-white text-amber-950/80";
2766
2927
  }
2767
2928
  function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
2768
- const lineNumberCellClassName = layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0";
2769
2929
  return /* @__PURE__ */ jsx("span", {
2770
2930
  "data-file-line-number-cell": "true",
2771
- style: layout === "compact" ? { width: lineNumberColumnWidth } : void 0,
2772
- className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, lineNumberCellClassName, getLineNumberTone(line)),
2931
+ style: layout === "compact" ? {
2932
+ width: lineNumberColumnWidth,
2933
+ minWidth: lineNumberColumnWidth
2934
+ } : void 0,
2935
+ className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line)),
2773
2936
  children: readVisibleLineNumber(line)
2774
2937
  });
2775
2938
  }
2776
2939
  function FileOperationCodeCell({ line }) {
2777
2940
  return /* @__PURE__ */ jsx("span", {
2778
2941
  "data-file-code-row": "true",
2779
- className: cn("block min-w-full whitespace-pre px-2.5", getCodeRowTone(line)),
2942
+ className: cn("block min-w-0 flex-1 whitespace-pre px-2.5", getCodeRowTone(line)),
2780
2943
  children: line.text || " "
2781
2944
  });
2782
2945
  }
2783
2946
  function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth }) {
2784
2947
  return /* @__PURE__ */ jsxs("div", {
2785
2948
  "data-file-line-row": "true",
2786
- className: cn("grid w-max min-w-full", FILE_ROW_CLASS_NAME),
2787
- style: buildRowTemplateColumns({
2788
- showLineNumbers,
2789
- lineNumberColumnWidth
2790
- }),
2949
+ className: FILE_ROW_CLASS_NAME,
2791
2950
  children: [showLineNumbers ? /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
2792
2951
  layout: "compact",
2793
2952
  line,
@@ -2798,6 +2957,7 @@ function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth })
2798
2957
  function FileOperationWorkspaceSurface({ block }) {
2799
2958
  const showLineNumbers = readHasBlockLineNumbers(block);
2800
2959
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2960
+ const codeColumnWidth = readCodeColumnWidth(block);
2801
2961
  return /* @__PURE__ */ jsxs("div", {
2802
2962
  "data-file-code-surface": "true",
2803
2963
  "data-file-code-surface-layout": "workspace",
@@ -2817,11 +2977,16 @@ function FileOperationWorkspaceSurface({ block }) {
2817
2977
  }) : null, /* @__PURE__ */ jsxs("div", {
2818
2978
  "data-file-code-canvas": "true",
2819
2979
  className: "flex h-full min-h-full min-w-0 flex-1 flex-col bg-white",
2820
- children: [block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
2821
- "data-file-code-canvas-row": "true",
2822
- className: FILE_ROW_CLASS_NAME,
2823
- children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
2824
- }, readLineKey("code", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-white" })]
2980
+ children: [/* @__PURE__ */ jsx("div", {
2981
+ "data-file-code-stack": "true",
2982
+ className: "min-w-full",
2983
+ style: { minWidth: codeColumnWidth },
2984
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
2985
+ "data-file-code-canvas-row": "true",
2986
+ className: FILE_ROW_CLASS_NAME,
2987
+ children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
2988
+ }, readLineKey("code", line, index)))
2989
+ }), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-white" })]
2825
2990
  })]
2826
2991
  });
2827
2992
  }
@@ -2829,15 +2994,22 @@ function FileOperationCodeSurface({ block, layout = "compact" }) {
2829
2994
  if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, { block });
2830
2995
  const showLineNumbers = readHasBlockLineNumbers(block);
2831
2996
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2997
+ const codeColumnWidth = readCodeColumnWidth(block);
2998
+ const surfaceMinWidth = showLineNumbers ? `calc(${lineNumberColumnWidth} + ${codeColumnWidth})` : codeColumnWidth;
2832
2999
  return /* @__PURE__ */ jsx("div", {
2833
3000
  "data-file-code-surface": "true",
2834
3001
  "data-file-code-surface-layout": "compact",
2835
3002
  className: "overflow-x-auto custom-scrollbar-amber bg-white",
2836
- children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
2837
- line,
2838
- showLineNumbers,
2839
- lineNumberColumnWidth
2840
- }, readLineKey("row", line, index)))
3003
+ children: /* @__PURE__ */ jsx("div", {
3004
+ "data-file-code-stack": "true",
3005
+ className: "min-w-full",
3006
+ style: { minWidth: surfaceMinWidth },
3007
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
3008
+ line,
3009
+ showLineNumbers,
3010
+ lineNumberColumnWidth
3011
+ }, readLineKey("row", line, index)))
3012
+ })
2841
3013
  });
2842
3014
  }
2843
3015
  function FileOperationLinesGrid({ block }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",