render the Claude pane natively from the transcript (T-137)
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
The Phase-1 wedge of epic T-132 (D-75): the Claude pane no longer renders the PTY's TUI. It runs claude in tmux as before (so the transcript is written) but displays the conversation as native cards read from the transcript via TranscriptReader — user / assistant markdown / thinking / tool-use / tool-result. The whole list sits under a new no-Material ClideSelectionArea (SelectableRegion-based, since Flutter's SelectionArea is Material and D-7 bans it), so text selects and copies across cards — recovering the terminal's one real advantage. ClaudePane drops its Terminal model and the resize-driven spawn trigger (spawn now fires once on didChangeDependencies with a fixed tmux size, since the TUI isn't shown); pane.output is no longer consumed. The terminal builtin is untouched and still available as a general tool. Input/composer is the next ticket (T-138). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/// Accumulates [ConversationItem]s from a transcript stream for the
|
||||
/// native Claude conversation view (epic T-132, D-75).
|
||||
///
|
||||
/// Thin [ChangeNotifier] over a `Stream<ConversationItem>` (normally
|
||||
/// [TranscriptReader.stream]). Kept separate from the widget so it can
|
||||
/// be unit-tested with a plain stream and reused per teammate panel
|
||||
/// when the team work (T-139/T-140) lands.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/builtin/claude/src/transcript_reader.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ConversationController extends ChangeNotifier {
|
||||
/// Listens to [stream] and accumulates items. [onDispose] is invoked
|
||||
/// from [dispose] — wire it to the reader's `dispose` so cancelling
|
||||
/// the view tears down the underlying tail.
|
||||
ConversationController({
|
||||
required Stream<ConversationItem> stream,
|
||||
Future<void> Function()? onDispose,
|
||||
}) : _onDispose = onDispose {
|
||||
_sub = stream.listen(_onItem);
|
||||
}
|
||||
|
||||
/// Convenience: build a controller backed by a live [TranscriptReader]
|
||||
/// for [workspacePath].
|
||||
factory ConversationController.forWorkspace(String workspacePath) {
|
||||
final reader = TranscriptReader(workspacePath);
|
||||
return ConversationController(stream: reader.stream, onDispose: reader.dispose);
|
||||
}
|
||||
|
||||
final Future<void> Function()? _onDispose;
|
||||
late final StreamSubscription<ConversationItem> _sub;
|
||||
final List<ConversationItem> _items = [];
|
||||
|
||||
/// Items in arrival (transcript) order.
|
||||
List<ConversationItem> get items => List.unmodifiable(_items);
|
||||
|
||||
bool get isEmpty => _items.isEmpty;
|
||||
|
||||
void _onItem(ConversationItem item) {
|
||||
_items.add(item);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_sub.cancel());
|
||||
unawaited(_onDispose?.call());
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user