@molecule/app-ide-react 1.15.0 → 1.17.0

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 (44) hide show
  1. package/README.md +193 -7
  2. package/dist/components/ChatPanel.d.ts +10 -1
  3. package/dist/components/ChatPanel.d.ts.map +1 -1
  4. package/dist/components/ChatPanel.js +136 -14
  5. package/dist/components/ChatPanel.js.map +1 -1
  6. package/dist/components/MarkdownContent.d.ts.map +1 -1
  7. package/dist/components/MarkdownContent.js +27 -7
  8. package/dist/components/MarkdownContent.js.map +1 -1
  9. package/dist/components/TestStatusBar.d.ts +56 -0
  10. package/dist/components/TestStatusBar.d.ts.map +1 -0
  11. package/dist/components/TestStatusBar.js +105 -0
  12. package/dist/components/TestStatusBar.js.map +1 -0
  13. package/dist/components/TestsCard.d.ts +15 -2
  14. package/dist/components/TestsCard.d.ts.map +1 -1
  15. package/dist/components/TestsCard.js +42 -9
  16. package/dist/components/TestsCard.js.map +1 -1
  17. package/dist/components/ToolCallCard.d.ts +7 -1
  18. package/dist/components/ToolCallCard.d.ts.map +1 -1
  19. package/dist/components/ToolCallCard.js +516 -161
  20. package/dist/components/ToolCallCard.js.map +1 -1
  21. package/dist/components/tests-bar-utilities.d.ts +78 -103
  22. package/dist/components/tests-bar-utilities.d.ts.map +1 -1
  23. package/dist/components/tests-bar-utilities.js +130 -201
  24. package/dist/components/tests-bar-utilities.js.map +1 -1
  25. package/dist/components/tests-card-utilities.d.ts +78 -2
  26. package/dist/components/tests-card-utilities.d.ts.map +1 -1
  27. package/dist/components/tests-card-utilities.js +134 -6
  28. package/dist/components/tests-card-utilities.js.map +1 -1
  29. package/dist/components/tool-call-utilities.d.ts +81 -7
  30. package/dist/components/tool-call-utilities.d.ts.map +1 -1
  31. package/dist/components/tool-call-utilities.js +146 -6
  32. package/dist/components/tool-call-utilities.js.map +1 -1
  33. package/dist/hooks/useTestsBarVisible.d.ts +16 -0
  34. package/dist/hooks/useTestsBarVisible.d.ts.map +1 -0
  35. package/dist/hooks/useTestsBarVisible.js +39 -0
  36. package/dist/hooks/useTestsBarVisible.js.map +1 -0
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/types.d.ts +60 -3
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/package.json +2 -2
@@ -5,7 +5,7 @@ import { useThemeMode } from '@molecule/app-react';
5
5
  import { getClassMap } from '@molecule/app-ui';
6
6
  import { useCoarsePointer, useNarrowViewport } from '../hooks/useViewport.js';
7
7
  import { MarkdownContent } from './MarkdownContent.js';
8
- import { basename, extractFilePath, fileDiffStats, moleculeDocPath, normalizeAskUserInput, num, str, toolLabel, toolSummary, } from './tool-call-utilities.js';
8
+ import { basename, extractFilePath, fileDiffStats, isSkippedByUser, moleculeDocPath, normalizeAskUserInput, normalizeTaskListInput, num, parseJudgeVerdict, str, toolLabel, toolSummary, } from './tool-call-utilities.js';
9
9
  /**
10
10
  * Normalize a model-authored string for display: models sometimes double-escape
11
11
  * tool arguments, so a literal "\n"/"\t"/"\r" (backslash + letter) ends up in
@@ -305,7 +305,7 @@ function renderOut(name, output) {
305
305
  * @param props - Component props.
306
306
  * @returns The rendered tool call card element.
307
307
  */
308
- export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output, status, fileDiff, isUndone: isUndoneProp, onUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onAskUserResponse, className, }) {
308
+ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output, status, fileDiff, isUndone: isUndoneProp, onUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onAskUserResponse, onSkip, skipDisabledReason, className, }) {
309
309
  const cm = getClassMap();
310
310
  const isLight = useThemeMode() === 'light';
311
311
  // Touch/phone branches (called unconditionally, before the per-tool early
@@ -319,7 +319,14 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
319
319
  const [isUndoneLocal, setIsUndoneLocal] = useState(false);
320
320
  const isUndone = isUndoneProp ?? isUndoneLocal;
321
321
  const [isReverting, setIsReverting] = useState(false);
322
+ const [skipRequested, setSkipRequested] = useState(false);
323
+ // The person skipped this call. It is neither a success nor a failure, and
324
+ // the row has to say so — a green dot on a command that never ran is exactly
325
+ // the kind of quiet lie the honesty rule exists to stop.
326
+ const wasSkipped = isSkippedByUser(output);
322
327
  const hasError = (() => {
328
+ if (wasSkipped)
329
+ return false;
323
330
  if (status === 'error')
324
331
  return true;
325
332
  if (typeof output !== 'object' || output === null)
@@ -337,8 +344,9 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
337
344
  }
338
345
  return false;
339
346
  })();
340
- // gray → orange → green or red
341
- const dotColor = status === 'pending'
347
+ // gray → orange → green or red; a skipped call stays gray, because it did
348
+ // not happen.
349
+ const dotColor = status === 'pending' || wasSkipped
342
350
  ? '#888888'
343
351
  : status === 'running'
344
352
  ? '#e8a000'
@@ -382,6 +390,27 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
382
390
  setIsReverting(false);
383
391
  }
384
392
  }, [canRevert, isReverting, isUndone, fileDiff, filePath, onFileRevert, onUndoToggle, id]);
393
+ // Skip this call while it is still running — the turn keeps going, the model
394
+ // is told the command did not run. A host that answers `false` means the call
395
+ // had already finished (the stale-button race), which is not an error: the
396
+ // button simply returns to its resting state.
397
+ const canSkip = status === 'running' && onSkip != null;
398
+ const handleSkip = useCallback(async () => {
399
+ if (!onSkip || skipRequested || skipDisabledReason)
400
+ return;
401
+ setSkipRequested(true);
402
+ try {
403
+ const accepted = await onSkip(id);
404
+ if (accepted === false)
405
+ setSkipRequested(false);
406
+ }
407
+ catch (_error) {
408
+ // Deliberate noop: the host already logs whatever went wrong on its side,
409
+ // and there is nothing for the PERSON to do here — the call is still
410
+ // running, so the honest UI is the button back at rest to try again.
411
+ setSkipRequested(false);
412
+ }
413
+ }, [onSkip, skipRequested, skipDisabledReason, id]);
385
414
  // Tools that expand to show details inline.
