Symptom
Multiple reports — most recently in discussion #22 — that the @ file picker is unusable on real-sized repos. On a tree of ~5,000 files the popup typically shows nothing the user wants, and folder paths can't be picked at all. Other agents (opencode, etc.) handle the same trees fine.
What's there today
src/cli/ui/AtMentionSuggestions.tsx:36-85 — fixed 8-row popup, basename + dim suffix.
src/cli/ui/useCompletionPickers.ts:96-112 — on codeMode mount, runs one synchronous-ish walk of the whole repo via listFilesWithStatsAsync(rootDir, { maxResults: 500 }) and stores the result; the popup is empty until that promise resolves and fills atomically. No streaming, no progress indicator.
src/at-mentions.ts:119-178 — the walk respects .gitignore (per-directory layers) plus a hardcoded blacklist (node_modules, .git, dist, build, .next, …).
src/at-mentions.ts:238-330 — ranking is fuzzy-subsequence + substring scoring on the in-memory list, capped to 40 results per keystroke.
- Files only. The picker's candidate space is a list of file paths. Directory expansion at submit time exists (
src/at-mentions.ts:462-473, optional isDir? / listDir? callbacks), but the picker UI never offers a directory as a selectable item.
Why this fails on large repos
- 500-file hard cap. A 5,000-file repo loses 90% of paths before ranking ever runs. Whatever survives the cap is the unordered first 500 the walker happened to visit — usually deep paths under
src/, not the file the user wants.
- Blocking initial scan. No incremental fill. On a cold large repo, the popup is empty for the entire scan window; if the walk errors or times out, the popup is empty forever (silent
.catch(() => setAtFiles([]))).
- No folder picker. Common workflow — "give the agent the whole
src/auth/ directory" — requires the user to type the path manually. The directory-expansion machinery already exists at submit time; the picker just doesn't surface dirs.
- No async feedback. No "searching N files…" footer, no spinner, no way to know whether the picker is empty because the walk is still running, because nothing matched, or because the cap evicted the match.
Proposed UX (VSCode-style)
Two modes, swapping based on whether the user has typed a query after @:
Browse mode (no query) — @ alone, or @some/dir/:
- Show the immediate listing of the current directory only (root by default, or the path the user has typed so far).
- Folders rendered with a
/ suffix and selectable: picking a folder either drills in (Tab / Right) or commits the folder path (Enter), since submit-time expansion already handles directories.
- Files rendered as today.
- No recursion. Listing one directory is cheap regardless of repo size.
- Sort: recently-touched (existing LRU at
useCompletionPickers.ts:116-123) → folders → files, alpha within each group.
Search mode (any query typed) — @foo, @auth/log:
- Kick off an async walk across the full tree, streaming results into the popup as the walker visits each path that matches the fuzzy-subsequence test.
- Footer shows
searching… N files scanned while in flight; clears when done or when the user erases the query.
- Cancel + restart on each keystroke (debounced ~80ms).
- Keep the 40-row visible cap, but the underlying stream feeds results in best-score-first as ranking re-runs cheaply on each batch.
- Drop the 500-file walker cap; the cancelable streaming walk is what bounds work, not a fixed file count.
Acceptance criteria
@ on a 5,000-file repo immediately shows the root directory listing (folders + files) with no perceptible delay.
- Typing
@src/au on the same repo streams matches in within 100ms of the first keystroke; footer reflects scan progress.
- Picking a folder commits the folder path and inlines its listing on submit (existing behavior, just reachable from the picker now).
- Picking a file behaves identically to today.
- Backspacing the query returns to browse mode at the appropriate parent directory.
- Pressing Esc cancels any in-flight search.
Out of scope for this issue
- Line-range selection (
@path:start-end) — separate ask, file under a different issue if it comes up.
- Replacing the gitignore + hardcoded ignore set — keep both as-is.
- Indexing / persistent fuzzy index — start with cancelable streaming walk; revisit if scan latency on 50k+ trees is still painful.
Symptom
Multiple reports — most recently in discussion #22 — that the
@file picker is unusable on real-sized repos. On a tree of ~5,000 files the popup typically shows nothing the user wants, and folder paths can't be picked at all. Other agents (opencode, etc.) handle the same trees fine.What's there today
src/cli/ui/AtMentionSuggestions.tsx:36-85— fixed 8-row popup, basename + dim suffix.src/cli/ui/useCompletionPickers.ts:96-112— oncodeModemount, runs one synchronous-ish walk of the whole repo vialistFilesWithStatsAsync(rootDir, { maxResults: 500 })and stores the result; the popup is empty until that promise resolves and fills atomically. No streaming, no progress indicator.src/at-mentions.ts:119-178— the walk respects.gitignore(per-directory layers) plus a hardcoded blacklist (node_modules,.git,dist,build,.next, …).src/at-mentions.ts:238-330— ranking is fuzzy-subsequence + substring scoring on the in-memory list, capped to 40 results per keystroke.src/at-mentions.ts:462-473, optionalisDir?/listDir?callbacks), but the picker UI never offers a directory as a selectable item.Why this fails on large repos
src/, not the file the user wants..catch(() => setAtFiles([]))).src/auth/directory" — requires the user to type the path manually. The directory-expansion machinery already exists at submit time; the picker just doesn't surface dirs.Proposed UX (VSCode-style)
Two modes, swapping based on whether the user has typed a query after
@:Browse mode (no query) —
@alone, or@some/dir/:/suffix and selectable: picking a folder either drills in (Tab / Right) or commits the folder path (Enter), since submit-time expansion already handles directories.useCompletionPickers.ts:116-123) → folders → files, alpha within each group.Search mode (any query typed) —
@foo,@auth/log:searching… N files scannedwhile in flight; clears when done or when the user erases the query.Acceptance criteria
@on a 5,000-file repo immediately shows the root directory listing (folders + files) with no perceptible delay.@src/auon the same repo streams matches in within 100ms of the first keystroke; footer reflects scan progress.Out of scope for this issue
@path:start-end) — separate ask, file under a different issue if it comes up.