From 02db73674bc7da7d16b40f85ba2daa9448a02e44 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 25 Mar 2026 20:56:58 -0400 Subject: [PATCH 1/7] fix: move selection to adjacent item after session deletion When deleting a session in the session list dialog, the selection now moves to an adjacent item (next or previous) instead of staying at the same index which could point to a non-adjacent session. Changes: - Expose moveTo function in DialogSelectRef - Track index before deletion in dialog-session-list - Move to adjacent item after successful deletion This fix is compatible with session grouping and bookmarks. --- .../cmd/tui/component/dialog-session-list.tsx | 21 ++++++++++++++++++- .../src/cli/cmd/tui/ui/dialog-select.tsx | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 775969bfcb38..9641301bec21 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -1,5 +1,5 @@ import { useDialog } from "@tui/ui/dialog" -import { DialogSelect } from "@tui/ui/dialog-select" +import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" import { createMemo, createSignal, createResource, onMount, Show } from "solid-js" @@ -23,6 +23,8 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) + const [selectRef, setSelectRef] = createSignal>() + const [searchResults] = createResource(search, async (query) => { if (!query) return undefined @@ -65,6 +67,7 @@ export function DialogSessionList() { return ( { if (toDelete() === option.value) { + // Find current index before deletion + const ref = selectRef() + const currentIndex = ref?.filtered.findIndex((opt) => opt.value === option.value) ?? -1 + sdk.client.session.delete({ sessionID: option.value, }) setToDelete(undefined) + + // Move to adjacent item after deletion + if (ref && currentIndex >= 0) { + setTimeout(() => { + // Try to stay at same index (which will be next item after deletion) + // Or go to previous if we were at the end + const newIndex = Math.min(currentIndex, ref.filtered.length - 1) + if (newIndex >= 0) { + ref.moveTo(newIndex, true) + } + }, 50) + } return } setToDelete(option.value) 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 151f73cf7c0a..1d0f3cbe6570 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -45,6 +45,7 @@ export interface DialogSelectOption { export type DialogSelectRef = { filter: string filtered: DialogSelectOption[] + moveTo: (index: number, center?: boolean) => void } export function DialogSelect(props: DialogSelectProps) { @@ -224,6 +225,7 @@ export function DialogSelect(props: DialogSelectProps) { get filtered() { return filtered() }, + moveTo, } props.ref?.(ref) From bf55d0e7309b510b4f3a2bcc950e89d4f76c2dcf Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 07:15:27 -0400 Subject: [PATCH 2/7] fix: use scrollToValue for post-delete session selection --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 5 +++-- packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index be5c4abc7b83..ef9a86abcfdf 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -247,8 +247,9 @@ export function DialogSessionList() { if (ref && currentIndex >= 0) { setTimeout(() => { const newIndex = Math.min(currentIndex, ref.filtered.length - 1) - if (newIndex >= 0) { - ref.moveTo(newIndex, true) + const adjacentOption = ref.filtered[newIndex] + if (adjacentOption) { + ref.scrollToValue(adjacentOption.value, true) } }, 50) } 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 3aa61deeb68a..b397776ed5bf 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -51,6 +51,7 @@ export type DialogSelectRef = { filter: string filtered: DialogSelectOption[] moveTo: (index: number, center?: boolean) => void + scrollToValue: (value: T, center?: boolean) => void } export function DialogSelect(props: DialogSelectProps) { @@ -234,6 +235,12 @@ export function DialogSelect(props: DialogSelectProps) { return filtered() }, moveTo, + scrollToValue(value: T, center?: boolean) { + const index = flat().findIndex((opt) => isDeepEqual(opt.value, value)) + if (index >= 0) { + moveTo(index, center) + } + }, } props.ref?.(ref) From 535a518c1cc37588e0ba538d622d64ad41c20ead Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 07:36:16 -0400 Subject: [PATCH 3/7] fix: prevent auto-scroll to current session after delete --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 4 ++++ packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index ef9a86abcfdf..31cac3186184 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -238,6 +238,7 @@ export function DialogSessionList() { setToDelete(undefined) return } + if (ref) ref.skipAutoScroll = true if (wsStatus && wsStatus !== "connected") { await sync.session.refresh() } @@ -253,6 +254,9 @@ export function DialogSessionList() { } }, 50) } + setTimeout(() => { + if (ref) ref.skipAutoScroll = false + }, 100) return } setToDelete(option.value) 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 b397776ed5bf..e878cafbe8a1 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 type DialogSelectRef = { filtered: DialogSelectOption[] moveTo: (index: number, center?: boolean) => void scrollToValue: (value: T, center?: boolean) => void + skipAutoScroll: boolean } export function DialogSelect(props: DialogSelectProps) { @@ -149,7 +150,7 @@ export function DialogSelect(props: DialogSelectProps) { setTimeout(() => { if (filter.length > 0) { moveTo(0, true) - } else if (current) { + } else if (current && !ref.skipAutoScroll) { const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current)) if (currentIndex >= 0) { moveTo(currentIndex, true) @@ -228,6 +229,7 @@ export function DialogSelect(props: DialogSelectProps) { let scroll: ScrollBoxRenderable | undefined const ref: DialogSelectRef = { + skipAutoScroll: false, get filter() { return store.filter }, From 31410593e3c82dd02db7311106afaa78b7370e92 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 07:48:28 -0400 Subject: [PATCH 4/7] fix: suspend current session selection during delete refresh --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 31cac3186184..bb47587e23d6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -32,6 +32,7 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) const [selectRef, setSelectRef] = createSignal>() + const [skipCurrentSelection, setSkipCurrentSelection] = createSignal(false) const [searchResults, { refetch }] = createResource( @@ -185,7 +186,7 @@ export function DialogSessionList() { title="Sessions" options={options()} skipFilter={true} - current={currentSessionID()} + current={skipCurrentSelection() ? undefined : currentSessionID()} onFilter={setSearch} onMove={() => { setToDelete(undefined) @@ -239,6 +240,7 @@ export function DialogSessionList() { return } if (ref) ref.skipAutoScroll = true + setSkipCurrentSelection(true) if (wsStatus && wsStatus !== "connected") { await sync.session.refresh() } @@ -256,6 +258,7 @@ export function DialogSessionList() { } setTimeout(() => { if (ref) ref.skipAutoScroll = false + setSkipCurrentSelection(false) }, 100) return } From 7bb9810a91f27dfe4e3ffa4db9fac4d5dbe69deb Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 07:56:22 -0400 Subject: [PATCH 5/7] fix: preserve delete confirmation while selection stays put --- .../opencode/src/cli/cmd/tui/component/dialog-session-list.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index bb47587e23d6..bb2a3b295f31 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -188,7 +188,8 @@ export function DialogSessionList() { skipFilter={true} current={skipCurrentSelection() ? undefined : currentSessionID()} onFilter={setSearch} - onMove={() => { + onMove={(option) => { + if (toDelete() === option.value) return setToDelete(undefined) }} onSelect={(option) => { From 2275744c470ca049b63feff7953a19c927795875 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 08:12:34 -0400 Subject: [PATCH 6/7] fix: preserve adjacent session when deleting from list --- .../cli/cmd/tui/component/dialog-session-list.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index bb2a3b295f31..644307670426 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -207,6 +207,11 @@ export function DialogSessionList() { if (toDelete() === option.value) { const ref = selectRef() const currentIndex = ref?.filtered.findIndex((opt) => opt.value === option.value) ?? -1 + const adjacentID = + currentIndex < 0 + ? undefined + : ref?.filtered[Math.min(currentIndex + 1, ref.filtered.length - 1)]?.value ?? + ref?.filtered[currentIndex - 1]?.value const session = sessions().find((item) => item.id === option.value) const wsStatus = session?.workspaceID ? project.workspace.status(session.workspaceID) : undefined @@ -248,13 +253,9 @@ export function DialogSessionList() { if (search()) await refetch() setToDelete(undefined) - if (ref && currentIndex >= 0) { + if (ref && adjacentID) { setTimeout(() => { - const newIndex = Math.min(currentIndex, ref.filtered.length - 1) - const adjacentOption = ref.filtered[newIndex] - if (adjacentOption) { - ref.scrollToValue(adjacentOption.value, true) - } + ref.scrollToValue(adjacentID, true) }, 50) } setTimeout(() => { From 6c066b1268a11c7134a4fdc2506e7966606b85ed Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 6 May 2026 08:58:32 -0400 Subject: [PATCH 7/7] fix: prefer previous session when deleting last list item --- .../cli/cmd/tui/component/dialog-session-list.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 644307670426..e7521dc6c47f 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -32,9 +32,6 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) const [selectRef, setSelectRef] = createSignal>() - const [skipCurrentSelection, setSkipCurrentSelection] = createSignal(false) - - const [searchResults, { refetch }] = createResource( () => ({ query: search(), filter: sync.session.query() }), async (input) => { @@ -186,7 +183,7 @@ export function DialogSessionList() { title="Sessions" options={options()} skipFilter={true} - current={skipCurrentSelection() ? undefined : currentSessionID()} + current={currentSessionID()} onFilter={setSearch} onMove={(option) => { if (toDelete() === option.value) return @@ -210,8 +207,9 @@ export function DialogSessionList() { const adjacentID = currentIndex < 0 ? undefined - : ref?.filtered[Math.min(currentIndex + 1, ref.filtered.length - 1)]?.value ?? - ref?.filtered[currentIndex - 1]?.value + : currentIndex < (ref?.filtered.length ?? 0) - 1 + ? ref?.filtered[currentIndex + 1]?.value + : ref?.filtered[currentIndex - 1]?.value const session = sessions().find((item) => item.id === option.value) const wsStatus = session?.workspaceID ? project.workspace.status(session.workspaceID) : undefined @@ -246,7 +244,6 @@ export function DialogSessionList() { return } if (ref) ref.skipAutoScroll = true - setSkipCurrentSelection(true) if (wsStatus && wsStatus !== "connected") { await sync.session.refresh() } @@ -260,7 +257,6 @@ export function DialogSessionList() { } setTimeout(() => { if (ref) ref.skipAutoScroll = false - setSkipCurrentSelection(false) }, 100) return }