@molecule/app-ide-react 1.16.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 (31) hide show
  1. package/README.md +7 -1
  2. package/dist/components/ChatPanel.d.ts.map +1 -1
  3. package/dist/components/ChatPanel.js +57 -1
  4. package/dist/components/ChatPanel.js.map +1 -1
  5. package/dist/components/MarkdownContent.d.ts.map +1 -1
  6. package/dist/components/MarkdownContent.js +27 -7
  7. package/dist/components/MarkdownContent.js.map +1 -1
  8. package/dist/components/TestStatusBar.d.ts +56 -0
  9. package/dist/components/TestStatusBar.d.ts.map +1 -0
  10. package/dist/components/TestStatusBar.js +105 -0
  11. package/dist/components/TestStatusBar.js.map +1 -0
  12. package/dist/components/ToolCallCard.d.ts.map +1 -1
  13. package/dist/components/ToolCallCard.js +384 -68
  14. package/dist/components/ToolCallCard.js.map +1 -1
  15. package/dist/components/tests-bar-utilities.d.ts +78 -103
  16. package/dist/components/tests-bar-utilities.d.ts.map +1 -1
  17. package/dist/components/tests-bar-utilities.js +130 -201
  18. package/dist/components/tests-bar-utilities.js.map +1 -1
  19. package/dist/components/tool-call-utilities.d.ts +68 -7
  20. package/dist/components/tool-call-utilities.d.ts.map +1 -1
  21. package/dist/components/tool-call-utilities.js +124 -6
  22. package/dist/components/tool-call-utilities.js.map +1 -1
  23. package/dist/hooks/useTestsBarVisible.d.ts +16 -0
  24. package/dist/hooks/useTestsBarVisible.d.ts.map +1 -0
  25. package/dist/hooks/useTestsBarVisible.js +39 -0
  26. package/dist/hooks/useTestsBarVisible.js.map +1 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +6 -0
  30. package/dist/index.js.map +1 -1
  31. 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, isSkippedByUser, 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
@@ -442,6 +442,145 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
442
442
  setExpanded((e) => !e);
443
443
  }
444
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
+ }
445
584
  // ── save_plan: clickable row that opens the plan file in the editor ──────────
446
585
  if (name === 'save_plan') {
447
586
  const planOutput = (output ?? {});
@@ -481,10 +620,11 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
481
620
  }
482
621
  // ── ask_user: render interactive option list instead of a normal tool card ──
483
622
  if (name === 'ask_user') {
484
- // NEVER render raw tool input. `options` is declared `string[]` in the tool
485
- // schema, but the schema is a request to a language model, not a guarantee —
486
- // weaker models send `[{ label: 'Recipe box' }]`, and an object reaching JSX
487
- // 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.
488
628
  const askInput = normalizeAskUserInput(input);
489
629
  const askOutput = output;
490
630
  const serverAwaiting = typeof askOutput === 'object' && askOutput?.status === 'awaiting_response';
@@ -494,6 +634,12 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
494
634
  const selectedAnswer = isResponded ? askOutput : localAnswer;
495
635
  const [freeText, setFreeText] = useState('');
496
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);
497
643
  // Don't render until the question has streamed in. `tool_use_start` surfaces
498
644
  // the card the instant the call begins — before its input arrives — which
499
645
  // would otherwise flash an empty question + option list. Once the full input
@@ -503,6 +649,34 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
503
649
  return null;
504
650
  const borderClr = isLight ? '#d0d7de' : '#3d444d';
505
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;
506
680
  return (_jsxs("div", { className: className, style: {
507
681
  marginBottom: '8px',
508
682
  marginTop: '8px',
@@ -510,71 +684,213 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
510
684
  border: `1px solid ${borderClr}`,
511
685
  background: isLight ? '#f6f8fa' : 'rgba(255,255,255,0.04)',
512
686
  overflow: 'hidden',
513
- }, 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: {
514
722
  padding: '4px 12px 10px',
515
723
  fontSize: '13px',
516
724
  borderBottom: `1px solid ${borderClr}`,
517
- }, children: _jsx(MarkdownContent, { text: unescapeLiterals(askInput.question), isStreaming: false }) }), askInput.options.map((option, i) => {
518
- const isSelected = selectedAnswer === option;
519
- const isFaded = !isAwaiting && !isSelected;
520
- const isHover = isAwaiting && hoveredIdx === i;
521
- return (_jsxs("button", { type: "button", "data-mol-id": `ask-user-option-${i}`, disabled: !isAwaiting || onAskUserResponse == null, onClick: () => {
522
- // No handler = read-only (a project viewer): answering is editor work.
523
- if (onAskUserResponse == null)
524
- return;
525
- setLocalAnswer(option);
526
- onAskUserResponse(option);
527
- }, onMouseEnter: () => {
528
- if (isAwaiting)
529
- setHoveredIdx(i);
530
- }, onMouseLeave: () => setHoveredIdx(null), style: {
531
- display: 'flex',
532
- alignItems: 'center',
533
- gap: '10px',
534
- width: '100%',
535
- padding: '8px 12px',
536
- // Touch: full 44px rows — these are the PRIMARY discovery answers,
537
- // so they get the standalone-control floor, not the dense-row 32.
538
- ...(isCoarse ? { minHeight: 44 } : {}),
539
- border: 'none',
540
- borderTop: i > 0 ? `1px solid ${borderClr}` : 'none',
541
- background: isSelected
542
- ? isLight
543
- ? '#dbeafe'
544
- : 'rgba(59,130,246,0.2)'
545
- : isHover
546
- ? isLight
547
- ? '#eaeef2'
548
- : 'rgba(255,255,255,0.06)'
549
- : 'transparent',
550
- color: 'inherit',
551
- cursor: isAwaiting ? 'pointer' : 'default',
552
- textAlign: 'left',
553
- fontSize: '13px',
554
- opacity: isFaded ? 0.4 : 1,
555
- transition: 'background 80ms, opacity 80ms',
556
- }, children: [_jsx("span", { style: {
557
- display: 'inline-flex',
558
- alignItems: 'center',
559
- justifyContent: 'center',
560
- width: 22,
561
- height: 22,
562
- borderRadius: '5px',
563
- border: `1px solid ${isSelected ? (isLight ? '#93c5fd' : '#3b82f6') : borderClr}`,
564
- background: isSelected
565
- ? isLight
566
- ? '#3b82f6'
567
- : '#2563eb'
568
- : isLight
569
- ? '#fff'
570
- : 'rgba(255,255,255,0.08)',
571
- color: isSelected ? '#fff' : isLight ? '#57606a' : '#848d97',
572
- fontSize: '11px',
573
- fontWeight: 600,
574
- flexShrink: 0,
575
- fontFamily: '"SF Mono", Menlo, Consolas, "Courier New", monospace',
576
- }, 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));
577
- }), 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: {
578
894
  display: 'flex',
579
895
  gap: '4px',
580
896
  padding: '8px 12px',
@@ -639,7 +955,7 @@ export const ToolCallCard = memo(function ToolCallCard({ id, name, input, output
639
955
  fontWeight: 500,
640
956
  opacity: freeText.trim() ? 1 : 0.3,
641
957
  transition: 'background 100ms, border-color 100ms, color 100ms',
642
- }, 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: {
643
959
  padding: '8px 12px',
644
960
  borderTop: `1px solid ${borderClr}`,
645
961
  fontSize: '12px',