diff --git a/README.md b/README.md index a823207..078d08f 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ public class MyHandler : IActionHandler ### v0.7.3 — AX Agent 권한 코어 재구성 + 입력 계층 정리 -업데이트: 2026-04-04 13:55 (KST) +업데이트: 2026-04-04 14:01 (KST) | 분류 | 내용 | |------|------| @@ -281,6 +281,7 @@ public class MyHandler : IActionHandler | 탭별 설정 해석기 도입 | `AgentTabSettingsResolver`를 추가해 Cowork/Code 분기(검증 활성/Code 전용 도구 비활성)를 단일 경로로 정리 | | L4 통합 회귀 보강 | `PermissionModeCatalogTests`/`PermissionModePresentationCatalogTests`/`SlashCommandCatalogTests`를 추가하고 deny 우선 규칙을 `OperationModePolicyTests`에 반영해 권한·슬래시 회귀망을 강화 | | 권한 팝업 핵심 4모드 정렬 | 권한 팝업을 `소극 활용/적극 활용/계획 중심/완전 자동` 중심으로 단순화하고 `활용하지 않음/질문 없이 진행`은 `고급 모드` 접힘 섹션으로 분리 | +| slash 스크롤 체감 개선 | 휠/방향키 이동 시 전체 재렌더링을 제거하고 선택 하이라이트만 갱신하도록 바꿔 `/` 팝업 스크롤 반응성과 안정성을 개선 | | Slash palette 상태 분리 시작 | `ChatWindow`에 몰려 있던 slash 상태를 `SlashPaletteState`로 분리해 이후 Codex/Claude형 composer 개편 기반 마련 | | 런처 이미지 미리보기 추가 | `#` 클립보드 이미지 항목에서 `Shift+Enter`로 전용 미리보기 창을 열고, 줌·원본 해상도 확인·PNG/JPEG/BMP 저장·클립보드 복사를 지원 | | 검증 | `dotnet build` 경고 0 / 오류 0, `dotnet test` 436 passed / 0 failed | diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 052078d..f49e344 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -3413,3 +3413,20 @@ else: - `dotnet build src/AxCopilot/AxCopilot.csproj -c Debug -p:UseSharedCompilation=false -nodeReuse:false` 통과 (경고 0, 오류 0). - 테스트 재빌드 시 WPF 임시 생성물 누락(CS2001, `AxCopilot_*_wpftmp.csproj`)이 간헐 발생하여 다음으로 회귀 확인: - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --no-build --filter "FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~OperationModePolicyTests|FullyQualifiedName~PermissionModeCatalogTests|FullyQualifiedName~PermissionModePresentationCatalogTests|FullyQualifiedName~OperationModeReadinessTests"` 통과 (86 passed, 0 failed). + +## 2026-04-04 추가 진행 기록 (연속 실행 31차: slash 팝업 스크롤/선택 체감 개선) + +업데이트: 2026-04-04 14:01 (KST) + +### 1) slash 이동 시 재렌더링 제거 +- 기존: 휠/방향키 이동마다 `RenderSlashPage()`를 호출해 전체 항목을 다시 그림. +- 변경: 이동 시 `UpdateSlashSelectionVisualState()` + `EnsureSlashSelectionVisible()`만 호출하도록 전환. +- 효과: 스크롤 시 깜빡임/점프를 줄이고 선택 이동 체감이 더 부드러워짐. + +### 2) 선택 하이라이트 동기화 경로 정리 +- 항목 Hover/Home/End 이동에서 동일한 하이라이트 갱신 경로를 사용하도록 통일. +- 목록 재렌더가 필요한 경우(그룹 접힘/펼침, 검색 결과 변경)만 `RenderSlashPage()` 유지. + +### 3) 품질 게이트 +- `dotnet build src/AxCopilot/AxCopilot.csproj -c Debug -p:UseSharedCompilation=false -nodeReuse:false` 통과 (경고 0, 오류 0). +- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --no-build --filter "FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~SlashCommandCatalogTests|FullyQualifiedName~OperationModePolicyTests|FullyQualifiedName~PermissionModeCatalogTests|FullyQualifiedName~PermissionModePresentationCatalogTests"` 통과 (84 passed, 0 failed). diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 3099edc..d93989a 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -5315,7 +5315,6 @@ public partial class ChatWindow : Window var skillAvailable = skillDef?.IsAvailable ?? true; var absoluteIndex = i; - var isSelected = absoluteIndex == _slashPalette.SelectedIndex; var hoverBrushItem = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.LightGray; var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent; var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; @@ -5325,8 +5324,8 @@ public partial class ChatWindow : Window var item = new Border { - Background = isSelected ? hoverBrushItem : itemBg, - BorderBrush = isSelected ? accent : borderBrush, + Background = Brushes.Transparent, + BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(10), Padding = new Thickness(10, 7, 10, 7), @@ -5440,14 +5439,9 @@ public partial class ChatWindow : Window item.MouseEnter += (_, _) => { _slashPalette.SelectedIndex = absoluteIndex; - item.Background = hoverBrushItem; - item.BorderBrush = accent; - }; - item.MouseLeave += (_, _) => - { - item.Background = absoluteIndex == _slashPalette.SelectedIndex ? hoverBrushItem : itemBg; - item.BorderBrush = absoluteIndex == _slashPalette.SelectedIndex ? accent : borderBrush; + UpdateSlashSelectionVisualState(); }; + item.MouseLeave += (_, _) => UpdateSlashSelectionVisualState(); item.MouseLeftButtonDown += (_, _) => { _slashPalette.SelectedIndex = absoluteIndex; @@ -5499,6 +5493,7 @@ public partial class ChatWindow : Window SlashPopupFooter.Text = $"Enter 실행 · ↑↓/PgUp/PgDn 이동 · Home/End · Esc 닫기 · 표시 {visibleCommandCount + visibleSkillCount}/{total}"; } + UpdateSlashSelectionVisualState(); EnsureSlashSelectionVisible(); } @@ -5546,7 +5541,8 @@ public partial class ChatWindow : Window for (var i = 0; i < steps; i++) MoveSlashSelection(direction); - RenderSlashPage(); + UpdateSlashSelectionVisualState(); + EnsureSlashSelectionVisible(); } /// 키보드로 선택된 슬래시 아이템을 실행합니다. @@ -5591,6 +5587,28 @@ public partial class ChatWindow : Window SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + (bounds.Bottom - SlashScrollViewer.ViewportHeight) + 8); } + private void UpdateSlashSelectionVisualState() + { + if (_slashVisibleItemByAbsoluteIndex.Count == 0) + return; + + var selectedIndex = _slashPalette.SelectedIndex; + var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent; + var hoverBrushItem = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.LightGray; + var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; + var accent = TryFindResource("AccentColor") as Brush ?? Brushes.Blue; + + foreach (var (absoluteIndex, element) in _slashVisibleItemByAbsoluteIndex) + { + if (element is not Border border) + continue; + + var selected = absoluteIndex == selectedIndex; + border.Background = selected ? hoverBrushItem : itemBg; + border.BorderBrush = selected ? accent : borderBrush; + } + } + /// 슬래시 명령어 즐겨찾기를 토글하고 설정을 저장합니다. private void ToggleSlashFavorite(string cmd) { @@ -10881,14 +10899,16 @@ public partial class ChatWindow : Window { var visible = GetVisibleSlashOrderedIndices(); _slashPalette.SelectedIndex = visible.Count > 0 ? visible[0] : GetFirstVisibleSlashIndex(_slashPalette.Matches); - RenderSlashPage(); + UpdateSlashSelectionVisualState(); + EnsureSlashSelectionVisible(); e.Handled = true; } else if (e.Key == Key.End) { var visible = GetVisibleSlashOrderedIndices(); _slashPalette.SelectedIndex = visible.Count > 0 ? visible[^1] : GetFirstVisibleSlashIndex(_slashPalette.Matches); - RenderSlashPage(); + UpdateSlashSelectionVisualState(); + EnsureSlashSelectionVisible(); e.Handled = true; } else if (e.Key == Key.Enter && _slashPalette.SelectedIndex >= 0)