@nextclaw/agent-chat-ui 0.3.7 → 0.3.9

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.
Files changed (2) hide show
  1. package/dist/index.js +209 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1216,8 +1216,8 @@ function readChatComposerSnapshotFromEditorState(editorState) {
1216
1216
  return {
1217
1217
  nodes,
1218
1218
  selection: {
1219
- start: getOffsetFromPoint(selection.anchor),
1220
- end: getOffsetFromPoint(selection.focus)
1219
+ start: Math.min(getOffsetFromPoint(selection.anchor), getOffsetFromPoint(selection.focus)),
1220
+ end: Math.max(getOffsetFromPoint(selection.anchor), getOffsetFromPoint(selection.focus))
1221
1221
  }
1222
1222
  };
1223
1223
  });
@@ -1275,9 +1275,10 @@ function getDocumentLength(nodes) {
1275
1275
  function insertToken(params) {
1276
1276
  const { label, nodes, selection, tokenKey, tokenKind, trigger } = params;
1277
1277
  const documentLength = getDocumentLength(nodes);
1278
- const replaceStart = trigger?.start ?? selection?.start ?? documentLength;
1278
+ const [selectionStart, selectionEnd] = selection ? [Math.min(selection.start, selection.end), Math.max(selection.start, selection.end)] : [documentLength, documentLength];
1279
+ const replaceStart = trigger?.start ?? selectionStart;
1279
1280
  return {
1280
- nodes: replaceChatComposerRange(nodes, replaceStart, trigger?.end ?? selection?.end ?? replaceStart, [createChatComposerTokenNode({
1281
+ nodes: replaceChatComposerRange(nodes, replaceStart, trigger?.end ?? selectionEnd, [createChatComposerTokenNode({
1281
1282
  label,
1282
1283
  tokenKey,
1283
1284
  tokenKind
@@ -1293,13 +1294,11 @@ function getChatComposerNodesSignature(nodes) {
1293
1294
  }
1294
1295
  function replaceChatComposerSelectionWithText(params) {
1295
1296
  const { nodes, selection, text } = params;
1296
- const currentSelection = selection ?? {
1297
- start: getDocumentLength(nodes),
1298
- end: getDocumentLength(nodes)
1299
- };
1300
- const nextOffset = currentSelection.start + text.length;
1297
+ const documentLength = getDocumentLength(nodes);
1298
+ const [selectionStart, selectionEnd] = selection ? [Math.min(selection.start, selection.end), Math.max(selection.start, selection.end)] : [documentLength, documentLength];
1299
+ const nextOffset = selectionStart + text.length;
1301
1300
  return {
1302
- nodes: replaceChatComposerRange(nodes, currentSelection.start, currentSelection.end, [createChatComposerTextNode(text)]),
1301
+ nodes: replaceChatComposerRange(nodes, selectionStart, selectionEnd, [createChatComposerTextNode(text)]),
1303
1302
  selection: {
1304
1303
  start: nextOffset,
1305
1304
  end: nextOffset
@@ -1355,17 +1354,17 @@ function syncSelectedSkillsIntoChatComposer(params) {
1355
1354
  function deleteChatComposerContent(params) {
1356
1355
  const { direction, nodes, selection: currentSelection } = params;
1357
1356
  const documentLength = getDocumentLength(nodes);
1358
- const selection = currentSelection ?? {
1359
- start: documentLength,
1360
- end: documentLength
1361
- };
1362
- let rangeStart = selection.start;
1363
- let rangeEnd = selection.end;
1364
- if (selection.start === selection.end) if (direction === "backward" && selection.start > 0) rangeStart = selection.start - 1;
1365
- else if (direction === "forward" && selection.end < documentLength) rangeEnd = selection.end + 1;
1357
+ const [selectionStart, selectionEnd] = currentSelection ? [Math.min(currentSelection.start, currentSelection.end), Math.max(currentSelection.start, currentSelection.end)] : [documentLength, documentLength];
1358
+ let rangeStart = selectionStart;
1359
+ let rangeEnd = selectionEnd;
1360
+ if (rangeStart === rangeEnd) if (direction === "backward" && rangeStart > 0) rangeStart -= 1;
1361
+ else if (direction === "forward" && rangeEnd < documentLength) rangeEnd += 1;
1366
1362
  else return {
1367
1363
  nodes: normalizeChatComposerNodes(nodes),
1368
- selection
1364
+ selection: {
1365
+ start: rangeStart,
1366
+ end: rangeEnd
1367
+ }
1369
1368
  };
1370
1369
  return {
1371
1370
  nodes: replaceChatComposerRange(nodes, rangeStart, rangeEnd, []),
@@ -2303,6 +2302,25 @@ const IMAGE_EXTENSIONS = new Set([
2303
2302
  "tiff",
2304
2303
  "webp"
2305
2304
  ]);
2305
+ const AUDIO_EXTENSIONS = new Set([
2306
+ "aac",
2307
+ "flac",
2308
+ "m4a",
2309
+ "mp3",
2310
+ "ogg",
2311
+ "opus",
2312
+ "wav",
2313
+ "weba"
2314
+ ]);
2315
+ const VIDEO_EXTENSIONS = new Set([
2316
+ "avi",
2317
+ "m4v",
2318
+ "mov",
2319
+ "mp4",
2320
+ "mkv",
2321
+ "webm",
2322
+ "wmv"
2323
+ ]);
2306
2324
  const DOCUMENT_EXTENSIONS = new Set([
2307
2325
  "doc",
2308
2326
  "docx",
@@ -2360,6 +2378,36 @@ function readFileExtension(label) {
2360
2378
  function isImageDataUrl(dataUrl) {
2361
2379
  return typeof dataUrl === "string" && /^data:image\//i.test(dataUrl.trim());
2362
2380
  }
2381
+ function inferMimeTypeFromExtension(extension) {
2382
+ if (IMAGE_EXTENSIONS.has(extension)) {
2383
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
2384
+ if (extension === "svg") return "image/svg+xml";
2385
+ return `image/${extension}`;
2386
+ }
2387
+ if (AUDIO_EXTENSIONS.has(extension)) {
2388
+ if (extension === "mp3") return "audio/mpeg";
2389
+ if (extension === "m4a") return "audio/mp4";
2390
+ if (extension === "weba") return "audio/webm";
2391
+ return `audio/${extension}`;
2392
+ }
2393
+ if (VIDEO_EXTENSIONS.has(extension)) {
2394
+ if (extension === "mov") return "video/quicktime";
2395
+ if (extension === "m4v") return "video/mp4";
2396
+ return `video/${extension}`;
2397
+ }
2398
+ if (extension === "pdf") return "application/pdf";
2399
+ return null;
2400
+ }
2401
+ function inferMimeTypeFromFileName(fileName) {
2402
+ const extension = readFileExtension(fileName);
2403
+ if (!extension) return null;
2404
+ return inferMimeTypeFromExtension(extension);
2405
+ }
2406
+ function resolveRenderableMimeType(file) {
2407
+ const normalizedMimeType = file.mimeType.trim().toLowerCase();
2408
+ if (normalizedMimeType.length > 0 && normalizedMimeType !== "application/octet-stream") return normalizedMimeType;
2409
+ return inferMimeTypeFromFileName(file.label);
2410
+ }
2363
2411
  function isImageFileLike(file) {
2364
2412
  const normalizedMimeType = file.mimeType.trim().toLowerCase();
2365
2413
  const extension = readFileExtension(file.label);
@@ -2369,8 +2417,8 @@ function resolveFileCategory(label, mimeType) {
2369
2417
  const extension = readFileExtension(label);
2370
2418
  const normalizedMimeType = mimeType.toLowerCase();
2371
2419
  if (normalizedMimeType.startsWith("image/") || IMAGE_EXTENSIONS.has(extension)) return "image";
2372
- if (normalizedMimeType.startsWith("audio/")) return "audio";
2373
- if (normalizedMimeType.startsWith("video/")) return "video";
2420
+ if (normalizedMimeType.startsWith("audio/") || AUDIO_EXTENSIONS.has(extension)) return "audio";
2421
+ if (normalizedMimeType.startsWith("video/") || VIDEO_EXTENSIONS.has(extension)) return "video";
2374
2422
  if (normalizedMimeType.includes("pdf") || extension === "pdf") return "pdf";
2375
2423
  if (ARCHIVE_EXTENSIONS.has(extension) || /(zip|tar|gzip|rar|compressed|archive)/.test(normalizedMimeType)) return "archive";
2376
2424
  if (SHEET_EXTENSIONS.has(extension) || /(spreadsheet|sheet|excel|csv)/.test(normalizedMimeType)) return "sheet";
@@ -2402,6 +2450,18 @@ const DEFAULT_FILE_CATEGORY_LABELS = {
2402
2450
  sheet: "Spreadsheet",
2403
2451
  video: "Video"
2404
2452
  };
2453
+ const FILE_CATEGORY_ICONS = {
2454
+ archive: FileArchive,
2455
+ audio: FileAudio2,
2456
+ code: FileCode2,
2457
+ data: FileJson2,
2458
+ document: FileText,
2459
+ generic: File,
2460
+ image: FileImage,
2461
+ pdf: FileText,
2462
+ sheet: FileSpreadsheet,
2463
+ video: FileVideo2
2464
+ };
2405
2465
  function readFileCategoryLabel(category, texts) {
2406
2466
  return texts?.attachmentCategoryLabels?.[category] ?? DEFAULT_FILE_CATEGORY_LABELS[category];
2407
2467
  }
@@ -2417,20 +2477,17 @@ function renderActionPill(label, isUser, isInteractive) {
2417
2477
  children: label
2418
2478
  });
2419
2479
  }
2420
- function readFileCategoryIcon(category) {
2421
- if (category === "archive") return FileArchive;
2422
- if (category === "audio") return FileAudio2;
2423
- if (category === "code") return FileCode2;
2424
- if (category === "data") return FileJson2;
2425
- if (category === "pdf") return FileText;
2426
- if (category === "sheet") return FileSpreadsheet;
2427
- if (category === "image") return FileImage;
2428
- if (category === "video") return FileVideo2;
2429
- if (category === "document") return FileText;
2430
- return File;
2480
+ function renderActionLink(label, href, isUser) {
2481
+ return /* @__PURE__ */ jsx("a", {
2482
+ href,
2483
+ target: "_blank",
2484
+ rel: "noreferrer",
2485
+ 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"),
2486
+ children: label
2487
+ });
2431
2488
  }
2432
2489
  function FileCategoryGlyph({ category, isUser }) {
2433
- const Icon = readFileCategoryIcon(category);
2490
+ const Icon = FILE_CATEGORY_ICONS[category];
2434
2491
  return /* @__PURE__ */ jsx("div", {
2435
2492
  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]),
2436
2493
  children: /* @__PURE__ */ jsx(Icon, {
@@ -2439,14 +2496,10 @@ function FileCategoryGlyph({ category, isUser }) {
2439
2496
  })
2440
2497
  });
2441
2498
  }
2442
- function ChatMessageFile({ file, isUser = false, texts }) {
2443
- const { category, extension, sizeLabel } = buildChatMessageFileMeta(file);
2444
- const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
2445
- const isInteractive = Boolean(file.dataUrl);
2446
- const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
2447
- const categoryLabel = readFileCategoryLabel(category, texts);
2448
- 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"));
2449
- if (renderAsImage && file.dataUrl) return /* @__PURE__ */ jsx("a", {
2499
+ function renderImagePreview(params) {
2500
+ const { file, categoryLabel, sizeLabel, isUser } = params;
2501
+ if (!file.dataUrl) return null;
2502
+ return /* @__PURE__ */ jsx("a", {
2450
2503
  href: file.dataUrl,
2451
2504
  target: "_blank",
2452
2505
  rel: "noreferrer",
@@ -2469,7 +2522,10 @@ function ChatMessageFile({ file, isUser = false, texts }) {
2469
2522
  })]
2470
2523
  })
2471
2524
  });
2472
- const content = /* @__PURE__ */ jsxs("div", {
2525
+ }
2526
+ function renderFileCardHeader(params) {
2527
+ const { category, file, categoryLabel, sizeLabel, isUser, action } = params;
2528
+ return /* @__PURE__ */ jsxs("div", {
2473
2529
  className: "flex items-center gap-3 p-3.5",
2474
2530
  children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
2475
2531
  category,
@@ -2484,11 +2540,85 @@ function ChatMessageFile({ file, isUser = false, texts }) {
2484
2540
  className: "truncate text-[15px] font-semibold leading-5",
2485
2541
  children: file.label
2486
2542
  }), renderMetaLine(categoryLabel, sizeLabel, isUser)]
2487
- }), renderActionPill(actionLabel, isUser, isInteractive)]
2543
+ }), action]
2544
+ })
2545
+ })]
2546
+ });
2547
+ }
2548
+ function renderInlineMediaCard(params) {
2549
+ const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel } = params;
2550
+ if (!file.dataUrl || category !== "audio" && category !== "video") return null;
2551
+ const mediaMimeType = resolveRenderableMimeType(file);
2552
+ return /* @__PURE__ */ jsxs("div", {
2553
+ className: shellClasses,
2554
+ children: [renderFileCardHeader({
2555
+ category,
2556
+ file,
2557
+ categoryLabel,
2558
+ sizeLabel,
2559
+ isUser,
2560
+ action: renderActionLink(actionLabel, file.dataUrl, isUser)
2561
+ }), /* @__PURE__ */ jsx("div", {
2562
+ className: "px-3.5 pb-3.5",
2563
+ children: category === "audio" ? /* @__PURE__ */ jsx("audio", {
2564
+ controls: true,
2565
+ preload: "metadata",
2566
+ "aria-label": file.label,
2567
+ className: "block w-full",
2568
+ children: /* @__PURE__ */ jsx("source", {
2569
+ src: file.dataUrl,
2570
+ ...mediaMimeType ? { type: mediaMimeType } : {}
2571
+ })
2572
+ }) : /* @__PURE__ */ jsx("div", {
2573
+ className: cn("overflow-hidden rounded-[1rem] border", isUser ? "border-white/10 bg-slate-950/26" : "border-slate-200/80 bg-slate-100/80"),
2574
+ children: /* @__PURE__ */ jsx("video", {
2575
+ controls: true,
2576
+ preload: "metadata",
2577
+ playsInline: true,
2578
+ "aria-label": file.label,
2579
+ className: "block max-h-[28rem] w-full bg-black",
2580
+ children: /* @__PURE__ */ jsx("source", {
2581
+ src: file.dataUrl,
2582
+ ...mediaMimeType ? { type: mediaMimeType } : {}
2583
+ })
2584
+ })
2488
2585
  })
2489
2586
  })]
2490
2587
  });
2491
- if (!isInteractive) return /* @__PURE__ */ jsx("div", {
2588
+ }
2589
+ function ChatMessageFile({ file, isUser = false, texts }) {
2590
+ const { category, sizeLabel } = buildChatMessageFileMeta(file);
2591
+ const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
2592
+ const renderAsAudio = category === "audio" && Boolean(file.dataUrl);
2593
+ const renderAsVideo = category === "video" && Boolean(file.dataUrl);
2594
+ const isInteractive = Boolean(file.dataUrl);
2595
+ const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
2596
+ const categoryLabel = readFileCategoryLabel(category, texts);
2597
+ 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"));
2598
+ if (renderAsImage) return renderImagePreview({
2599
+ file,
2600
+ categoryLabel,
2601
+ sizeLabel,
2602
+ isUser
2603
+ });
2604
+ if (renderAsAudio || renderAsVideo) return renderInlineMediaCard({
2605
+ category,
2606
+ file,
2607
+ categoryLabel,
2608
+ sizeLabel,
2609
+ isUser,
2610
+ shellClasses,
2611
+ actionLabel
2612
+ });
2613
+ const content = renderFileCardHeader({
2614
+ category,
2615
+ file,
2616
+ categoryLabel,
2617
+ sizeLabel,
2618
+ isUser,
2619
+ action: renderActionPill(actionLabel, isUser, isInteractive)
2620
+ });
2621
+ if (!isInteractive || !file.dataUrl) return /* @__PURE__ */ jsx("div", {
2492
2622
  className: shellClasses,
2493
2623
  children: content
2494
2624
  });
@@ -2759,13 +2889,11 @@ function ToolCardHeaderSessionAction({ action, onAction }) {
2759
2889
  //#endregion
2760
2890
  //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
2761
2891
  const FILE_TEXT_CLASS_NAME = "font-mono text-[11px] leading-5";
2762
- const FILE_ROW_CLASS_NAME = `h-5 ${FILE_TEXT_CLASS_NAME}`;
2892
+ const FILE_ROW_CLASS_NAME = `flex h-5 w-full ${FILE_TEXT_CLASS_NAME}`;
2763
2893
  const FILE_LINE_NUMBER_CELL_CLASS_NAME = "flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
2764
- function formatLineNumber(value) {
2765
- return typeof value === "number" ? String(value) : "";
2766
- }
2767
2894
  function readVisibleLineNumber(line) {
2768
- return formatLineNumber(line.newLineNumber ?? line.oldLineNumber);
2895
+ const value = line.newLineNumber ?? line.oldLineNumber;
2896
+ return typeof value === "number" ? String(value) : "";
2769
2897
  }
2770
2898
  function readLineKey(prefix, line, index) {
2771
2899
  return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
@@ -2783,9 +2911,8 @@ function readLineNumberColumnWidth(block) {
2783
2911
  }, 0);
2784
2912
  return `${Math.max(6.5, Math.min(8, maxDigits + 3.5))}ch`;
2785
2913
  }
2786
- function buildRowTemplateColumns(params) {
2787
- if (!params.showLineNumbers) return;
2788
- return { gridTemplateColumns: `${params.lineNumberColumnWidth} minmax(0, 1fr)` };
2914
+ function readCodeColumnWidth(block) {
2915
+ return `calc(${block.lines.reduce((currentMax, line) => Math.max(currentMax, Math.max(1, line.text.length)), 1)}ch + 1.25rem)`;
2789
2916
  }
2790
2917
  function getLineNumberTone(line) {
2791
2918
  if (line.kind === "remove") return "border-r border-rose-200 bg-rose-50 text-rose-700";
@@ -2798,29 +2925,27 @@ function getCodeRowTone(line) {
2798
2925
  return "bg-white text-amber-950/80";
2799
2926
  }
2800
2927
  function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
2801
- const lineNumberCellClassName = layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0";
2802
2928
  return /* @__PURE__ */ jsx("span", {
2803
2929
  "data-file-line-number-cell": "true",
2804
- style: layout === "compact" ? { width: lineNumberColumnWidth } : void 0,
2805
- className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, lineNumberCellClassName, getLineNumberTone(line)),
2930
+ style: layout === "compact" ? {
2931
+ width: lineNumberColumnWidth,
2932
+ minWidth: lineNumberColumnWidth
2933
+ } : void 0,
2934
+ className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line)),
2806
2935
  children: readVisibleLineNumber(line)
2807
2936
  });
2808
2937
  }
2809
2938
  function FileOperationCodeCell({ line }) {
2810
2939
  return /* @__PURE__ */ jsx("span", {
2811
2940
  "data-file-code-row": "true",
2812
- className: cn("block min-w-full whitespace-pre px-2.5", getCodeRowTone(line)),
2941
+ className: cn("block min-w-0 flex-1 whitespace-pre px-2.5", getCodeRowTone(line)),
2813
2942
  children: line.text || " "
2814
2943
  });
2815
2944
  }
2816
2945
  function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth }) {
2817
2946
  return /* @__PURE__ */ jsxs("div", {
2818
2947
  "data-file-line-row": "true",
2819
- className: cn("grid w-max min-w-full", FILE_ROW_CLASS_NAME),
2820
- style: buildRowTemplateColumns({
2821
- showLineNumbers,
2822
- lineNumberColumnWidth
2823
- }),
2948
+ className: FILE_ROW_CLASS_NAME,
2824
2949
  children: [showLineNumbers ? /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
2825
2950
  layout: "compact",
2826
2951
  line,
@@ -2831,6 +2956,7 @@ function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth })
2831
2956
  function FileOperationWorkspaceSurface({ block }) {
2832
2957
  const showLineNumbers = readHasBlockLineNumbers(block);
2833
2958
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2959
+ const codeColumnWidth = readCodeColumnWidth(block);
2834
2960
  return /* @__PURE__ */ jsxs("div", {
2835
2961
  "data-file-code-surface": "true",
2836
2962
  "data-file-code-surface-layout": "workspace",
@@ -2850,11 +2976,16 @@ function FileOperationWorkspaceSurface({ block }) {
2850
2976
  }) : null, /* @__PURE__ */ jsxs("div", {
2851
2977
  "data-file-code-canvas": "true",
2852
2978
  className: "flex h-full min-h-full min-w-0 flex-1 flex-col bg-white",
2853
- children: [block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
2854
- "data-file-code-canvas-row": "true",
2855
- className: FILE_ROW_CLASS_NAME,
2856
- children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
2857
- }, readLineKey("code", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-white" })]
2979
+ children: [/* @__PURE__ */ jsx("div", {
2980
+ "data-file-code-stack": "true",
2981
+ className: "min-w-full",
2982
+ style: { minWidth: codeColumnWidth },
2983
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
2984
+ "data-file-code-canvas-row": "true",
2985
+ className: FILE_ROW_CLASS_NAME,
2986
+ children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
2987
+ }, readLineKey("code", line, index)))
2988
+ }), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-white" })]
2858
2989
  })]
2859
2990
  });
2860
2991
  }
@@ -2862,15 +2993,22 @@ function FileOperationCodeSurface({ block, layout = "compact" }) {
2862
2993
  if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, { block });
2863
2994
  const showLineNumbers = readHasBlockLineNumbers(block);
2864
2995
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2996
+ const codeColumnWidth = readCodeColumnWidth(block);
2997
+ const surfaceMinWidth = showLineNumbers ? `calc(${lineNumberColumnWidth} + ${codeColumnWidth})` : codeColumnWidth;
2865
2998
  return /* @__PURE__ */ jsx("div", {
2866
2999
  "data-file-code-surface": "true",
2867
3000
  "data-file-code-surface-layout": "compact",
2868
3001
  className: "overflow-x-auto custom-scrollbar-amber bg-white",
2869
- children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
2870
- line,
2871
- showLineNumbers,
2872
- lineNumberColumnWidth
2873
- }, readLineKey("row", line, index)))
3002
+ children: /* @__PURE__ */ jsx("div", {
3003
+ "data-file-code-stack": "true",
3004
+ className: "min-w-full",
3005
+ style: { minWidth: surfaceMinWidth },
3006
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
3007
+ line,
3008
+ showLineNumbers,
3009
+ lineNumberColumnWidth
3010
+ }, readLineKey("row", line, index)))
3011
+ })
2874
3012
  });
2875
3013
  }
2876
3014
  function FileOperationLinesGrid({ block }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",