feat(maestro): exclude hidden dirs from spawn-form autocomplete (NAP-329)
ci / rust (push) Successful in 5m6s

This commit is contained in:
2026-06-16 06:41:39 +00:00
parent 977800951d
commit 473aa948a8
13 changed files with 25 additions and 8 deletions
+12 -5
View File
@@ -28,7 +28,7 @@ pub const MAX_ENTRIES: usize = 1000;
/// List the immediate subdirectories of `requested`, expanding it against /// List the immediate subdirectories of `requested`, expanding it against
/// `home_dir` first. Returns directory **names** only (not full paths), sorted /// `home_dir` first. Returns directory **names** only (not full paths), sorted
/// case-insensitively, with hidden entries included. /// case-insensitively. Hidden directories (dotfiles) are excluded.
pub fn list_dir(home_dir: &str, requested: &str) -> Result<DirListing, ErrorInfo> { pub fn list_dir(home_dir: &str, requested: &str) -> Result<DirListing, ErrorInfo> {
let dir = resolve(home_dir, requested); let dir = resolve(home_dir, requested);
@@ -36,8 +36,14 @@ pub fn list_dir(home_dir: &str, requested: &str) -> Result<DirListing, ErrorInfo
let mut entries: Vec<String> = Vec::new(); let mut entries: Vec<String> = Vec::new();
for entry in read.flatten() { for entry in read.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
// Skip hidden directories — the spawn form rarely targets dotfile dirs
// and they only clutter the completion list.
if name.starts_with('.') {
continue;
}
if is_dir(&entry) { if is_dir(&entry) {
entries.push(entry.file_name().to_string_lossy().into_owned()); entries.push(name);
} }
} }
// Case-insensitive sort so `Projects` and `projects` interleave the way a // Case-insensitive sort so `Projects` and `projects` interleave the way a
@@ -124,12 +130,13 @@ mod tests {
} }
#[test] #[test]
fn lists_dirs_only_sorted_case_insensitively_including_hidden() { fn lists_visible_dirs_only_sorted_case_insensitively() {
let tmp = fixture(); let tmp = fixture();
let home = tmp.path().to_string_lossy(); let home = tmp.path().to_string_lossy();
let listing = list_dir(&home, "").unwrap(); let listing = list_dir(&home, "").unwrap();
// The file is excluded; dirs (incl. hidden + symlinked dir) are present. // Files and hidden dirs are excluded; visible dirs (incl. a symlinked
assert!(listing.entries.contains(&".config".to_string())); // dir) are present.
assert!(!listing.entries.contains(&".config".to_string()));
assert!(listing.entries.contains(&"projects".to_string())); assert!(listing.entries.contains(&"projects".to_string()));
assert!(listing.entries.contains(&"Downloads".to_string())); assert!(listing.entries.contains(&"Downloads".to_string()));
assert!(!listing.entries.contains(&"notes.txt".to_string())); assert!(!listing.entries.contains(&"notes.txt".to_string()));
+1 -1
View File
@@ -77,7 +77,7 @@ pub struct DirListing {
pub path: PathBuf, pub path: PathBuf,
/// Immediate subdirectory names (not full paths), sorted case-insensitively. /// Immediate subdirectory names (not full paths), sorted case-insensitively.
/// Directories only — the spawn form's targets are always dirs. Hidden /// Directories only — the spawn form's targets are always dirs. Hidden
/// entries (dotfiles) are included, matching shell completion. /// directories (dotfiles) are excluded.
pub entries: Vec<String>, pub entries: Vec<String>,
/// True when `entries` was truncated at the daemon's cap; the client can /// True when `entries` was truncated at the daemon's cap; the client can
/// surface "narrow your search" rather than implying the dir is small. /// surface "narrow your search" rather than implying the dir is small.
@@ -33,7 +33,7 @@ The daemon lists **one directory's immediate subdirectories**; the web caches
the result per `(daemonId, dir)` and filters the trailing path segment the result per `(daemonId, dir)` and filters the trailing path segment
client-side, re-querying only when the user crosses a `/`. Fewer round-trips, client-side, re-querying only when the user crosses a `/`. Fewer round-trips,
instant filtering, still live. Directories only (cwd / base-repo are always instant filtering, still live. Directories only (cwd / base-repo are always
dirs); hidden dirs included (shell parity); sorted case-insensitively; capped. dirs); hidden dirs excluded; sorted case-insensitively; capped.
### Wire protocol (`maestro-protocol`) ### Wire protocol (`maestro-protocol`)
@@ -92,7 +92,7 @@ machinery, so older peers degrade gracefully.
- Protocol: serde round-trip for the four new variants + `DirListing`, and an - Protocol: serde round-trip for the four new variants + `DirListing`, and an
unknown-variant decode (already covered generically). unknown-variant decode (already covered generically).
- Daemon `dirlist`: temp-dir listing (dirs-only, hidden included, sorted), `~` - Daemon `dirlist`: temp-dir listing (dirs-only, hidden excluded, sorted), `~`
and relative + empty expansion, and `not_found` / `not_a_directory` codes. and relative + empty expansion, and `not_found` / `not_a_directory` codes.
- Daemon `broker`: `forward_list_dir` round-trip + fail-fast-while-disconnected, - Daemon `broker`: `forward_list_dir` round-trip + fail-fast-while-disconnected,
mirroring the existing forward_create/kill tests. mirroring the existing forward_create/kill tests.
Submodule worktrees/linear/nap-256-tmux-async added at 5ea8d0fa59
Submodule worktrees/linear/nap-257-web-proxy-caps added at b0774934e8
Submodule worktrees/linear/nap-258-aba-deregister added at 599f3ed178
Submodule worktrees/linear/nap-260-create-rollback added at 2f4dde85ac
Submodule worktrees/linear/nap-263-broker-backpressure added at ae3775946d
Submodule worktrees/linear/nap-265-broker-dial added at ae588b76d5
Submodule worktrees/linear/nap-266-jwt-binding added at 939d67615a
Submodule worktrees/linear/nap-270-perimeter-tests added at f3f696790c
Submodule worktrees/linear/nap-272-typed-perimeter added at eb6aeb4241
Submodule worktrees/linear/nap-273a-urlencoder added at 4a008684f6