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:
@@ -401,3 +401,5 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
|
|||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'review', 'done', NULL, '2026-06-06 08:31:25', '2026-06-06 08:31:25', '2026-06-06 08:31:25', NULL, '231d1bb74f6f154f7e888760f1c4e3a6', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'review', 'done', NULL, '2026-06-06 08:31:25', '2026-06-06 08:31:25', '2026-06-06 08:31:25', NULL, '231d1bb74f6f154f7e888760f1c4e3a6', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:33:42', '2026-06-06 08:33:42', '2026-06-06 08:33:42', NULL, '1ce52a9aabbedd3f4164095e1a0a291e', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:33:42', '2026-06-06 08:33:42', '2026-06-06 08:33:42', NULL, '1ce52a9aabbedd3f4164095e1a0a291e', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'in_progress', 'done', NULL, '2026-06-06 08:50:12', '2026-06-06 08:50:12', '2026-06-06 08:50:12', NULL, 'ec94ca747fc852db6bf514b4d5079085', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'in_progress', 'done', NULL, '2026-06-06 08:50:12', '2026-06-06 08:50:12', '2026-06-06 08:50:12', NULL, 'ec94ca747fc852db6bf514b4d5079085', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'done', 'in_progress', NULL, '2026-06-06 09:04:03', '2026-06-06 09:04:03', '2026-06-06 09:04:03', NULL, '9dd345ff2fad6f79a7a1ba7f872dc761', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'in_progress', 'done', NULL, '2026-06-06 09:20:02', '2026-06-06 09:20:02', '2026-06-06 09:20:02', NULL, 'bf55d4f34a22b9d6e69563bc3e9fd95a', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
|||||||
@@ -921,3 +921,5 @@ New scope for this ticket:
|
|||||||
Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:31:25', NULL, 'd2d8d30c578bce8229924619542d5aa7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:31:25', NULL, 'd2d8d30c578bce8229924619542d5aa7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:33:42', NULL, '41f3cdaedfb436b6102ce6aebbbc0161', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:33:42', NULL, '41f3cdaedfb436b6102ce6aebbbc0161', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:50:12', NULL, 'f2e24a5a804b0d3b95bef0be2f8327b7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:50:12', NULL, 'f2e24a5a804b0d3b95bef0be2f8327b7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 09:04:03', NULL, '6458d14b4f6178c4da81afdb70a6eb6b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open <reader> <id|path>'' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 09:20:02', NULL, '747c6d2aa0dad416c150621aa01b9d3d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
|||||||
+4
-2
@@ -18,8 +18,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `clide ui open diff <path>` reveals the diff tab and scrolls to that file's
|
- `clide ui open diff <path>` reveals the diff in a split above the Claude
|
||||||
diff, highlighting its header — the diff-panel arm of `ui open` (T-233).
|
conversation, scrolls to that file and highlights its header — the
|
||||||
|
diff-panel arm of `ui open`. Workspace tabs other than Claude/editor now
|
||||||
|
reveal alongside the conversation with a close affordance (T-233).
|
||||||
- Image cards in the Claude conversation log: `clide image show <path>
|
- Image cards in the Claude conversation log: `clide image show <path>
|
||||||
[--caption …]` renders an image inline (PNG/JPG/JPEG/GIF/WebP/BMP),
|
[--caption …]` renders an image inline (PNG/JPG/JPEG/GIF/WebP/BMP),
|
||||||
clide-owned and display-only (D-78). The path is resolved workspace-relative
|
clide-owned and display-only (D-78). The path is resolved workspace-relative
|
||||||
|
|||||||
+73
-5
@@ -958,10 +958,18 @@ class _WorkspaceSlot extends StatelessWidget {
|
|||||||
final editorTab = tabs.where((t) => t.id == _editorTabId).firstOrNull;
|
final editorTab = tabs.where((t) => t.id == _editorTabId).firstOrNull;
|
||||||
|
|
||||||
final claude = tabs.where((t) => t.id == _claudeTabId).firstOrNull;
|
final claude = tabs.where((t) => t.id == _claudeTabId).firstOrNull;
|
||||||
final primary = claude ?? active;
|
final primaryPane = KeyedSubtree(key: _kWorkspacePrimary, child: (claude ?? active).build(ctx));
|
||||||
final primaryPane = KeyedSubtree(key: _kWorkspacePrimary, child: primary.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);
|
return Container(color: tokens.panelBackground, child: primaryPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,10 +979,18 @@ class _WorkspaceSlot extends StatelessWidget {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (ctx, constraints) {
|
builder: (ctx, constraints) {
|
||||||
final totalHeight = constraints.maxHeight;
|
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(
|
return Column(
|
||||||
children: [
|
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),
|
_EditorDragHandle(arrangement: kernel.arrangement, totalHeight: totalHeight),
|
||||||
Expanded(child: primaryPane),
|
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 {
|
class _EditorDragHandle extends StatefulWidget {
|
||||||
const _EditorDragHandle({required this.arrangement, required this.totalHeight});
|
const _EditorDragHandle({required this.arrangement, required this.totalHeight});
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,38 @@ void main() {
|
|||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('a non-Claude workspace tab reveals in the split above Claude with a close (T-233)', (tester) async {
|
||||||
|
registerTabs();
|
||||||
|
f.services.panels.contribute(TabContribution(id: 'diff.view', slot: Slots.workspace, title: 'Diff', build: (_) => const Text('DIFF')));
|
||||||
|
f.services.panels.activateTab(Slots.workspace, 'diff.view');
|
||||||
|
await pumpLayout(tester);
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
// Revealed alongside the conversation: both the diff and Claude render.
|
||||||
|
expect(find.text('DIFF'), findsOneWidget);
|
||||||
|
expect(find.text('CLAUDE'), findsOneWidget);
|
||||||
|
|
||||||
|
// The close affordance returns to full-Claude.
|
||||||
|
await tester.tap(find.bySemanticsLabel('Close'));
|
||||||
|
await tester.pump();
|
||||||
|
expect(f.services.panels.activeTabIn(Slots.workspace), 'claude.primary');
|
||||||
|
expect(find.text('DIFF'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('with no Claude pane, an active workspace tab takes the whole slot (no reveal chrome)', (tester) async {
|
||||||
|
final p = f.services.panels;
|
||||||
|
p.contribute(TabContribution(id: 'files.tree', slot: Slots.sidebar, title: 'Files', build: (_) => const Text('SIDEBAR')));
|
||||||
|
p.contribute(TabContribution(id: 'diff.view', slot: Slots.workspace, title: 'Diff', build: (_) => const Text('DIFF')));
|
||||||
|
p.contribute(TabContribution(id: 'markdown.viewer', slot: Slots.contextPanel, title: 'Preview', build: (_) => const Text('CONTEXT')));
|
||||||
|
p.activateTab(Slots.workspace, 'diff.view');
|
||||||
|
await pumpLayout(tester);
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(find.text('DIFF'), findsOneWidget);
|
||||||
|
// No alongside-Claude chrome, since there is no Claude pane to sit over.
|
||||||
|
expect(find.bySemanticsLabel('Close'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('global intents dispatch through the app-root Actions', (tester) async {
|
testWidgets('global intents dispatch through the app-root Actions', (tester) async {
|
||||||
await pumpApp(tester);
|
await pumpApp(tester);
|
||||||
final ctx = tester.element(find.byType(RootLayout));
|
final ctx = tester.element(find.byType(RootLayout));
|
||||||
|
|||||||
Reference in New Issue
Block a user