From 3b3f3cedad4480749de725ad1450755a02354c1b Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Tue, 24 Mar 2026 12:20:00 +0800 Subject: [PATCH] fix(ui): keep assistant panes tightly packed --- lib/features/assistant/assistant_page.dart | 31 ++++++---- test/features/assistant_page_suite.dart | 69 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/lib/features/assistant/assistant_page.dart b/lib/features/assistant/assistant_page.dart index 1bdb895c..844762fa 100644 --- a/lib/features/assistant/assistant_page.dart +++ b/lib/features/assistant/assistant_page.dart @@ -25,6 +25,9 @@ import '../../widgets/surface_card.dart'; const double _assistantComposerDefaultInputHeight = 78; const double _assistantWorkspaceMinConversationHeight = 180; const double _assistantWorkspaceMinLowerPaneHeight = 124; +const double _assistantHorizontalResizeHandleWidth = 6; +const double _assistantHorizontalPaneGap = 2; +const double _assistantVerticalResizeHandleHeight = 10; class AssistantPage extends StatefulWidget { const AssistantPage({ @@ -294,7 +297,7 @@ class _AssistantPageState extends State { ), if (!_sidePaneCollapsed) SizedBox( - width: 6, + width: _assistantHorizontalResizeHandleWidth, child: PaneResizeHandle( axis: Axis.horizontal, onDelta: (delta) { @@ -306,7 +309,7 @@ class _AssistantPageState extends State { }, ), ), - const SizedBox(width: 2), + const SizedBox(width: _assistantHorizontalPaneGap), Expanded(child: mainWorkspace), ], ); @@ -344,7 +347,7 @@ class _AssistantPageState extends State { ), ), SizedBox( - width: 6, + width: _assistantHorizontalResizeHandleWidth, child: PaneResizeHandle( axis: Axis.horizontal, onDelta: (delta) { @@ -356,7 +359,7 @@ class _AssistantPageState extends State { }, ), ), - const SizedBox(width: 2), + const SizedBox(width: _assistantHorizontalPaneGap), Expanded(child: mainWorkspace), ], ); @@ -376,6 +379,10 @@ class _AssistantPageState extends State { builder: (context, constraints) { final baseComposerHeight = constraints.maxHeight >= 900 ? 180.0 : 152.0; final composerContentWidth = math.max(240.0, constraints.maxWidth - 32); + final availableWorkspaceHeight = math.max( + 0.0, + constraints.maxHeight - _assistantVerticalResizeHandleHeight, + ); final attachmentExtraHeight = _estimatedComposerWrapSectionHeight( itemCount: _attachments.length, availableWidth: composerContentWidth, @@ -387,20 +394,20 @@ class _AssistantPageState extends State { averageChipWidth: 132, ); final defaultComposerHeight = math.min( - math.max(0.0, constraints.maxHeight - 2), + availableWorkspaceHeight, baseComposerHeight + math.max( - 0, + 0.0, _composerInputHeight - _assistantComposerDefaultInputHeight, ) + attachmentExtraHeight + selectedSkillExtraHeight, ); final composerHeightUpperBound = math.min( - math.max(0.0, constraints.maxHeight - 2), + availableWorkspaceHeight, math.max( _assistantWorkspaceMinLowerPaneHeight, - constraints.maxHeight - _assistantWorkspaceMinConversationHeight, + availableWorkspaceHeight - _assistantWorkspaceMinConversationHeight, ), ); final composerHeightLowerBound = math.min( @@ -435,7 +442,7 @@ class _AssistantPageState extends State { ), SizedBox( key: const Key('assistant-workspace-resize-handle'), - height: 10, + height: _assistantVerticalResizeHandleHeight, child: PaneResizeHandle( axis: Axis.vertical, onDelta: (delta) { @@ -1305,7 +1312,11 @@ class _AssistantPageState extends State { double _resolveMaxSidePaneWidth(double viewportWidth) { final maxWidthByViewport = - viewportWidth - _mainWorkspaceMinWidth - _sidePaneViewportPadding; + viewportWidth - + _mainWorkspaceMinWidth - + _sidePaneViewportPadding - + _assistantHorizontalResizeHandleWidth - + _assistantHorizontalPaneGap; return maxWidthByViewport .clamp(_sidePaneMinWidth, viewportWidth - _sidePaneViewportPadding) .toDouble(); diff --git a/test/features/assistant_page_suite.dart b/test/features/assistant_page_suite.dart index 2db62622..11044804 100644 --- a/test/features/assistant_page_suite.dart +++ b/test/features/assistant_page_suite.dart @@ -19,6 +19,7 @@ import 'package:xworkmate/runtime/runtime_coordinator.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; import 'package:xworkmate/theme/app_theme.dart'; +import 'package:xworkmate/widgets/pane_resize_handle.dart'; import '../test_support.dart'; @@ -573,6 +574,74 @@ void main() { expect(expandedConversationHeight, greaterThan(initialConversationHeight)); }); + testWidgets( + 'AssistantPage keeps all three panes tightly packed after resize', + (WidgetTester tester) async { + final controller = await createTestController(tester); + + await pumpPage( + tester, + child: AssistantPage(controller: controller, onOpenDetail: (_) {}), + platform: TargetPlatform.macOS, + ); + + final pageRect = tester.getRect(find.byType(AssistantPage)); + final taskRail = find.byKey(const Key('assistant-task-rail')); + final horizontalHandle = find.byType(PaneResizeHandle).first; + final verticalHandle = find.byKey( + const Key('assistant-workspace-resize-handle'), + ); + final conversationShell = find.byKey( + const Key('assistant-conversation-shell'), + ); + final composerShell = find.byKey(const Key('assistant-composer-shell')); + + await tester.drag(horizontalHandle, const Offset(360, 0)); + await tester.pumpAndSettle(); + await tester.drag(verticalHandle, const Offset(0, 260)); + await tester.pumpAndSettle(); + + final taskRailRect = tester.getRect(taskRail); + final horizontalHandleRect = tester.getRect(horizontalHandle); + final conversationRect = tester.getRect(conversationShell); + final verticalHandleRect = tester.getRect(verticalHandle); + final composerRect = tester.getRect(composerShell); + + expect(taskRailRect.left, moreOrLessEquals(pageRect.left, epsilon: 0.01)); + expect( + taskRailRect.right, + moreOrLessEquals(horizontalHandleRect.left, epsilon: 0.01), + ); + expect( + horizontalHandleRect.right, + moreOrLessEquals(conversationRect.left, epsilon: 2.01), + ); + expect( + conversationRect.top, + moreOrLessEquals(pageRect.top, epsilon: 0.01), + ); + expect( + conversationRect.bottom, + moreOrLessEquals(verticalHandleRect.top, epsilon: 0.01), + ); + expect( + verticalHandleRect.bottom, + moreOrLessEquals(composerRect.top, epsilon: 0.01), + ); + expect( + composerRect.bottom, + moreOrLessEquals(pageRect.bottom, epsilon: 0.01), + ); + expect( + composerRect.right, + moreOrLessEquals(pageRect.right, epsilon: 0.01), + ); + expect(conversationRect.width, greaterThan(620)); + expect(conversationRect.height, greaterThanOrEqualTo(180)); + expect(composerRect.height, greaterThanOrEqualTo(124)); + }, + ); + // Known flutter_tester host-exit hang in this widget scenario. testWidgets( 'AssistantPage syncs task selection with execution target menu and connection chip',