386
415
  const EXPANDABLE = new Set([
387
416
  'exec_command',
@@ -413,6 +442,145 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
413
442
  setExpanded((e) => !e);
414
443
  }
415
444
  : undefined;
445
+ // ── update_task_list: the working checklist the user watches ───────────────
446
+ if (name === 'update_task_list') {
447
+ const todos = normalizeTaskListInput(input);
448
+ const completed = todos.filter((todo) => todo.status === 'completed').length;
449
+ const borderClr = isLight ? '#d0d7de' : '#3d444d';
450
+ const statusGlyph = (status) => status === 'completed' ? '✓' : status === 'in_progress' ? '◐' : '○';
451
+ const statusColor = (status) => status === 'completed'
452
+ ? '#3fb950'
453
+ : status === 'in_progress'
454
+ ? isLight
455
+ ? '#2563eb'
456
+ : '#60a5fa'
457
+ : isLight
458
+ ? '#848d97'
459
+ : '#6e7681';
460
+ return (_jsxs("div", { className: className, "data-mol-id": "task-list-card", style: {
461
+ marginBottom: '8px',
462
+ marginTop: '8px',
463
+ borderRadius: '8px',
464
+ border: `1px solid ${borderClr}`,
465
+ background: isLight ? '#f6f8fa' : 'rgba(255,255,255,0.04)',
466
+ overflow: 'hidden',
467
+ }, children: [_jsxs("div", { style: {
468
+ display: 'flex',
469
+ alignItems: 'center',
470
+ justifyContent: 'space-between',
471
+ gap: '8px',
472
+ padding: '6px 12px',
473
+ borderBottom: todos.length > 0 ? `1px solid ${borderClr}` : 'none',
474
+ fontSize: '12px',
475
+ fontWeight: 600,
476
+ }, children: [_jsx("span", { children: t('ide.chat.taskListTitle', undefined, { defaultValue: 'Working plan' }) }), todos.length > 0 && (_jsx("span", { style: { fontWeight: 400, opacity: 0.65 }, children: t('ide.chat.taskListProgress', { done: completed, total: todos.length }, {
477
+ defaultValue: '{{done}} of {{total}} done',
478
+ }) }))] }), todos.map((todo, i) => (_jsxs("div", { style: {
479
+ display: 'flex',
480
+ alignItems: 'flex-start',
481
+ gap: '8px',
482
+ padding: '4px 12px',
483
+ borderTop: i > 0 ? `1px solid ${isLight ? '#eaeef2' : 'rgba(255,255,255,0.06)'}` : 'none',
484
+ fontSize: '12.5px',
485
+ ...(todo.status === 'completed'
486
+ ? { opacity: 0.55, textDecoration: 'line-through' }
487
+ : {}),
488
+ }, children: [_jsx("span", { "aria-hidden": true, style: {
489
+ flexShrink: 0,
490
+ width: 16,
491
+ fontSize: '12px',
492
+ lineHeight: '17px',
493
+ color: statusColor(todo.status),
494
+ fontWeight: 600,
495
+ }, children: statusGlyph(todo.status) }), _jsxs("span", { style: { flex: 1, minWidth: 0 }, children: [todo.priority === 'high' && todo.status !== 'completed' && (_jsx("span", { style: {
496
+ color: isLight ? '#d63a2f' : '#f87171',
497
+ fontWeight: 600,
498
+ marginRight: '4px',
499
+ }, children: "!" })), todo.content] })] }, i)))] }));
500
+ }
501
+ // ── spawn_agent: subagent card — explore report / judge verdict ─────────────
502
+ if (name === 'spawn_agent') {
503
+ const inp = (input ?? {});
504
+ const kind = inp.type === 'judge' ? 'judge' : 'explore';
505
+ const out = (output ?? {});
506
+ const errorText = typeof out.error === 'string' ? out.error : '';
507
+ const report = typeof out.report === 'string' ? out.report : '';
508
+ const verdict = kind === 'judge' ? parseJudgeVerdict(report) : null;
509
+ const [expanded, setExpanded] = useState(false);
510
+ const borderClr = isLight ? '#d0d7de' : '#3d444d';
511
+ const verdictTint = isLight ? 'rgba(63,185,80,0.08)' : 'rgba(63,185,80,0.12)';
512
+ const failTint = isLight ? 'rgba(248,81,73,0.08)' : 'rgba(248,81,73,0.14)';
513
+ const verdictColor = verdict?.verdict === 'PASS' ? '#3fb950' : '#f8554f';
514
+ const bodyText = verdict ? verdict.rest : report;
515
+ return (_jsxs("div", { className: className, "data-mol-id": "subagent-card", style: {
516
+ marginBottom: '8px',
517
+ marginTop: '8px',
518
+ borderRadius: '8px',
519
+ border: `1px solid ${borderClr}`,
520
+ background: isLight ? '#f6f8fa' : 'rgba(255,255,255,0.04)',
521
+ overflow: 'hidden',
522
+ }, children: [_jsxs("div", { style: {
523
+ display: 'flex',
524
+ alignItems: 'center',
525
+ gap: '8px',
526
+ padding: '6px 12px',
527
+ fontSize: '12px',
528
+ fontWeight: 600,
529
+ borderBottom: report || errorText ? `1px solid ${borderClr}` : 'none',
530
+ }, children: [_jsx("span", { "aria-hidden": true, style: {
531
+ width: 8,
532
+ height: 8,
533
+ borderRadius: '50%',
534
+ flexShrink: 0,
535
+ background: status === 'error' || errorText
536
+ ? '#f04040'
537
+ : status === 'done'
538
+ ? '#3fb950'
539
+ : status === 'running'
540
+ ? '#e8a000'
541
+ : '#888888',
542
+ } }), _jsx("span", { children: kind === 'judge'
543
+ ? t('ide.chat.subagent.judge', undefined, { defaultValue: 'Acceptance judge' })
544
+ : t('ide.chat.subagent.explore', undefined, { defaultValue: 'Research subagent' }) }), status === 'running' && (_jsx("span", { style: { fontWeight: 400, opacity: 0.6 }, children: t('ide.chat.subagent.working', undefined, { defaultValue: 'working…' }) }))] }), errorText && (_jsx("div", { style: {
545
+ padding: '8px 12px',
546
+ fontSize: '12px',
547
+ color: '#f8554f',
548
+ borderBottom: `1px solid ${borderClr}`,
549
+ }, children: errorText })), verdict && (_jsxs("div", { "data-mol-id": "subagent-verdict", style: {
550
+ padding: '8px 12px',
551
+ background: verdict.verdict === 'PASS' ? verdictTint : failTint,
552
+ borderBottom: `1px solid ${borderClr}`,
553
+ }, children: [_jsxs("div", { style: {
554
+ display: 'flex',
555
+ alignItems: 'center',
556
+ gap: '6px',
557
+ fontSize: '13px',
558
+ fontWeight: 700,
559
+ color: verdictColor,
560
+ }, children: [verdict.verdict === 'PASS' ? '✓' : '✕', verdict.verdict === 'PASS'
561
+ ? t('ide.chat.subagent.verdictPass', undefined, { defaultValue: 'PASS' })
562
+ : t('ide.chat.subagent.verdictFail', undefined, { defaultValue: 'FAIL' })] }), verdict.issues.length > 0 && (_jsx("ul", { style: {
563
+ margin: '6px 0 0',
564
+ paddingLeft: '18px',
565
+ fontSize: '12px',
566
+ display: 'flex',
567
+ flexDirection: 'column',
568
+ gap: '2px',
569
+ opacity: 0.9,
570
+ }, children: verdict.issues.map((issue, i) => (_jsx("li", { children: issue }, i))) }))] })), bodyText && (_jsxs("div", { style: { padding: '8px 12px' }, children: [_jsx("div", { style: {
571
+ fontSize: '12px',
572
+ whiteSpace: 'pre-wrap',
573
+ maxHeight: expanded ? undefined : 150,
574
+ overflow: 'hidden',
575
+ opacity: 0.9,
576
+ }, children: bodyText }), bodyText.length > 600 && (_jsx("button", { type: "button", "data-mol-id": "subagent-report-toggle", className: cm.button({ variant: 'link', color: 'primary', size: 'xs' }), onClick: () => setExpanded((e) => !e), style: { marginTop: '4px' }, children: expanded
577
+ ? t('ide.chat.subagent.hideReport', undefined, {
578
+ defaultValue: 'Show less',
579
+ })
580
+ : t('ide.chat.subagent.showReport', undefined, {
581
+ defaultValue: 'Show full report',
582
+ }) }))] }))] }));
583
+ }
416
584
  // ── save_plan: clickable row that opens the plan file in the editor ──────────
417
585
  if (name === 'save_plan') {
418
586
  const planOutput = (output ?? {});
@@ -452,10 +620,11 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
452
620
  }
453
621
  // ── ask_user: render interactive option list instead of a normal tool card ──
454
622
  if (name === 'ask_user') {
455
- // NEVER render raw tool input. `options` is declared `string[]` in the tool
456
- // schema, but the schema is a request to a language model, not a guarantee —
457
- // weaker models send `[{ label: 'Recipe box' }]`, and an object reaching JSX
458
- // throws React error #31 during render, which takes down the entire IDE.
623
+ // NEVER render raw tool input. `options` is declared as strings-or-objects
624
+ // in the tool schema, but the schema is a request to a language model, not
625
+ // a guarantee — weaker models send `[{ label: 'Recipe box' }]`, nested
626
+ // arrays, or quote-escaped pseudo-JSON, and an object reaching JSX throws
627
+ // React error #31 during render, which takes down the entire IDE.
459
628
  const askInput = normalizeAskUserInput(input);
460
629
  const askOutput = output;
461
630
  const serverAwaiting = typeof askOutput === 'object' && askOutput?.status === 'awaiting_response';
@@ -465,6 +634,12 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
465
634
  const selectedAnswer = isResponded ? askOutput : localAnswer;
466
635
  const [freeText, setFreeText] = useState('');
467
636
  const [hoveredIdx, setHoveredIdx] = useState(null);
637
+ // Multi-select picks in progress (labels); sent as one string joined with
638
+ // '; ' on confirm — the same shape an answered multi-select persists as.
639
+ const [multiPicks, setMultiPicks] = useState([]);
640
+ // Narrow screens have no room for the side-by-side preview pane; there a
641
+ // preview expands inline under its option row instead.
642
+ const [expandedPreviewIdx, setExpandedPreviewIdx] = useState(null);
468
643
  // Don't render until the question has streamed in. `tool_use_start` surfaces
469
644
  // the card the instant the call begins — before its input arrives — which
470
645
  // would otherwise flash an empty question + option list. Once the full input
@@ -474,6 +649,34 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
474
649
  return null;
475
650
  const borderClr = isLight ? '#d0d7de' : '#3d444d';
476
651
  const labelChar = (i) => String.fromCharCode(65 + i); // A, B, C, …
652
+ const hasPreviews = askInput.options.some((option) => option.preview);
653
+ const multi = askInput.multiSelect && askInput.options.length > 0;
654
+ // The plan-approval card renders its two decisions as prominent design-
655
+ // system CTAs (approve = primary solid, request changes = outline) instead
656
+ // of letter-badged rows — this is the product's single most consequential
657
+ // click, and it should look like it.
658
+ const reviewCtas = !!askInput.planReview && !multi && askInput.options.length === 2 && !hasPreviews;
659
+ const answeredLabels = multi && typeof selectedAnswer === 'string'
660
+ ? selectedAnswer
661
+ .split('; ')
662
+ .map((part) => part.trim())
663
+ .filter(Boolean)
664
+ : [];
665
+ const selectedSet = new Set([...multiPicks, ...answeredLabels]);
666
+ // Which option's preview the pane shows: the hovered/focused one, else the
667
+ // picked one, else the first option that has a preview at all.
668
+ const previewIdx = (() => {
669
+ if (!hasPreviews)
670
+ return null;
671
+ const candidate = hoveredIdx ?? expandedPreviewIdx;
672
+ if (candidate != null && askInput.options[candidate]?.preview)
673
+ return candidate;
674
+ const pickedIdx = askInput.options.findIndex((option) => selectedSet.has(option.label));
675
+ if (pickedIdx >= 0 && askInput.options[pickedIdx].preview)
676
+ return pickedIdx;
677
+ return askInput.options.findIndex((option) => option.preview);
678
+ })();
679
+ const previewOption = previewIdx != null ? askInput.options[previewIdx] : undefined;
477
680
  return (_jsxs("div", { className: className, style: {
478
681
  marginBottom: '8px',
479
682
  marginTop: '8px',
@@ -481,71 +684,213 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
481
684
  border: `1px solid ${borderClr}`,
482
685
  background: isLight ? '#f6f8fa' : 'rgba(255,255,255,0.04)',
483
686
  overflow: 'hidden',
484
- }, children: [_jsx("div", { style: {
687
+ }, children: [askInput.planReview ? (_jsxs("div", { "data-mol-id": "plan-review-card", style: {
688
+ padding: '10px 12px 10px',
689
+ fontSize: '13px',
690
+ borderBottom: `1px solid ${borderClr}`,
691
+ }, children: [_jsx("div", { style: {
692
+ fontSize: '11px',
693
+ fontWeight: 600,
694
+ letterSpacing: '0.03em',
695
+ textTransform: 'uppercase',
696
+ opacity: 0.6,
697
+ marginBottom: '6px',
698
+ }, children: t('ide.chat.planReview.badge', undefined, {
699
+ defaultValue: 'Plan ready for review',
700
+ }) }), _jsx("div", { style: { fontSize: '14px', fontWeight: 600, marginBottom: '4px' }, children: askInput.planReview.name ||
701
+ (askInput.planReview.path ? basename(askInput.planReview.path) : '') ||
702
+ t('ide.chat.planReview.fallbackTitle', undefined, {
703
+ defaultValue: 'Implementation plan',
704
+ }) }), _jsxs("div", { style: {
705
+ display: 'flex',
706
+ alignItems: 'center',
707
+ gap: '10px',
708
+ fontSize: '12px',
709
+ opacity: 0.75,
710
+ marginBottom: askInput.planReview.preview.length > 0 ? '8px' : 0,
711
+ }, children: [_jsx("span", { children: t('ide.chat.planReview.steps', { count: askInput.planReview.steps }, {
712
+ defaultValue: '{{count}} steps',
713
+ }) }), askInput.planReview.path && onFileOpen && (_jsx("button", { type: "button", "data-mol-id": "plan-review-open", className: cm.button({ variant: 'link', color: 'primary', size: 'xs' }), onClick: () => onFileOpen(askInput.planReview.path), children: t('ide.chat.planReview.openPlan', undefined, { defaultValue: 'Open plan' }) }))] }), askInput.planReview.preview.length > 0 && (_jsx("ul", { style: {
714
+ margin: 0,
715
+ paddingLeft: '18px',
716
+ fontSize: '12px',
717
+ opacity: 0.8,
718
+ display: 'flex',
719
+ flexDirection: 'column',
720
+ gap: '2px',
721
+ }, children: askInput.planReview.preview.map((step, i) => (_jsx("li", { children: step }, i))) })), askInput.planReview.steps > askInput.planReview.preview.length && (_jsx("div", { style: { fontSize: '11px', opacity: 0.55, marginTop: '4px' }, children: t('ide.chat.planReview.more', { count: askInput.planReview.steps - askInput.planReview.preview.length }, { defaultValue: '+ {{count}} more steps' }) }))] })) : (_jsxs("div", { style: {
485
722
  padding: '4px 12px 10px',
486
723
  fontSize: '13px',
487
724
  borderBottom: `1px solid ${borderClr}`,
488
- }, children: _jsx(MarkdownContent, { text: unescapeLiterals(askInput.question), isStreaming: false }) }), askInput.options.map((option, i) => {
489
- const isSelected = selectedAnswer === option;
490
- const isFaded = !isAwaiting && !isSelected;
491
- const isHover = isAwaiting && hoveredIdx === i;
492
- return (_jsxs("button", { type: "button", "data-mol-id": `ask-user-option-${i}`, disabled: !isAwaiting || onAskUserResponse == null, onClick: () => {
493
- // No handler = read-only (a project viewer): answering is editor work.
494
- if (onAskUserResponse == null)
495
- return;
496
- setLocalAnswer(option);
497
- onAskUserResponse(option);
498
- }, onMouseEnter: () => {
499
- if (isAwaiting)
500
- setHoveredIdx(i);
501
- }, onMouseLeave: () => setHoveredIdx(null), style: {
502
- display: 'flex',
503
- alignItems: 'center',
504
- gap: '10px',
505
- width: '100%',
506
- padding: '8px 12px',
507
- // Touch: full 44px rows — these are the PRIMARY discovery answers,
508
- // so they get the standalone-control floor, not the dense-row 32.
509
- ...(isCoarse ? { minHeight: 44 } : {}),
510
- border: 'none',
511
- borderTop: i > 0 ? `1px solid ${borderClr}` : 'none',
512
- background: isSelected
513
- ? isLight
514
- ? '#dbeafe'
515
- : 'rgba(59,130,246,0.2)'
516
- : isHover
517
- ? isLight
518
- ? '#eaeef2'
519
- : 'rgba(255,255,255,0.06)'
520
- : 'transparent',
521
- color: 'inherit',
522
- cursor: isAwaiting ? 'pointer' : 'default',
523
- textAlign: 'left',
524
- fontSize: '13px',
525
- opacity: isFaded ? 0.4 : 1,
526
- transition: 'background 80ms, opacity 80ms',
527
- }, children: [_jsx("span", { style: {
528
- display: 'inline-flex',
529
- alignItems: 'center',
530
- justifyContent: 'center',
531
- width: 22,
532
- height: 22,
533
- borderRadius: '5px',
534
- border: `1px solid ${isSelected ? (isLight ? '#93c5fd' : '#3b82f6') : borderClr}`,
535
- background: isSelected
536
- ? isLight
537
- ? '#3b82f6'
538
- : '#2563eb'
539
- : isLight
540
- ? '#fff'
541
- : 'rgba(255,255,255,0.08)',
542
- color: isSelected ? '#fff' : isLight ? '#57606a' : '#848d97',
543
- fontSize: '11px',
544
- fontWeight: 600,
545
- flexShrink: 0,
546
- fontFamily: '"SF Mono", Menlo, Consolas, "Courier New", monospace',
547
- }, children: labelChar(i) }), _jsx("span", { style: { flex: 1 }, children: option }), isSelected && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "14", height: "14", fill: isLight ? '#2563eb' : '#60a5fa', children: _jsx("path", { d: "M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" }) }))] }, i));
548
- }), isAwaiting && askInput.allowFreeText !== false && onAskUserResponse != null && (_jsxs("div", { style: {
725
+ }, children: [_jsx(MarkdownContent, { text: unescapeLiterals(askInput.question), isStreaming: false }), multi && isAwaiting && (_jsx("div", { style: { fontSize: '11px', fontStyle: 'italic', opacity: 0.55, marginTop: '4px' }, children: t('ide.chat.askUserMultiHint', undefined, {
726
+ defaultValue: 'You can choose more than one.',
727
+ }) }))] })), _jsxs("div", { style: { display: 'flex', alignItems: 'stretch' }, children: [_jsxs("div", { style: {
728
+ flex: hasPreviews && !isNarrow ? '1 1 55%' : '1 1 100%',
729
+ minWidth: 0,
730
+ display: 'flex',
731
+ flexDirection: 'column',
732
+ }, children: [reviewCtas ? (_jsx("div", { style: {
733
+ display: 'flex',
734
+ flexWrap: 'wrap',
735
+ gap: '8px',
736
+ padding: '10px 12px',
737
+ }, children: askInput.options.map((option, i) => {
738
+ const isPicked = selectedAnswer === option.label;
739
+ return (_jsx("button", { type: "button", "data-mol-id": `plan-review-cta-${i}`, disabled: !isAwaiting || onAskUserResponse == null, className: cm.cn(cm.button(i === 0
740
+ ? { variant: 'solid', color: 'primary', size: 'sm' }
741
+ : { variant: 'outline', color: 'secondary', size: 'sm' }), cm.touchTargetCompact), style: {
742
+ flex: isNarrow ? '1 1 100%' : '1 1 auto',
743
+ ...(isPicked ? { outline: '2px solid #3fb950', outlineOffset: '1px' } : {}),
744
+ }, onClick: () => {
745
+ if (onAskUserResponse == null)
746
+ return;
747
+ setLocalAnswer(option.label);
748
+ onAskUserResponse(option.label);
749
+ }, children: isPicked ? `✓ ${option.label}` : option.label }, i));
750
+ }) })) : (askInput.options.map((option, i) => {
751
+ const isSelected = selectedSet.has(option.label);
752
+ const isFaded = !isAwaiting && !isSelected;
753
+ const isHover = isAwaiting && hoveredIdx === i;
754
+ const isPreviewExpanded = expandedPreviewIdx === i;
755
+ return (_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'stretch' }, children: [_jsxs("button", { type: "button", "data-mol-id": `ask-user-option-${i}`, disabled: !isAwaiting || onAskUserResponse == null, onClick: () => {
756
+ // No handler = read-only (a project viewer): answering is editor work.
757
+ if (onAskUserResponse == null)
758
+ return;
759
+ if (multi) {
760
+ // Multi-select toggles a pick; a Confirm row submits them.
761
+ setMultiPicks((picks) => picks.includes(option.label)
762
+ ? picks.filter((p) => p !== option.label)
763
+ : [...picks, option.label]);
764
+ return;
765
+ }
766
+ setLocalAnswer(option.label);
767
+ onAskUserResponse(option.label);
768
+ }, onFocus: () => {
769
+ if (isAwaiting)
770
+ setHoveredIdx(i);
771
+ }, onMouseEnter: () => {
772
+ if (isAwaiting)
773
+ setHoveredIdx(i);
774
+ }, onMouseLeave: () => setHoveredIdx(null), style: {
775
+ display: 'flex',
776
+ alignItems: 'center',
777
+ gap: '10px',
778
+ flex: 1,
779
+ minWidth: 0,
780
+ padding: option.description ? '7px 12px' : '8px 12px',
781
+ // Touch: full 44px rows — these are the PRIMARY discovery answers,
782
+ // so they get the standalone-control floor, not the dense-row 32.
783
+ ...(isCoarse ? { minHeight: 44 } : {}),
784
+ border: 'none',
785
+ borderTop: i > 0 ? `1px solid ${borderClr}` : 'none',
786
+ background: isSelected
787
+ ? isLight
788
+ ? '#dbeafe'
789
+ : 'rgba(59,130,246,0.2)'
790
+ : isHover
791
+ ? isLight
792
+ ? '#eaeef2'
793
+ : 'rgba(255,255,255,0.06)'
794
+ : 'transparent',
795
+ color: 'inherit',
796
+ cursor: isAwaiting ? 'pointer' : 'default',
797
+ textAlign: 'left',
798
+ fontSize: '13px',
799
+ opacity: isFaded ? 0.4 : 1,
800
+ transition: 'background 80ms, opacity 80ms',
801
+ }, children: [_jsx("span", { style: {
802
+ display: 'inline-flex',
803
+ alignItems: 'center',
804
+ justifyContent: 'center',
805
+ width: 22,
806
+ height: 22,
807
+ borderRadius: multi ? '6px' : '5px',
808
+ border: `1px solid ${isSelected ? (isLight ? '#93c5fd' : '#3b82f6') : borderClr}`,
809
+ background: isSelected
810
+ ? isLight
811
+ ? '#3b82f6'
812
+ : '#2563eb'
813
+ : isLight
814
+ ? '#fff'
815
+ : 'rgba(255,255,255,0.08)',
816
+ color: isSelected ? '#fff' : isLight ? '#57606a' : '#848d97',
817
+ fontSize: '11px',
818
+ fontWeight: 600,
819
+ flexShrink: 0,
820
+ fontFamily: '"SF Mono", Menlo, Consolas, "Courier New", monospace',
821
+ }, children: multi && isSelected ? '✓' : labelChar(i) }), _jsxs("span", { style: { flex: 1, minWidth: 0 }, children: [_jsx("span", { style: { display: 'block' }, children: option.label }), option.description && (_jsx("span", { style: {
822
+ display: 'block',
823
+ fontSize: '11px',
824
+ opacity: 0.65,
825
+ marginTop: '1px',
826
+ }, children: option.description }))] }), !multi && isSelected && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "14", height: "14", fill: isLight ? '#2563eb' : '#60a5fa', children: _jsx("path", { d: "M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" }) }))] }), hasPreviews && isNarrow && option.preview && (_jsx("button", { type: "button", "data-mol-id": `ask-user-option-preview-${i}`, "aria-label": t('ide.chat.askUserPreview', undefined, {
827
+ defaultValue: 'Preview',
828
+ }), disabled: !isAwaiting, onClick: () => setExpandedPreviewIdx(isPreviewExpanded ? null : i), style: {
829
+ flexShrink: 0,
830
+ width: 36,
831
+ border: 'none',
832
+ borderTop: i > 0 ? `1px solid ${borderClr}` : 'none',
833
+ borderLeft: `1px solid ${borderClr}`,
834
+ background: isPreviewExpanded
835
+ ? isLight
836
+ ? '#eaeef2'
837
+ : 'rgba(255,255,255,0.06)'
838
+ : 'transparent',
839
+ color: 'inherit',
840
+ cursor: isAwaiting ? 'pointer' : 'default',
841
+ fontSize: '14px',
842
+ opacity: 0.7,
843
+ transform: isPreviewExpanded ? 'rotate(90deg)' : 'none',
844
+ transition: 'transform 80ms',
845
+ }, children: "\u203A" }))] }), hasPreviews && isNarrow && option.preview && isPreviewExpanded && (_jsx("div", { style: {
846
+ borderTop: `1px solid ${borderClr}`,
847
+ background: isLight ? '#fff' : 'rgba(255,255,255,0.03)',
848
+ padding: '8px 12px',
849
+ maxHeight: 240,
850
+ overflowY: 'auto',
851
+ fontSize: '12px',
852
+ }, children: _jsx(MarkdownContent, { text: option.preview, isStreaming: false }) }))] }, i));
853
+ })), multi && isAwaiting && onAskUserResponse != null && (_jsxs("div", { style: {
854
+ display: 'flex',
855
+ justifyContent: 'flex-end',
856
+ gap: '8px',
857
+ alignItems: 'center',
858
+ padding: '8px 12px',
859
+ borderTop: `1px solid ${borderClr}`,
860
+ }, children: [_jsx("span", { style: { fontSize: '11px', opacity: 0.6 }, children: t('ide.chat.askUserSelectedCount', undefined, {
861
+ defaultValue: '{{count}} selected',
862
+ count: multiPicks.length,
863
+ }) }), _jsx("button", { type: "button", "data-mol-id": "ask-user-multiselect-confirm", disabled: multiPicks.length === 0, className: cm.cn(cm.button({ variant: 'solid', color: 'primary', size: 'xs' }), cm.touchTargetCompact), onClick: () => {
864
+ if (multiPicks.length === 0)
865
+ return;
866
+ setLocalAnswer(multiPicks.join('; '));
867
+ onAskUserResponse(multiPicks.join('; '));
868
+ }, children: t('ide.chat.askUserMultiConfirm', undefined, {
869
+ defaultValue: 'Confirm choice',
870
+ }) })] }))] }), hasPreviews && !isNarrow && (_jsxs("div", { style: {
871
+ flex: '1 1 45%',
872
+ minWidth: 0,
873
+ borderLeft: `1px solid ${borderClr}`,
874
+ display: 'flex',
875
+ flexDirection: 'column',
876
+ background: isLight ? '#fff' : 'rgba(255,255,255,0.03)',
877
+ }, children: [_jsx("div", { style: {
878
+ padding: '6px 12px',
879
+ fontSize: '11px',
880
+ fontWeight: 600,
881
+ opacity: 0.6,
882
+ borderBottom: `1px solid ${borderClr}`,
883
+ whiteSpace: 'nowrap',
884
+ overflow: 'hidden',
885
+ textOverflow: 'ellipsis',
886
+ }, children: previewOption
887
+ ? previewOption.label
888
+ : t('ide.chat.askUserPreview', undefined, { defaultValue: 'Preview' }) }), _jsx("div", { style: {
889
+ padding: '8px 12px',
890
+ maxHeight: 360,
891
+ overflowY: 'auto',
892
+ fontSize: '12px',
893
+ }, children: previewOption?.preview && (_jsx(MarkdownContent, { text: previewOption.preview, isStreaming: false })) })] }))] }), isAwaiting && askInput.allowFreeText !== false && onAskUserResponse != null && (_jsxs("div", { style: {
549
894
  display: 'flex',
550
895
  gap: '4px',
551
896
  padding: '8px 12px',
@@ -610,7 +955,7 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
610
955
  fontWeight: 500,
611
956
  opacity: freeText.trim() ? 1 : 0.3,
612
957
  transition: 'background 100ms, border-color 100ms, color 100ms',
613
- }, children: t('ide.chat.askUserSubmit', undefined, { defaultValue: 'Send' }) })] })), selectedAnswer && !askInput.options.includes(selectedAnswer) && (_jsx("div", { style: {
958
+ }, children: t('ide.chat.askUserSubmit', undefined, { defaultValue: 'Send' }) })] })), selectedAnswer && !askInput.options.some((option) => option.label === selectedAnswer) && (_jsx("div", { style: {
614
959
  padding: '8px 12px',
615
960
  borderTop: `1px solid ${borderClr}`,
616
961
  fontSize: '12px',
@@ -623,97 +968,107 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
623
968
  opacity: 0.5,
624
969
  }, children: askInput.hint }))] }));
