런처 색인 과부하와 AX Agent 리사이즈 재렌더 부담 완화
Some checks failed
Release Gate / gate (push) Has been cancelled

- 런처 색인에서 .git, node_modules, bin, obj, dist, packages, venv, __pycache__, target 경로를 스캔/감시 제외해 유휴 CPU와 재색인 부담을 줄임

- 런처 재귀 파일 스캔을 제외 폴더 인지형 순회로 바꾸고 무지개 글로우 타이머 주기를 낮춰 visible 상태 UI 갱신 비용을 완화함

- AX Agent는 SizeChanged 때마다 전체 RenderMessages를 즉시 호출하지 않고 디바운스 후 한 번만 반영하도록 바꿔 창 조작 시 무거운 느낌을 줄임

- README와 DEVELOPMENT 문서를 2026-04-06 18:46 (KST) 기준으로 갱신하고 Release 빌드에서 경고 0 / 오류 0을 확인함
This commit is contained in:
2026-04-06 19:07:09 +09:00
parent e4e3e49419
commit 4df5d5d874
5 changed files with 124 additions and 6 deletions

View File

@@ -10,6 +10,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
- 업데이트: 2026-04-06 15:31 (KST)
- 코워크/코드 하단 우측의 권한 요청 버튼은 footer 작업 바와 더 자연스럽게 이어지도록 외곽 테두리를 제거해 플랫한 형태로 정리했습니다.
- 업데이트: 2026-04-06 18:46 (KST)
- 런처 색인에서 `.git`, `node_modules`, `bin`, `obj`, `dist`, `packages`, `venv`, `__pycache__`, `target` 같은 무거운 폴더를 스캔/감시 대상에서 제외해 유휴 CPU와 재색인 부담을 줄였습니다.
- 런처 무지개 글로우 갱신 주기를 낮추고, AX Agent는 창 크기 변화 시 메시지 전체 재렌더를 짧게 묶어서 한 번만 반영하도록 바꿔 창 조작 시 버벅이는 느낌을 줄였습니다.
- 업데이트: 2026-04-06 15:26 (KST)
- AX Agent 채팅/코워크/코드 하단 안내 문구를 현재 구현 기준으로 다시 정리했습니다. 입력창 워터마크는 탭 종류와 작업 폴더 선택 여부에 따라 실제 가능한 작업을 더 정확히 안내합니다.
- 선택된 프리셋 안내도 placeholder 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.

View File

@@ -1,5 +1,8 @@
# AX Copilot - 媛쒕컻 臾몄꽌
- Document update: 2026-04-06 18:46 (KST) - Tightened launcher indexing for real-world projects by ignoring `.git`, `.vs`, `node_modules`, `bin`, `obj`, `dist`, `packages`, `venv`, `__pycache__`, and `target` directories during both watcher handling and full scans. The launcher now skips noisy build/cache trees instead of reacting to them like searchable content.
- Document update: 2026-04-06 18:46 (KST) - Replaced the launcher's recursive `EnumerateFiles(..., AllDirectories)` walk with an ignore-aware manual traversal so large repos avoid descending into dependency and build folders, and slowed the rainbow glow timer from `20ms` to `40ms`.
- Document update: 2026-04-06 18:46 (KST) - Added a deferred responsive-layout timer to `ChatWindow` so AX Agent no longer calls `RenderMessages()` on every `SizeChanged` event. Resizing and layout changes are now coalesced into a single update after a short delay, reducing the heavy-window feel during agent-window manipulation.
- Document update: 2026-04-06 15:31 (KST) - Removed the visible outer border from the Cowork/Code footer permission selector so it reads as a flatter inline action in the bottom work bar.
- Document update: 2026-04-06 15:26 (KST) - Reworked bottom guidance copy for Chat/Cowork/Code around the actual AX Agent behavior. Input watermark text now comes from a shared helper that considers the active tab and whether a work folder is selected, instead of using overly broad static text.
- Document update: 2026-04-06 15:26 (KST) - Adjusted the selected preset guide so it prefers concise preset descriptions over raw placeholder prompts, keeping the footer guide and the composer watermark in distinct roles.

View File

