Skip to content

Show repository disk size in sidebar group headers#246

Merged
StephaneDelcroix merged 3 commits intomainfrom
fix/how-slow-would-it-be-to-indicate-for-eac-20260228-0814
Feb 28, 2026
Merged

Show repository disk size in sidebar group headers#246
StephaneDelcroix merged 3 commits intomainfrom
fix/how-slow-would-it-be-to-indicate-for-eac-20260228-0814

Conversation

@StephaneDelcroix
Copy link
Copy Markdown
Collaborator

Summary

Shows disk size (bare clone + worktrees) for each tracked repository in the sidebar group headers.

Changes

  • RepoManager: Added GetRepoDiskSize(), RefreshDiskSizesAsync(), GetDirectorySizeBytes(), and FormatSize() with a ConcurrentDictionary-based cache (5-minute TTL)
  • SessionSidebar: Displays disk size as a dim label next to session count in repo group headers, and includes it in the tooltip. Periodic refresh via a Timer (every 5 min), disposed properly on cleanup.
  • CSS: Added .group-disk-size style for the size label
  • Tests: 16 unit tests covering FormatSize (locale-safe), directory scanning, cache behavior, and non-existent path handling

Design

  • Disk sizes computed on background thread via Task.Run — never blocks UI
  • Cached with 5-minute TTL to avoid expensive repeated scans
  • Fires OnStateChanged after refresh to trigger sidebar re-render
  • Handles missing directories gracefully (returns 0)

@StephaneDelcroix StephaneDelcroix force-pushed the fix/how-slow-would-it-be-to-indicate-for-eac-20260228-0814 branch from 82c69eb to 283b543 Compare February 28, 2026 09:53
@StephaneDelcroix
Copy link
Copy Markdown
Collaborator Author

Multi-Model Re-Review: PR #246 — Show repository disk size

Previous Findings Status

  • 🟡 TTL/timer mismatch (cache TTL = timer period → disk size blinks away every 5 min): FIXED ✅ — DiskSizeCacheTtl raised to 7 min in commit 283b543; entries remain valid through the entire 5-min refresh scan.

New Findings (5-model consensus, 3+ models required)

🟡 MODERATE — RepoManager.cs:RefreshDiskSizesAsync — No in-flight scan guard (4/5 models)

RefreshDiskSizesAsync launches a new Task.Run unconditionally every time the timer fires. On a machine with many large repos, a recursive stat()-per-file walk can take longer than 5 minutes. When that happens, two scans run concurrently, doubling disk I/O and firing OnStateChanged twice. No data corruption (ConcurrentDictionary is safe), but it's wasteful and causes a redundant re-render burst.

Fix: add a simple reentrance guard at the top of the method:

private int _scanning;
public Task RefreshDiskSizesAsync()
{
    if (Interlocked.CompareExchange(ref _scanning, 1, 0) != 0) return Task.CompletedTask;
    return Task.Run(() => { try { /* existing body */ } finally { _scanning = 0; } });
}

🟢 MINOR — RepoManager.cs:RefreshDiskSizesAsyncOnStateChanged invoked from background thread (3/5 models)

The call OnStateChanged?.Invoke() fires from inside Task.Run(). Today this is safe because the sole subscriber (SessionSidebar.OnRepoStateChanged) wraps its handler in InvokeAsync(StateHasChanged). However, every other service in the codebase (CopilotService, DemoService) marshals OnStateChanged to the UI thread at the source via SyncContext.Post()/InvokeOnUI(). A future subscriber that follows the same producer-marshals convention will get an InvalidOperationException. Low urgency but inconsistent with the established pattern.

🟢 MINOR — RepoManager.cs:GetDirectorySizeBytesSearchOption.AllDirectories follows symlinks (3/5 models)

Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) follows directory symlinks on macOS. Worktrees with symlinked node_modules or other vendored directories could be counted in both the bare clone and the worktree scan, inflating the reported size. Fix: use EnumerationOptions { RecurseSubdirectories = true, AttributesToSkip = FileAttributes.ReparsePoint }.


