슬래시 탐색 정합화와 권한 요청창 문구 복구를 반영하고 개발문서 이력을 갱신
Some checks failed
Release Gate / gate (push) Has been cancelled

- ChatWindow: slash palette 이동 기준을 렌더 순서로 통일해 방향키/휠/Home/End 선택 체감 불일치를 해소

- ChatWindow: 최근 권한 거부 카드 액션 순서를 활용하지 않음→소극 활용→적극 활용→예외 해제로 정렬

- ChatWindow: /permissions, /allowed-tools 사용법 표기를 deny→default→acceptedits→plan→bypass→dontask→status 순서로 통일

- PermissionRequestWindow: 권한 선택/위험도/미리보기/명령 위험도 문구를 한국어 중심으로 정리하고 깨진 문자열을 복구

- README.md: 업데이트 시각을 2026-04-04 13:07(KST)로 갱신하고 이번 반영 항목 2건 추가

- docs/DEVELOPMENT.md: 연속 실행 25차 이력(슬래시 정합화, 권한 다이얼로그 복구, 품질 게이트 결과) 추가

- 검증: dotnet build 경고 0/오류 0, 슬래시/운영모드 필터 테스트 43건 통과
This commit is contained in:
2026-04-04 13:12:57 +09:00
parent 6e65cf6026
commit b7431146c8
4 changed files with 1228 additions and 36 deletions

View File

@@ -222,7 +222,7 @@ public class MyHandler : IActionHandler
### v0.7.3 — AX Agent 권한 코어 재구성 + 입력 계층 정리
업데이트: 2026-04-04 12:41 (KST)
업데이트: 2026-04-04 13:07 (KST)
| 분류 | 내용 |
|------|------|
@@ -273,6 +273,8 @@ public class MyHandler : IActionHandler
| 권한 상태 표시 간소화 | 권한 상태 텍스트(`/permissions`, `/allowed-tools`)를 운영 모드 포함 축약형으로 정리하고 권한 버튼 툴팁에 동일 정보를 반영 |
| 설정창 외부 진입 안정화 | AX Agent 설정창 오픈 시 리소스 병합 실패를 방어하고, 외부 진입 경로를 Dispatcher 기반으로 안정화 |
| 모델 라벨 반응형 보강 | 컴포저 상단 모델 라벨에 말줄임(`MaxWidth` + `CharacterEllipsis`)을 적용해 좁은 폭에서 레이아웃 깨짐을 방지 |
| 슬래시 탐색 순서 정합화 | `/` 팝업의 방향키/휠/Home/End 이동 기준을 렌더 순서(핀/최근 정렬 적용 순서)로 통일해 스크롤·선택 체감 불일치를 해소 |
| 권한 요청창 한국어/인코딩 복구 | `PermissionRequestWindow`의 깨진 문자열을 복구하고 권한 선택/위험도/미리보기 문구를 한국어 기준으로 정리 |
| Slash palette 상태 분리 시작 | `ChatWindow`에 몰려 있던 slash 상태를 `SlashPaletteState`로 분리해 이후 Codex/Claude형 composer 개편 기반 마련 |
| 런처 이미지 미리보기 추가 | `#` 클립보드 이미지 항목에서 `Shift+Enter`로 전용 미리보기 창을 열고, 줌·원본 해상도 확인·PNG/JPEG/BMP 저장·클립보드 복사를 지원 |
| 검증 | `dotnet build` 경고 0 / 오류 0, `dotnet test` 436 passed / 0 failed |

View File

