Skip to content

Stop rebuilding every composer's @-mention index after each agent tool call - #1029

Open
zcy22606 wants to merge 1 commit into
hardbeat920:mainfrom
zcy22606:fix/mention-index-rebuilds
Open

zcy22606 wants to merge 1 commit into
hardbeat920:mainfrom
zcy22606:fix/mention-index-rebuilds

Conversation

@zcy22606

@zcy22606 zcy22606 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • loadProjectFiles keeps the previous array when a rescan returns the same listing (same paths and isDir, in order) and doesn't notify subscribers. The last listing per checkout is kept through invalidateProjectFiles(cwd) (bounded at CACHE_LIMIT * 2) so the comparison still works after the cache entry is dropped; a full invalidateProjectFiles() clears it.
  • New sharedMentionIndex(files, extras) in fileMentions.ts: composers holding the same listing (and the same notes) share one MentionIndex (a WeakMap keyed by the listing array, at most 4 note sets per listing) instead of each building its own.
  • A composer in an inactive tab (enabled === false) stops holding the project listing and picks it up from the cache again when shown.

Why

When a shell tool finishes, nudgeOpenEditors in App.tsx calls nudgeWorkspace(cwd) twice (immediately and 150 ms later), and a finished edit tool calls it once. Each call runs invalidateProjectFiles(cwd) + notifyDirsChanged(), which rescans the checkout. Every scan stored a fresh array and called notifyProjectFilesChanged(), so every mounted Composer for that checkout, hidden tabs included, got a new files array and re-ran buildMentionIndex in its own useMemo, even though nothing on disk had changed.

buildMentionIndex is linear in the listing but not cheap: about 16 ms for 15,000 paths in Node (below), and about 50 ms in a real window on a 15,000-file multi-repo root. With 23 sessions open on that root, every agent tool call cost about 1.1 s of main thread, enough to break an IME composition or swallow a click. A heap snapshot of that window also held about 170,000 file entries, mostly the per-composer copies kept by hidden tabs.

UI

No visual change. @ suggestions behave the same; a hidden tab's composer reloads its listing from the cache when shown.

Validation

  • New tests, both failing on main:
    • fileIndex.test.ts › "keeps the same array and stays quiet when a rescan finds nothing new" (the toBe identity check fails on main)
    • fileMentions.test.ts › "sharedMentionIndex builds once per listing and extras, matching a direct build"
  • Synthetic listing of 15,000 paths (6 repos × 50 dirs × 50 files), median of runs in vitest:
    • one buildMentionIndex: ~16 ms
    • 23 composers before (one build each): ~345 ms
    • 23 composers after (sharedMentionIndex): ~16 ms
    • extra cost of comparing an unchanged rescan: ~0.25 ms
  • npm run check:web: vitest (5365 passed, 13 skipped) and tsc --noEmit. No Rust changes.

Checklist

  • I ran npm run check (check:web; no Rust files changed)
  • This PR is small and focused
  • I did not mix unrelated changes

Summary by CodeRabbit

  • Performance Improvements
    • File listings that have not changed now avoid unnecessary updates, helping keep the interface responsive during refreshes.
    • File mention suggestions reuse results when the available project and note files are unchanged, while reflecting changes to those files.

…l call

Every finished shell or edit tool rescans the project listing (a shell
tool twice), and each scan stored a fresh array and notified everyone,
so every mounted composer of that checkout, hidden tabs included, rebuilt
its mention index even though nothing on disk had changed. On a
15,000-file root with 23 sessions open that was about a second of main
thread per tool call.

- An unchanged rescan keeps the previous array and notifies no one; the
  last listing per checkout survives invalidation so it can be compared.
- Composers with the same listing and notes share one mention index.
- A composer in an inactive tab lets go of the listing until shown again.
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7d63c0d9-7f5a-404f-bcdf-f9686d452ce7
📥 Commits

Reviewing files that changed from the base of the PR and between e49a5ae and 6ea930c.

📒 Files selected for processing: 5
  • src/features/files/model/fileIndex.test.ts
  • src/features/files/model/fileIndex.ts
  • src/features/files/model/fileMentions.test.ts
  • src/features/files/model/fileMentions.ts
  • src/features/sessions/ui/Composer.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review.

📝 Walkthrough

The file index now reuses arrays for unchanged listings. Mention indexes are cached by project-file listing and extras. Composer uses the shared index and skips file loading and subscription when disabled.

Changes: File Listing and Mentions

Layer / File(s) Summary
Retain unchanged file listings
src/features/files/model/fileIndex.ts, src/features/files/model/fileIndex.test.ts
The file index compares listing lengths and ordered paths and directory flags. It reuses the prior array when listings match, avoids notifying subscribers for unchanged listings, and bounds retained snapshots. The regression test verifies array reuse and no notification after an equivalent refresh.
Cache mention indexes
src/features/files/model/fileMentions.ts, src/features/files/model/fileMentions.test.ts
sharedMentionIndex reuses indexes for the same project-file array and extras key. It stores up to four extra-set indexes per listing. Tests cover reuse, distinct indexes for changed extras, and matching labels.
Use shared indexes in Composer
src/features/sessions/ui/Composer.tsx
Composer builds mention indexes from project files and, when notes are enabled, note files. When disabled, it uses the empty-path cache and skips file loading and subscription.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: hardbeat920

Merge Risk: ⚪ Minimal · up to 6ea93

No actionable merge-blocking risk is established for this change; it is ready for normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: preventing repeated @-mention index rebuilds after agent tool calls.
Description check Passed The description includes all required sections, explains the change and rationale, documents UI impact, and completes the checklist. The added validation details provide useful supporting context.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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