move the per-session status to the bottom status bar (T-145)

Per feedback, the model · permission-mode · context line reads better in
the status bar than as a strip above the conversation. Adds a generic,
publisher-agnostic status-bar context slot: a pane publishes a short
string to the `statusbar.context` MessageBus channel and the bar shows
the latest. The active Claude sub-tab publishes (inactive panes stay
quiet, so no race); switching tabs swaps the slot to the focused pane.

Replaces the in-pane ClaudeStatusStrip with a formatStatusLine helper +
PaneContextStatusItem (the status-bar widget) and a StatusItemContribution.
ClaudeSessionHost passes `active` so only the visible sub-tab publishes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 10:37:16 +02:00
co-authored by Claude Opus 4.7
parent 06cf9f8298
commit b0d5aa36f4
10 changed files with 250 additions and 141 deletions
@@ -1,58 +0,0 @@
/// Tests for the per-session status strip (T-145): formatters and the
/// widget's render of model · permission-mode · context.
library;
import 'package:clide/builtin/claude/src/claude_status_strip.dart';
import 'package:clide/builtin/claude/src/transcript_reader.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
void main() {
group('status formatters', () {
test('shortModelLabel strips the claude- prefix and dots the version', () {
expect(shortModelLabel('claude-opus-4-7'), 'opus 4.7');
expect(shortModelLabel('claude-sonnet-4-6'), 'sonnet 4.6');
expect(shortModelLabel('weird'), 'weird');
});
test('permissionModeLabel humanises CC modes', () {
expect(permissionModeLabel('acceptEdits'), 'accept-edits');
expect(permissionModeLabel('bypassPermissions'), 'bypass');
expect(permissionModeLabel('plan'), 'plan');
expect(permissionModeLabel('default'), 'default');
expect(permissionModeLabel('something-new'), 'something-new');
});
test('formatTokenCount uses k / M / raw', () {
expect(formatTokenCount(500), '500');
expect(formatTokenCount(765000), '765k');
expect(formatTokenCount(1200000), '1.2M');
});
});
group('ClaudeStatusStrip', () {
late KernelFixture f;
setUp(() async => f = await KernelFixture.create());
tearDown(() => f.dispose());
testWidgets('renders model, mode, and context', (tester) async {
await tester.pumpWidget(harness(
f,
const ClaudeStatusStrip(
status: SessionStatus(model: 'claude-opus-4-7', permissionMode: 'acceptEdits', contextTokens: 765000),
),
));
expect(find.textContaining('opus 4.7'), findsOneWidget);
expect(find.textContaining('accept-edits'), findsOneWidget);
expect(find.textContaining('765k ctx'), findsOneWidget);
});
testWidgets('empty status renders nothing', (tester) async {
await tester.pumpWidget(harness(f, const ClaudeStatusStrip(status: SessionStatus())));
expect(find.byType(ClideText), findsNothing);
});
});
}
@@ -0,0 +1,42 @@
/// Tests for the per-session status line formatting (T-145).
library;
import 'package:clide/builtin/claude/src/claude_status.dart';
import 'package:clide/builtin/claude/src/transcript_reader.dart';
import 'package:test/test.dart';
void main() {
group('status formatters', () {
test('shortModelLabel strips the claude- prefix and dots the version', () {
expect(shortModelLabel('claude-opus-4-7'), 'opus 4.7');
expect(shortModelLabel('claude-sonnet-4-6'), 'sonnet 4.6');
expect(shortModelLabel('weird'), 'weird');
});
test('permissionModeLabel humanises CC modes', () {
expect(permissionModeLabel('acceptEdits'), 'accept-edits');
expect(permissionModeLabel('bypassPermissions'), 'bypass');
expect(permissionModeLabel('plan'), 'plan');
expect(permissionModeLabel('default'), 'default');
expect(permissionModeLabel('something-new'), 'something-new');
});
test('formatTokenCount uses k / M / raw', () {
expect(formatTokenCount(500), '500');
expect(formatTokenCount(765000), '765k');
expect(formatTokenCount(1200000), '1.2M');
});
});
group('formatStatusLine', () {
test('joins the present fields', () {
const s = SessionStatus(model: 'claude-opus-4-7', permissionMode: 'acceptEdits', contextTokens: 21000);
expect(formatStatusLine(s), 'opus 4.7 · accept-edits · 21k ctx');
});
test('omits absent fields', () {
expect(formatStatusLine(const SessionStatus(model: 'claude-sonnet-4-6')), 'sonnet 4.6');
expect(formatStatusLine(const SessionStatus()), '');
});
});
}
@@ -0,0 +1,40 @@
/// Tests for the status-bar in-pane context slot (T-145): it shows the
/// latest text published on the bus channel and clears on empty.
library;
import 'package:clide/builtin/claude/src/pane_context_status.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
void main() {
late KernelFixture f;
setUp(() async => f = await KernelFixture.create());
tearDown(() => f.dispose());
testWidgets('renders the latest published context, clears on empty', (tester) async {
await tester.pumpWidget(harness(f, const PaneContextStatusItem()));
await tester.pump();
expect(find.byType(ClideText), findsNothing); // nothing published yet
publishPaneContext(f.services.messages, 'builtin.claude', 'opus 4.7 · default · 21k ctx');
await tester.pump();
await tester.pump();
expect(find.textContaining('opus 4.7'), findsOneWidget);
// A newer publisher overwrites the slot.
publishPaneContext(f.services.messages, 'builtin.editor', 'lib/app.dart · modified');
await tester.pump();
await tester.pump();
expect(find.textContaining('opus 4.7'), findsNothing);
expect(find.textContaining('lib/app.dart'), findsOneWidget);
// Empty clears it.
publishPaneContext(f.services.messages, 'builtin.editor', '');
await tester.pump();
await tester.pump();
expect(find.byType(ClideText), findsNothing);
});
}