컨텍스트 사용량 링 정렬 회귀 수정

원인: 하단 컨텍스트 사용량 트랙은 22x22 원형인데 arc는 center=15, radius=11 하드코딩으로 더 크게 그려져 오른쪽으로 밀려 보였습니다.

수정: TokenUsageTrack과 TokenUsageArc를 같은 크기와 가운데 정렬로 맞추고, 실제 트랙 지름과 스트로크 두께를 기준으로 중심점·반지름을 계산하는 helper를 추가했습니다. 크기 변경 시에도 arc가 다시 그려지도록 캐시 조건도 보강했습니다.

검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_token_ring_alignment\ -p:IntermediateOutputPath=obj\verify_token_ring_alignment\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_token_ring_alignment_tests\ -p:IntermediateOutputPath=obj\verify_token_ring_alignment_tests\ (통과 53)
This commit is contained in:
2026-04-15 20:14:20 +09:00
parent bea9335ec0
commit 9ad587d230
6 changed files with 72 additions and 5 deletions

View File

@@ -1,5 +1,9 @@
# AX Commander
- 업데이트: 2026-04-15 20:12 (KST)
- AX Agent 하단 컨텍스트 사용량 링이 트랙보다 오른쪽으로 밀려 보이던 정렬 문제를 수정했습니다. `src/AxCopilot/Views/ChatWindow.xaml`은 진행 arc를 트랙과 같은 `22x22` 기준으로 가운데 정렬하고, `src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs`는 실제 트랙 지름 기준으로 중심점과 반지름을 계산해 그리도록 바뀌었습니다.
- `src/AxCopilot/Views/ChatWindow.xaml.cs`에 링 중심/반지름 계산 helper를 추가했고, `src/AxCopilot.Tests/Views/ChatWindowSlashPolicyTests.cs`에 지름·스트로크 두께별 계산 회귀 테스트를 넣었습니다.
- 업데이트: 2026-04-15 20:06 (KST)
- 빠른 로컬 테스트용 Windows 빌드 스크립트 [build-quick.ps1](/E:/AX%20Copilot%20-%20Codex/build-quick.ps1)를 추가했습니다. 인스톨러/`dist` 패키징 없이 `src/AxCopilot/AxCopilot.csproj``dotnet build -c Release -r win-x64`로 빌드합니다.
- 기본 출력 경로는 `src\AxCopilot\bin\Release\net8.0-windows10.0.17763.0\win-x64`이며, 기본 실행은 `--no-restore` 경로를 우선 사용해 반복 테스트 속도를 줄였습니다. 첫 실행이나 패키지 변경 시에는 `-Restore`, 실행 중 잠금이 있으면 `-StopRunningApp` 옵션을 사용할 수 있습니다.

View File

@@ -1533,3 +1533,9 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫
- 빠른 로컬 테스트 전용 Windows 스크립트 `build-quick.ps1`를 루트에 추가했습니다. 목적은 인스톨러 빌드와 `dist` 패키징을 건너뛰고 `src/AxCopilot/AxCopilot.csproj`만 직접 `Release + win-x64`로 빌드해 `src\\AxCopilot\\bin\\Release\\net8.0-windows10.0.17763.0\\win-x64` 출력물을 빠르게 갱신하는 것입니다.
- 스크립트는 기본적으로 `dotnet build --no-restore` 경로를 사용하고, `obj\\project.assets.json`이 없거나 사용자가 `-Restore`를 준 경우에만 restore를 수행합니다. 잠금 문제를 줄이기 위해 `-StopRunningApp`, 출력물 정리를 원할 때 `-Clean` 옵션도 함께 지원합니다.
- 검증: `powershell -ExecutionPolicy Bypass -File .\\build-quick.ps1` 실행으로 빠른 빌드 스크립트가 정상 완료되고 출력 경로가 갱신되는 것을 확인했습니다.
업데이트: 2026-04-15 20:12 (KST)
- AX Agent 하단 컨텍스트 사용량 링이 트랙보다 오른쪽으로 밀려 보이던 시각 회귀를 수정했습니다. 원인은 `src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs``22x22` 트랙 위에 arc를 `center=15`, `radius=11` 하드코딩으로 그려 실제 트랙 중심선보다 크게 렌더링하던 점이었습니다.
- `src/AxCopilot/Views/ChatWindow.xaml`에서 `TokenUsageTrack`을 명시적으로 분리하고, `TokenUsageArc`를 트랙과 같은 크기/가운데 정렬/`Stretch=None`으로 맞췄습니다. `src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs`는 실제 트랙 지름을 읽어 arc 크기를 맞추고, `src/AxCopilot/Views/ChatWindow.xaml.cs``CalculateCircularRingMetrics(...)` helper로 중심점과 반지름을 계산하도록 변경했습니다.
- `src/AxCopilot.Tests/Views/ChatWindowSlashPolicyTests.cs``CalculateCircularRingMetrics_ShouldAlignCenterlineToTrack`를 추가해 지름과 스트로크 두께에 따른 계산 회귀를 고정했습니다.
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_token_ring_alignment\\ -p:IntermediateOutputPath=obj\\verify_token_ring_alignment\\` 경고 0 / 오류 0
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_token_ring_alignment_tests\\ -p:IntermediateOutputPath=obj\\verify_token_ring_alignment_tests\\` 통과 53

