앱 시작 시 무거운 초기화 지연으로 유휴 성능 개선
Some checks failed
Release Gate / gate (push) Has been cancelled

앱 시작 직후 AX Agent 창을 미리 생성하던 경로를 제거하고 실제로 AX Agent를 열 때만 ChatWindow를 만들도록 정리했습니다.

런처 검색 인덱스 전체 스캔과 파일 감시 시작도 부팅 시 즉시 실행하지 않고, 런처를 실제로 표시할 때 한 번만 지연 시작하도록 변경했습니다.

README와 DEVELOPMENT 문서를 2026-04-06 17:35(KST) 기준으로 갱신했고, Release 빌드 검증에서 경고 0 오류 0을 확인했습니다.
This commit is contained in:
2026-04-06 17:20:37 +09:00
parent ccb39f0fe0
commit 353c5ce471
3 changed files with 41 additions and 24 deletions

View File

@@ -1301,3 +1301,7 @@ MIT License
- 선택 텍스트 AI 명령의 기본값을 보수적으로 다시 조정했다. [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs) 에서 `선택 텍스트 명령 사용` 기본값을 `false`로 바꾸고, 활성 AI 명령 목록도 기본은 빈 리스트가 되도록 변경했다.
- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `BuildTextActionCommandsPanel()`에서 `최소 1개 유지`를 강제하던 로직을 제거해 `다시 쓰기`를 포함한 모든 텍스트 AI 명령을 실제로 비활성화할 수 있게 수정했다.
- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 안내 문구도 현재 동작 기준으로 갱신해, 모든 명령을 꺼두면 선택 텍스트 팝업에는 `AX Commander 열기`만 남는다는 점을 명확히 안내하도록 정리했다.
- 업데이트: 2026-04-06 17:35 (KST)
- 앱이 아무 창도 열지 않은 유휴 상태에서 PC를 무겁게 만들던 추가 초기화 경로를 줄였다. [App.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/App.xaml.cs) 에서 앱 시작 직후 숨겨진 [ChatWindow](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 를 미리 생성하던 `PrewarmChatWindow()` 호출을 제거해, AX Agent를 실제로 열기 전에는 무거운 UI 트리를 만들지 않도록 바꿨다.
- 같은 파일에서 [IndexService](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/IndexService.cs) 의 전체 인덱스 빌드와 `FileSystemWatcher` 시작도 앱 시작 시 즉시 수행하지 않고, 사용자가 실제로 런처를 열 때 `EnsureIndexWarmupStarted()`로 한 번만 지연 시작하도록 바꿨다.
- 이 변경으로 런처/AX Agent를 열지 않은 상태에서 불필요한 전체 파일 스캔과 감시 훅, 숨겨진 대형 창 초기화가 줄어들어 PC 전체 체감 부하를 더 낮추도록 정리했다.

View File

@@ -4988,3 +4988,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- Document update: 2026-04-06 17:18 (KST) - `FileDialogWatcher` is no longer started at app boot when file-dialog integration is disabled. `App.xaml.cs` now toggles the global WinEvent hook through `UpdateFileDialogWatcherState()`, and `SchedulerService.cs` now self-stops when no enabled schedules remain. This directly targets the 35% idle CPU symptom reported while neither the launcher nor AX Agent was open.
- Document update: 2026-04-06 17:24 (KST) - Changed the default launcher text-action profile to opt-in. `AppSettings.cs` and `SettingsViewModel.cs` now default `EnableTextAction` to `false`, and `TextActionCommands` now defaults to an empty list instead of all commands enabled.
- Document update: 2026-04-06 17:24 (KST) - Removed the forced “at least one command must remain enabled” behavior from `SettingsWindow.xaml.cs`. The text-action command panel now allows every AI command, including `rewrite`, to be turned off, and the hint text in `SettingsWindow.xaml` now explains that the popup falls back to showing only `AX Commander 열기` when all AI commands are disabled.
- Document update: 2026-04-06 17:35 (KST) - Reduced another startup performance hotspot in `App.xaml.cs` by removing the idle-time `PrewarmChatWindow()` path. AX Agent is no longer instantiated in the background at app startup and is created only when the user actually opens it.
- Document update: 2026-04-06 17:35 (KST) - Changed launcher search warmup from eager startup work to on-demand initialization. `App.xaml.cs` now starts the first `IndexService.BuildAsync()` scan and `StartWatchers()` only when the launcher is actually shown through `ShowLauncherWindow()`, instead of running a full index scan and watcher hookup at boot even when the launcher is never opened.

View File

@@ -24,6 +24,7 @@ public partial class App : System.Windows.Application
private DockBarWindow? _dockBar;
private FileDialogWatcher? _fileDialogWatcher;
private volatile IndexService? _indexService;
private int _indexWarmupStarted;
public IndexService? IndexService => _indexService;
public SettingsService? SettingsService => _settings;
public ClipboardHistoryService? ClipboardHistoryService => _clipboardHistory;
@@ -287,14 +288,8 @@ public partial class App : System.Windows.Application
() => _clipboardHistory?.Initialize(),
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
// ─── ChatWindow 미리 생성 (앱 유휴 시점에 숨겨진 채로 초기화) ──────────
// 이후 OpenAiChat() 시 창 생성 비용 없이 즉시 열림
Dispatcher.BeginInvoke(
() => PrewarmChatWindow(),
System.Windows.Threading.DispatcherPriority.SystemIdle);
// ─── 인덱스 빌드 (백그라운드) + 완료 후 FileSystemWatcher 시작 ────────
_ = indexService.BuildAsync().ContinueWith(_ => indexService.StartWatchers());
// ─── 인덱스 빌드/감시는 실제 런처를 사용할 때 시작 ───────────────────
// 앱 시작 직후 전체 스캔과 감시 훅을 붙이지 않아 유휴 체감 부하를 줄임
// ─── 글로벌 훅 + 스니펫 확장기 ───────────────────────────────────────
_inputListener = new InputListener();
@@ -327,7 +322,7 @@ public partial class App : System.Windows.Application
if (_launcher == null || _launcher.IsVisible) return;
if (_settings?.Settings.Launcher.EnableFileDialogIntegration != true) return;
WindowTracker.Capture();
_launcher.Show();
ShowLauncherWindow();
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
() => _launcher.SetInputText("cd "));
});
@@ -375,7 +370,7 @@ public partial class App : System.Windows.Application
{
if (_launcher == null) return;
UsageStatisticsService.RecordLauncherOpen();
_launcher.Show();
ShowLauncherWindow();
});
return;
}
@@ -423,7 +418,7 @@ public partial class App : System.Windows.Application
{
case TextActionPopup.ActionResult.OpenLauncher:
UsageStatisticsService.RecordLauncherOpen();
_launcher.Show();
ShowLauncherWindow();
break;
case TextActionPopup.ActionResult.None:
break; // Esc 또는 포커스 잃음
@@ -438,7 +433,7 @@ public partial class App : System.Windows.Application
else
{
UsageStatisticsService.RecordLauncherOpen();
_launcher.Show();
ShowLauncherWindow();
}
});
}
@@ -514,7 +509,7 @@ public partial class App : System.Windows.Application
};
// ! 프리픽스로 AX Agent에 전달
_launcher?.Show();
ShowLauncherWindow();
_launcher?.SetInputText($"! {prompt}");
}
@@ -581,7 +576,7 @@ public partial class App : System.Windows.Application
_trayMenu
.AddHeader(versionText)
.AddItem("\uE7C5", "AX Commander 호출하기", () =>
Dispatcher.Invoke(() => _launcher?.Show()))
Dispatcher.Invoke(ShowLauncherWindow))
.AddItem("\uE8BD", "AX Agent 대화하기", () =>
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem)
.AddItem("\uE8A7", "독 바 표시", () =>
@@ -644,7 +639,7 @@ public partial class App : System.Windows.Application
if (settings.Settings.AiEnabled)
OpenAiChat();
else
_launcher?.Show();
ShowLauncherWindow();
});
}
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
@@ -679,14 +674,30 @@ public partial class App : System.Windows.Application
/// <summary>AX Agent 창 열기 (트레이 메뉴 등에서 호출).</summary>
private Views.ChatWindow? _chatWindow;
/// <summary>
/// ChatWindow를 백그라운드에서 미리 생성합니다 (앱 시작 후 저우선순위로 호출).
/// 이후 OpenAiChat() 시 창 생성 비용 없이 즉시 Show/Activate만 수행합니다.
/// </summary>
internal void PrewarmChatWindow()
private void EnsureIndexWarmupStarted()
{
if (_chatWindow != null || _settings == null) return;
_chatWindow = new Views.ChatWindow(_settings);
if (_indexService == null) return;
if (Interlocked.Exchange(ref _indexWarmupStarted, 1) == 1) return;
_ = Task.Run(async () =>
{
try
{
await _indexService.BuildAsync().ConfigureAwait(false);
_indexService.StartWatchers();
}
catch (Exception ex)
{
LogService.Warn($"런처 인덱스 초기화 실패: {ex.Message}");
}
});
}
private void ShowLauncherWindow()
{
if (_launcher == null) return;
EnsureIndexWarmupStarted();
_launcher.Show();
}
private void OpenAiChat()
@@ -714,7 +725,7 @@ public partial class App : System.Windows.Application
_dockBar.OnQuickSearch = query =>
{
if (_launcher == null) return;
_launcher.Show();
ShowLauncherWindow();
_launcher.Activate(); // 독 바 뒤가 아닌 전면에 표시
if (!string.IsNullOrEmpty(query))
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
@@ -729,7 +740,7 @@ public partial class App : System.Windows.Application
_dockBar.OnOpenAgent = () =>
{
if (_launcher == null) return;
_launcher.Show();
ShowLauncherWindow();
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
() => _launcher.SetInputText("!"));
};