diff options
| author | Adam <[email protected]> | 2025-12-27 20:40:25 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-27 20:40:25 -0600 |
| commit | 1b5bf32ce560f44dd558f3951121c3f4b0560e85 (patch) | |
| tree | 14a0f32dacd9deaf6bdd57bf5ccf03d6a9b8747e | |
| parent | 2e972b3fdc4b7f3ccb2224f693391a8f11572c3c (diff) | |
| download | opencode-1b5bf32ce560f44dd558f3951121c3f4b0560e85.tar.gz opencode-1b5bf32ce560f44dd558f3951121c3f4b0560e85.zip | |
chore: permissions ux
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 38 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 6 | ||||
| -rw-r--r-- | packages/ui/src/components/toast.tsx | 5 | ||||
| -rw-r--r-- | packages/ui/src/hooks/create-auto-scroll.tsx | 17 |
4 files changed, 56 insertions, 10 deletions
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index bd368bb6c..0b4e040b7 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -41,7 +41,7 @@ import { } from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd" import { useProviders } from "@/hooks/use-providers" -import { showToast, Toast } from "@opencode-ai/ui/toast" +import { showToast, Toast, toaster } from "@opencode-ai/ui/toast" import { useGlobalSDK } from "@/context/global-sdk" import { useNotification } from "@/context/notification" import { Binary } from "@opencode-ai/util/binary" @@ -118,13 +118,15 @@ export default function Layout(props: ParentProps) { }) onMount(() => { - const seenPermissions = new Set<string>() + const seenSessions = new Set<string>() + const toastBySession = new Map<string, number>() const unsub = globalSDK.event.listen((e) => { if (e.details?.type !== "permission.updated") return const directory = e.name const permission = e.details.properties - if (seenPermissions.has(permission.id)) return - seenPermissions.add(permission.id) + const sessionKey = `${directory}:${permission.sessionID}` + if (seenSessions.has(sessionKey)) return + seenSessions.add(sessionKey) const currentDir = params.dir ? base64Decode(params.dir) : undefined const currentSession = params.id if (directory === currentDir && permission.sessionID === currentSession) return @@ -133,7 +135,7 @@ export default function Layout(props: ParentProps) { if (directory === currentDir && session?.parentID === currentSession) return const sessionTitle = session?.title ?? "New session" const projectName = getFilename(directory) - showToast({ + const toastId = showToast({ persistent: true, icon: "checklist", title: "Permission required", @@ -144,7 +146,6 @@ export default function Layout(props: ParentProps) { onClick: () => { navigate(`/${base64Encode(directory)}/session/${permission.sessionID}`) }, - dismissAfter: true, }, { label: "Dismiss", @@ -152,8 +153,33 @@ export default function Layout(props: ParentProps) { }, ], }) + toastBySession.set(sessionKey, toastId) }) onCleanup(unsub) + + createEffect(() => { + const currentDir = params.dir ? base64Decode(params.dir) : undefined + const currentSession = params.id + if (!currentDir || !currentSession) return + const sessionKey = `${currentDir}:${currentSession}` + const toastId = toastBySession.get(sessionKey) + if (toastId !== undefined) { + toaster.dismiss(toastId) + toastBySession.delete(sessionKey) + seenSessions.delete(sessionKey) + } + const [store] = globalSync.child(currentDir) + const childSessions = store.session.filter((s) => s.parentID === currentSession) + for (const child of childSessions) { + const childKey = `${currentDir}:${child.id}` + const childToastId = toastBySession.get(childKey) + if (childToastId !== undefined) { + toaster.dismiss(childToastId) + toastBySession.delete(childKey) + seenSessions.delete(childKey) + } + } + }) }) function sortSessions(a: Session, b: Session) { diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index ce4845a71..8cb426387 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -329,6 +329,12 @@ export function SessionTurn( }) createEffect(() => { + if (permissionParts().length > 0) { + autoScroll.forceScrollToBottom() + } + }) + + createEffect(() => { if (working() || !isLastUserMessage()) return const diffs = message()?.summary?.diffs diff --git a/packages/ui/src/components/toast.tsx b/packages/ui/src/components/toast.tsx index 7e90e9f2f..f34c46d42 100644 --- a/packages/ui/src/components/toast.tsx +++ b/packages/ui/src/components/toast.tsx @@ -92,7 +92,6 @@ export type ToastVariant = "default" | "success" | "error" | "loading" export interface ToastAction { label: string onClick: "dismiss" | (() => void) - dismissAfter?: boolean } export interface ToastOptions { @@ -132,10 +131,8 @@ export function showToast(options: ToastOptions | string) { onClick={() => { if (typeof action.onClick === "function") { action.onClick() - if (action.dismissAfter) toaster.dismiss(props.toastId) - } else { - toaster.dismiss(props.toastId) } + toaster.dismiss(props.toastId) }} > {action.label} diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx index e262b7c69..b780f47c6 100644 --- a/packages/ui/src/hooks/create-auto-scroll.tsx +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -35,6 +35,22 @@ export function createAutoScroll(options: AutoScrollOptions) { }) } + function forceScrollToBottom() { + if (!scrollRef) return + + setStore("userScrolled", false) + isAutoScrolling = true + if (autoScrollTimeout) clearTimeout(autoScrollTimeout) + autoScrollTimeout = setTimeout(() => { + isAutoScrolling = false + }, 1000) + + scrollRef.scrollTo({ + top: scrollRef.scrollHeight, + behavior: "smooth", + }) + } + function handleScroll() { if (!scrollRef) return @@ -161,6 +177,7 @@ export function createAutoScroll(options: AutoScrollOptions) { handleScroll, handleInteraction, scrollToBottom, + forceScrollToBottom, userScrolled: () => store.userScrolled, } } |
