diff --git a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx
index abd348da..5ba67292 100644
--- a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx
+++ b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx
@@ -1026,6 +1026,46 @@ export const PromptInputTextarea = ({
form?.requestSubmit();
}
+ // Handle backspace to delete entire mention at once
+ if (e.key === "Backspace" && controller) {
+ const textarea = e.currentTarget;
+ const cursorPos = textarea.selectionStart;
+ const selectionEnd = textarea.selectionEnd;
+
+ // Only handle if no text is selected (cursor is at a single position)
+ if (cursorPos === selectionEnd) {
+ // Check if cursor is right after a mention
+ for (const label of mentionLabels) {
+ const mentionText = `@${label}`;
+ const startPos = cursorPos - mentionText.length;
+ if (startPos >= 0) {
+ const textBefore = currentValue.substring(startPos, cursorPos);
+ if (textBefore === mentionText) {
+ // Check if it's at word boundary (start of string or preceded by whitespace)
+ if (startPos === 0 || /\s/.test(currentValue[startPos - 1])) {
+ e.preventDefault();
+ const newText = currentValue.substring(0, startPos) + currentValue.substring(cursorPos);
+ controller.textInput.setInput(newText);
+ // Remove the mention from state
+ if (mentionsCtx) {
+ const mentionToRemove = mentionsCtx.mentions.find(m => m.displayName === label);
+ if (mentionToRemove) {
+ mentionsCtx.removeMention(mentionToRemove.id);
+ }
+ }
+ // Set cursor position after React updates
+ setTimeout(() => {
+ textarea.selectionStart = startPos;
+ textarea.selectionEnd = startPos;
+ }, 0);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
// Remove last attachment when Backspace is pressed and textarea is empty
if (
e.key === "Backspace" &&
@@ -1094,7 +1134,7 @@ export const PromptInputTextarea = ({
segment.highlighted ? (
{segment.text}
diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
index c6c00374..1f059503 100644
--- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx
+++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
@@ -294,7 +294,7 @@ export function ChatSidebar({
}
}
- const handleKeyDown = (e: React.KeyboardEvent) => {
+ const handleKeyDown = (e: React.KeyboardEvent) => {
// If mention popover is open, let it handle navigation keys
if (activeMention && ['ArrowDown', 'ArrowUp', 'Tab', 'Escape'].includes(e.key)) {
return
@@ -311,6 +311,41 @@ export function ChatSidebar({
handleSubmit()
}
}
+
+ // Handle backspace to delete entire mention at once
+ if (e.key === 'Backspace') {
+ const textarea = e.currentTarget
+ const cursorPos = textarea.selectionStart
+ const selectionEnd = textarea.selectionEnd
+
+ // Only handle if no text is selected (cursor is at a single position)
+ if (cursorPos !== selectionEnd) return
+
+ // Check if cursor is right after a mention
+ for (const label of mentionLabels) {
+ const mentionText = `@${label}`
+ const startPos = cursorPos - mentionText.length
+ if (startPos >= 0) {
+ const textBefore = message.substring(startPos, cursorPos)
+ if (textBefore === mentionText) {
+ // Check if it's at word boundary (start of string or preceded by whitespace)
+ if (startPos === 0 || /\s/.test(message[startPos - 1])) {
+ e.preventDefault()
+ const newText = message.substring(0, startPos) + message.substring(cursorPos)
+ onMessageChange(newText)
+ // Remove the mention from state
+ setMentions(prev => prev.filter(m => m.displayName !== label))
+ // Set cursor position after React updates
+ setTimeout(() => {
+ textarea.selectionStart = startPos
+ textarea.selectionEnd = startPos
+ }, 0)
+ return
+ }
+ }
+ }
+ }
+ }
}
const renderConversationItem = (item: ConversationItem) => {