격려문구 알림 팝업 자동 종료 안정성 개선
Some checks failed
Release Gate / gate (push) Has been cancelled

- ReminderPopupWindow의 자동 닫힘 경로를 점검하고 UI 타이머와 실제 종료 타이머를 분리함
- 남은 시간 계산을 절대 시각 기준으로 바꾸고 Task.Delay 기반 종료 fail-safe를 추가해 팝업이 남는 경우를 줄임
- 창 종료 시 CancellationToken과 타이머를 함께 정리해 후속 종료 처리도 안전하게 맞춤
- README와 DEVELOPMENT 문서에 변경 목적과 검증 결과를 로컬 시각 기준으로 기록함
- 검증: 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-06 15:41:27 +09:00
parent 71fd5f0bb7
commit 2ae56b2510
3 changed files with 44 additions and 7 deletions

View File

@@ -1258,3 +1258,7 @@ MIT License
- 업데이트: 2026-04-06 15:41 (KST)
- 채팅/코워크 빈 상태 화면의 세로 정렬 기준을 조정했다. [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 상단 아이콘, 제목, 설명, 프리셋 목록을 하나의 세로 묶음으로 다시 구성해 화면 높이가 늘어나도 함께 상하 중앙 정렬되도록 수정했다.
- 이전처럼 프리셋 카드만 중앙에 오고 상단 설명 블록은 위쪽에 남아 보이던 레이아웃 불균형을 줄여, 빈 상태 화면 전체가 더 자연스럽게 가운데 정렬되도록 맞췄다.
- 업데이트: 2026-04-06 15:48 (KST)
- 일정 시간마다 표시되는 격려문구 알림 팝업의 자동 닫힘 경로를 점검하고 [ReminderPopupWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ReminderPopupWindow.xaml.cs)를 보강했다.
- 기존에는 `DispatcherTimer` 틱만으로 카운트다운과 닫힘을 함께 처리했는데, UI 틱이 밀리면 팝업 종료가 늦어질 여지가 있었다. 이제 카운트다운 표시와 실제 자동 종료를 분리해 `Task.Delay + CancellationToken` 기반 종료를 추가하고, 남은 시간은 절대 시각 기준으로 계산하도록 바꿨다.
- 이 변경으로 지정 시간이 지난 뒤에도 격려 팝업이 남아 있는 증상을 줄이고, 자동 닫힘이 더 안정적으로 동작하도록 맞췄다.

View File

@@ -4965,3 +4965,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- Document update: 2026-04-06 15:35 (KST) - Reordered the AX Agent internal-settings common tab in `ChatWindow.xaml` so `서비스와 모델` and `등록 모델 관리` now appear consecutively. This matches the actual model-management flow more closely.
- Document update: 2026-04-06 15:35 (KST) - Moved the `운영 모드` chooser below the conversation management/storage area and restored section separators around the moved blocks so the common-tab layout reads in clearer grouped sections.
- Document update: 2026-04-06 15:41 (KST) - Reworked the empty-state layout in `ChatWindow.xaml` so the icon/title/description block and the preset grid live inside one vertically centered stack. This fixes the previous behavior where only the preset cards looked centered while the descriptive header stayed visually high on tall windows.
- Document update: 2026-04-06 15:48 (KST) - Hardened the unlock-reminder popup auto-close path in `ReminderPopupWindow.xaml.cs`. The window now tracks an absolute close deadline and uses a separate `Task.Delay + CancellationToken` fail-safe to close the popup even if the UI timer tick is delayed.
- Document update: 2026-04-06 15:48 (KST) - The countdown bar still updates through `DispatcherTimer`, but the actual close action is now guaranteed by the background delay path. This reduces cases where encouragement popups remain visible after the configured display duration.

View File

@@ -1,4 +1,6 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
@@ -26,7 +28,9 @@ public partial class ReminderPopupWindow : Window
// ─── 타이머 ───────────────────────────────────────────────────────────────
private readonly DispatcherTimer _timer;
private readonly EventHandler _tickHandler;
private int _remaining;
private readonly CancellationTokenSource _autoCloseCts = new();
private readonly DateTime _closeAtUtc;
private readonly int _displaySeconds;
public ReminderPopupWindow(
string quoteText,
@@ -56,9 +60,10 @@ public partial class ReminderPopupWindow : Window
: "오늘 방금 시작했습니다";
// ── 카운트다운 ──
_remaining = Math.Max(3, cfg.DisplaySeconds);
CountdownBar.Maximum = _remaining;
CountdownBar.Value = _remaining;
_displaySeconds = Math.Max(3, cfg.DisplaySeconds);
_closeAtUtc = DateTime.UtcNow.AddSeconds(_displaySeconds);
CountdownBar.Maximum = _displaySeconds;
CountdownBar.Value = _displaySeconds;
// ── 위치: 레이아웃 완료 후 설정 ──
Loaded += (_, _) =>
@@ -71,13 +76,15 @@ public partial class ReminderPopupWindow : Window
// ── 타이머 ──
_tickHandler = (_, _) =>
{
_remaining--;
CountdownBar.Value = _remaining;
if (_remaining <= 0) Close();
var remainingSeconds = Math.Max(0, (_closeAtUtc - DateTime.UtcNow).TotalSeconds);
CountdownBar.Value = remainingSeconds;
if (remainingSeconds <= 0)
Close();
};
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += _tickHandler;
_timer.Start();
_ = StartAutoCloseAsync(_autoCloseCts.Token);
// ── Esc 키 닫기 ──
KeyDown += (_, e) =>
@@ -132,10 +139,34 @@ public partial class ReminderPopupWindow : Window
private void CloseBtn_Click(object sender, RoutedEventArgs e) => Close();
private async Task StartAutoCloseAsync(CancellationToken cancellationToken)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(_displaySeconds), cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
await Dispatcher.InvokeAsync(() =>
{
if (IsVisible)
Close();
}, DispatcherPriority.Background, cancellationToken);
}
catch (TaskCanceledException)
{
}
catch (OperationCanceledException)
{
}
}
protected override void OnClosed(EventArgs e)
{
_autoCloseCts.Cancel();
_timer.Stop();
_timer.Tick -= _tickHandler;
_autoCloseCts.Dispose();
base.OnClosed(e);
}
}