스크롤 맨아래 이동 버튼 위치와 입력바 연동 보정
AX Agent의 스크롤 맨아래 이동 FAB가 입력창 아래로 잘려 보이던 문제를 수정했습니다. ChatWindow.xaml에서 버튼을 Grid.RowSpan=2로 옮기고 기본 하단 여백을 높여 메시지 영역과 입력 바를 함께 기준으로 배치되도록 맞췄습니다. ChatWindow.xaml.cs에는 UpdateScrollToBottomFabPosition()을 추가해 ComposerShell 높이, 입력창 크기 변화, 창 리사이즈 시 FAB 하단 margin을 자동 계산하도록 연결했습니다. 함께 Loaded 구간의 InputBox/InputBorder 이벤트 연결을 null-safe로 정리해 경고 없이 같은 UI 흐름을 유지하도록 보강했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_scroll_to_bottom_fab\ -p:IntermediateOutputPath=obj\verify_scroll_to_bottom_fab\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_scroll_to_bottom_fab_tests\ -p:IntermediateOutputPath=obj\verify_scroll_to_bottom_fab_tests\ (통과 59)
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# AX Commander
|
||||
|
||||
- 업데이트: 2026-04-16 00:01 (KST)
|
||||
- AX Agent 스크롤 맨아래 이동 FAB가 입력창 아래로 잘려 보이던 위치 문제를 조정했습니다. `src/AxCopilot/Views/ChatWindow.xaml`에서 버튼을 `Grid.RowSpan="2"`로 옮겨 메시지 영역과 입력 바를 함께 기준으로 잡고, 기본 하단 여백도 더 넉넉하게 올렸습니다.
|
||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`에는 `UpdateScrollToBottomFabPosition()`을 추가해 `ComposerShell` 높이, 입력창 크기 변화, 창 리사이즈에 맞춰 버튼 하단 여백을 자동 계산하도록 연결했습니다. 이제 입력창이 커지거나 상태 행 높이가 변해도 FAB가 그 위에 안전하게 떠 있습니다.
|
||||
- 함께 `Loaded` 초기화 구간의 `InputBox`/`InputBorder` nullability를 정리해 빌드 경고 없이 같은 UI 흐름을 유지하도록 보강했습니다.
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_scroll_to_bottom_fab\\ -p:IntermediateOutputPath=obj\\verify_scroll_to_bottom_fab\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_scroll_to_bottom_fab_tests\\ -p:IntermediateOutputPath=obj\\verify_scroll_to_bottom_fab_tests\\` 통과 59
|
||||
|
||||
- 업데이트: 2026-04-15 23:52 (KST)
|
||||
- AX Agent 채팅 본문이 오른쪽 여백을 더 활용하도록 반응형 폭 계산을 넓혔습니다. `src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs`에서 본문 최대폭을 `1040`, 입력창 폭을 `980` 기준까지 확장하고 `MessageList` 폭 계산도 함께 조정해 큰 창에서 process feed와 응답 본문이 너무 이르게 잘리지 않도록 맞췄습니다.
|
||||
- `src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs`, `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`에서는 process feed 헤더/이벤트 줄과 라이브 thinking 줄을 말줄임 우선 대신 줄바꿈 우선으로 바꿨습니다. 필요한 미리보기 카드만 기존 ellipsis를 유지하고, 나머지 진행 로그는 같은 폭 안에서 더 길게 읽히도록 정리했습니다.
|
||||
|
||||
@@ -1695,6 +1695,14 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_project_scaffold_layout\\ -p:IntermediateOutputPath=obj\\verify_project_scaffold_layout\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "IntentGateServiceTests|ProjectScaffoldProfileCatalogTests|SkillServiceRuntimePolicyTests|AgentLoopCodeQualityTests" -p:OutputPath=bin\\verify_project_scaffold_layout_tests\\ -p:IntermediateOutputPath=obj\\verify_project_scaffold_layout_tests\\` 통과 183
|
||||
업데이트: 2026-04-16 00:01 (KST)
|
||||
- AX Agent 스크롤 맨아래 이동 FAB 위치를 입력창 높이에 연동되도록 조정했습니다. `src/AxCopilot/Views/ChatWindow.xaml`에서 FAB를 `Grid.Row="3" Grid.RowSpan="2"`로 옮겨 메시지 영역과 입력 바를 함께 기준으로 잡고, 기본 하단 여백을 높여 잘려 보이던 배치를 먼저 보정했습니다.
|
||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`에는 `UpdateScrollToBottomFabPosition()`을 추가했습니다. 이 메서드는 `ComposerShell.ActualHeight`를 읽어 FAB 하단 margin을 계산하고, 스크롤 상태 갱신, 창 로드, 입력창 크기 변화, 입력 바 크기 변화, 창 리사이즈 때마다 다시 적용됩니다.
|
||||
- 같은 `Loaded` 구간에서 `InputBox`, `InputBorder` 포커스/드래그 이벤트 연결도 null-safe로 정리해 이번 위치 보정과 함께 `CS8602` 경고가 남지 않도록 맞췄습니다.
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_scroll_to_bottom_fab\\ -p:IntermediateOutputPath=obj\\verify_scroll_to_bottom_fab\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_scroll_to_bottom_fab_tests\\ -p:IntermediateOutputPath=obj\\verify_scroll_to_bottom_fab_tests\\` 통과 59
|
||||
|
||||
업데이트: 2026-04-15 23:52 (KST)
|
||||
- AX Agent 채팅 본문 반응형 폭 계산을 조정했습니다. `src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs`의 본문 상한을 `1040`, 입력창 상한을 `980`까지 넓히고 `MessageList` 최대 폭도 함께 늘려 넓은 창에서 process feed와 본문 로그가 불필요하게 조기 잘리지 않도록 정리했습니다.
|
||||
- `src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs`는 process feed 헤더, 실행 이력 요약, 배너 헤더를 줄바꿈 우선으로 변경했습니다. 기존 `CharacterEllipsis`는 3줄 미리보기처럼 요약 카드가 실제로 필요한 곳에만 남기고, 이벤트 본문/요약 줄은 가능한 한 전체 문장을 읽을 수 있게 했습니다.
|
||||
|
||||
@@ -1764,10 +1764,10 @@
|
||||
</ListBox>
|
||||
|
||||
<!-- ── 스크롤투바텀 FAB (Claude 스타일) ── -->
|
||||
<Button x:Name="ScrollToBottomFab" Grid.Row="3"
|
||||
<Button x:Name="ScrollToBottomFab" Grid.Row="3" Grid.RowSpan="2"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="0,0,28,16" Width="36" Height="36"
|
||||
Margin="0,0,28,96" Width="36" Height="36"
|
||||
Style="{StaticResource GhostBtn}"
|
||||
Click="ScrollToBottomFab_Click"
|
||||
ToolTip="아래로 스크롤" Cursor="Hand"
|
||||
|
||||
@@ -562,7 +562,13 @@ public partial class ChatWindow : Window
|
||||
UpdateTopicPresetScrollMode();
|
||||
UpdateResponsiveChatLayout();
|
||||
UpdateInputBoxHeight();
|
||||
InputBox.Focus();
|
||||
UpdateScrollToBottomFabPosition();
|
||||
if (ComposerShell != null)
|
||||
ComposerShell.SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
if (InputBox != null)
|
||||
InputBox.SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
InputBox?.Focus();
|
||||
// ── 무거운 작업은 유휴 시점에 비동기 실행 ──
|
||||
// A-1: 패널 이벤트 위임 1회 초기화 — 개별 람다 대신 부모 레벨에서 처리
|
||||
InitConversationPanelDelegation();
|
||||
@@ -618,59 +624,65 @@ public partial class ChatWindow : Window
|
||||
// 입력 바 포커스 글로우 효과
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var inputFocusBorderBrush = TryFindResource("InputFocusBorderColor") as Brush ?? borderBrush;
|
||||
InputBox.GotFocus += (_, _) => InputBorder.BorderBrush = inputFocusBorderBrush;
|
||||
InputBox.LostFocus += (_, _) => InputBorder.BorderBrush = borderBrush;
|
||||
if (InputBox != null && InputBorder != null)
|
||||
{
|
||||
InputBox.GotFocus += (_, _) => InputBorder.BorderBrush = inputFocusBorderBrush;
|
||||
InputBox.LostFocus += (_, _) => InputBorder.BorderBrush = borderBrush;
|
||||
}
|
||||
|
||||
// 드래그 앤 드롭 파일 첨부 — Claude Desktop 스타일
|
||||
InputBorder.AllowDrop = true;
|
||||
Brush? _dragOverOriginalBorder = null;
|
||||
InputBorder.DragEnter += (_, de) =>
|
||||
if (InputBorder != null)
|
||||
{
|
||||
if (de.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
InputBorder.AllowDrop = true;
|
||||
Brush? _dragOverOriginalBorder = null;
|
||||
InputBorder.DragEnter += (_, de) =>
|
||||
{
|
||||
_dragOverOriginalBorder = InputBorder.BorderBrush;
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
InputBorder.BorderBrush = accent;
|
||||
InputBorder.BorderThickness = new Thickness(2);
|
||||
}
|
||||
};
|
||||
InputBorder.DragLeave += (_, _) =>
|
||||
{
|
||||
if (_dragOverOriginalBorder != null)
|
||||
if (de.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
_dragOverOriginalBorder = InputBorder.BorderBrush;
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
InputBorder.BorderBrush = accent;
|
||||
InputBorder.BorderThickness = new Thickness(2);
|
||||
}
|
||||
};
|
||||
InputBorder.DragLeave += (_, _) =>
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
};
|
||||
InputBorder.DragOver += (_, de) =>
|
||||
{
|
||||
de.Effects = de.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
de.Handled = true;
|
||||
};
|
||||
InputBorder.Drop += (_, de) =>
|
||||
{
|
||||
// 드래그 오버 하이라이트 복원
|
||||
if (_dragOverOriginalBorder != null)
|
||||
if (_dragOverOriginalBorder != null)
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
};
|
||||
InputBorder.DragOver += (_, de) =>
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
de.Effects = de.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
de.Handled = true;
|
||||
};
|
||||
InputBorder.Drop += (_, de) =>
|
||||
{
|
||||
// 드래그 오버 하이라이트 복원
|
||||
if (_dragOverOriginalBorder != null)
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
|
||||
if (de.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
|
||||
{
|
||||
// AI 액션 팝업은 Cowork/Code 탭에서만, 그 외에는 항상 단순 첨부
|
||||
var enableAi = _settings.Settings.Llm.EnableDragDropAiActions
|
||||
&& _activeTab is "Cowork" or "Code";
|
||||
if (enableAi && files.Length <= 5)
|
||||
ShowDropActionMenu(files);
|
||||
else
|
||||
foreach (var f in files) AddAttachedFile(f);
|
||||
if (de.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
|
||||
{
|
||||
// AI 액션 팝업은 Cowork/Code 탭에서만, 그 외에는 항상 단순 첨부
|
||||
var enableAi = _settings.Settings.Llm.EnableDragDropAiActions
|
||||
&& _activeTab is "Cowork" or "Code";
|
||||
if (enableAi && files.Length <= 5)
|
||||
ShowDropActionMenu(files);
|
||||
else
|
||||
foreach (var f in files) AddAttachedFile(f);
|
||||
|
||||
InputBox.Focus();
|
||||
}
|
||||
};
|
||||
InputBox?.Focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 스킬 시스템 초기화
|
||||
if (_settings.Settings.Llm.EnableSkillSystem)
|
||||
@@ -684,16 +696,19 @@ public partial class ChatWindow : Window
|
||||
SlashChipClose.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
HideSlashChip(restoreText: true);
|
||||
InputBox.Focus();
|
||||
InputBox?.Focus();
|
||||
};
|
||||
|
||||
// InputBox에서 슬래시 팝업 열린 상태로 마우스 휠 → 팝업 스크롤
|
||||
InputBox.PreviewMouseWheel += (_, me) =>
|
||||
if (InputBox != null)
|
||||
{
|
||||
if (!SlashPopup.IsOpen) return;
|
||||
me.Handled = true;
|
||||
SlashPopup_ScrollByDelta(me.Delta);
|
||||
};
|
||||
InputBox.PreviewMouseWheel += (_, me) =>
|
||||
{
|
||||
if (!SlashPopup.IsOpen) return;
|
||||
me.Handled = true;
|
||||
SlashPopup_ScrollByDelta(me.Delta);
|
||||
};
|
||||
}
|
||||
|
||||
// 탭 UI 초기 상태
|
||||
UpdateFolderBar();
|
||||
@@ -979,7 +994,10 @@ public partial class ChatWindow : Window
|
||||
var atBottom = GetTranscriptVerticalOffset() >= GetTranscriptScrollableHeight() - 120;
|
||||
_userScrolled = !atBottom;
|
||||
if (ScrollToBottomFab != null)
|
||||
{
|
||||
UpdateScrollToBottomFabPosition();
|
||||
ScrollToBottomFab.Visibility = _userScrolled ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToBottomFab_Click(object sender, RoutedEventArgs e)
|
||||
@@ -987,6 +1005,16 @@ public partial class ChatWindow : Window
|
||||
ForceScrollToEnd();
|
||||
}
|
||||
|
||||
private void UpdateScrollToBottomFabPosition()
|
||||
{
|
||||
if (ScrollToBottomFab == null)
|
||||
return;
|
||||
|
||||
var composerHeight = ComposerShell?.ActualHeight ?? 0;
|
||||
var bottomOffset = Math.Max(96, composerHeight + 18);
|
||||
ScrollToBottomFab.Margin = new Thickness(0, 0, 28, bottomOffset);
|
||||
}
|
||||
|
||||
private void AutoScrollIfNeeded()
|
||||
{
|
||||
if (!_userScrolled)
|
||||
|
||||
Reference in New Issue
Block a user