From 93ba853b6a004392825d41f6411eca84fecde186 Mon Sep 17 00:00:00 2001 From: KevinYoung-Kw Date: Wed, 15 Apr 2026 13:59:47 +0800 Subject: [PATCH 1/3] fix: retain existing input text when selecting slash commands with optional details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **问题** 在输入框中已有文字时,选择带有 "Add details(optional)" 的斜杠命令(非 immediate badge 类命令),输入框内容会被直接清空。期望已有文本应作为 details 保留。 **修复** - `src/lib/message-input-logic.ts` - `resolveItemSelection` 的 `set_badge` 分支现在计算 `newInputValue`,去掉 `/` 触发符和过滤文本,保留触发位置前后的用户输入内容 - `src/hooks/useSlashCommands.ts` - `insertItem` 的 `set_badge` 分支使用 `result.newInputValue` 回填输入框,替代之前的硬编码 `setInputValue('')` - `electron/main.ts` - dev 模式端口支持 `PORT` 环境变量,避免与本地其他服务冲突 **影响范围** 仅影响 badge 类斜杠命令的输入框回填行为;immediate 命令(如 /clear)仍保持原有清空逻辑不变。 Co-Authored-By: Claude Opus 4.6 (1M context) --- electron/main.ts | 2 +- src/hooks/useSlashCommands.ts | 2 +- src/lib/message-input-logic.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 1165a590..a22e4ce3 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1385,7 +1385,7 @@ app.whenReady().then(async () => { let port: number; if (isDev) { - port = 3000; + port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; console.log(`Dev mode: connecting to http://127.0.0.1:${port}`); serverPort = port; createWindow(`http://127.0.0.1:${port}`); diff --git a/src/hooks/useSlashCommands.ts b/src/hooks/useSlashCommands.ts index f320f123..5cf1d969 100644 --- a/src/hooks/useSlashCommands.ts +++ b/src/hooks/useSlashCommands.ts @@ -196,7 +196,7 @@ export function useSlashCommands(opts: { // Block during streaming — badges dispatch as slash/skill prompts, not queueable if (isStreaming) { closePopover(); return; } setBadge(result.badge!); - setInputValue(''); + setInputValue(result.newInputValue ?? ''); closePopover(); setTimeout(() => textareaRef.current?.focus(), 0); return; diff --git a/src/lib/message-input-logic.ts b/src/lib/message-input-logic.ts index 213a2c94..e1986e4c 100644 --- a/src/lib/message-input-logic.ts +++ b/src/lib/message-input-logic.ts @@ -100,8 +100,11 @@ export function resolveItemSelection( return { action: 'immediate_command', commandValue: item.value }; } - // Non-immediate commands: show as badge + // Non-immediate commands: show as badge, preserving any text outside the trigger if (popoverMode === 'skill') { + const before = inputValue.slice(0, triggerPos); + const cursorEnd = triggerPos + popoverFilter.length + 1; + const after = inputValue.slice(cursorEnd); return { action: 'set_badge', badge: { @@ -111,6 +114,7 @@ export function resolveItemSelection( kind: item.kind || 'slash_command', installedSource: item.installedSource, }, + newInputValue: before + after, }; } From 9d4b010cb84e962f79524a2971593aa1e39d3eb3 Mon Sep 17 00:00:00 2001 From: KevinYoung-Kw Date: Wed, 15 Apr 2026 14:05:07 +0800 Subject: [PATCH 2/3] fix: place cursor at end of input after slash command selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **补充修复** 在 #1 斜杠命令保留已有文本的修复基础上,进一步确保选择命令后光标自动定位到输入框尾部,而不是默认的首部。 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/hooks/useSlashCommands.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hooks/useSlashCommands.ts b/src/hooks/useSlashCommands.ts index 5cf1d969..42f5f762 100644 --- a/src/hooks/useSlashCommands.ts +++ b/src/hooks/useSlashCommands.ts @@ -198,7 +198,13 @@ export function useSlashCommands(opts: { setBadge(result.badge!); setInputValue(result.newInputValue ?? ''); closePopover(); - setTimeout(() => textareaRef.current?.focus(), 0); + setTimeout(() => { + const el = textareaRef.current; + if (!el) return; + el.focus(); + const pos = el.value.length; + el.setSelectionRange(pos, pos); + }, 0); return; case 'insert_file_mention': From 3a6cb0486c7f57396bed2170e6f67908625cbe6b Mon Sep 17 00:00:00 2001 From: KevinYoung-Kw Date: Wed, 15 Apr 2026 14:58:08 +0800 Subject: [PATCH 3/3] refactor(slash): extract splitAroundTrigger helper and guard env PORT parsing --- electron/main.ts | 3 ++- src/lib/message-input-logic.ts | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index a22e4ce3..60b18418 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1385,7 +1385,8 @@ app.whenReady().then(async () => { let port: number; if (isDev) { - port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; + const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : NaN; + port = Number.isNaN(envPort) ? 3000 : envPort; console.log(`Dev mode: connecting to http://127.0.0.1:${port}`); serverPort = port; createWindow(`http://127.0.0.1:${port}`); diff --git a/src/lib/message-input-logic.ts b/src/lib/message-input-logic.ts index e1986e4c..925c88d0 100644 --- a/src/lib/message-input-logic.ts +++ b/src/lib/message-input-logic.ts @@ -84,6 +84,21 @@ export function filterItems(items: PopoverItem[], filter: string): PopoverItem[] ); } +/** + * Splits input text around a popover trigger, removing the trigger character + * and any filter text that was typed after it. + */ +function splitAroundTrigger( + inputValue: string, + triggerPos: number, + popoverFilter: string, +): { before: string; after: string } { + const before = inputValue.slice(0, triggerPos); + const cursorEnd = triggerPos + popoverFilter.length + 1; // +1 to consume the trigger character + const after = inputValue.slice(cursorEnd); + return { before, after }; +} + /** * Determines what happens when an item is selected from the popover. * Used by insertItem in useSlashCommands. @@ -102,9 +117,7 @@ export function resolveItemSelection( // Non-immediate commands: show as badge, preserving any text outside the trigger if (popoverMode === 'skill') { - const before = inputValue.slice(0, triggerPos); - const cursorEnd = triggerPos + popoverFilter.length + 1; - const after = inputValue.slice(cursorEnd); + const { before, after } = splitAroundTrigger(inputValue, triggerPos, popoverFilter); return { action: 'set_badge', badge: { @@ -119,9 +132,7 @@ export function resolveItemSelection( } // File mention: insert into text - const before = inputValue.slice(0, triggerPos); - const cursorEnd = triggerPos + popoverFilter.length + 1; - const after = inputValue.slice(cursorEnd); + const { before, after } = splitAroundTrigger(inputValue, triggerPos, popoverFilter); const insertText = `@${item.value} `; return { action: 'insert_file_mention',