채팅 본문 폭과 진행 로그 줄바꿈 레이아웃 개선

채팅 본문과 process feed가 큰 창의 오른쪽 여백을 더 활용하도록 반응형 폭 계산을 조정했습니다. 본문 최대폭을 1040, 입력창 폭을 980 기준까지 확장하고 MessageList 최대폭을 함께 늘려 조기 말줄임이 덜 발생하도록 정리했습니다.

process feed 헤더, 실행 이력 요약, 라이브 thinking 줄은 줄바꿈 우선으로 바꾸고 필요한 미리보기 카드에만 ellipsis를 남겼습니다. 입력창 위 StreamMetricsLabel과 PulseDotBar 간격도 다시 맞춰 우측 시간·토큰 표시가 좌측 진행 문구 폭을 과하게 잠식하지 않도록 보정했습니다.

검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_chat_width_wrap\ -p:IntermediateOutputPath=obj\verify_chat_width_wrap\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatStreamingUiPolicyTests^|ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_chat_width_wrap_tests\ -p:IntermediateOutputPath=obj\verify_chat_width_wrap_tests\ (통과 74)
This commit is contained in:
2026-04-15 23:54:50 +09:00
parent 6810fb1954
commit 5161e46ac2
6 changed files with 63 additions and 26 deletions

View File