No Issues Found (Investigated, Clean)

  • firstRender guard prevents duplicate timer creation on re-renders
  • _diskSizeCache thread-safety: ConcurrentDictionary handles concurrent reads/writes correctly
  • SessionSidebar.Dispose() correctly unsubscribes from OnStateChanged and disposes the timer
  • FormatSize uses InvariantCulture and correct KB/MB/GB thresholds
  • ✅ Singleton injection: RepoManager is a DI singleton, so instance _diskSizeCache is shared correctly

Test Coverage

16 new tests cover FormatSize, GetDirectorySizeBytes, GetRepoDiskSize cache TTL, and RefreshDiskSizesAsync. The concurrent-scan path and the symlink edge case are not tested.


CI

⚠️ Pre-existing failure: Scenario_FullReflectCycleWithScoring (locale decimal separator mismatch, unrelated to this PR).


Verdict

⚠️ Request Changes — The MODERATE concurrent-scan issue should be addressed before merge (easy one-liner fix). The two MINOR issues are low-urgency but worth tracking.

@StephaneDelcroix
Copy link
Copy Markdown
Collaborator Author

✅ Multi-Model Re-Review (Round 2): Ready to Merge

Commit: 409c831

Previous Findings Status

Finding Status
🟡 TTL/timer mismatch — disk size blinks away every 5min FIXED (round 1 — TTL raised to 7min)
🟡 No reentrance guard — overlapping scans on slow machines FIXED (round 2 — Interlocked.CompareExchange guard)
🟢 OnStateChanged from background thread N/A — subscriber uses InvokeAsync(StateHasChanged), safe
🟢 EnumerateFiles follows symlinks N/A — accepted known limitation

Round 2 Re-Review (5-model consensus, 3+ required to include)

No new CRITICAL or MODERATE issues found. Two pre-known MINOR items noted by 2/5 models:

  • 🟢 RepoManager.csOnStateChanged fires from background thread: All 5 existing OnStateChanged call sites in RepoManager follow the same pattern (caller-context, subscribers marshal themselves). Consistent with the codebase. Subscriber already uses InvokeAsync. No action needed.

  • 🟢 RepoManager.csEnumerateFiles(..., SearchOption.AllDirectories) follows dir symlinks: Accepted limitation. Worst case: slightly inflated size display for repos with symlinked node_modules. The outer catch {} prevents crashes.


Test Coverage

16 tests covering: FormatSize boundary values, GetDirectorySizeBytes (empty/files/nonexistent), cache TTL logic, worktree inclusion, and missing path handling. CI: ⚠️ pre-existing locale failure (Scenario_FullReflectCycleWithScoring) unrelated to this PR.


Verdict: ✅ Approve

Both rounds of fixes applied and verified. All 1632 non-pre-existing tests pass. The implementation is solid — reentrance guard, correct TTL, proper timer lifecycle (firstRender guard + Dispose). Ready to merge.

StephaneDelcroix and others added 3 commits February 28, 2026 19:48
Add disk size calculation for each tracked repository (bare clone + worktrees)
displayed in the sidebar group header tooltip and as a dim label next to the
session count. Sizes are computed on a background thread, cached for 5 minutes,
and refreshed periodically via a timer.

- Add GetRepoDiskSize, RefreshDiskSizesAsync, GetDirectorySizeBytes, FormatSize
  to RepoManager with ConcurrentDictionary-based cache and 5-min TTL
- Show disk size badge in sidebar repo group headers
- Add CSS for .group-disk-size label
- Add 16 unit tests covering FormatSize, directory scanning, cache behavior
- Timer disposed in SessionSidebar.Dispose to prevent leaks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change DiskSizeCacheTtl from 5min to 7min so the cache entry
remains valid while the periodic 5min refresh runs in the
background. Prevents the disk size from disappearing every
refresh cycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevent overlapping disk size scans when a scan takes longer
than the 5-minute timer interval. Uses Interlocked.CompareExchange
to skip the scan if one is already in progress.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@StephaneDelcroix StephaneDelcroix force-pushed the fix/how-slow-would-it-be-to-indicate-for-eac-20260228-0814 branch from 409c831 to 2ab9259 Compare February 28, 2026 18:50
@StephaneDelcroix StephaneDelcroix merged commit 879f512 into main Feb 28, 2026
@StephaneDelcroix StephaneDelcroix deleted the fix/how-slow-would-it-be-to-indicate-for-eac-20260228-0814 branch February 28, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant