코워크·코드 경량 모드 애니메이션 비용 추가 완화
Some checks failed
Release Gate / gate (push) Has been cancelled

- 실행 중 경량 라이브 진행 모드에서 메시지 진입 애니메이션과 진행 마커 펄스를 줄여 UI 스레드 부담을 더 낮춤

- AX Agent 라이브 카드의 등장·퇴장 애니메이션과 아이콘 opacity 펄스도 경량 모드에서는 생략하도록 조정함

- README와 DEVELOPMENT 문서를 2026-04-08 12:40 (KST) 기준으로 갱신함

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
2026-04-08 23:49:38 +09:00
parent 117320af02
commit 8db1a1ffb0
5 changed files with 56 additions and 16 deletions

View File

@@ -1511,3 +1511,8 @@ MIT License
- `claw-code`의 가상화 메시지 리스트에 바로 가기 전 단계로, AX transcript에 `실행 중 렌더 윈도우 축소`를 적용했습니다. 코워크/코드 스트리밍 중에는 최근 항목 위주로 더 작은 타임라인만 렌더하고, 평상시에는 기존 범위를 유지합니다.
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `GetActiveTimelineRenderLimit()`를 추가해, 일반 스트리밍은 96개, 경량 라이브 진행 모드는 60개만 렌더하도록 조정했습니다.
- `ScheduleExecutionHistoryRender`, `ScheduleAgentUiEvent`, `ScheduleTaskSummaryRefresh`, `ScheduleInputUiRefresh`도 경량 모드에서는 이미 타이머가 대기 중일 때 다시 stop/start 하지 않게 바꿔, 이벤트 폭주 시 dispatcher 예약 churn을 줄였습니다.
- 업데이트: 2026-04-08 12:40 (KST)
- 코워크/코드 경량 라이브 진행 모드에서는 메시지 진입 애니메이션과 진행 마커 펄스도 줄였습니다.
- [ChatWindow.MessageInteractions.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.MessageInteractions.cs)의 메시지 엔트리 애니메이션은 실행 중 경량 모드에서 즉시 표시로 바뀌어, 새 UI 요소가 들어올 때마다 opacity/translate 애니메이션이 누적되지 않게 했습니다.
- [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)의 진행 마커 펄스는 경량 모드에서 정적 점 상태로 간소화했습니다.
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 AX Agent 라이브 카드도 경량 모드에서는 등장/퇴장 애니메이션과 아이콘 opacity 펄스를 줄여, 실행 중 레이아웃/애니메이션 비용을 더 낮췄습니다.

View File

@@ -5465,3 +5465,16 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- 코워크/코드 실행 중 transcript 재구성 대상 수 자체 감소
- 高频 이벤트 환경에서 dispatcher timer 재예약 churn 감소
- `claw-code`의 virtualized list 이전 단계로서 실행 중 체감 부하를 더 낮춤
## 2026-04-08 12:40 (KST)
- [ChatWindow.MessageInteractions.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.MessageInteractions.cs)
- `ApplyMessageEntryAnimation(...)`를 인스턴스 메서드로 전환하고, `스트리밍 + 경량 라이브 진행 모드`에서는 opacity/translate 애니메이션 없이 즉시 표시되도록 바꿨다.
- [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)
- `ApplyLiveWaitingPulseToMarker(...)`를 인스턴스 메서드로 바꾸고, 경량 모드에서는 펄스 애니메이션 대신 정적 마커만 남기도록 조정했다.
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)
- AX Agent 라이브 카드의 등장/퇴장 애니메이션과 헤더 아이콘 opacity 펄스를 경량 모드에서는 생략하도록 변경했다.
- 기대 효과
- 코워크/코드 실행 중 누적 애니메이션 수 감소
- 긴 세션에서 opacity/translate/scale animation이 UI 스레드에 주는 부담 완화
- 진행 카드는 유지하되 시각 효과 비용은 더 낮춘 상태로 동작

View File

@@ -568,8 +568,15 @@ public partial class ChatWindow
};
}
private static void ApplyLiveWaitingPulseToMarker(Border marker)
private void ApplyLiveWaitingPulseToMarker(Border marker)
{
if (_isStreaming && IsLightweightLiveProgressMode())
{
marker.Opacity = 0.92;
marker.RenderTransform = Transform.Identity;
return;
}
marker.RenderTransformOrigin = new Point(0.5, 0.5);
marker.RenderTransform = new ScaleTransform(1, 1);

View File

@@ -272,8 +272,15 @@ public partial class ChatWindow
};
}
private static void ApplyMessageEntryAnimation(FrameworkElement element)
private void ApplyMessageEntryAnimation(FrameworkElement element)
{
if (_isStreaming && IsLightweightLiveProgressMode())
{
element.Opacity = 1;
element.RenderTransform = Transform.Identity;
return;
}
element.Opacity = 0;
element.RenderTransform = new TranslateTransform(0, 16);
element.BeginAnimation(UIElement.OpacityProperty,

View File

@@ -7039,16 +7039,21 @@ public partial class ChatWindow : Window
Width = msgMaxWidth,
MaxWidth = msgMaxWidth,
Margin = new Thickness(0, 4, 0, 6),
Opacity = 0,
RenderTransform = new TranslateTransform(0, 8),
Opacity = IsLightweightLiveProgressMode(runTab) ? 1 : 0,
RenderTransform = IsLightweightLiveProgressMode(runTab)
? Transform.Identity
: new TranslateTransform(0, 8),
};
container.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(260)));
((TranslateTransform)container.RenderTransform).BeginAnimation(
TranslateTransform.YProperty,
new DoubleAnimation(8, 0, TimeSpan.FromMilliseconds(280))
{ EasingFunction = new System.Windows.Media.Animation.QuadraticEase
{ EasingMode = System.Windows.Media.Animation.EasingMode.EaseOut } });
if (!IsLightweightLiveProgressMode(runTab))
{
container.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(260)));
((TranslateTransform)container.RenderTransform).BeginAnimation(
TranslateTransform.YProperty,
new DoubleAnimation(8, 0, TimeSpan.FromMilliseconds(280))
{ EasingFunction = new System.Windows.Media.Animation.QuadraticEase
{ EasingMode = System.Windows.Media.Animation.EasingMode.EaseOut } });
}
// 헤더: 펄싱 아이콘 + 에이전트 이름 + 경과 시간
var headerGrid = new Grid { Margin = new Thickness(2, 0, 0, 3) };
@@ -7057,10 +7062,13 @@ public partial class ChatWindow : Window
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var liveIcon = CreateMiniLauncherIcon(pixelSize: 4.0);
liveIcon.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation(1.0, 0.35, TimeSpan.FromMilliseconds(750))
{ AutoReverse = true, RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever,
EasingFunction = new System.Windows.Media.Animation.SineEase() });
if (!IsLightweightLiveProgressMode(runTab))
{
liveIcon.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation(1.0, 0.35, TimeSpan.FromMilliseconds(750))
{ AutoReverse = true, RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever,
EasingFunction = new System.Windows.Media.Animation.SineEase() });
}
Grid.SetColumn(liveIcon, 0);
headerGrid.Children.Add(liveIcon);
@@ -7191,7 +7199,7 @@ public partial class ChatWindow : Window
_agentLiveSubItemTexts.Clear();
_agentLiveCurrentCategory = null;
if (animated && MessagePanel != null && MessagePanel.Children.Contains(toRemove))
if (animated && MessagePanel != null && MessagePanel.Children.Contains(toRemove) && !IsLightweightLiveProgressMode())
{
var anim = new DoubleAnimation(toRemove.Opacity, 0, TimeSpan.FromMilliseconds(160));
anim.Completed += (_, _) => MessagePanel?.Children.Remove(toRemove);