@@ -1,5 +1,13 @@
# AX Commander # AX Commander
- 업데이트: 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를 유지하고, 나머지 진행 로그는 같은 폭 안에서 더 길게 읽히도록 정리했습니다.
- `src/AxCopilot/Views/ChatWindow.xaml`은 입력창 위 상태 행의 `PulseDotBar``StreamMetricsLabel` 간 간격을 다시 잡아 우측 시간·토큰 표시가 붙어 있으면서도 좌측 진행 문구 폭을 덜 잠식하도록 조정했습니다.
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_chat_width_wrap\\ -p:IntermediateOutputPath=obj\\verify_chat_width_wrap\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_chat_width_wrap_tests\\ -p:IntermediateOutputPath=obj\\verify_chat_width_wrap_tests\\` 통과 74
- 업데이트: 2026-04-15 22:45 (KST) - 업데이트: 2026-04-15 22:45 (KST)
- HTML 보고서 뒤쪽으로 갈수록 폰트 크기와 카드 레이아웃이 흔들리던 raw body 호환 문제를 보강했습니다. `src/AxCopilot/Services/Agent/TemplateService.cs``h4`, `dl`, `matrix`, `comparison`, `decision_matrix`, `board_report`, `metrics`, `roadmap` 같은 레거시 블록 전용 CSS를 추가해, 구조화 섹션이 아닌 자유 본문 HTML로 생성된 보고서도 앞부분과 같은 문서 톤을 유지하도록 맞췄습니다. - HTML 보고서 뒤쪽으로 갈수록 폰트 크기와 카드 레이아웃이 흔들리던 raw body 호환 문제를 보강했습니다. `src/AxCopilot/Services/Agent/TemplateService.cs``h4`, `dl`, `matrix`, `comparison`, `decision_matrix`, `board_report`, `metrics`, `roadmap` 같은 레거시 블록 전용 CSS를 추가해, 구조화 섹션이 아닌 자유 본문 HTML로 생성된 보고서도 앞부분과 같은 문서 톤을 유지하도록 맞췄습니다.
- 특히 `roadmap` 안의 `<span class="timeline">`가 기존 전역 `.timeline` 블록 스타일과 충돌해 뒤쪽 일정/담당 배지가 세로 타임라인처럼 깨지던 문제를 별도 override로 분리했습니다. 다크 모드와 모바일 레이아웃도 함께 맞춰 이후 생성되는 HTML 보고서가 같은 문제를 반복하지 않도록 정리했습니다. - 특히 `roadmap` 안의 `<span class="timeline">`가 기존 전역 `.timeline` 블록 스타일과 충돌해 뒤쪽 일정/담당 배지가 세로 타임라인처럼 깨지던 문제를 별도 override로 분리했습니다. 다크 모드와 모바일 레이아웃도 함께 맞춰 이후 생성되는 HTML 보고서가 같은 문제를 반복하지 않도록 정리했습니다.

View File

@@ -1695,3 +1695,11 @@ 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 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 - `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-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줄 미리보기처럼 요약 카드가 실제로 필요한 곳에만 남기고, 이벤트 본문/요약 줄은 가능한 한 전체 문장을 읽을 수 있게 했습니다.
- `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`에서는 라이브 thinking 로그를 Grid 기반으로 바꾸고 최대 길이도 화면 폭에 따라 확장해, 실시간 진행 카드 아래 로그가 오른쪽 여백을 더 활용하면서 자연스럽게 줄바꿈되도록 맞췄습니다.
- `src/AxCopilot/Views/ChatWindow.xaml``PulseDotBar``StreamMetricsLabel` 사이 간격을 다시 조정해 입력창 위 우측 시간/토큰 메트릭이 고정되면서도 좌측 진행 문구 폭을 과하게 잠식하지 않도록 보정했습니다.
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_chat_width_wrap\\ -p:IntermediateOutputPath=obj\\verify_chat_width_wrap\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_chat_width_wrap_tests\\ -p:IntermediateOutputPath=obj\\verify_chat_width_wrap_tests\\` 통과 74

View File

@@ -287,6 +287,7 @@ public partial class ChatWindow
MaxWidth = msgMaxWidth, MaxWidth = msgMaxWidth,
Margin = new Thickness(62, 1, 12, 1), Margin = new Thickness(62, 1, 12, 1),
}; };
var headerTextMaxWidth = Math.Max(180, msgMaxWidth - 28);
if (liveWaitingStyle) if (liveWaitingStyle)
{ {
@@ -324,6 +325,8 @@ public partial class ChatWindow
FontSize = 12, FontSize = 12,
Foreground = secondaryText, Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.Wrap,
MaxWidth = headerTextMaxWidth,
}); });
stack.Children.Add(waitRow); stack.Children.Add(waitRow);
} }
@@ -360,7 +363,8 @@ public partial class ChatWindow
FontSize = 12, FontSize = 12,
Foreground = headerColor, Foreground = headerColor,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
TextTrimming = TextTrimming.CharacterEllipsis, TextWrapping = TextWrapping.Wrap,
MaxWidth = headerTextMaxWidth,
}); });
stack.Children.Add(headerRow); stack.Children.Add(headerRow);
@@ -543,7 +547,8 @@ public partial class ChatWindow
FontSize = 12, FontSize = 12,
Foreground = secondaryText, Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
TextTrimming = TextTrimming.CharacterEllipsis, TextWrapping = TextWrapping.Wrap,
MaxWidth = Math.Max(180, msgMaxWidth - 28),
}); });
container.Children.Add(headerRow); container.Children.Add(headerRow);
@@ -568,7 +573,8 @@ public partial class ChatWindow
FontSize = 11, FontSize = 11,
Foreground = secondaryText, Foreground = secondaryText,
Opacity = 0.75, Opacity = 0.75,
TextTrimming = TextTrimming.CharacterEllipsis, TextWrapping = TextWrapping.Wrap,
MaxWidth = Math.Max(180, msgMaxWidth - 16),
Margin = new Thickness(0, 1, 0, 0), Margin = new Thickness(0, 1, 0, 0),
}; };
if (!string.IsNullOrWhiteSpace(evt.FilePath)) if (!string.IsNullOrWhiteSpace(evt.FilePath))
@@ -1842,6 +1848,7 @@ public partial class ChatWindow
MaxWidth = msgMaxWidth2, MaxWidth = msgMaxWidth2,
Margin = new Thickness(62, 1, 12, 1), Margin = new Thickness(62, 1, 12, 1),
}; };
var headerTextMaxWidth2 = Math.Max(180, msgMaxWidth2 - 28);
if (!string.IsNullOrWhiteSpace(evt.RunId)) if (!string.IsNullOrWhiteSpace(evt.RunId))
_runBannerAnchors[evt.RunId] = stack2; _runBannerAnchors[evt.RunId] = stack2;
@@ -1874,7 +1881,8 @@ public partial class ChatWindow
FontSize = 12, FontSize = 12,
Foreground = headerColor2, Foreground = headerColor2,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
TextTrimming = TextTrimming.CharacterEllipsis, TextWrapping = TextWrapping.Wrap,
MaxWidth = headerTextMaxWidth2,
}); });
stack2.Children.Add(headerRow2); stack2.Children.Add(headerRow2);

View File

@@ -212,16 +212,19 @@ public partial class ChatWindow
/// <summary>채팅 본문 폭을 세 탭에서 동일한 기준으로 맞춥니다.</summary> /// <summary>채팅 본문 폭을 세 탭에서 동일한 기준으로 맞춥니다.</summary>
private double GetMessageMaxWidth() private double GetMessageMaxWidth()
{ {
if (_lastResponsiveMessageWidth >= 100)
return Math.Clamp(_lastResponsiveMessageWidth, 320, 1040);
var hostWidth = _lastResponsiveComposerWidth; var hostWidth = _lastResponsiveComposerWidth;
if (hostWidth < 100) if (hostWidth < 100)
hostWidth = ComposerShell?.ActualWidth ?? 0; hostWidth = ComposerShell?.ActualWidth ?? 0;
if (hostWidth < 100) if (hostWidth < 100)
hostWidth = MessageList?.ActualWidth ?? 0; hostWidth = MessageList?.ActualWidth ?? 0;
if (hostWidth < 100) if (hostWidth < 100)
hostWidth = 1120; hostWidth = 1280;
var maxW = hostWidth - 44; var maxW = hostWidth - 24;
return Math.Clamp(maxW, 320, 760); return Math.Clamp(maxW, 320, 1040);
} }
private bool UpdateResponsiveChatLayout() private bool UpdateResponsiveChatLayout()
@@ -234,17 +237,17 @@ public partial class ChatWindow
// 메시지 축과 입력축이 같은 중심선을 공유하도록 // 메시지 축과 입력축이 같은 중심선을 공유하도록
// 본문 폭 상한을 조금 더 낮추고 창 폭 변화에 더 부드럽게 반응시킵니다. // 본문 폭 상한을 조금 더 낮추고 창 폭 변화에 더 부드럽게 반응시킵니다.
var contentWidth = Math.Max(360, viewportWidth - 24); var contentWidth = Math.Max(360, viewportWidth - 12);
// 고정 최대폭 — 큰 창에서 안정적, 작은 창에서만 축소 // 큰 창에서는 본문을 더 넓게 쓰고, 입력창은 약간만 좁게 유지합니다.
var messageWidth = Math.Min(contentWidth - 10, 800); var messageWidth = Math.Min(contentWidth - 8, 1040);
var composerWidth = Math.Min(contentWidth - 24, 760); var composerWidth = Math.Min(contentWidth - 12, 980);
var changed = false; var changed = false;
if (Math.Abs(_lastResponsiveMessageWidth - messageWidth) > 8) if (Math.Abs(_lastResponsiveMessageWidth - messageWidth) > 8)
{ {
_lastResponsiveMessageWidth = messageWidth; _lastResponsiveMessageWidth = messageWidth;
if (MessageList != null) if (MessageList != null)
MessageList.MaxWidth = messageWidth + 48; MessageList.MaxWidth = messageWidth + 64;
if (EmptyState != null) if (EmptyState != null)
EmptyState.MaxWidth = messageWidth; EmptyState.MaxWidth = messageWidth;
changed = true; changed = true;

View File

@@ -338,18 +338,21 @@ public partial class ChatWindow
case AgentEventType.Thinking: case AgentEventType.Thinking:
{ {
var thinkingSummaryMaxLength = msgMaxWidth > 900 ? 220 : 140;
var thinkText = AgentProgressSummarySanitizer.NormalizeThinkingSummary( var thinkText = AgentProgressSummarySanitizer.NormalizeThinkingSummary(
agentEvent.Summary, agentEvent.Summary,
agentEvent.ToolName, agentEvent.ToolName,
maxLength: 100); maxLength: thinkingSummaryMaxLength);
if (string.IsNullOrWhiteSpace(thinkText)) break; if (string.IsNullOrWhiteSpace(thinkText)) break;
var thinkRow = new StackPanel var thinkRow = new Grid
{ {
Orientation = Orientation.Horizontal,
Margin = new Thickness(22, 2, 0, 2), Margin = new Thickness(22, 2, 0, 2),
}; };
thinkRow.Children.Add(new TextBlock thinkRow.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
thinkRow.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var thinkIcon = new TextBlock
{ {
Text = "\uE915", Text = "\uE915",
FontFamily = s_segoeIconFont, FontFamily = s_segoeIconFont,
@@ -357,17 +360,23 @@ public partial class ChatWindow
Foreground = new SolidColorBrush(Color.FromRgb(0x59, 0xA5, 0xF5)), Foreground = new SolidColorBrush(Color.FromRgb(0x59, 0xA5, 0xF5)),
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 4, 0), Margin = new Thickness(0, 0, 4, 0),
}); };
thinkRow.Children.Add(new TextBlock Grid.SetColumn(thinkIcon, 0);
thinkRow.Children.Add(thinkIcon);
var thinkLabel = new TextBlock
{ {
Text = thinkText, Text = thinkText,
FontSize = 10.5, FontSize = 10.5,
FontStyle = FontStyles.Italic, FontStyle = FontStyles.Italic,
Foreground = secondaryText, Foreground = secondaryText,
Opacity = 0.82, Opacity = 0.82,
TextTrimming = TextTrimming.CharacterEllipsis, TextWrapping = TextWrapping.Wrap,
MaxWidth = msgMaxWidth - 60, MaxWidth = Math.Max(180, msgMaxWidth - 52),
}); VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetColumn(thinkLabel, 1);
thinkRow.Children.Add(thinkLabel);
_v2LiveContainer.Children.Add(thinkRow); _v2LiveContainer.Children.Add(thinkRow);
AutoScrollIfNeeded(); AutoScrollIfNeeded();
break; break;
@@ -432,11 +441,12 @@ public partial class ChatWindow
return; return;
var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(agentEvent, _activeTab); var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(agentEvent, _activeTab);
var statusSummaryMaxLength = GetMessageMaxWidth() > 900 ? 260 : 180;
var normalizedThinking = agentEvent.Type == AgentEventType.Thinking var normalizedThinking = agentEvent.Type == AgentEventType.Thinking
? AgentProgressSummarySanitizer.NormalizeThinkingSummary( ? AgentProgressSummarySanitizer.NormalizeThinkingSummary(
agentEvent.Summary, agentEvent.Summary,
agentEvent.ToolName, agentEvent.ToolName,
maxLength: 160) maxLength: statusSummaryMaxLength)
: string.Empty; : string.Empty;
var message = string.IsNullOrWhiteSpace(normalizedThinking) var message = string.IsNullOrWhiteSpace(normalizedThinking)
? narrative.Message ? narrative.Message

View File

@@ -2166,7 +2166,7 @@
<Border x:Name="ComposerShell" Grid.Row="4" <Border x:Name="ComposerShell" Grid.Row="4"
Margin="24,0,24,12" Margin="24,0,24,12"
Width="Auto" Width="Auto"
MaxWidth="900" MaxWidth="980"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Bottom"> VerticalAlignment="Bottom">
<StackPanel HorizontalAlignment="Stretch"> <StackPanel HorizontalAlignment="Stretch">
@@ -2181,9 +2181,9 @@
<Border x:Name="PulseDotBar" <Border x:Name="PulseDotBar"
Grid.Column="0" Grid.Column="0"
Visibility="Collapsed" Visibility="Collapsed"
HorizontalAlignment="Left" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="6,0,0,0"> Margin="6,0,18,0">
<StackPanel> <StackPanel>
<!-- 레거시 펄스 점 (코드비하인드 참조용, 화면에 표시 안 함) --> <!-- 레거시 펄스 점 (코드비하인드 참조용, 화면에 표시 안 함) -->
<Ellipse x:Name="PulseDot1" Width="7" Height="7" Fill="{DynamicResource AccentColor}" Opacity="0.3" Visibility="Collapsed"/> <Ellipse x:Name="PulseDot1" Width="7" Height="7" Fill="{DynamicResource AccentColor}" Opacity="0.3" Visibility="Collapsed"/>
@@ -2262,7 +2262,7 @@
Visibility="Collapsed" Visibility="Collapsed"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Margin="12,0,6,2" Margin="18,0,0,2"
FontSize="11" FontSize="11"
Foreground="{DynamicResource SecondaryText}" Foreground="{DynamicResource SecondaryText}"
Opacity="0.7" Opacity="0.7"