From a73f0485c34b46450d883064cb7d011c9d5ea1a4 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 10 Jun 2026 11:39:38 +0200 Subject: [PATCH] route every tool use through the collapser (T-305 P3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every tool use now renders as a ClideCollapserCard over a one-item list (a single tool is a list of one) — no separate single-card path. The collapser carries the echoed last line, the count, and the aggregate status (spinner while in-flight, check/cross once resolved); the inner content card holds the call body + folded CALL/PROMPT/RESULT segments and its own per-item mark. Inside a run (activity/edits/agent), tools render as the bare inner content card so collapsers don't nest. ConversationCard gains a `margin` param so inner cards carry no stream margin; the collapser pads its inner canvas evenly on all sides (the inner card no longer jams under the header). Rewrote the conversation_view tests for the new structure and added a single-tool golden. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 15 +- lib/builtin/claude/src/conversation_card.dart | 8 +- lib/builtin/claude/src/conversation_view.dart | 159 ++++++++++-------- lib/widgets/src/clide_collapser_card.dart | 6 +- .../claude/conversation_view_test.dart | 52 +++--- test/goldens/collapser_card_goldens_test.dart | 2 +- test/goldens/goldens/linux/collapser_card.png | Bin 36137 -> 36195 bytes test/goldens/goldens/linux/tool_collapser.png | Bin 0 -> 16835 bytes test/goldens/tool_collapser_goldens_test.dart | 74 ++++++++ 9 files changed, 219 insertions(+), 97 deletions(-) create mode 100644 test/goldens/goldens/linux/tool_collapser.png create mode 100644 test/goldens/tool_collapser_goldens_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e4975753..fdbd5bac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,12 +194,15 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ### Changed -- The conversation stream's **collapsible cards** (activity runs, edit runs, - sub-agent runs) now share one `ClideCollapserCard` primitive with consistent - chrome: the collapsed ticker leads with the card's label, the step/edit count - sits in a fixed-width slot, and the status tick (spinner / check / cross) is - pinned hard against the card's right edge while the chevron hugs the left. A - `color` drives the border + label tint per card type. (T-305) +- **Every tool use in the conversation is now a collapsible card** on one + `ClideCollapserCard` primitive — activity runs, edit runs, sub-agent runs, and + each individual tool call (a single tool is a collapser over a one-item list). + Consistent chrome throughout: the collapsed ticker leads with the label + + echoed last line, the count sits in a fixed-width slot, and the status tick + (spinner while running, check / cross once done) is pinned hard against the + right edge while the chevron hugs the left; a `color` drives the border + + label tint. The inner content card carries its own per-item status, with even + padding on all sides. (T-305) - The Claude composer's **slash typeahead**, the team-chat **@-mention** list, and the status-bar **theme switcher** now ride the shared `ClideAnchoredOverlay` + `ClideMenu` popover primitive, alongside the menu diff --git a/lib/builtin/claude/src/conversation_card.dart b/lib/builtin/claude/src/conversation_card.dart index 571dfc2f..cb6e5967 100644 --- a/lib/builtin/claude/src/conversation_card.dart +++ b/lib/builtin/claude/src/conversation_card.dart @@ -61,8 +61,14 @@ class ConversationCard extends StatefulWidget { this.borderColor, this.status = ConversationCardStatus.none, this.extraSegments = const [], + this.margin = const EdgeInsets.only(bottom: 14), }); + /// Outer margin below the card. The stream rhythm is `bottom: 14` (T-282); + /// a card used as a collapser's inner item passes [EdgeInsets.zero] so the + /// collapser owns the surrounding padding evenly (T-305). + final EdgeInsetsGeometry margin; + final ConversationCardVariant variant; final Color accent; final String label; @@ -185,7 +191,7 @@ class _ConversationCardState extends State { ], ); return Padding( - padding: const EdgeInsets.only(bottom: 14), + padding: widget.margin, child: MouseRegion( onEnter: (_) => setState(() => _hover = true), onExit: (_) => setState(() => _hover = false), diff --git a/lib/builtin/claude/src/conversation_view.dart b/lib/builtin/claude/src/conversation_view.dart index 25ba8120..466dc54d 100644 --- a/lib/builtin/claude/src/conversation_view.dart +++ b/lib/builtin/claude/src/conversation_view.dart @@ -289,6 +289,7 @@ class _ConversationViewState extends State { key: ValueKey('turn.${item.uuid}'), item: item, tokens: tokens, + collapseTools: true, toolUseOutcomes: widget.toolUseOutcomes, toolUseById: widget.controller.toolUseById, resultByToolUseId: resultByToolUseId, @@ -371,6 +372,7 @@ class _ConversationTurn extends StatelessWidget { super.key, required this.item, required this.tokens, + this.collapseTools = false, this.toolUseOutcomes = const {}, this.toolUseById = const {}, this.resultByToolUseId = const {}, @@ -380,6 +382,16 @@ class _ConversationTurn extends StatelessWidget { final ConversationItem item; final SurfaceTokens tokens; + + /// When true (top-level stream items), a tool use renders as its own + /// collapser over a one-item list (T-305). When false (already inside a run / + /// edit collapser), it renders the bare inner content card so collapsers + /// don't nest. + final bool collapseTools; + + /// Card bottom margin: the stream rhythm (14) at top level, or the collapser's + /// even inner spacing (10) when this turn is a collapser child (T-305). + EdgeInsetsGeometry get _childMargin => collapseTools ? const EdgeInsets.only(bottom: 14) : const EdgeInsets.only(bottom: kClideCardHeaderPadH); final Map toolUseOutcomes; /// Index from toolUseId → AssistantToolUse, for result-card pairing (T-168). @@ -414,12 +426,14 @@ class _ConversationTurn extends StatelessWidget { collapsible: true, collapsedByDefault: true, collapsedSummary: _firstLine(i.text), + margin: _childMargin, body: ClideText(i.text, muted: true, fontSize: clideFontMeta), ), UserMessage() => ConversationCard( accent: tokens.globalFocus, label: 'you', copyText: i.text, + margin: _childMargin, // Pasted-image @path tokens render as inline thumbnails that open the // lightbox (T-236/T-254); copyText keeps the original text verbatim. body: ClideMarkdown( @@ -436,6 +450,7 @@ class _ConversationTurn extends StatelessWidget { accent: i.isSidechain ? tokens.globalTextMuted : claudeAccent, label: i.isSidechain ? 'agent' : 'claude', copyText: i.text, + margin: _childMargin, body: ClideMarkdown(i.text, onRecordTap: (id) => _openRecord(context, id), onLinkTap: (url) => _openUrl(context, url)), ), AssistantThinkingMessage() => ConversationCard( @@ -445,9 +460,10 @@ class _ConversationTurn extends StatelessWidget { copyText: i.thinking, collapsible: true, collapsedByDefault: true, + margin: _childMargin, body: ClideText(i.thinking, muted: true, fontSize: clideFontMeta), ), - AssistantToolUse() => _toolUse(i), + AssistantToolUse() => collapseTools ? _toolUseCollapser(i) : _toolContentCard(i), ToolResultMessage() => _toolResult(i), ImageMessage() => _image(context, i), }; @@ -528,78 +544,30 @@ class _ConversationTurn extends StatelessWidget { ), ); - Widget _toolUse(AssistantToolUse t) { - // T-262: fold the paired result into this card. A successful result becomes - // a "result" segment below the call + a green header check; a failed result - // stamps a red header cross but stays a separate prominent error card (the - // result is not suppressed — see _visibleItems). No result yet (in-flight) → - // no mark, no segment. - final result = resultByToolUseId[t.toolUseId]; - final succeeded = result != null && !result.isError; - final status = result == null ? ConversationCardStatus.none : (result.isError ? ConversationCardStatus.error : ConversationCardStatus.success); - - // T-264: an Agent/Task call nests its whole sub-agent run in a holder below - // the card. When a run is shown, the returned-result segment would just - // duplicate the run's final output, so drop it (note E) — but keep it when - // there's no captured run, so the output is never lost. - final isAgent = _isAgentTool(t.name); - final runItems = isAgent ? (runByToolUseId[t.toolUseId] ?? const []) : const []; - final hasRun = runItems.isNotEmpty; - - // T-263: an Agent/Task card folds its sub-agent prompt(s) in. Layered order - // when expanded (note E): call input (body) → prompt → returned result. - final segments = [ - for (final p in promptsByToolUseId[t.toolUseId] ?? const []) - CardSegment(label: 'prompt', child: ClideText(p.text, muted: true, fontSize: clideFontMeta)), - if (succeeded && !(isAgent && hasRun)) CardSegment(label: 'result', child: ClideCodeBlock(source: result.content, language: _resultLanguage(t))), - ]; - - // A resolved permission-prompted call: collapsed, green if approved / red - // if denied — a quiet record of what was permitted (D-78). It still folds - // its result + outcome check like any other merged card (T-262). + /// A standalone tool use (T-305): every tool use is a collapser over a + /// one-item list. The collapser carries the echoed last line, the count, and + /// the aggregate status (spinner while in-flight, check / cross once resolved) + /// — pushed up from the item; the inner card holds the call body + segments + /// and its own per-item mark. An Agent/Task call also nests its visible + /// sub-agent run in a second collapser below (T-264). + Widget _toolUseCollapser(AssistantToolUse t) { final outcome = toolUseOutcomes[t.toolUseId]; - final ConversationCard card; - if (outcome != null) { - final color = outcome ? tokens.statusSuccess : tokens.statusError; - card = ConversationCard( - variant: ConversationCardVariant.bordered, - accent: color, - borderColor: color, - label: t.name, - copyText: const JsonEncoder.withIndent(' ').convert(t.input), - collapsible: true, - collapsedByDefault: true, - collapsedSummary: _toolUseSummary(t), - status: status, - body: toolInputBody(tokens, t.name, t.input), - extraSegments: segments, - ); - } else { - // Per-tool body rendering (T-168): Bash → command block, Edit/Write → - // diff, Read/Grep/LS → path label, others → indented JSON. Always - // collapsible so a bulky write body doesn't dominate the scroll. - card = ConversationCard( - variant: ConversationCardVariant.bordered, - accent: tokens.globalFocus, - label: t.name, - copyText: const JsonEncoder.withIndent(' ').convert(t.input), - collapsible: true, - collapsedByDefault: true, - collapsedSummary: _toolUseSummary(t), - status: status, - body: toolInputBody(tokens, t.name, t.input), - extraSegments: segments, - ); - } + final color = outcome == null ? tokens.globalFocus : (outcome ? tokens.statusSuccess : tokens.statusError); + final collapser = ClideCollapserCard( + label: t.name, + color: color, + collapsedSummary: _toolUseSummary(t), + counter: '1 step', + status: _toolRunStatus(t), + children: [_toolContentCard(t)], + ); - if (!hasRun) return card; - // T-264: nest the sub-agent run in a holder UNDER the Agent card, so a - // reader can tell where the sub-agent work begins and ends. The run stays - // VISIBLE (not folded away) — this is attribution + containment. + final runItems = _isAgentTool(t.name) ? (runByToolUseId[t.toolUseId] ?? const []) : const []; + if (runItems.isEmpty) return collapser; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - card, + collapser, Padding( padding: const EdgeInsets.only(left: 12), child: ClideCollapserCard( @@ -625,6 +593,61 @@ class _ConversationTurn extends StatelessWidget { ); } + /// The inner content card for a tool use (T-305): the call body + folded + /// CALL/PROMPT/RESULT segments + its own per-item status mark, with NO own + /// collapse caret — the enclosing collapser owns collapse. Used both as a + /// run/edit child and as the single child of a standalone tool's collapser. + Widget _toolContentCard(AssistantToolUse t) { + // T-262: fold the paired result into this card. A successful result becomes + // a "result" segment below the call + a green header check; a failed result + // stamps a red header cross. No result yet (in-flight) → no mark, no segment. + final result = resultByToolUseId[t.toolUseId]; + final succeeded = result != null && !result.isError; + final status = result == null ? ConversationCardStatus.none : (result.isError ? ConversationCardStatus.error : ConversationCardStatus.success); + + // T-264: an Agent/Task call's run is shown in its own collapser, so the + // returned-result segment would just duplicate the run's final output — + // drop it (note E) when there's a captured run, keep it otherwise. + final isAgent = _isAgentTool(t.name); + final runItems = isAgent ? (runByToolUseId[t.toolUseId] ?? const []) : const []; + final hasRun = runItems.isNotEmpty; + + // T-263: an Agent/Task card folds its sub-agent prompt(s) in. Layered order + // (note E): call input (body) → prompt → returned result. + final segments = [ + for (final p in promptsByToolUseId[t.toolUseId] ?? const []) + CardSegment(label: 'prompt', child: ClideText(p.text, muted: true, fontSize: clideFontMeta)), + if (succeeded && !(isAgent && hasRun)) CardSegment(label: 'result', child: ClideCodeBlock(source: result.content, language: _resultLanguage(t))), + ]; + + // A resolved permission-prompted call is tinted green if approved / red if + // denied — a quiet record of what was permitted (D-78). + final outcome = toolUseOutcomes[t.toolUseId]; + final accent = outcome == null ? tokens.globalFocus : (outcome ? tokens.statusSuccess : tokens.statusError); + return ConversationCard( + variant: ConversationCardVariant.bordered, + accent: accent, + borderColor: outcome == null ? null : accent, + label: t.name, + copyText: const JsonEncoder.withIndent(' ').convert(t.input), + status: status, + body: toolInputBody(tokens, t.name, t.input), + extraSegments: segments, + // Inside a collapser the surrounding padding is even on all sides + // (T-305): a matching bottom margin is the canvas's bottom inset and the + // inter-item gap in a multi-item run. + margin: const EdgeInsets.only(bottom: kClideCardHeaderPadH), + ); + } + + /// Aggregate run status for a tool's collapser tick: a spinner while the call + /// is in-flight, settling to a check or cross once the result lands (T-305). + ClideRunStatus _toolRunStatus(AssistantToolUse t) { + final r = resultByToolUseId[t.toolUseId]; + if (r == null) return ClideRunStatus.running; + return r.isError ? ClideRunStatus.error : ClideRunStatus.success; + } + /// Per-tool language for the folded result code block (T-262): Read shows the /// file's content, so colorize by the file's grammar; Bash output is shell; /// everything else (Grep/LS/Write/Edit confirmations/…) falls back to plain. diff --git a/lib/widgets/src/clide_collapser_card.dart b/lib/widgets/src/clide_collapser_card.dart index 47eeede1..9ffdc6b0 100644 --- a/lib/widgets/src/clide_collapser_card.dart +++ b/lib/widgets/src/clide_collapser_card.dart @@ -187,8 +187,12 @@ class _ClideCollapserCardState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _headerRow(tokens), + // Even padding around the inner item canvas (T-305): the sides + + // top match, and each inner item carries a matching bottom margin + // (so the last item's margin is the bottom inset and items in a + // multi-item run are evenly separated) — hence bottom 0 here. Padding( - padding: const EdgeInsets.fromLTRB(kClideCardHeaderPadH, 0, kClideCardHeaderPadH, 8), + padding: const EdgeInsets.fromLTRB(kClideCardHeaderPadH, kClideCardHeaderPadH, kClideCardHeaderPadH, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/test/builtin/claude/conversation_view_test.dart b/test/builtin/claude/conversation_view_test.dart index 87133831..86bd2099 100644 --- a/test/builtin/claude/conversation_view_test.dart +++ b/test/builtin/claude/conversation_view_test.dart @@ -190,7 +190,17 @@ void main() { final stream = StreamController.broadcast(); final c = ConversationController(stream: stream.stream); addTearDown(c.dispose); - await tester.pumpWidget(harness(f, ConversationView(controller: c, foldLevel: FoldLevel.none))); + await tester.pumpWidget(harness( + f, + Builder( + // The in-flight Bash collapser shows a live spinner (T-305); disable + // animations so it renders static and pumpAndSettle can settle. + builder: (ctx) => MediaQuery( + data: MediaQuery.of(ctx).copyWith(disableAnimations: true), + child: ConversationView(controller: c, foldLevel: FoldLevel.none), + ), + ), + )); stream.add(AssistantToolUse(uuid: 'A', timestamp: _t, isSidechain: false, toolUseId: 'A', name: 'Bash', input: const {'command': 'echo a'})); stream.add(AssistantThinkingMessage(uuid: 'B', timestamp: _t, isSidechain: false, thinking: 'thinking body')); await tester.pumpAndSettle(); @@ -413,8 +423,8 @@ void main() { // Collapsed by default: the prompt is hidden. expect(find.text('find all the widgets'), findsNothing); - // Expand the Agent card → a "prompt" segment reveals the folded prompt. - await tester.tap(find.bySemanticsLabel('Expand')); + // Expand the Agent collapser → a "prompt" segment reveals the folded prompt. + await tester.tap(find.bySemanticsLabel('Task, 1 step, collapsed')); await tester.pumpAndSettle(); expect(find.text('prompt'), findsOneWidget); // segment sub-label expect(find.text('find all the widgets'), findsOneWidget); @@ -438,9 +448,9 @@ void main() { UserMessage(uuid: 'pB', timestamp: _t, isSidechain: true, parentUuid: 'mB', text: 'PROMPT FOR B'), UserMessage(uuid: 'pA', timestamp: _t, isSidechain: true, parentUuid: 'mA', text: 'PROMPT FOR A'), ]); - // Two collapsed Agent cards; the first Expand caret belongs to card A. + // Two collapsed Agent collapsers; the first belongs to card A. expect(find.text('you'), findsNothing); - await tester.tap(find.bySemanticsLabel('Expand').first); + await tester.tap(find.bySemanticsLabel('Task, 1 step, collapsed').first); await tester.pumpAndSettle(); // Only card A is expanded → its prompt (A) shows; B's stays folded away. // Nearest-preceding would have put A's prompt under B, revealing nothing. @@ -475,8 +485,8 @@ void main() { // The Task's returned result (main chain) — equals the run's final prose. ToolResultMessage(uuid: 'tr', timestamp: _t, isSidechain: false, parentUuid: 'mA', toolUseId: 'tA', content: 'THE FINAL ANSWER', isError: false), ]); - // Expand the Agent card (its "result" segment would show here if kept)… - await tester.tap(find.bySemanticsLabel('Expand')); + // Expand the Agent collapser (its "result" segment would show here if kept)… + await tester.tap(find.bySemanticsLabel('Task, 1 step, collapsed')); await tester.pumpAndSettle(); // …and the nested run. await tester.tap(find.bySemanticsLabel('agent run, 1 step, collapsed')); @@ -563,10 +573,12 @@ void main() { toolUseOutcomes: {'x1': true}, // approved ); final handle = tester.ensureSemantics(); - expect(find.text('Write'), findsOneWidget); // shown (resolved) - expect(find.bySemanticsLabel('Expand'), findsOneWidget); // collapsed caret - // T-262: the resolved card also folds its result + a success check. - expect(find.bySemanticsLabel('succeeded'), findsOneWidget); + expect(find.text('Write'), findsOneWidget); // shown (resolved) — collapser label + // T-305: the resolved tool is its own collapser, collapsed by default. + expect(find.bySemanticsLabel('Write, 1 step, collapsed'), findsOneWidget); + // T-262: the resolved card folds its result + carries a success check on + // the collapser (the inner card is hidden while collapsed). + expect(find.byWidgetPredicate((w) => w is ClideStatusIndicator && w.status == ClideRunStatus.success), findsOneWidget); handle.dispose(); }); @@ -583,10 +595,10 @@ void main() { await pumpWith(tester, [ _tool('Bash', {'command': 'ls -la'}) ]); - // Card starts collapsed — the command appears as the collapsed summary. + // Collapser starts collapsed — the command appears as the echoed summary. expect(find.text('ls -la'), findsOneWidget); - // Expand to verify the body is a bash code block. - await tester.tap(find.byType(ClideIcon)); + // Expand the collapser to verify the inner body is a bash code block. + await tester.tap(find.bySemanticsLabel('Bash, 1 step, collapsed')); await tester.pump(); final blocks = tester.widgetList(find.byType(ClideCodeBlock)).toList(); expect(blocks.any((b) => b.language == 'bash' && b.source.contains('ls -la')), isTrue); @@ -608,14 +620,14 @@ void main() { ]); // No standalone success result card — it's folded into the Read card. expect(find.text('Read · result'), findsNothing); - // The merged card shows a success check. - expect(find.bySemanticsLabel('succeeded'), findsOneWidget); + // The collapser shows a success check while collapsed. + expect(find.byWidgetPredicate((w) => w is ClideStatusIndicator && w.status == ClideRunStatus.success), findsOneWidget); // Collapsed by default: neither the call body nor the result is shown yet. expect(find.text('final answer = 42;'), findsNothing); // Expand: the call segment, a "result" sub-label, and the folded result // as a colorized code block (Read → grammar from the .dart path). - await tester.tap(find.bySemanticsLabel('Expand')); + await tester.tap(find.bySemanticsLabel('Read, 1 step, collapsed')); await tester.pumpAndSettle(); expect(find.text('result'), findsOneWidget); // segment sub-label final blocks = tester.widgetList(find.byType(ClideCodeBlock)).toList(); @@ -639,7 +651,7 @@ void main() { _tool('Bash', {'command': 'echo hi'}), _result('hi\nthere'), ]); - await tester.tap(find.bySemanticsLabel('Expand')); + await tester.tap(find.bySemanticsLabel('Bash, 1 step, collapsed')); await tester.pumpAndSettle(); final blocks = tester.widgetList(find.byType(ClideCodeBlock)).toList(); expect(blocks.any((b) => b.language == 'bash' && b.source == 'hi\nthere'), isTrue); @@ -654,8 +666,8 @@ void main() { // The error stays a separate prominent card… expect(find.text('Bash · error'), findsOneWidget); expect(find.text('permission denied'), findsOneWidget); - // …and the call card carries a red failure mark for symmetry (note C). - expect(find.bySemanticsLabel('failed'), findsOneWidget); + // …and the call collapser carries a red failure mark for symmetry (note C). + expect(find.byWidgetPredicate((w) => w is ClideStatusIndicator && w.status == ClideRunStatus.error), findsOneWidget); handle.dispose(); }); diff --git a/test/goldens/collapser_card_goldens_test.dart b/test/goldens/collapser_card_goldens_test.dart index b6322fb8..35460da8 100644 --- a/test/goldens/collapser_card_goldens_test.dart +++ b/test/goldens/collapser_card_goldens_test.dart @@ -15,7 +15,7 @@ void main() { // A stand-in inner item card: content + its own per-item status mark (the // collapser carries the aggregate; items keep their own — T-305). Widget item(String label, ClideRunStatus status) => Padding( - padding: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.only(bottom: 10), child: DecoratedBox( decoration: BoxDecoration( border: Border.all(color: const Color(0xFF393E48)), diff --git a/test/goldens/goldens/linux/collapser_card.png b/test/goldens/goldens/linux/collapser_card.png index 2b00cf5ad33523f480f34591c645f833ba59e883..b15b6629b775917f42d55f7a350c179e2c317457 100644 GIT binary patch delta 17990 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%0AP=H3j)R+Z_lo%G`feBY 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 delta 17983 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 diff --git a/test/goldens/goldens/linux/tool_collapser.png b/test/goldens/goldens/linux/tool_collapser.png new file mode 100644 index 0000000000000000000000000000000000000000..035d075a14b5faf03fba70fdde50a0b1f7ea6431 GIT binary patch literal 16835 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 literal 0 HcmV?d00001 diff --git a/test/goldens/tool_collapser_goldens_test.dart b/test/goldens/tool_collapser_goldens_test.dart new file mode 100644 index 00000000..bb2e9046 --- /dev/null +++ b/test/goldens/tool_collapser_goldens_test.dart @@ -0,0 +1,74 @@ +import 'package:alchemist/alchemist.dart'; +import 'package:clide/builtin/claude/src/conversation_card.dart'; +import 'package:clide/widgets/widgets.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../helpers/kernel_fixture.dart'; +import '../helpers/widget_harness.dart'; + +/// Visualises the T-305 single-tool rendering: every tool use is a collapser +/// over a one-item list. Collapsed → ticker (label + echoed line + 1 step + +/// status). Expanded → the inner content card (body + folded result + its own +/// per-item mark; the collapser carries the aggregate). +void main() { + late KernelFixture f; + setUp(() async => f = await KernelFixture.create()); + tearDown(() async => f.dispose()); + + Widget bashContent() => const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFF4C9AFF), + label: 'Bash', + status: ConversationCardStatus.success, + body: Text('npm test', style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), textDirection: TextDirection.ltr), + extraSegments: [ + CardSegment( + label: 'result', child: Text('All 42 tests passed', style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), textDirection: TextDirection.ltr)), + ], + ); + + goldenTest( + 'single tool collapser (T-305): collapsed ticker + expanded inner card', + fileName: 'tool_collapser', + builder: () => GoldenTestGroup( + columns: 1, + children: [ + GoldenTestScenario( + name: 'collapsed — Bash, 1 step', + child: SizedBox( + width: 420, + child: harness( + f, + ClideCollapserCard( + label: 'Bash', + color: const Color(0xFF4C9AFF), + collapsedSummary: 'npm test', + counter: '1 step', + status: ClideRunStatus.success, + children: [bashContent()], + ), + ), + ), + ), + GoldenTestScenario( + name: 'expanded — inner content card', + child: SizedBox( + width: 420, + child: harness( + f, + ClideCollapserCard( + label: 'Bash', + color: const Color(0xFF4C9AFF), + counter: '1 step', + status: ClideRunStatus.success, + initiallyExpanded: true, + children: [bashContent()], + ), + ), + ), + ), + ], + ), + ); +}