add image-viewer card + clide image show verb

Drives an image inline into the Claude conversation log over the same
bus-publish path as ui.toast/ui.open, keeping the dispatcher handler
Flutter-free. The card is display-only per D-78; the verb registers a
CommandSchema so it surfaces in clide capabilities for T-248 discovery.

Closes T-249.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 10:22:38 +02:00
co-authored by Claude Opus 4.8
parent 3cf77f40ed
commit b446bd024d
13 changed files with 515 additions and 2 deletions
+24
View File
@@ -10,6 +10,8 @@ import 'package:clide/builtin/claude/src/pane_context_status.dart';
import 'package:clide/builtin/claude/src/claude_meta_sidebar.dart';
import 'package:clide/builtin/claude/src/session_index.dart';
import 'package:clide/builtin/claude/src/session_storage.dart';
import 'package:clide/builtin/claude/src/transcript_reader.dart' show ImageMessage;
import 'package:clide/src/daemon/image_commands.dart' show imageShowChannel;
import 'package:clide/builtin/claude/src/team_chat_sidebar.dart' show TeamChatPane;
import 'package:clide/builtin/claude/src/team_panel_host.dart';
import 'package:clide/extension/extension.dart';
@@ -321,6 +323,28 @@ class ClaudeExtension extends ClideExtension {
// session outlives its pane and is shared across surfaces.
_orchestrator = ClaudeSessionOrchestrator();
activeSessionOrchestrator = _orchestrator;
// `clide image show <path>` (T-249): the dispatcher resolves + publishes an
// 'image' message; we inject the matching card into the conversation the
// user is looking at (the primary lead, else the first visible session).
_subs.add(ctx.messages.subscribe(channel: imageShowChannel).listen(_onImageShow));
}
/// Inject an [ImageMessage] from a published `image` bus message (T-249).
/// Dropped silently if no live conversation is available — the CLI already
/// reported success at publish time, and a missing pane is transient.
void _onImageShow(Message m) {
final path = m.data['path'] as String?;
if (path == null || path.isEmpty) return;
final target = _orchestrator?.byId('primary') ?? _orchestrator?.visibleSessions.firstOrNull;
if (target == null) return;
target.conversation.inject(ImageMessage(
uuid: 'image-${DateTime.now().microsecondsSinceEpoch}',
timestamp: DateTime.now(),
isSidechain: false,
path: path,
caption: m.data['caption'] as String?,
));
}
@override