From c6e041eeb07aab032cd628f1c4dc99dc30bec899 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 21:11:33 +0000 Subject: [PATCH] feat: execute slash commands immediately on autocomplete selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When selecting a slash command from the autocomplete menu, the action now executes immediately and clears the input, providing a more streamlined UX. Changes: - Added onCommandExecute prop to MentionEditor - Modified slash command suggestion to call onCommandExecute on selection - Clears editor content immediately after selection - Added handleCommandExecute callback in ChatViewer - Executes action and shows toast notifications Before: Type "/" → select "/join" → press Enter → executes After: Type "/" → select "/join" → executes immediately This matches the expected behavior for command selection - when you choose a command from autocomplete, you want to execute it, not just insert it into the text field. Tests: All 804 tests passing Build: Successful Lint: No new errors --- src/components/ChatViewer.tsx | 32 +++++++++++++++++++++++++ src/components/editor/MentionEditor.tsx | 23 +++++++++++------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index babb434..a2ab769 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -18,6 +18,7 @@ import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter"; import { Nip53Adapter } from "@/lib/chat/adapters/nip-53-adapter"; import type { ChatProtocolAdapter } from "@/lib/chat/adapters/base-adapter"; import type { Message } from "@/types/chat"; +import type { ChatAction } from "@/types/chat-actions"; import { parseSlashCommand } from "@/lib/chat/slash-command-parser"; import { UserName } from "./nostr/UserName"; import { RichText } from "./nostr/RichText"; @@ -497,6 +498,36 @@ export function ChatViewer({ } }; + // Handle command execution from autocomplete + const handleCommandExecute = useCallback( + async (action: ChatAction) => { + if (!conversation || !hasActiveAccount || isSending) return; + + setIsSending(true); + try { + const result = await adapter.executeAction(action.name, { + activePubkey: activeAccount.pubkey, + activeSigner: activeAccount.signer, + conversation, + }); + + if (result.success) { + toast.success(result.message || "Action completed"); + } else { + toast.error(result.message || "Action failed"); + } + } catch (error) { + console.error("[Chat] Failed to execute action:", error); + const errorMessage = + error instanceof Error ? error.message : "Action failed"; + toast.error(errorMessage); + } finally { + setIsSending(false); + } + }, + [conversation, hasActiveAccount, isSending, adapter, activeAccount], + ); + // Handle reply button click const handleReply = useCallback((messageId: string) => { setReplyTo(messageId); @@ -813,6 +844,7 @@ export function ChatViewer({ searchProfiles={searchProfiles} searchEmojis={searchEmojis} searchCommands={searchCommands} + onCommandExecute={handleCommandExecute} onSubmit={(content, emojiTags) => { if (content.trim()) { handleSend(content, replyTo, emojiTags); diff --git a/src/components/editor/MentionEditor.tsx b/src/components/editor/MentionEditor.tsx index a4b31cf..dfb5cee 100644 --- a/src/components/editor/MentionEditor.tsx +++ b/src/components/editor/MentionEditor.tsx @@ -56,6 +56,7 @@ export interface MentionEditorProps { searchProfiles: (query: string) => Promise; searchEmojis?: (query: string) => Promise; searchCommands?: (query: string) => Promise; + onCommandExecute?: (action: ChatAction) => Promise; autoFocus?: boolean; className?: string; } @@ -159,6 +160,7 @@ export const MentionEditor = forwardRef< searchProfiles, searchEmojis, searchCommands, + onCommandExecute, autoFocus = false, className = "", }, @@ -620,15 +622,17 @@ export const MentionEditor = forwardRef< ...slashCommandSuggestion, command: ({ editor, props }: any) => { // props is the ChatAction - // Replace the entire content with just the command - editor - .chain() - .focus() - .deleteRange({ from: 0, to: editor.state.doc.content.size }) - .insertContentAt(0, [ - { type: "text", text: `/${props.name}` }, - ]) - .run(); + // Execute the command immediately and clear the editor + editor.commands.clearContent(); + if (onCommandExecute) { + // Execute action asynchronously + onCommandExecute(props).catch((error) => { + console.error( + "[MentionEditor] Command execution failed:", + error, + ); + }); + } }, }, renderLabel({ node }) { @@ -643,6 +647,7 @@ export const MentionEditor = forwardRef< mentionSuggestion, emojiSuggestion, slashCommandSuggestion, + onCommandExecute, placeholder, ]);