diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index 601eb82bc48..7a6d293dab1 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -85,6 +85,7 @@ export function Autocomplete(props: {
index: 0,
selected: 0,
visible: false as AutocompleteRef["visible"],
+ mouseHasMoved: false,
})
const [positionTick, setPositionTick] = createSignal(0)
@@ -588,9 +589,17 @@ export function Autocomplete(props: {
setStore({
visible: mode,
index: props.input().cursorOffset,
+ mouseHasMoved: false,
})
}
+ // Reset mouse movement tracking when filter changes to prevent accidental selection
+ // when items change position under a stationary cursor
+ createEffect(() => {
+ filter()
+ setStore("mouseHasMoved", false)
+ })
+
function hide() {
const text = props.input().plainText
if (store.visible === "/" && !text.endsWith(" ") && text.startsWith("/")) {
@@ -723,41 +732,54 @@ export function Autocomplete(props: {
{...SplitBorder}
borderColor={theme.border}
>
- (scroll = r)}
- backgroundColor={theme.backgroundMenu}
- height={height()}
- scrollbarOptions={{ visible: false }}
- >
-
- No matching items
-
- }
+ setStore("mouseHasMoved", true)}>
+ (scroll = r)}
+ backgroundColor={theme.backgroundMenu}
+ height={height()}
+ scrollbarOptions={{ visible: false }}
>
- {(option, index) => (
- moveTo(index)}
- onMouseUp={() => select()}
- >
-
- {option().display}
-
-
-
- {option().description}
+
+ No matching items
+
+ }
+ >
+ {(option, index) => (
+ {
+ // Once mouse moves, enable hover selection and immediately select this item
+ if (!store.mouseHasMoved) {
+ setStore("mouseHasMoved", true)
+ moveTo(index)
+ }
+ }}
+ onMouseOver={() => {
+ // Only select on hover if mouse has moved (prevents accidental selection)
+ if (!store.mouseHasMoved) return
+ moveTo(index)
+ }}
+ onMouseUp={() => select()}
+ >
+
+ {option().display}
-
-
- )}
-
-
+
+
+ {option().description}
+
+
+
+ )}
+
+
+
)
}
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index d3239ebac63..a3378030884 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -52,6 +52,7 @@ export function DialogSelect(props: DialogSelectProps) {
const [store, setStore] = createStore({
selected: 0,
filter: "",
+ mouseHasMoved: false,
})
createEffect(
@@ -109,6 +110,10 @@ export function DialogSelect(props: DialogSelectProps) {
createEffect(
on([() => store.filter, () => props.current], ([filter, current]) => {
+ // Reset mouse movement tracking when filter changes to prevent accidental selection
+ // when items change position under a stationary cursor
+ setStore("mouseHasMoved", false)
+
setTimeout(() => {
if (filter.length > 0) {
moveTo(0, true)
@@ -248,61 +253,75 @@ export function DialogSelect(props: DialogSelectProps) {
}
>
- (scroll = r)}
- maxHeight={height()}
- >
-
- {([category, options], index) => (
- <>
-
- 0 ? 1 : 0} paddingLeft={3}>
-
- {category}
-
-
-
-
- {(option) => {
- const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
- const current = createMemo(() => isDeepEqual(option.value, props.current))
- return (
- {
- option.onSelect?.(dialog)
- props.onSelect?.(option)
- }}
- onMouseOver={() => {
- const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
- if (index === -1) return
- moveTo(index)
- }}
- backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
- paddingLeft={current() || option.gutter ? 1 : 3}
- paddingRight={3}
- gap={1}
- >
-
-
- )
- }}
-
- >
- )}
-
-
+ setStore("mouseHasMoved", true)}>
+ (scroll = r)}
+ maxHeight={height()}
+ >
+
+ {([category, options], index) => (
+ <>
+
+ 0 ? 1 : 0} paddingLeft={3}>
+
+ {category}
+
+
+
+
+ {(option) => {
+ const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
+ const current = createMemo(() => isDeepEqual(option.value, props.current))
+ return (
+ {
+ // Once mouse moves, enable hover selection and immediately select this item
+ if (!store.mouseHasMoved) {
+ setStore("mouseHasMoved", true)
+ const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
+ if (index !== -1) moveTo(index)
+ }
+ }}
+ onMouseUp={() => {
+ option.onSelect?.(dialog)
+ props.onSelect?.(option)
+ }}
+ onMouseOver={() => {
+ // Only select on hover if mouse has moved (prevents accidental selection)
+ if (!store.mouseHasMoved) return
+
+ const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
+ if (index === -1) return
+
+ moveTo(index)
+ }}
+ backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
+ paddingLeft={current() || option.gutter ? 1 : 3}
+ paddingRight={3}
+ gap={1}
+ >
+
+
+ )
+ }}
+
+ >
+ )}
+
+
+
}>