View File

@@ -198,4 +198,21 @@ public class ChatWindowSlashPolicyTests
var result = method!.Invoke(null, new object?[] { runTab });
result.Should().Be(expected);
}
[Theory]
[InlineData(22, 2, 11, 10)]
[InlineData(28, 2, 14, 13)]
[InlineData(22, 0, 11, 11)]
[InlineData(0, 2, 0, 0)]
public void CalculateCircularRingMetrics_ShouldAlignCenterlineToTrack(
double diameter,
double strokeThickness,
double expectedCenter,
double expectedRadius)
{
var (center, radius) = ChatWindow.CalculateCircularRingMetrics(diameter, strokeThickness);
center.Should().Be(expectedCenter);
radius.Should().Be(expectedRadius);
}
}

View File

@@ -138,7 +138,24 @@ public partial class ChatWindow
TokenUsageCard.ToolTip = null;
UpdateCircularUsageArc(TokenUsageArc, usageRatio, 15, 15, 11);
var ringDiameter = ResolveTokenUsageRingDiameter();
TokenUsageArc.Width = ringDiameter;
TokenUsageArc.Height = ringDiameter;
var (center, radius) = CalculateCircularRingMetrics(ringDiameter, TokenUsageArc.StrokeThickness);
UpdateCircularUsageArc(TokenUsageArc, usageRatio, center, center, radius);
}
private double ResolveTokenUsageRingDiameter()
{
var actual = TokenUsageTrack?.ActualWidth ?? 0;
if (actual > 0)
return actual;
var declared = TokenUsageTrack?.Width ?? 0;
if (declared > 0)
return declared;
return TokenUsageArc?.Width > 0 ? TokenUsageArc.Width : 22;
}
private void TokenUsageCard_MouseEnter(object sender, MouseEventArgs e)

View File

@@ -2673,7 +2673,8 @@
MouseLeave="TokenUsageCard_MouseLeave">
<Grid Width="28" Height="28">
<!-- Ring track (subtle background ring) -->
<Ellipse Width="22" Height="22"
<Ellipse x:Name="TokenUsageTrack"
Width="22" Height="22"
Stroke="{DynamicResource BorderColor}"
StrokeThickness="2"
Opacity="0.45"
@@ -2681,10 +2682,16 @@
VerticalAlignment="Center"/>
<!-- Usage arc (progress) -->
<Path x:Name="TokenUsageArc"
Width="22"
Height="22"
Stroke="{DynamicResource AccentColor}"
StrokeThickness="2"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"/>
StrokeEndLineCap="Round"
Stretch="None"
SnapsToDevicePixels="True"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<!-- Hidden data elements (referenced in code) -->
<StackPanel Visibility="Collapsed">
<TextBlock x:Name="TokenUsagePercentText"

View File

@@ -7347,16 +7347,32 @@ public partial class ChatWindow : Window
return value.ToString("N0");
}
internal static (double Center, double Radius) CalculateCircularRingMetrics(double diameter, double strokeThickness)
{
var safeDiameter = Math.Max(0, diameter);
var safeStrokeThickness = Math.Max(0, strokeThickness);
var center = safeDiameter / 2;
var radius = Math.Max(0, center - (safeStrokeThickness / 2));
return (center, radius);
}
private double _lastArcRatio = -1;
private double _lastArcDiameter = -1;
private double _lastArcStrokeThickness = -1;
private void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
{
ratio = Math.Clamp(ratio, 0, 0.9999);
var effectiveDiameter = Math.Max(0, radius * 2 + path.StrokeThickness);
// 비율 변화가 1% 미만이면 렌더링 생략 (250ms마다 호출되므로 불필요한 재생성 방지)
if (Math.Abs(ratio - _lastArcRatio) < 0.01)
// 비율 변화가 1% 미만이어도 크기나 두께가 바뀌면 다시 그립니다.
if (Math.Abs(ratio - _lastArcRatio) < 0.01
&& Math.Abs(effectiveDiameter - _lastArcDiameter) < 0.01
&& Math.Abs(path.StrokeThickness - _lastArcStrokeThickness) < 0.01)
return;
_lastArcRatio = ratio;
_lastArcDiameter = effectiveDiameter;
_lastArcStrokeThickness = path.StrokeThickness;
if (ratio <= 0)
{