group consecutive same-file edits into one collapsed card (T-296)

A run of 2+ consecutive edits to the same file now folds into one ClideHolderCard
labelled '# edits' (coalesceEditRuns, run after groupConversation) instead of a
stack of cards; a different file or an interleaving step splits the run. Every
edit stays reachable on expand.

The holder gained an optional aggregate status. New owned primitives: ClideSpinner
(the logo mark, monochrome, 3D Y-axis rotation, reduced-motion-aware) and
ClideStatusIndicator (running→spinner / success→check / error→cross, with an
AnimatedSwitcher seam for a richer transition later — kept self-contained, not
built on ConversationCard's mark). The activity card shares the same indicator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 18:34:27 +02:00
co-authored by Claude Opus 4.8
parent 6efb3b5d2b
commit 407ed25ab0
10 changed files with 447 additions and 6 deletions
+13
View File
@@ -30,6 +30,7 @@ class ClideHolderCard extends StatefulWidget {
required this.children,
this.title = 'Activity',
this.initiallyExpanded = false,
this.status,
});
/// One-line gist of the latest step, shown in the collapsed ticker.
@@ -47,6 +48,10 @@ class ClideHolderCard extends StatefulWidget {
final bool initiallyExpanded;
/// Optional aggregate run status (spinner / check / cross) shown at the head
/// of the ticker + header — the run's live state (T-296). Null shows nothing.
final ClideRunStatus? status;
@override
State<ClideHolderCard> createState() => _ClideHolderCardState();
}
@@ -108,6 +113,10 @@ class _ClideHolderCardState extends State<ClideHolderCard> {
),
),
const SizedBox(width: 8),
if (widget.status != null) ...[
ClideStatusIndicator(status: widget.status!, size: 12),
const SizedBox(width: 8),
],
ClideText(widget.stepLabel, fontSize: clideFontCaption, color: tokens.globalTextMuted),
],
),
@@ -186,6 +195,10 @@ class _ClideHolderCardState extends State<ClideHolderCard> {
child: ClideText(widget.title, fontSize: clideFontCaption, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
),
const SizedBox(width: 8),
if (widget.status != null) ...[
ClideStatusIndicator(status: widget.status!, size: 12),
const SizedBox(width: 8),
],
ClideText(widget.stepLabel, fontSize: clideFontCaption, color: tokens.globalTextMuted),
],
),