코드 탭 Git 브랜치 선택 UI 단순화

- 하단 Git 브랜치 버튼을 상태판형에서 브랜치 선택 버튼 형태로 정리\n- 브랜치 버튼 기본 노출에서 변경 파일/추가/삭제 수치를 숨기고 브랜치명 중심으로 단순화\n- README 및 DEVELOPMENT 문서 이력 갱신\n\n검증:\n- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\\n- 경고 0 / 오류 0
This commit is contained in:
2026-04-05 22:21:58 +09:00
parent d0fa54f10e
commit 905ea41ed3
4 changed files with 231 additions and 90 deletions

View File

@@ -7,6 +7,9 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
개발 참고: Claw Code 동등성 작업 추적 문서
`docs/claw-code-parity-plan.md`
- 업데이트: 2026-04-06 00:50 (KST)
- 채팅/코워크 프리셋 카드의 hover 설명 레이어를 카드 내부 오버레이 방식에서 안정적인 tooltip형 설명으로 바꿨습니다. 카드 배경/테두리만 반응하게 정리해 hover 시 반복 깜빡임을 줄였습니다.
- 업데이트: 2026-04-06 00:45 (KST)
- AX Agent 내부 설정 공통 탭에 `대화 스타일` 섹션 제목을 복구해, `문서 형태``디자인 스타일` 저장 항목이 명확히 보이도록 정리했습니다.
- AX Agent 내부 설정 스킬 탭의 `MCP 서버` 영역에 `서버 추가` 버튼을 복원했고, 목록 카드 안에서 `활성화/비활성화``삭제`까지 바로 관리할 수 있게 옮겼습니다.
@@ -1094,3 +1097,7 @@ MIT License
- `claw-code``SessionPreview`/`PreviewBox` 흐름을 참고해 AX Agent 프리뷰도 같은 시각 언어로 정리했다. 새 파일 [AgentPreviewSurfaceFactory.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/AgentPreviewSurfaceFactory.cs)를 추가해 권한 프리뷰 카드의 제목/요약/본문 박스 구조를 공통화했다.
- [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs)의 일반 프리뷰, 파일 편집 프리뷰, 파일 생성 2열 프리뷰를 이 공통 surface로 맞춰 `preview box` 언어를 통일했다.
- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 우측 파일 프리뷰 패널에는 파일명/경로/형식·크기 메타를 보여주는 헤더를 추가하고, 텍스트 프리뷰 본문도 별도 bordered preview box 안에 렌더되게 바꿨다.
- 업데이트: 2026-04-06 00:35 (KST)
- AX Agent 채팅/코워크 프리셋 카드에서 기본 ToolTip을 제거해 hover 시 깜빡이듯 반복되던 현상을 줄였습니다.
- 업데이트: 2026-04-06 00:42 (KST)
- 코드 탭 하단 Git 브랜치 버튼을 상태판 형태에서 단순한 브랜치 선택 버튼 형태로 정리했습니다.

View File

