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 17653af6b9a9..4cbf62c7d048 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 @@ -7,6 +7,7 @@ import { Locale } from "@/util/locale" import { useProject } from "@tui/context/project" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" +import { useKV } from "../context/kv" import { useLocal } from "../context/local" import { Flag } from "@opencode-ai/core/flag/flag" import { DialogSessionRename } from "./dialog-session-rename" @@ -27,7 +28,9 @@ export function DialogSessionList() { const { theme } = useTheme() const sdk = useSDK() const local = useLocal() + const kv = useKV() const toast = useToast() + const filterEnabled = createMemo(() => kv.get("session_directory_filter_enabled", true) as boolean) const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) const deleteHint = useCommandShortcut("session.delete") @@ -218,7 +221,7 @@ export function DialogSessionList() { return ( ) }, }, + { + command: "session.list.toggle_filter", + // Acts on dialog state, not a row, so it must work even when the + // filter has emptied the list (the case this feature exists for). + requireSelection: false, + get title() { + return filterEnabled() ? "show all" : "filter to dir" + }, + onTrigger: async () => { + kv.set("session_directory_filter_enabled", !filterEnabled()) + await sync.session.refresh() + }, + }, ]} footerHints={quickSwitchFooterHints()} /> diff --git a/packages/opencode/src/cli/cmd/tui/config/keybind.ts b/packages/opencode/src/cli/cmd/tui/config/keybind.ts index c03123aed1c0..019d80afed75 100644 --- a/packages/opencode/src/cli/cmd/tui/config/keybind.ts +++ b/packages/opencode/src/cli/cmd/tui/config/keybind.ts @@ -56,6 +56,7 @@ export const Definitions = { app_toggle_diffwrap: keybind("none", "Toggle diff wrapping"), app_toggle_paste_summary: keybind("none", "Toggle paste summary"), app_toggle_session_directory_filter: keybind("none", "Toggle session directory filtering"), + session_list_toggle_filter: keybind("f", "Toggle directory filter in session list"), command_list: keybind("ctrl+p", "List available commands"), help_show: keybind("none", "Open help dialog"), docs_open: keybind("none", "Open documentation"), @@ -255,6 +256,7 @@ export const CommandMap = { app_toggle_diffwrap: "app.toggle.diffwrap", app_toggle_paste_summary: "app.toggle.paste_summary", app_toggle_session_directory_filter: "app.toggle.session_directory_filter", + session_list_toggle_filter: "session.list.toggle_filter", command_list: "command.palette.show", help_show: "help.show", docs_open: "docs.open", 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 700735d38cbf..7ba6bbb13fda 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -36,6 +36,8 @@ export interface DialogSelectProps { title: string side?: "left" | "right" disabled?: boolean + /** When false, the action fires even with no row selected (e.g. an empty list). Defaults to requiring a selection. */ + requireSelection?: boolean onTrigger: (option: DialogSelectOption) => void }[] footerHints?: { @@ -304,8 +306,8 @@ export function DialogSelect(props: DialogSelectProps) { run() { setStore("input", "keyboard") const option = selected() - if (!option) return - item.onTrigger(option) + if (item.requireSelection !== false && !option) return + item.onTrigger(option as DialogSelectOption) }, })), ],