add shared pane widgets + Q-023 ssh-remote question

ClidePtyView is a theme-bound wrapper around xterm.dart's TerminalView
— token-derived TerminalTheme, JetBrainsMono as the face, Semantics
live-region label so screen readers + Playwright both hear it. The
consumer (terminal / Claude extensions) owns the `Terminal` model and
wires IPC pane.write / pane.output → terminal.write() themselves; the
widget deliberately has no IPC dependency so it stays trivially
testable.

ClidePaneChrome is the shared pane header — title + subtitle + leading
icon + trailing widgets + optional close button. The close button is
null-conditional so primary Claude panes (D-041, landing in step 7)
can render without one.

xterm 4.0.0 added as a justified runtime dep + logged in
licenses.yaml per D-042. 3 new widget tests cover header rendering,
close-button presence, and the close-tap round-trip.

Q-023 records the SSH-remote-development question so the daemon + IPC
seams don't accrete local-only assumptions during Tier 1-5.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-04-22 09:19:24 +02:00
co-authored by Claude
parent 3715a2e191
commit 50d9e00ea5
9 changed files with 444 additions and 0 deletions
@@ -0,0 +1,65 @@
import 'package:clide_app/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';
void main() {
group('ClidePaneChrome', () {
late KernelFixture f;
setUp(() async {
f = await KernelFixture.create();
});
tearDown(() => f.dispose());
testWidgets('renders title + subtitle', (tester) async {
await tester.pumpWidget(
harness(
f,
const ClidePaneChrome(
title: 'terminal — ~/clide',
subtitle: 'bash · 80×24',
child: SizedBox.shrink(),
),
),
);
expect(find.text('terminal — ~/clide'), findsOneWidget);
expect(find.text('bash · 80×24'), findsOneWidget);
});
testWidgets('no close button when onClose is null', (tester) async {
await tester.pumpWidget(
harness(
f,
const ClidePaneChrome(
title: 'primary claude',
child: SizedBox.shrink(),
),
),
);
final handle = tester.ensureSemantics();
expect(find.bySemanticsLabel('Close pane'), findsNothing);
handle.dispose();
});
testWidgets('close button invokes onClose when present', (tester) async {
var pressed = false;
await tester.pumpWidget(
harness(
f,
ClidePaneChrome(
title: 'terminal',
onClose: () => pressed = true,
child: const SizedBox.shrink(),
),
),
);
final handle = tester.ensureSemantics();
expect(find.bySemanticsLabel('Close pane'), findsOneWidget);
await tester.tap(find.bySemanticsLabel('Close pane'));
expect(pressed, isTrue);
handle.dispose();
});
});
}