route every tool use through the collapser (T-305 P3)

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 11:39:38 +02:00
co-authored by Claude Opus 4.8
parent 24739b0515
commit a73f0485c3
9 changed files with 219 additions and 97 deletions
@@ -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)),
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -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()],
),
),
),
),
],
),
);
}