diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index c05e7d479..ddb0aa975 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -8,6 +8,9 @@ import { Keybind } from "@opencode-ai/ui/keybind" import { writeClipboardViaBridge } from "@/components/prompt-input/clipboard-bridge" import { showToast } from "@/utils/toast" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { DialogFooter, DialogHeader, DialogTitleGroup, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2" +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { getFilename } from "@opencode-ai/core/util/path" import { batch, createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js" import { createStore } from "solid-js/store" @@ -785,6 +788,34 @@ function SessionChatsDropdown() { } } + // amicode#310: permanently delete an archived session with confirmation dialog. + const dialog = useDialog() + function deleteArchivedSession(session: Session) { + dialog.show(() => ( + { + const ctx = getServerCtx() + if (!ctx) return + try { + await (ctx.sdk.client.session.delete as Function)({ + sessionID: session.id, + directory: session.directory, + }) + setArchivedSessions((prev) => prev.filter((s) => s.id !== session.id)) + } catch (cause) { + showToast({ + title: language.t("session.delete.failed.title"), + description: String(cause), + }) + } + dialog.close() + }} + onCancel={() => dialog.close()} + /> + )) + } + async function openSession(session: Session) { // Close flyout first so its Portal unmounts cleanly. setOpen(false) @@ -838,6 +869,8 @@ function SessionChatsDropdown() { const target = e.target as Node if (flyoutRoot?.contains(target)) return if (triggerRef?.contains(target)) return + // Don't dismiss if the click landed inside a dialog (e.g. delete confirmation) + if (target instanceof Element && target.closest("[data-dialog-layer], [data-component='dialog-overlay']")) return setOpen(false) } const onKey = (e: KeyboardEvent) => { @@ -1006,6 +1039,7 @@ function SessionChatsDropdown() { session={session} onOpen={openSession} onUnarchive={unarchiveSession} + onDelete={deleteArchivedSession} /> )} @@ -1090,6 +1124,7 @@ function ArchivedSessionDropdownRow(props: { session: Session onOpen: (session: Session) => void onUnarchive: (session: Session) => void + onDelete: (session: Session) => void }) { const title = createMemo(() => sessionTitle(props.session.title) || props.session.id) @@ -1106,7 +1141,7 @@ function ArchivedSessionDropdownRow(props: { {title()} -
+
+ + } + aria-label="Delete permanently" + onClick={(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + void props.onDelete(props.session) + }} + /> +
) } + +// amicode#310: confirmation dialog for permanently deleting an archived session. +function DialogDeleteArchivedSession(props: { + session: Session + onConfirm: () => void + onCancel: () => void +}) { + const language = useLanguage() + const name = createMemo(() => sessionTitle(props.session.title) || props.session.id) + + return ( + + + + + + + {language.t("common.cancel")} + + + {language.t("session.delete.button")} + + + + ) +} diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 147f3150f..f3634edb7 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -34,6 +34,7 @@ import { Icon } from "@opencode-ai/ui/icon" import { usePlatform } from "@/context/platform" import { DateTime } from "luxon" import { useDialog } from "@opencode-ai/ui/context/dialog" +import { DialogFooter, DialogHeader, DialogTitleGroup, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2" import { useDirectoryPicker } from "@/components/directory-picker" import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server" import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2" @@ -419,6 +420,33 @@ function HomeDesign() { } } + // amicode#310: permanently delete an archived session with confirmation dialog. + function deleteArchivedSession(session: Session) { + dialog.show(() => ( + { + const ctx = focusedServerCtx() + if (!ctx) return + try { + await (ctx.sdk.client.session.delete as Function)({ + sessionID: session.id, + directory: session.directory, + }) + setArchivedSessions((prev) => prev.filter((s) => s.id !== session.id)) + } catch (cause) { + showToast({ + title: language.t("session.delete.failed.title"), + description: errorMessage(cause, language.t("common.requestFailed")), + }) + } + dialog.close() + }} + onCancel={() => dialog.close()} + /> + )) + } + // amicode#273 AC7: extend search results to include archived sessions with badge. const archivedSearchResults = createMemo(() => { const query = search().toLowerCase() @@ -837,7 +865,11 @@ function HomeDesign() { createEffect(() => { if (!sessionsOpen()) return const onDown = (e: MouseEvent) => { - if (flyoutRoot && !flyoutRoot.contains(e.target as Node)) setSessionsOpen(false) + const target = e.target as Node + if (flyoutRoot && flyoutRoot.contains(target)) return + // Don't dismiss if the click landed inside a dialog (e.g. delete confirmation) + if (target instanceof Element && target.closest("[data-dialog-layer], [data-component='dialog-overlay']")) return + setSessionsOpen(false) } const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setSessionsOpen(false) @@ -1247,6 +1279,7 @@ function HomeDesign() { session={session} openSession={openSession} onUnarchive={unarchiveSession} + onDelete={deleteArchivedSession} /> )} @@ -2122,10 +2155,12 @@ function HomeCardsSkeleton() { } // amicode#273 AC5: archived session row — click opens read-only, hover shows unarchive. +// amicode#310: added onDelete for permanent deletion. function ArchivedSessionRow(props: { session: Session openSession: (session: Session) => void onUnarchive: (session: Session) => void + onDelete: (session: Session) => void }) { const title = createMemo(() => sessionTitle(props.session.title) || props.session.id) @@ -2143,7 +2178,7 @@ function ArchivedSessionRow(props: {
+ + } + aria-label="Delete permanently" + onClick={(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + void props.onDelete(props.session) + }} + /> +
) } +// amicode#310: confirmation dialog for permanently deleting an archived session. +function DialogDeleteArchivedSession(props: { + session: Session + onConfirm: () => void + onCancel: () => void +}) { + const language = useLanguage() + const name = createMemo(() => sessionTitle(props.session.title) || props.session.id) + + return ( + + + + + + + {language.t("common.cancel")} + + + {language.t("session.delete.button")} + + + + ) +} + diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx index 39ef8ea1c..f8ed14614 100644 --- a/packages/ui/src/context/dialog.tsx +++ b/packages/ui/src/context/dialog.tsx @@ -77,7 +77,7 @@ function init() { const mount = (element: DialogElement, owner: Owner, onClose: (() => void) | undefined, layer: number) => { const id = Math.random().toString(36).slice(2) - const zIndex = 50 + layer * 10 + const zIndex = 10000 + layer * 10 let dispose: (() => void) | undefined let setClosing: ((closing: boolean) => void) | undefined diff --git a/packages/ui/src/v2/components/tooltip-v2.css b/packages/ui/src/v2/components/tooltip-v2.css index 315d1f7cd..3461bad39 100644 --- a/packages/ui/src/v2/components/tooltip-v2.css +++ b/packages/ui/src/v2/components/tooltip-v2.css @@ -3,11 +3,11 @@ } [data-popper-positioner]:has([data-component="tooltip-v2"]) { - z-index: 1000; + z-index: 10000; } [data-component="tooltip-v2"] { - z-index: 1000; + z-index: 10000; box-sizing: border-box; display: inline-flex; flex-direction: row;