reveal non-Claude workspace tabs in a split above Claude

A live test of ui open diff surfaced that the workspace slot hardcoded
the Claude pane (primary = claude ?? active), so activating diff.view
flipped the registry but never rendered. Generalise _WorkspaceSlot: when
a non-Claude, non-editor workspace tab is the active one, reveal it in
the split region above Claude with a close affordance back to full-Claude
— so clide ui open diff actually shows the diff alongside the
conversation. Fixes the reveal for terminal/team-chat tabs too.

Closes T-233.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 11:20:02 +02:00
co-authored by Claude Opus 4.8
parent 870b773381
commit b64028ecf9
5 changed files with 113 additions and 7 deletions
+73 -5
View File
@@ -958,10 +958,18 @@ class _WorkspaceSlot extends StatelessWidget {
final editorTab = tabs.where((t) => t.id == _editorTabId).firstOrNull;
final claude = tabs.where((t) => t.id == _claudeTabId).firstOrNull;
final primary = claude ?? active;
final primaryPane = KeyedSubtree(key: _kWorkspacePrimary, child: primary.build(ctx));
final primaryPane = KeyedSubtree(key: _kWorkspacePrimary, child: (claude ?? active).build(ctx));
if (!editorOpen || editorTab == null) {
// A non-Claude, non-editor workspace tab being the active one (e.g.
// diff.view revealed by `clide ui open diff`, T-233) shows in the split
// region above Claude — "review alongside the conversation" — with a
// close affordance back to full-Claude. Only when Claude exists below
// it; with no Claude pane the active tab just takes the whole slot, as
// before. The editor keeps its own editorOpen-gated split.
final reveal = (claude != null && active.id != _claudeTabId && active.id != _editorTabId) ? active : null;
final topTab = reveal ?? (editorOpen ? editorTab : null);
if (topTab == null) {
return Container(color: tokens.panelBackground, child: primaryPane);
}
@@ -971,10 +979,18 @@ class _WorkspaceSlot extends StatelessWidget {
child: LayoutBuilder(
builder: (ctx, constraints) {
final totalHeight = constraints.maxHeight;
final editorHeight = (totalHeight * ratio).clamp(60.0, totalHeight - 60.0);
final topHeight = (totalHeight * ratio).clamp(60.0, totalHeight - 60.0);
return Column(
children: [
SizedBox(height: editorHeight, child: editorTab.build(ctx)),
SizedBox(
height: topHeight,
child: reveal != null
? _RevealedTab(
tab: reveal,
onClose: () => kernel.panels.activateTab(Slots.workspace, _claudeTabId),
)
: topTab.build(ctx),
),
_EditorDragHandle(arrangement: kernel.arrangement, totalHeight: totalHeight),
Expanded(child: primaryPane),
],
@@ -987,6 +1003,58 @@ class _WorkspaceSlot extends StatelessWidget {
}
}
/// A non-Claude workspace tab revealed in the split region above Claude
/// (T-233): a thin chrome header (title + close) over the tab's body, so the
/// user can review it alongside the conversation and dismiss it back to
/// full-Claude. The editor uses its own split path and never renders here.
class _RevealedTab extends StatelessWidget {
const _RevealedTab({required this.tab, required this.onClose});
final TabContribution tab;
final VoidCallback onClose;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return Column(
children: [
Container(
height: 28,
padding: const EdgeInsets.only(left: 10, right: 4),
color: tokens.panelHeader,
child: Row(
children: [
Expanded(
child: ClideText(
_SlotBody._resolveTitle(context, tab),
fontSize: clideFontCaption,
color: tokens.panelHeaderForeground,
maxLines: 1,
),
),
Semantics(
button: true,
label: 'Close',
excludeSemantics: true,
onTap: onClose,
child: ClideTappable(
onTap: onClose,
tooltip: 'Close',
builder: (_, hovered, __) => Padding(
padding: const EdgeInsets.all(6),
child: ClideIcon(PhosphorIcons.xMark, size: 12, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
),
),
),
],
),
),
Expanded(child: tab.build(context)),
],
);
}
}
class _EditorDragHandle extends StatefulWidget {
const _EditorDragHandle({required this.arrangement, required this.totalHeight});