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>
85 lines
2.4 KiB
Dart
85 lines
2.4 KiB
Dart
import 'package:clide/widgets/widgets.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
import 'claude_pane.dart';
|
||
|
||
/// Hosts the primary Claude pane plus N user-spawned secondary
|
||
/// sessions per D-41. Uses [MultitabPane] for the tab strip
|
||
/// (drag-reorder, close ×, + button) and [IndexedStack]-mode
|
||
/// keep-alive so switching tabs doesn't tear down the underlying
|
||
/// PTY-backed terminal.
|
||
class ClaudeSessionHost extends StatefulWidget {
|
||
const ClaudeSessionHost({super.key});
|
||
|
||
@override
|
||
State<ClaudeSessionHost> createState() => ClaudeSessionHostState();
|
||
}
|
||
|
||
class ClaudeSessionHostState extends State<ClaudeSessionHost> {
|
||
static const _primaryId = 'primary';
|
||
|
||
late final MultitabController<_Session> _controller;
|
||
int _nextSecondary = 1;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_controller = MultitabController<_Session>(
|
||
initial: [
|
||
MultitabEntry<_Session>(
|
||
id: _primaryId,
|
||
title: 'primary',
|
||
payload: const _Session(isPrimary: true),
|
||
// Primary persists across clide restarts and never gets a
|
||
// close affordance (D-41).
|
||
closeable: false,
|
||
reorderable: false,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_controller.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
/// Public entry point used by the `claude.new-secondary` command.
|
||
void addSecondary() {
|
||
final index = _nextSecondary++;
|
||
_controller.add(MultitabEntry<_Session>(
|
||
id: 'secondary-$index',
|
||
title: 'session $index',
|
||
payload: _Session(isPrimary: false, secondaryIndex: index),
|
||
));
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MultitabPane<_Session>(
|
||
controller: _controller,
|
||
keepAlive: true,
|
||
onAddRequested: addSecondary,
|
||
bodyBuilder: (ctx, entry) {
|
||
final s = entry.payload;
|
||
return ClaudePane(
|
||
isPrimary: s.isPrimary,
|
||
secondaryIndex: s.secondaryIndex,
|
||
// The MultitabPane already provides the tab strip header;
|
||
// suppressing the ClaudePane's own chrome avoids a double row.
|
||
showChrome: false,
|
||
// Only the visible sub-tab publishes to the status-bar slot.
|
||
active: entry.id == _controller.activeId,
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|
||
|
||
class _Session {
|
||
const _Session({required this.isPrimary, this.secondaryIndex});
|
||
final bool isPrimary;
|
||
final int? secondaryIndex;
|
||
}
|