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 775969bfcb3..31c0c7f8d5e 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 @@ -2,7 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } 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" +import { createMemo, createSignal, createResource, onMount } from "solid-js" import { Locale } from "@/util/locale" import { useKeybind } from "../context/keybind" import { useTheme } from "../context/theme" @@ -11,6 +11,9 @@ import { DialogSessionRename } from "./dialog-session-rename" import { useKV } from "../context/kv" import { createDebouncedSignal } from "../util/signal" import { Spinner } from "./spinner" +import { Keybind } from "@/util/keybind" + +const EMPTY_FAVORITE = "__session_favorite_empty__" export function DialogSessionList() { const dialog = useDialog() @@ -23,6 +26,9 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) + const [favorite, setFavorite] = kv.signal("session_favorites", []) + const [favoriteOnly, setFavoriteOnly] = createSignal(false) + const favorites = createMemo(() => new Set(favorite())) const [searchResults] = createResource(search, async (query) => { if (!query) return undefined @@ -36,9 +42,10 @@ export function DialogSessionList() { const options = createMemo(() => { const today = new Date().toDateString() - return sessions() + const result = sessions() .filter((x) => x.parentID === undefined) .toSorted((a, b) => b.time.updated - a.time.updated) + .filter((x) => !favoriteOnly() || favorites().has(x.id)) .map((x) => { const date = new Date(x.time.updated) let category = date.toDateString() @@ -46,10 +53,13 @@ export function DialogSessionList() { category = "Today" } const isDeleting = toDelete() === x.id + const isFavorite = favorites().has(x.id) const status = sync.data.session_status?.[x.id] const isWorking = status?.type === "busy" return { - title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, + title: isDeleting + ? `Press ${keybind.print("session_delete")} again to confirm` + : `${isFavorite ? "★ " : ""}${x.title}`, bg: isDeleting ? theme.error : undefined, value: x.id, category, @@ -57,6 +67,15 @@ export function DialogSessionList() { gutter: isWorking ? : undefined, } }) + + if (!favoriteOnly() || result.length > 0) return result + return [ + { + title: "No favorite sessions", + value: EMPTY_FAVORITE, + footer: "Shift+Tab: Show all", + }, + ] }) onMount(() => { @@ -65,7 +84,7 @@ export function DialogSessionList() { return ( { + if (option.value === EMPTY_FAVORITE) return route.navigate({ type: "session", sessionID: option.value, @@ -81,10 +101,38 @@ export function DialogSessionList() { dialog.clear() }} keybind={[ + { + keybind: Keybind.parse("ctrl+f")[0], + title: "favorite", + onTrigger: (option) => { + if (option.value === EMPTY_FAVORITE) return + setFavorite((old) => { + const next: string[] = Array.isArray(old) ? old : [] + const result = next.includes(option.value) + ? next.filter((x) => x !== option.value) + : [option.value, ...next] + if (favoriteOnly() && result.length === 0) { + setFavoriteOnly(false) + } + return result + }) + setToDelete(undefined) + }, + }, + { + keybind: Keybind.parse("shift+tab")[0], + title: favoriteOnly() ? "show all" : "show favorites", + onTrigger: () => { + if (!favoriteOnly() && favorites().size === 0) return + setFavoriteOnly((x) => !x) + setToDelete(undefined) + }, + }, { keybind: keybind.all.session_delete?.[0], title: "delete", onTrigger: async (option) => { + if (option.value === EMPTY_FAVORITE) return if (toDelete() === option.value) { sdk.client.session.delete({ sessionID: option.value, @@ -99,6 +147,7 @@ export function DialogSessionList() { keybind: keybind.all.session_rename?.[0], title: "rename", onTrigger: async (option) => { + if (option.value === EMPTY_FAVORITE) return dialog.replace(() => ) }, },