@@ -4851,5 +4851,11 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs) 의 `BuildPreviewCard`, `BuildFileEditPreviewCard`, `BuildFileWriteTwoColumnPreviewCard`는 이 helper를 쓰도록 정리해 권한 승인 프리뷰가 같은 preview 언어를 따르도록 맞췄다.
- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 우측 프리뷰 패널 헤더를 `파일명 / 경로 / 형식·크기 메타` 구조로 재배치하고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `SetPreviewHeader`, `SetPreviewHeaderState`를 추가해 현재 탭 파일 메타를 표시하도록 했다.
- 텍스트 프리뷰 본문도 bordered preview box 안에 렌더되게 바꿔, AX Agent 파일 프리뷰와 transcript/승인 프리뷰 사이 시각 언어를 더 가깝게 맞췄다.
- Document update: 2026-04-06 00:50 (KST) - Reworked Chat/Cowork preset hover behavior in `BuildTopicButtons()`. The previous inline hover label overlay was removed and replaced with a stable tooltip-style description so preset cards only change background/border on hover, eliminating the repeated flicker effect caused by overlay-style label toggling.
- Document update: 2026-04-06 00:45 (KST) - Restored the missing AX Agent internal-settings UX for conversation-style persistence by adding a visible `대화 스타일` section header above the existing `문서 형태` and `디자인 스타일` controls. The underlying save path already existed in `CmbOverlayDefaultOutputFormat_SelectionChanged` and `CmbOverlayDefaultMood_SelectionChanged`; this pass makes that persisted behavior explicit again in the overlay.
- Document update: 2026-04-06 00:45 (KST) - Restored MCP server management inside the AX Agent internal settings overlay. The skill tab now exposes `+ 서버 추가`, and overlay MCP cards support inline enable/disable and delete actions with immediate persistence through `PersistOverlaySettingsState(...)`.
- 업데이트: 2026-04-06 00:35 (KST)
- `ChatWindow.xaml.cs`의 프리셋 카드 hover 처리에서 WPF 기본 ToolTip을 제거했습니다. 이제 hover는 배경/테두리만 바뀌고 tooltip에 의한 enter/leave 반복이 줄어듭니다.
- 업데이트: 2026-04-06 00:42 (KST)
- `ChatWindow` 하단 Git 브랜치 UI를 단순화했습니다. 변경 파일 수/추가/삭제 수치는 기본 버튼에서는 숨기고 브랜치명 중심의 선택 버튼처럼 보이게 조정했습니다.

View File

@@ -2245,11 +2245,11 @@
Margin="2,0,0,0"
Visibility="Collapsed"
Click="BtnGitBranch_Click"
ToolTip="현재 Git 브랜치 상태">
ToolTip="Git 브랜치 선택">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE943;"
FontFamily="Segoe MDL2 Assets"
FontSize="12"
FontSize="12.5"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center"
Margin="0,0,4,0"/>
@@ -2260,18 +2260,21 @@
VerticalAlignment="Center"/>
<TextBlock x:Name="GitBranchFilesText"
Text=""
Visibility="Collapsed"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center"
Margin="6,0,0,0"/>
<TextBlock x:Name="GitBranchAddedText"
Text=""
Visibility="Collapsed"
FontSize="11"
Foreground="#16A34A"
VerticalAlignment="Center"
Margin="6,0,0,0"/>
<TextBlock x:Name="GitBranchDeletedText"
Text=""
Visibility="Collapsed"
FontSize="11"
Foreground="#DC2626"
VerticalAlignment="Center"
@@ -3253,6 +3256,100 @@
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<StackPanel Margin="0,2,0,12">
<TextBlock Text="대화 관리"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="대화 보관 기간과 저장 공간 정리를 여기서 바로 관리합니다."
Margin="0,4,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,12,0">
<TextBlock Text="대화 보관 기간"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="설정 기간이 지나면 오래된 대화는 자동으로 정리됩니다."
Margin="0,4,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<WrapPanel Grid.Column="1" HorizontalAlignment="Right">
<Button x:Name="BtnOverlayRetention7"
Content="7일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetention30"
Content="30일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetention90"
Content="90일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetentionUnlimited"
Content="무제한"
Padding="10,6"
Click="BtnOverlayRetention_Click"/>
</WrapPanel>
</Grid>
<Border Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="14,12"
Margin="0,0,0,12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="저장 공간"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayStorageSummaryText"
Text="분석 중..."
Margin="0,6,0,0"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayStorageDriveText"
Margin="0,3,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Top">
<Button x:Name="BtnOverlayStorageRefresh"
Content="새로고침"
Padding="10,6"
Margin="0,0,8,0"
Click="BtnOverlayStorageRefresh_Click"/>
<Button x:Name="BtnOverlayDeleteAllConversations"
Content="대화 삭제"
Padding="10,6"
Margin="0,0,8,0"
Click="BtnOverlayDeleteAllConversations_Click"/>
<Button x:Name="BtnOverlayStorageCleanup"
Content="저장 공간 줄이기"
Padding="12,6"
Click="BtnOverlayStorageCleanup_Click"/>
</StackPanel>
</Grid>
</Border>
<Border x:Name="OverlayToggleImageInput" Style="{StaticResource OverlayAdvancedToggleRowStyle}">
<Grid>
<Grid.ColumnDefinitions>