@@ -3282,3 +3282,25 @@ else:
## 2026-04-04 추가 진행 기록 (연속 실행 25차: 슬래시 탐색 정합화 + 권한 요청창 문구 복구)
업데이트: 2026-04-04 13:07 (KST)
### 1) slash palette 탐색 정합화
- ChatWindow에 렌더 순서 전용 인덱스(_slashVisibleAbsoluteOrder)를 추가.
- / 팝업 렌더 시 실제 표시 순서를 기록하고, 방향키/휠 이동이 원본 목록 인덱스가 아닌 렌더 순서를 기준으로 동작하도록 보정.
- Home/End 키도 동일 기준(현재 화면에 표시된 첫/마지막 항목)으로 이동하도록 통일.
### 2) 권한 액션/표기 일관성 보강
- 최근 권한 거부 카드의 빠른 액션 순서를 활용하지 않음 → 소극 활용 → 적극 활용 → 예외 해제로 정렬.
- /permissions, /allowed-tools 사용법 표기를 동일 순서(deny|default|acceptedits|plan|bypass|dontask|status)로 정리.
### 3) 권한 요청 다이얼로그 한국어/인코딩 복구
- PermissionRequestWindow의 옵션/도움말/헤더/위험도/미리보기 문구를 한국어 기준으로 통일.
- 깨진 문자열(권한 확인/작업 허용/위험도 라벨) 복구.
- 파일 요약 문구에서 인코딩 손상 텍스트를 정리(존재함 · 용량 · 수정 시각).
- 명령 위험도 분류 문구를 한국어로 변환.
### 4) 품질 게이트
- dotnet build src/AxCopilot/AxCopilot.csproj 통과 (경고 0, 오류 0).
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --no-build --filter "FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~OperationModeReadinessTests" 통과 (43 passed, 0 failed).

View File

