TUI: Unified mentions tweaks + polish mentions rendering (#23363)

This change keeps unified @mentions behind the mentions_v2 gate, moves
the flag to under-development, and polishes mention rendering/history
behavior.

It also adds a few small improvements to the mentions feature around
mention rendering and history round-tripping for plugin/tool mentions in
message edit scenarios. Plugin selections now insert `@` mentions with
better casing, and saved history preserves the visible sigil so recalled
messages look the same as what the user typed.

- Preserves `@` sigils when encoding/decoding mention history for
tool/plugin paths.
- Improves plugin mention insertion so display names/casing are
reflected more cleanly in the composer.
- Update composer to render user-entered plugin mentions in the same
color as the mentions menu. ALso applies to recalled/edited messages.
- Left/right arrows no longer switch unified-mention search modes after
an @mention has already been accepted (Ex: arrowing left through a
composed message that contains @mentions).
- Keeps bound mentions stable around punctuation, so accepted `@`
mentions do not reopen the popup and punctuated `$` mentions still
persist to cross-session history.

**Steps to test**
- Ensure mentions_v2 is enabled through configuration or `--enable
mentions_v2`
- Type `@` in the TUI composer and verify filesystem/plugin/skill
results are displayed in the unified mentions menu.
- Select a plugin mention from the `@` popup and confirm the inserted
text is an `@...` mention with casing, then recall/edit the message and
confirm it still renders as `@...`.
- Mention a skill and verify that skills still insert as `$skill`
mentions rather than `@` mentions.
- Verify punctuated mentions such as `@plugin.` and `($skill)` keep
their bound mention behavior across editing and history recall.
This commit is contained in:
canvrno-oai authored and GitHub committed 2026-05-28 10:30:15 -07:00
1 parent 489bf38658
commit 6c1215dac6
14 files changed
+1170 -104

No files matched your search

@@ -363,6 +363,7 @@ impl ChatWidget {
let encoded_mentions = mention_bindings
.iter()
.map(|binding| LinkedMention {
sigil: binding.sigil,
mention: binding.mention.clone(),
path: binding.path.clone(),
})
@@ -570,6 +570,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
Vec::new(),
Vec::new(),
vec![MentionBinding {
sigil: '$',
mention: "figma".to_string(),
path: user_skill_path.to_string_lossy().into_owned(),
}],
@@ -605,6 +606,7 @@ async fn blocked_image_restore_preserves_mention_bindings() {
path: PathBuf::from("/tmp/blocked.png"),
}];
let mention_bindings = vec![MentionBinding {
sigil: '$',
mention: "file".to_string(),
path: "/tmp/skills/file/SKILL.md".to_string(),
}];
@@ -1236,6 +1238,7 @@ async fn submit_user_message_ignores_inaccessible_app_mentions_from_bindings() {
remote_image_urls: Vec::new(),
text_elements: Vec::new(),
mention_bindings: vec![MentionBinding {
sigil: '$',
mention: "arabica-uae".to_string(),
path: "app://arabica_uae".to_string(),
}],
@@ -1244,6 +1244,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
remote_image_urls: Vec::new(),
text_elements: Vec::new(),
mention_bindings: vec![MentionBinding {
sigil: '$',
mention: "sample".to_string(),
path: "plugin://sample@test".to_string(),
}],
@@ -875,6 +875,7 @@ async fn manual_interrupt_restores_pending_steer_mention_bindings_to_composer()
chat.on_agent_message_delta("Final answer line\n".to_string());
let mention_bindings = vec![MentionBinding {
sigil: '$',
mention: "figma".to_string(),
path: "/tmp/skills/figma/SKILL.md".to_string(),
}];
@@ -656,6 +656,7 @@ async fn goal_slash_command_uses_plain_text_for_mentions() {
Vec::new(),
Vec::new(),
vec![MentionBinding {
sigil: '$',
mention: "figma".to_string(),
path: "app://figma".to_string(),
}],
@@ -917,6 +918,7 @@ fn merged_history_record_preserves_raw_text_and_rebased_elements() {
remote_image_urls: Vec::new(),
text_elements: vec![TextElement::new((4..10).into(), Some("$figma".to_string()))],
mention_bindings: vec![MentionBinding {
sigil: '$',
mention: "figma".to_string(),
path: "app://figma".to_string(),
}],
@@ -1034,6 +1036,7 @@ async fn interrupted_merged_message_history_encodes_mentions_once() {
Vec::new(),
Vec::new(),
vec![MentionBinding {
sigil: '$',
mention: "figma".to_string(),
path: "app://figma".to_string(),
}],