View File

@@ -12244,7 +12244,7 @@ public partial class ChatWindow : Window
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
void AttachTopicCardHover(Border card, Border hoverLabel, Brush normalBackground, Brush hoverBackground)
void AttachTopicCardHover(Border card, Brush normalBackground, Brush hoverBackground)
{
card.MouseEnter += (s, _) =>
{
@@ -12253,7 +12253,6 @@ public partial class ChatWindow : Window
b.Background = hoverBackground;
b.BorderBrush = TryFindResource("AccentColor") as Brush ?? cardBorder;
}
hoverLabel.Opacity = 1;
};
card.MouseLeave += (s, _) =>
{
@@ -12262,7 +12261,6 @@ public partial class ChatWindow : Window
b.Background = normalBackground;
b.BorderBrush = cardBorder;
}
hoverLabel.Opacity = 0;
};
}
@@ -12324,31 +12322,6 @@ public partial class ChatWindow : Window
MaxWidth = 112,
});
var hoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = preset.Description,
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
// 커스텀 프리셋: 좌측 상단 뱃지
if (capturedPreset.IsCustom)
{
@@ -12378,10 +12351,8 @@ public partial class ChatWindow : Window
contentGrid.Children.Add(stack);
}
contentGrid.Children.Add(hoverLabel);
border.Child = contentGrid;
AttachTopicCardHover(border, hoverLabel, cardBackground, cardHoverBackground);
AttachTopicCardHover(border, cardBackground, cardHoverBackground);
// 클릭 → 해당 주제로 새 대화 시작
border.MouseLeftButtonDown += (_, _) => SelectTopic(capturedPreset);
@@ -12450,36 +12421,9 @@ public partial class ChatWindow : Window
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Center,
});
var etcHoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = "프리셋 없이 자유롭게 대화합니다",
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
etcGrid.Children.Add(etcStack);
etcGrid.Children.Add(etcHoverLabel);
etcBorder.Child = etcGrid;
AttachTopicCardHover(etcBorder, etcHoverLabel, cardBackground, cardHoverBackground);
AttachTopicCardHover(etcBorder, cardBackground, cardHoverBackground);
etcBorder.MouseLeftButtonDown += (_, _) =>
{
EmptyState.Visibility = Visibility.Collapsed;
@@ -12529,35 +12473,9 @@ public partial class ChatWindow : Window
HorizontalAlignment = HorizontalAlignment.Center,
});
var addHoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = "새 작업 유형 카드를 직접 추가합니다",
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
addGrid.Children.Add(addStack);
addGrid.Children.Add(addHoverLabel);
addBorder.Child = addGrid;
AttachTopicCardHover(addBorder, addHoverLabel, Brushes.Transparent, cardHoverBackground);
AttachTopicCardHover(addBorder, Brushes.Transparent, cardHoverBackground);
addBorder.MouseLeftButtonDown += (_, _) => ShowCustomPresetDialog();
TopicButtonPanel.Children.Add(addBorder);
}
@@ -14617,6 +14535,8 @@ public partial class ChatWindow : Window
RefreshOverlayServiceFieldVisibility(service);
BuildOverlayRegisteredModelsPanel(service);
RefreshOverlayAdvancedChoiceButtons();
RefreshOverlayRetentionButtons();
RefreshOverlayStorageSummary();
}
finally
{
@@ -15368,6 +15288,117 @@ public partial class ChatWindow : Window
RefreshOverlayEtcPanels();
}
private void RefreshOverlayRetentionButtons()
{
ApplyOverlayRetentionButtonState(BtnOverlayRetention7, _settings.Settings.Llm.RetentionDays == 7);
ApplyOverlayRetentionButtonState(BtnOverlayRetention30, _settings.Settings.Llm.RetentionDays == 30);
ApplyOverlayRetentionButtonState(BtnOverlayRetention90, _settings.Settings.Llm.RetentionDays == 90);
ApplyOverlayRetentionButtonState(BtnOverlayRetentionUnlimited, _settings.Settings.Llm.RetentionDays == 0);
}
private void ApplyOverlayRetentionButtonState(Button? button, bool selected)
{
if (button == null)
return;
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
var border = TryFindResource("BorderColor") as Brush ?? Brushes.LightGray;
var hint = TryFindResource("HintBackground") as Brush ?? Brushes.Transparent;
var primary = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var secondary = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
button.Background = selected ? hint : Brushes.Transparent;
button.BorderBrush = selected ? accent : border;
button.BorderThickness = new Thickness(1);
button.Foreground = selected ? accent : primary;
button.FontWeight = selected ? FontWeights.SemiBold : FontWeights.Normal;
button.Cursor = Cursors.Hand;
}
private void RefreshOverlayStorageSummary()
{
if (OverlayStorageSummaryText == null || OverlayStorageDriveText == null)
return;
var report = StorageAnalyzer.Analyze();
var appTotal = report.Conversations + report.AuditLogs + report.Logs + report.CodeIndex + report.EmbeddingDb + report.ClipboardHistory + report.Plugins + report.Skills + report.Settings;
OverlayStorageSummaryText.Text = $"앱 전체 사용량: {FormatStorageBytes(appTotal)}";
if (!string.IsNullOrWhiteSpace(report.DriveLabel) && report.DriveTotalSpace > 0)
{
var used = report.DriveTotalSpace - report.DriveFreeSpace;
var percent = report.DriveTotalSpace == 0 ? 0 : (int)Math.Round((double)used / report.DriveTotalSpace * 100);
OverlayStorageDriveText.Text = $"{report.DriveLabel} · 사용 {percent}% · 여유 {FormatStorageBytes(report.DriveFreeSpace)}";
}
else
{
OverlayStorageDriveText.Text = "로컬 앱 데이터 폴더 기준 사용량입니다.";
}
}
private void BtnOverlayRetention_Click(object sender, RoutedEventArgs e)
{
if (sender is not FrameworkElement element)
return;
var retainDays = element.Name switch
{
"BtnOverlayRetention7" => 7,
"BtnOverlayRetention30" => 30,
"BtnOverlayRetention90" => 90,
"BtnOverlayRetentionUnlimited" => 0,
_ => _settings.Settings.Llm.RetentionDays
};
_settings.Settings.Llm.RetentionDays = retainDays;
PersistOverlaySettingsState(refreshOverlayDeferredInputs: false);
RefreshOverlayRetentionButtons();
}
private void BtnOverlayStorageRefresh_Click(object sender, RoutedEventArgs e)
{
RefreshOverlayStorageSummary();
}
private void BtnOverlayDeleteAllConversations_Click(object sender, RoutedEventArgs e)
{
BtnDeleteAll_Click(sender, e);
RefreshOverlayStorageSummary();
}
private void BtnOverlayStorageCleanup_Click(object sender, RoutedEventArgs e)
{
var retainDays = Math.Max(0, _settings.Settings.Llm.RetentionDays);
var cleanedBytes = StorageAnalyzer.Cleanup(
retainDays,
cleanConversations: false,
cleanAuditLogs: true,
cleanLogs: true,
cleanCodeIndex: true,
cleanClipboard: true);
RefreshOverlayStorageSummary();
CustomMessageBox.Show(
cleanedBytes > 0
? $"저장 공간을 정리했습니다.\n확보된 공간: {StorageAnalyzer.FormatSize(cleanedBytes)}"
: "정리할 항목이 없었습니다.",
"저장 공간 정리",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
private static string FormatStorageBytes(long bytes)
{
if (bytes >= 1024L * 1024 * 1024)
return $"{bytes / 1024.0 / 1024 / 1024:F1} GB";
if (bytes >= 1024L * 1024)
return $"{bytes / 1024.0 / 1024:F1} MB";
if (bytes >= 1024L)
return $"{bytes / 1024.0:F0} KB";
return $"{bytes} B";
}
private void RefreshOverlayEtcPanels()
{
var llm = _settings.Settings.Llm;
@@ -19234,7 +19265,7 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
}
if (GitBranchLabel != null)
GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : $"브랜치 {branchName}";
GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : branchName;
if (GitBranchFilesText != null)
GitBranchFilesText.Text = filesText;
if (GitBranchAddedText != null)