@@ -1882,9 +1882,9 @@ public partial class ChatWindow : Window
return button;
}
actionRow.Children.Add(CreateActionButton("활용하지 않음", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("소극 활용", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
actionRow.Children.Add(CreateActionButton("적극 활용", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
actionRow.Children.Add(CreateActionButton("활용하지 않음", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("예외 해제", "#F3F4F6", "#374151", () => SetToolPermissionOverride(latestDenied.ToolName!, null)));
deniedStack.Children.Add(actionRow);
}
@@ -5033,6 +5033,7 @@ public partial class ChatWindow : Window
// ── 슬래시 명령어 팝업 상태 ──
private readonly SlashPaletteState _slashPalette = new();
private readonly Dictionary<int, FrameworkElement> _slashVisibleItemByAbsoluteIndex = new();
private readonly List<int> _slashVisibleAbsoluteOrder = new();
// ── 슬래시 명령어 (탭별 분류) ──
@@ -5294,11 +5295,14 @@ public partial class ChatWindow : Window
: GetSlashSectionExpanded("slash_commands", true);
}
private IReadOnlyList<int> GetVisibleSlashOrderedIndices() => _slashVisibleAbsoluteOrder;
/// <summary>현재 슬래시 명령어 항목을 스크롤 리스트로 렌더링합니다.</summary>
private void RenderSlashPage()
{
SlashItems.Items.Clear();
_slashVisibleItemByAbsoluteIndex.Clear();
_slashVisibleAbsoluteOrder.Clear();
var total = _slashPalette.Matches.Count;
var totalSkills = _slashPalette.Matches.Count(x => x.IsSkill);
var totalCommands = total - totalSkills;
@@ -5535,6 +5539,7 @@ public partial class ChatWindow : Window
SlashItems.Items.Add(item);
_slashVisibleItemByAbsoluteIndex[absoluteIndex] = item;
_slashVisibleAbsoluteOrder.Add(absoluteIndex);
}
SlashItems.Items.Add(CreateSlashSectionHeader("slash_commands", "명령", totalCommands, commandsExpanded));
@@ -5588,33 +5593,28 @@ public partial class ChatWindow : Window
private void MoveSlashSelection(int direction)
{
if (_slashPalette.Matches.Count == 0)
var visibleOrder = GetVisibleSlashOrderedIndices();
if (visibleOrder.Count == 0)
return;
if (_slashPalette.SelectedIndex < 0 || !IsSlashItemVisibleByIndex(_slashPalette.SelectedIndex))
_slashPalette.SelectedIndex = GetFirstVisibleSlashIndex(_slashPalette.Matches);
if (_slashPalette.SelectedIndex < 0)
var currentPosition = -1;
for (var i = 0; i < visibleOrder.Count; i++)
{
if (visibleOrder[i] != _slashPalette.SelectedIndex)
continue;
currentPosition = i;
break;
}
if (currentPosition < 0)
{
_slashPalette.SelectedIndex = visibleOrder[0];
return;
}
if (direction < 0)
{
for (var i = _slashPalette.SelectedIndex - 1; i >= 0; i--)
{
if (!IsSlashItemVisibleByIndex(i)) continue;
_slashPalette.SelectedIndex = i;
return;
}
}
else if (direction > 0)
{
for (var i = _slashPalette.SelectedIndex + 1; i < _slashPalette.Matches.Count; i++)
{
if (!IsSlashItemVisibleByIndex(i)) continue;
_slashPalette.SelectedIndex = i;
return;
}
}
if (direction < 0 && currentPosition > 0)
_slashPalette.SelectedIndex = visibleOrder[currentPosition - 1];
else if (direction > 0 && currentPosition < visibleOrder.Count - 1)
_slashPalette.SelectedIndex = visibleOrder[currentPosition + 1];
}
/// <summary>슬래시 팝업을 Delta 방향으로 스크롤합니다.</summary>
@@ -7349,11 +7349,11 @@ public partial class ChatWindow : Window
if (permAction == "status")
{
AppendLocalSlashResult(_activeTab, "/permissions", $"{BuildPermissionStatusText()}\n사용법: /permissions default|acceptedits|plan|bypass|dontask|deny|status");
AppendLocalSlashResult(_activeTab, "/permissions", $"{BuildPermissionStatusText()}\n사용법: /permissions deny|default|acceptedits|plan|bypass|dontask|status");
return;
}
OpenPermissionPanelFromSlash("/permissions", "사용법: /permissions default|acceptedits|plan|bypass|dontask|deny|status");
OpenPermissionPanelFromSlash("/permissions", "사용법: /permissions deny|default|acceptedits|plan|bypass|dontask|status");
return;
}
if (string.Equals(slashSystem, "__ALLOWED_TOOLS__", StringComparison.Ordinal))
@@ -7367,11 +7367,11 @@ public partial class ChatWindow : Window
if (toolAction == "status")
{
AppendLocalSlashResult(_activeTab, "/allowed-tools", $"{BuildPermissionStatusText()}\n사용법: /allowed-tools default|acceptedits|plan|bypass|dontask|deny|status");
AppendLocalSlashResult(_activeTab, "/allowed-tools", $"{BuildPermissionStatusText()}\n사용법: /allowed-tools deny|default|acceptedits|plan|bypass|dontask|status");
return;
}
OpenPermissionPanelFromSlash("/allowed-tools", "사용법: /allowed-tools default|acceptedits|plan|bypass|dontask|deny|status");
OpenPermissionPanelFromSlash("/allowed-tools", "사용법: /allowed-tools deny|default|acceptedits|plan|bypass|dontask|status");
return;
}
if (string.Equals(slashSystem, "__MODEL__", StringComparison.Ordinal))
@@ -10961,18 +10961,15 @@ public partial class ChatWindow : Window
}
else if (e.Key == Key.Home)
{
_slashPalette.SelectedIndex = GetFirstVisibleSlashIndex(_slashPalette.Matches);
var visible = GetVisibleSlashOrderedIndices();
_slashPalette.SelectedIndex = visible.Count > 0 ? visible[0] : GetFirstVisibleSlashIndex(_slashPalette.Matches);
RenderSlashPage();
e.Handled = true;
}
else if (e.Key == Key.End)
{
for (var i = _slashPalette.Matches.Count - 1; i >= 0; i--)
{
if (!IsSlashItemVisibleByIndex(i)) continue;
_slashPalette.SelectedIndex = i;
break;
}
var visible = GetVisibleSlashOrderedIndices();
_slashPalette.SelectedIndex = visible.Count > 0 ? visible[^1] : GetFirstVisibleSlashIndex(_slashPalette.Matches);
RenderSlashPage();
e.Handled = true;
}

File diff suppressed because it is too large Load Diff