fix(desktop): one IME-aware Enter helper for plugins too; guard the sites main added since

- The Kanban plugin cannot import `@/lib/ime` (plugin fence), so its
  `./ime-enter` twin duplicated the predicate. Export `isSubmitEnter` from
  `@hermes/plugin-sdk` and drop the twin so one helper owns the policy.
- `BoardNameField` in kanban/board-switcher.tsx (main's refactor of the
  create/rename board dialogs) submitted on composition Enter; guarded.
- Telegram allowed-ID input and the clarify-card textarea submitted on the
  post-compositionend keyCode-229 Enter; both now use `isSubmitEnter`.
This commit is contained in:
teknium1
2026-09-13 19:53:02 -07:00
committed by Teknium
parent 5cbd994575
commit 09b74dea64
8 changed files with 14 additions and 48 deletions

View File

@@ -16,6 +16,7 @@ import {
import { useI18n } from '@/i18n'
import { openExternalLink } from '@/lib/external-link'
import { Check, ExternalLink, QrCode, Save, X } from '@/lib/icons'
import { isSubmitEnter } from '@/lib/ime'
import { cn } from '@/lib/utils'
import { CREDENTIAL_CONTROL_CLASS } from '../settings/credential-key-ui'
@@ -326,7 +327,7 @@ export function TelegramQrSetup({ onApplied, platform, scopeProfile }: TelegramQ
className={CREDENTIAL_CONTROL_CLASS}
onChange={event => setNewAllowedId(event.target.value)}
onKeyDown={event => {
if (event.key === 'Enter') {
if (isSubmitEnter(event)) {
event.preventDefault()
addAllowedId()
}

View File

@@ -24,6 +24,7 @@ import { Tip } from '@/components/ui/tooltip'
import { useI18n } from '@/i18n'
import { triggerHaptic } from '@/lib/haptics'
import { CircleLetterA, Loader2, MessageQuestion } from '@/lib/icons'
import { isSubmitEnter } from '@/lib/ime'
import { visibleClarifyCard } from '@/lib/keybinds/composer-focus-keys'
import { cn } from '@/lib/utils'
import {
@@ -592,11 +593,7 @@ function ClarifyToolSinglePending({
const handleTextareaKey = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.nativeEvent.isComposing) {
return
}
if (event.key === 'Enter' && !event.shiftKey) {
if (isSubmitEnter(event) && !event.shiftKey) {
event.preventDefault()
submitAnswer()
}

View File

@@ -21,6 +21,7 @@ import {
DropdownMenuTrigger,
host,
Input,
isSubmitEnter,
Select,
SelectContent,
SelectItem,
@@ -160,7 +161,7 @@ function BoardNameField({
<Input
autoFocus
onChange={event => onChange(event.target.value)}
onKeyDown={event => event.key === 'Enter' && onEnter()}
onKeyDown={event => isSubmitEnter(event) && onEnter()}
placeholder={k.boardNamePlaceholder}
value={value}
/>

View File

@@ -33,6 +33,7 @@ import {
formatModifierToken,
host,
Input,
isSubmitEnter,
Loader,
SearchField,
Select,
@@ -79,7 +80,6 @@ import {
} from './api'
import { BoardSwitcher } from './board-switcher'
import { TaskDrawer } from './drawer'
import { shouldSubmitOnEnter } from './ime-enter'
import { EMPTY_OVERRIDE, ModelOverrideField, overrideCreateFields, type TaskModelOverride } from './model-override'
import { OrchestrationPanel } from './orchestration'
import { columnMeta, type KanbanBoard, type KanbanTask, type TaskEstimate } from './types'
@@ -684,7 +684,7 @@ function NewTaskDialog({
autoFocus
onChange={event => setTitle(event.target.value)}
onKeyDown={event => {
if (shouldSubmitOnEnter(event)) {
if (isSubmitEnter(event)) {
event.preventDefault()
void submit()
}

View File

@@ -18,6 +18,7 @@ import {
DropdownMenuTrigger,
ErrorState,
host,
isSubmitEnter,
Loader,
LogView,
Textarea,
@@ -45,7 +46,6 @@ import {
taskKey,
uploadAttachment
} from './api'
import { shouldSubmitOnEnter } from './ime-enter'
import { ModelOverrideField, overridePatch } from './model-override'
import {
type Diagnostic,
@@ -323,7 +323,7 @@ function CommentComposer({
className={cn('field-sizing-content max-h-40 min-h-0 resize-none', running ? 'pr-[3.5rem]' : 'pr-[5rem]')}
onChange={event => setBody(event.target.value)}
onKeyDown={event => {
if (shouldSubmitOnEnter(event) && !event.shiftKey) {
if (isSubmitEnter(event) && !event.shiftKey) {
event.preventDefault()
submit()
}

View File

@@ -1,21 +0,0 @@
import { describe, expect, it } from 'vitest'
import { shouldSubmitOnEnter } from './ime-enter'
describe('shouldSubmitOnEnter', () => {
it('submits a normal Enter keypress', () => {
expect(shouldSubmitOnEnter({ key: 'Enter', nativeEvent: {} })).toBe(true)
})
it('does not submit while IME composition is active', () => {
expect(shouldSubmitOnEnter({ key: 'Enter', nativeEvent: { isComposing: true } })).toBe(false)
})
it('does not submit Chromium composition-boundary keyCode 229', () => {
expect(shouldSubmitOnEnter({ key: 'Enter', nativeEvent: { keyCode: 229 } })).toBe(false)
})
it('does not submit non-Enter keys', () => {
expect(shouldSubmitOnEnter({ key: 'Escape', nativeEvent: {} })).toBe(false)
})
})

View File

@@ -1,16 +0,0 @@
export interface ImeKeyEvent {
key: string
nativeEvent: {
isComposing?: boolean
keyCode?: number
}
}
/**
* Enter confirms an IME conversion before it should act as a submit shortcut.
* Chromium can report the legacy 229 keyCode around composition boundaries,
* so keep that fallback in addition to the standard isComposing signal.
*/
export function shouldSubmitOnEnter(event: ImeKeyEvent): boolean {
return event.key === 'Enter' && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229
}

View File

@@ -1683,6 +1683,10 @@ export { triggerHaptic as haptic } from '@/lib/haptics'
export type { HermesOpenTarget } from '@/lib/hermes-open-target'
/** The app's lucide icon set (RefreshCw, LayoutDashboard, Activity, …). */
export * as icons from '@/lib/icons'
/** IME-aware Enter: true only for a real submit Enter, never a CJK composition
* commit (`isComposing` or the legacy keyCode 229). Use it on every plugin
* text field whose bare Enter performs an action. */
export { isSubmitEnter } from '@/lib/ime'
export { type KeybindContribution, KEYBINDS_AREA } from '@/lib/keybinds/actions'
export { formatModifierToken } from '@/lib/keybinds/combo'
/** A `Map` with a ceiling, for the module-level caches a plugin keeps across