@@ -13,6 +13,21 @@ namespace AxCopilot.Services;
/// </summary>
public class IndexService : IDisposable
{
private static readonly HashSet<string> IgnoredDirectoryNames = new(StringComparer.OrdinalIgnoreCase)
{
".git",
".vs",
"node_modules",
"bin",
"obj",
"dist",
"packages",
"__pycache__",
".venv",
"venv",
"target"
};
private readonly SettingsService _settings;
private readonly List<FileSystemWatcher> _watchers = new();
private readonly object _timerLock = new();
@@ -190,6 +205,9 @@ public class IndexService : IDisposable
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
{
if (ShouldIgnorePath(e.FullPath))
return;
var rootPath = (sender as FileSystemWatcher)?.Path;
if (TryApplyIncrementalChange(e.ChangeType, e.FullPath, rootPath))
return;
@@ -199,6 +217,9 @@ public class IndexService : IDisposable
private void OnWatcherRenamed(object sender, RenamedEventArgs e)
{
if (ShouldIgnorePath(e.FullPath) && ShouldIgnorePath(e.OldFullPath))
return;
var rootPath = (sender as FileSystemWatcher)?.Path;
if (TryApplyIncrementalRename(e.OldFullPath, e.FullPath, rootPath))
return;
@@ -276,6 +297,9 @@ public class IndexService : IDisposable
private bool AddPathEntryIncrementally(string fullPath, string rootPath)
{
if (ShouldIgnorePath(fullPath))
return false;
var updated = false;
lock (_indexLock)
@@ -307,6 +331,9 @@ public class IndexService : IDisposable
private bool RemovePathEntriesIncrementally(string fullPath)
{
if (ShouldIgnorePath(fullPath))
return false;
var normalizedPath = NormalizePath(fullPath);
lock (_indexLock)
{
@@ -340,6 +367,9 @@ public class IndexService : IDisposable
private void ScheduleRebuild(string triggerPath)
{
if (ShouldIgnorePath(triggerPath))
return;
LogService.Info($"파일 변경 감지: {triggerPath} — {RebuildDebounceMs}ms 후 전체 재빌드 예약");
lock (_timerLock)
{
@@ -686,7 +716,7 @@ public class IndexService : IDisposable
try
{
var count = 0;
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
foreach (var file in EnumerateFilesWithIgnoredDirectories(dir))
{
ct.ThrowIfCancellationRequested();
@@ -704,7 +734,7 @@ public class IndexService : IDisposable
{
ct.ThrowIfCancellationRequested();
var name = Path.GetFileName(subDir);
if (name.StartsWith(".", StringComparison.Ordinal))
if (name.StartsWith(".", StringComparison.Ordinal) || IgnoredDirectoryNames.Contains(name))
continue;
entries.Add(CreateFolderEntry(subDir));
@@ -717,6 +747,78 @@ public class IndexService : IDisposable
}, ct);
}
private static IEnumerable<string> EnumerateFilesWithIgnoredDirectories(string rootDir)
{
var pending = new Stack<string>();
pending.Push(rootDir);
while (pending.Count > 0)
{
var current = pending.Pop();
IEnumerable<string> files;
try
{
files = Directory.EnumerateFiles(current, "*.*", SearchOption.TopDirectoryOnly);
}
catch (UnauthorizedAccessException)
{
continue;
}
catch (DirectoryNotFoundException)
{
continue;
}
foreach (var file in files)
yield return file;
IEnumerable<string> directories;
try
{
directories = Directory.EnumerateDirectories(current, "*", SearchOption.TopDirectoryOnly);
}
catch (UnauthorizedAccessException)
{
continue;
}
catch (DirectoryNotFoundException)
{
continue;
}
foreach (var directory in directories)
{
if (ShouldIgnorePath(directory))
continue;
pending.Push(directory);
}
}
}
private static bool ShouldIgnorePath(string path)
{
if (string.IsNullOrWhiteSpace(path))
return false;
try
{
var normalized = NormalizePath(path);
foreach (var segment in normalized.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
{
if (IgnoredDirectoryNames.Contains(segment))
return true;
}
}
catch
{
return false;
}
return false;
}
private static IndexEntry CreateFileEntry(string filePath)
{
var ext = Path.GetExtension(filePath).ToLowerInvariant();

View File

@@ -93,6 +93,7 @@ public partial class ChatWindow : Window
private readonly DispatcherTimer _conversationPersistTimer;
private readonly DispatcherTimer _agentUiEventTimer;
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
private readonly DispatcherTimer _responsiveLayoutTimer;
private CancellationTokenSource? _gitStatusRefreshCts;
private int _displayedLength; // 현재 화면에 표시된 글자 수
private ResourceDictionary? _agentThemeDictionary;
@@ -273,6 +274,14 @@ public partial class ChatWindow : Window
_tokenUsagePopupCloseTimer.Stop();
CloseTokenUsagePopupIfIdle();
};
_responsiveLayoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(90) };
_responsiveLayoutTimer.Tick += (_, _) =>
{
_responsiveLayoutTimer.Stop();
UpdateTopicPresetScrollMode();
if (UpdateResponsiveChatLayout())
RenderMessages(preserveViewport: true);
};
KeyDown += ChatWindow_KeyDown;
MouseMove += (_, _) =>
@@ -401,9 +410,8 @@ public partial class ChatWindow : Window
return;
}
UpdateTopicPresetScrollMode();
if (UpdateResponsiveChatLayout())
RenderMessages(preserveViewport: true);
_responsiveLayoutTimer.Stop();
_responsiveLayoutTimer.Start();
};
Closed += (_, _) =>
{
@@ -415,6 +423,7 @@ public partial class ChatWindow : Window
_typingTimer.Stop();
_conversationSearchTimer.Stop();
_inputUiRefreshTimer.Stop();
_responsiveLayoutTimer.Stop();
_llm.Dispose();
};
}

View File

@@ -617,7 +617,7 @@ public partial class LauncherWindow : Window
_rainbowTimer = new System.Windows.Threading.DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(20)
Interval = TimeSpan.FromMilliseconds(40)
};
var startTime = DateTime.UtcNow;
_rainbowTimer.Tick += (_, _) =>