From 46ab1fdf62130ff591516b8753dfd4660fda02d1 Mon Sep 17 00:00:00 2001 From: reidliu41 Date: Mon, 11 May 2026 17:24:39 +0800 Subject: [PATCH] Scope skill completions to /skill Keep individual skills out of the top-level slash command menu so large skill collections do not crowd out built-in commands. Skills still complete after `/skill`, including both the full skill list after `/skill ` and prefix matches after `/skill `. (cherry picked from commit 57f8e3ad84dad9cf46290c0dc23e2b26504196df) --- crates/tui/src/tui/widgets/mod.rs | 67 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/crates/tui/src/tui/widgets/mod.rs b/crates/tui/src/tui/widgets/mod.rs index 7d290e7c..ae435409 100644 --- a/crates/tui/src/tui/widgets/mod.rs +++ b/crates/tui/src/tui/widgets/mod.rs @@ -1986,23 +1986,20 @@ pub(crate) fn slash_completion_hints( } } - // Cached skills - let skill_prefix = completing_skill_arg.unwrap_or(prefix); - let prefix_lower = skill_prefix.to_ascii_lowercase(); - for (skill_name, skill_desc) in cached_skills { - let skill_name_lower = skill_name.to_ascii_lowercase(); - let command_prefix_matches = completing_skill_arg.is_none() - && (prefix_lower.is_empty() - || "skill".starts_with(&prefix_lower) - || skill_name_lower.starts_with(&prefix_lower)); - let skill_arg_matches = - completing_skill_arg.is_some() && skill_name_lower.starts_with(&prefix_lower); - if command_prefix_matches || skill_arg_matches { - entries.push(SlashMenuEntry { - name: format!("/skill {skill_name}"), - description: skill_desc.clone(), - is_skill: true, - }); + // Cached skills are arguments to `/skill`, not top-level commands. Keep + // the top-level slash menu focused on commands and expand skills only + // after the user has selected the skill command. + let prefix_lower = completing_skill_arg.unwrap_or(prefix).to_ascii_lowercase(); + if completing_skill_arg.is_some() { + for (skill_name, skill_desc) in cached_skills { + let skill_name_lower = skill_name.to_ascii_lowercase(); + if skill_name_lower.starts_with(&prefix_lower) { + entries.push(SlashMenuEntry { + name: format!("/skill {skill_name}"), + description: skill_desc.clone(), + is_skill: true, + }); + } } } @@ -2373,39 +2370,41 @@ mod tests { } #[test] - fn slash_completion_hints_include_skills() { + fn slash_completion_hints_hide_skills_from_top_level_menu() { let cached_skills = vec![ ("search-files".to_string(), "Search files".to_string()), ("my-review".to_string(), "Review code".to_string()), ]; let hints = slash_completion_hints("/", 128, &cached_skills, Locale::En, None); - assert!( - hints - .iter() - .any(|hint| hint.name == "/skill search-files" && hint.is_skill) - ); - assert!( - hints - .iter() - .any(|hint| hint.name == "/skill my-review" && hint.is_skill) - ); + assert!(hints.iter().any(|hint| hint.name == "/skill")); + assert!(hints.iter().any(|hint| hint.name == "/skills")); + assert!(!hints.iter().any(|hint| hint.is_skill)); } #[test] - fn slash_completion_hints_skills_match_prefix() { + fn slash_completion_hints_hide_skills_from_top_level_prefix() { let cached_skills = vec![ ("search-files".to_string(), "Search files".to_string()), ("my-review".to_string(), "Review code".to_string()), ]; let hints = slash_completion_hints("/se", 128, &cached_skills, Locale::En, None); - assert!( - hints - .iter() - .any(|hint| hint.name == "/skill search-files" && hint.is_skill) - ); + assert!(!hints.iter().any(|hint| hint.name == "/skill search-files")); assert!(!hints.iter().any(|hint| hint.name == "/skill my-review")); } + #[test] + fn slash_completion_hints_complete_skill_argument_all() { + let cached_skills = vec![ + ("search-files".to_string(), "Search files".to_string()), + ("my-review".to_string(), "Review code".to_string()), + ]; + let hints = slash_completion_hints("/skill ", 128, &cached_skills, Locale::En, None); + assert_eq!(hints.len(), 2); + assert!(hints.iter().any(|hint| hint.name == "/skill search-files")); + assert!(hints.iter().any(|hint| hint.name == "/skill my-review")); + assert!(hints.iter().all(|hint| hint.is_skill)); + } + #[test] fn slash_completion_hints_complete_skill_argument_prefix() { let cached_skills = vec![