625
970
  }
626
- return (_jsxs("div", { className: className, style: { marginBottom: '4px' }, children: [_jsxs("button", { type: "button", onClick: handleClick, onDoubleClick: isNewFile && filePath && onFileDoubleClick
627
- ? () => {
628
- onFileDoubleClick(filePath);
629
- }
630
- : undefined, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), style: {
631
- display: 'flex',
632
- // Coarse: the undo icon's 32px touch box inflates the label row, so
633
- // top-alignment would leave the diff stats/chevron riding high —
634
- // center everything instead. Desktop keeps flex-start + px nudges.
635
- alignItems: isCoarse ? 'center' : 'flex-start',
636
- gap: '6px',
637
- background: 'none',
638
- border: 'none',
639
- cursor: handleClick ? 'pointer' : 'default',
640
- color: 'inherit',
641
- textAlign: 'left',
642
- padding: '2px 0',
643
- // Touch: ~20px rows are untappable — 32px is the dense-row floor.
644
- ...(isCoarse ? { minHeight: 32 } : {}),
645
- width: '100%',
646
- }, children: [_jsx("span", { style: { flex: 1, minWidth: 0 }, children: _jsxs("span", { style: { fontSize: '13px', display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsxs("svg", { width: "10", height: "10", viewBox: "0 0 10 10", style: { flexShrink: 0 }, children: [_jsx("circle", { cx: "5", cy: "5", r: "3", fill: dotColor, opacity: "0.35" }), _jsx("circle", { cx: "5", cy: "5", r: "3", fill: "none", stroke: dotColor, strokeWidth: "2" })] }), _jsx("span", { style: {
647
- flex: 1,
648
- minWidth: 0,
649
- overflow: 'hidden',
650
- textOverflow: 'ellipsis',
651
- whiteSpace: 'nowrap',
652
- }, children: renderLabel(name, input, filePath, onFileOpen, onFileDoubleClick) }), summary && (_jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { flexShrink: 0, marginLeft: '8px', whiteSpace: 'nowrap' }, children: summary })), canRevert && status !== 'running' && (_jsx("span", { role: "button", tabIndex: 0, title: isUndone
653
- ? t('ide.chat.redoChange', undefined, { defaultValue: 'Re-apply this change' })
654
- : t('ide.chat.undoChange', undefined, { defaultValue: 'Undo this change' }), onClick: handleRevert, onKeyDown: (e) => {
655
- if (e.key === 'Enter' || e.key === ' ') {
656
- e.preventDefault();
657
- handleRevert(e);
658
- }
659
- }, onMouseEnter: (e) => {
660
- e.currentTarget.style.background = 'rgba(128,128,128,0.2)';
661
- e.currentTarget.style.opacity = '1';
662
- }, onMouseLeave: (e) => {
663
- e.currentTarget.style.background = 'transparent';
664
- e.currentTarget.style.opacity = '';
665
- }, style: {
666
- display: 'inline-flex',
667
- alignItems: 'center',
668
- justifyContent: 'center',
669
- // Touch: hover can't reveal it, so it rests visible at 0.6 and
670
- // gets a 32px hit box (the floor for these dense inline rows).
671
- width: isCoarse ? 32 : 20,
672
- height: isCoarse ? 32 : 20,
673
- borderRadius: 4,
674
- flexShrink: 0,
675
- // Nudge up 1px: the 13px glyph sat slightly below the text's
676
- // optical center of the compact 20px box (moot at 32px).
677
- position: 'relative',
678
- top: isCoarse ? 0 : '-1px',
679
- cursor: isReverting ? 'wait' : 'pointer',
680
- opacity: isReverting ? 0.3 : isHovered || isCoarse ? 0.6 : 0,
681
- transition: 'opacity 100ms, background 100ms',
682
- }, children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: isUndone ? (_jsx("path", { d: "M14.78 6.28a.749.749 0 0 0 0-1.06l-3.5-3.5a.749.749 0 1 0-1.06 1.06L12.439 5H5.251l-.001.007L5.251 5a.8.8 0 0 0-.171.019A4.501 4.501 0 0 0 5.5 14h1.704a.75.75 0 0 0 0-1.5H5.5a3 3 0 1 1 0-6h6.939L10.22 8.72a.749.749 0 1 0 1.06 1.06l3.5-3.5Z" })) : (_jsx("path", { d: "M1.22 6.28a.749.749 0 0 1 0-1.06l3.5-3.5a.749.749 0 1 1 1.06 1.06L3.561 5h7.188l.001.007L10.749 5c.058 0 .116.007.171.019A4.501 4.501 0 0 1 10.5 14H8.796a.75.75 0 0 1 0-1.5H10.5a3 3 0 1 0 0-6H3.561L5.78 8.72a.749.749 0 1 1-1.06 1.06l-3.5-3.5Z" })) }) }))] }) }), diffStats && (_jsxs("span", { style: {
971
+ return (_jsxs("div", { className: className, style: { marginBottom: '4px' }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }, children: [_jsxs("button", { type: "button", onClick: handleClick, onDoubleClick: isNewFile && filePath && onFileDoubleClick
972
+ ? () => {
973
+ onFileDoubleClick(filePath);
974
+ }
975
+ : undefined, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), style: {
683
976
  display: 'flex',
684
- gap: '4px',
685
- flexShrink: 0,
686
- marginTop: isCoarse ? 0 : '2px',
687
- fontSize: '11px',
688
- fontFamily: '"SF Mono", Menlo, Consolas, "Courier New", monospace',
689
- opacity: isHovered ? 1 : 0.6,
690
- transition: 'opacity 100ms',
691
- }, children: [diffStats.added > 0 && (_jsxs("span", { style: {
692
- color: isUndone
693
- ? isLight
694
- ? '#cf222e'
695
- : '#f47067'
696
- : isLight
697
- ? '#1a7f37'
698
- : '#57ab5a',
699
- textDecoration: isUndone ? 'line-through' : undefined,
700
- }, children: ["+", diffStats.added] })), diffStats.removed > 0 && (_jsxs("span", { style: {
701
- color: isUndone
702
- ? isLight
703
- ? '#1a7f37'
704
- : '#57ab5a'
705
- : isLight
706
- ? '#cf222e'
707
- : '#f47067',
708
- textDecoration: isUndone ? 'line-through' : undefined,
709
- }, children: ["-", diffStats.removed] }))] })), (hasDetails || isFileDiff || isDocOpen || (isNewFile && filePath && onFileOpen)) && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "14", height: "14", style: {
710
- display: 'block',
711
- flexShrink: 0,
712
- marginTop: isCoarse ? 0 : '3px',
713
- transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
714
- transition: 'transform 150ms, opacity 100ms',
715
- opacity: isHovered ? 0.85 : 0.35,
716
- }, children: _jsx("polyline", { points: "6,4 10,8 6,12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }))] }), expanded && hasDetails && (_jsx("div", { className: cm.surfaceSecondary, style: {
977
+ // Coarse: the undo icon's 32px touch box inflates the label row, so
978
+ // top-alignment would leave the diff stats/chevron riding high —
979
+ // center everything instead. Desktop keeps flex-start + px nudges.
980
+ alignItems: isCoarse ? 'center' : 'flex-start',
981
+ gap: '6px',
982
+ background: 'none',
983
+ border: 'none',
984
+ cursor: handleClick ? 'pointer' : 'default',
985
+ color: 'inherit',
986
+ textAlign: 'left',
987
+ padding: '2px 0',
988
+ // Touch: ~20px rows are untappable — 32px is the dense-row floor.
989
+ ...(isCoarse ? { minHeight: 32 } : {}),
990
+ width: '100%',
991
+ // The row shares its line with the Skip button: without this it
992
+ // keeps its intrinsic min-width and pushes the Skip off a 390px
993
+ // screen instead of letting the label ellipsise.
994
+ minWidth: 0,
995
+ }, children: [_jsx("span", { style: { flex: 1, minWidth: 0 }, children: _jsxs("span", { style: { fontSize: '13px', display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsxs("svg", { width: "10", height: "10", viewBox: "0 0 10 10", style: { flexShrink: 0 }, children: [_jsx("circle", { cx: "5", cy: "5", r: "3", fill: dotColor, opacity: "0.35" }), _jsx("circle", { cx: "5", cy: "5", r: "3", fill: "none", stroke: dotColor, strokeWidth: "2" })] }), _jsx("span", { style: {
996
+ flex: 1,
997
+ minWidth: 0,
998
+ overflow: 'hidden',
999
+ textOverflow: 'ellipsis',
1000
+ whiteSpace: 'nowrap',
1001
+ }, children: renderLabel(name, input, filePath, onFileOpen, onFileDoubleClick) }), summary && (_jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { flexShrink: 0, marginLeft: '8px', whiteSpace: 'nowrap' }, children: summary })), canRevert && status !== 'running' && (_jsx("span", { role: "button", tabIndex: 0, title: isUndone
1002
+ ? t('ide.chat.redoChange', undefined, {
1003
+ defaultValue: 'Re-apply this change',
1004
+ })
1005
+ : t('ide.chat.undoChange', undefined, { defaultValue: 'Undo this change' }), onClick: handleRevert, onKeyDown: (e) => {
1006
+ if (e.key === 'Enter' || e.key === ' ') {
1007
+ e.preventDefault();
1008
+ handleRevert(e);
1009
+ }
1010
+ }, onMouseEnter: (e) => {
1011
+ e.currentTarget.style.background = 'rgba(128,128,128,0.2)';
1012
+ e.currentTarget.style.opacity = '1';
1013
+ }, onMouseLeave: (e) => {
1014
+ e.currentTarget.style.background = 'transparent';
1015
+ e.currentTarget.style.opacity = '';
1016
+ }, style: {
1017
+ display: 'inline-flex',
1018
+ alignItems: 'center',
1019
+ justifyContent: 'center',
1020
+ // Touch: hover can't reveal it, so it rests visible at 0.6 and
1021
+ // gets a 32px hit box (the floor for these dense inline rows).
1022
+ width: isCoarse ? 32 : 20,
1023
+ height: isCoarse ? 32 : 20,
1024
+ borderRadius: 4,
1025
+ flexShrink: 0,
1026
+ // Nudge up 1px: the 13px glyph sat slightly below the text's
1027
+ // optical center of the compact 20px box (moot at 32px).
1028
+ position: 'relative',
1029
+ top: isCoarse ? 0 : '-1px',
1030
+ cursor: isReverting ? 'wait' : 'pointer',
1031
+ opacity: isReverting ? 0.3 : isHovered || isCoarse ? 0.6 : 0,
1032
+ transition: 'opacity 100ms, background 100ms',
1033
+ }, children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: isUndone ? (_jsx("path", { d: "M14.78 6.28a.749.749 0 0 0 0-1.06l-3.5-3.5a.749.749 0 1 0-1.06 1.06L12.439 5H5.251l-.001.007L5.251 5a.8.8 0 0 0-.171.019A4.501 4.501 0 0 0 5.5 14h1.704a.75.75 0 0 0 0-1.5H5.5a3 3 0 1 1 0-6h6.939L10.22 8.72a.749.749 0 1 0 1.06 1.06l3.5-3.5Z" })) : (_jsx("path", { d: "M1.22 6.28a.749.749 0 0 1 0-1.06l3.5-3.5a.749.749 0 1 1 1.06 1.06L3.561 5h7.188l.001.007L10.749 5c.058 0 .116.007.171.019A4.501 4.501 0 0 1 10.5 14H8.796a.75.75 0 0 1 0-1.5H10.5a3 3 0 1 0 0-6H3.561L5.78 8.72a.749.749 0 1 1-1.06 1.06l-3.5-3.5Z" })) }) }))] }) }), diffStats && (_jsxs("span", { style: {
1034
+ display: 'flex',
1035
+ gap: '4px',
1036
+ flexShrink: 0,
1037
+ marginTop: isCoarse ? 0 : '2px',
1038
+ fontSize: '11px',
1039
+ fontFamily: '"SF Mono", Menlo, Consolas, "Courier New", monospace',
1040
+ opacity: isHovered ? 1 : 0.6,
1041
+ transition: 'opacity 100ms',
1042
+ }, children: [diffStats.added > 0 && (_jsxs("span", { style: {
1043
+ color: isUndone
1044
+ ? isLight
1045
+ ? '#cf222e'
1046
+ : '#f47067'
1047
+ : isLight
1048
+ ? '#1a7f37'
1049
+ : '#57ab5a',
1050
+ textDecoration: isUndone ? 'line-through' : undefined,
1051
+ }, children: ["+", diffStats.added] })), diffStats.removed > 0 && (_jsxs("span", { style: {
1052
+ color: isUndone
1053
+ ? isLight
1054
+ ? '#1a7f37'
1055
+ : '#57ab5a'
1056
+ : isLight
1057
+ ? '#cf222e'
1058
+ : '#f47067',
1059
+ textDecoration: isUndone ? 'line-through' : undefined,
1060
+ }, children: ["-", diffStats.removed] }))] })), (hasDetails || isFileDiff || isDocOpen || (isNewFile && filePath && onFileOpen)) && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "14", height: "14", style: {
1061
+ display: 'block',
1062
+ flexShrink: 0,
1063
+ marginTop: isCoarse ? 0 : '3px',
1064
+ transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
1065
+ transition: 'transform 150ms, opacity 100ms',
1066
+ opacity: isHovered ? 0.85 : 0.35,
1067
+ }, children: _jsx("polyline", { points: "6,4 10,8 6,12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }))] }), canSkip && (_jsx("button", { type: "button", "data-mol-id": `tool-call-skip-${id}`, onClick: () => {
1068
+ void handleSkip();
1069
+ }, disabled: skipRequested || skipDisabledReason != null, title: skipDisabledReason ?? undefined, className: cm.cn(cm.button({ variant: 'solid', color: 'primary', size: 'xs' })), style: { flexShrink: 0 }, children: skipRequested
1070
+ ? t('ide.chat.skippingToolCall', undefined, { defaultValue: 'Skipping…' })
1071
+ : t('ide.chat.skipToolCall', undefined, { defaultValue: 'Skip' }) }))] }), expanded && hasDetails && (_jsx("div", { className: cm.surfaceSecondary, style: {
717
1072
  marginLeft: '14px',
718
1073
  marginTop: '4px',
719
1074
  marginBottom: '4px',