diff options
| author | Adam <[email protected]> | 2025-12-15 10:13:29 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-15 10:22:06 -0600 |
| commit | 8ce0966987a0de46d64307c02f729a290f2f3cf3 (patch) | |
| tree | 0491fa32dbf6ea602004a712ba88ddef7da10ec4 | |
| parent | 8cb26b606645e2d71826dc22acaf1f26a707f0c9 (diff) | |
| download | opencode-8ce0966987a0de46d64307c02f729a290f2f3cf3.tar.gz opencode-8ce0966987a0de46d64307c02f729a290f2f3cf3.zip | |
wip(desktop): progress
| -rw-r--r-- | packages/desktop/src/context/global-sync.tsx | 4 | ||||
| -rw-r--r-- | packages/desktop/src/context/notification.tsx | 21 | ||||
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 21 |
3 files changed, 38 insertions, 8 deletions
diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index bebce64d7..77640c090 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -100,7 +100,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple async function loadSessions(directory: string) { globalSDK.client.session.list({ directory }).then((x) => { - const oneHourAgo = Date.now() - 60 * 60 * 1000 + const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000 const nonArchived = (x.data ?? []) .slice() .filter((s) => !s.time.archived) @@ -109,7 +109,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple const sessions = nonArchived.filter((s, i) => { if (i < 5) return true const updated = new Date(s.time.updated).getTime() - return updated > oneHourAgo + return updated > fourHoursAgo }) const [, setStore] = child(directory) setStore("session", sessions) diff --git a/packages/desktop/src/context/notification.tsx b/packages/desktop/src/context/notification.tsx index 705551944..9843066ea 100644 --- a/packages/desktop/src/context/notification.tsx +++ b/packages/desktop/src/context/notification.tsx @@ -2,6 +2,8 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { makePersisted } from "@solid-primitives/storage" import { useGlobalSDK } from "./global-sdk" +import { useGlobalSync } from "./global-sync" +import { Binary } from "@opencode-ai/util/binary" import { EventSessionError } from "@opencode-ai/sdk/v2" import { makeAudioPlayer } from "@solid-primitives/audio" import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac" @@ -32,6 +34,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi const idlePlayer = makeAudioPlayer(idleSound) const errorPlayer = makeAudioPlayer(errorSound) const globalSDK = useGlobalSDK() + const globalSync = useGlobalSync() const [store, setStore] = makePersisted( createStore({ @@ -57,22 +60,32 @@ export const { use: useNotification, provider: NotificationProvider } = createSi } switch (event.type) { case "session.idle": { + const sessionID = event.properties.sessionID + const [syncStore] = globalSync.child(directory) + const match = Binary.search(syncStore.session, sessionID, (s) => s.id) + const isChild = match.found && syncStore.session[match.index].parentID + if (isChild) break idlePlayer.play() - const session = event.properties.sessionID setStore("list", store.list.length, { ...base, type: "turn-complete", - session, + session: sessionID, }) break } case "session.error": { + const sessionID = event.properties.sessionID + if (sessionID) { + const [syncStore] = globalSync.child(directory) + const match = Binary.search(syncStore.session, sessionID, (s) => s.id) + const isChild = match.found && syncStore.session[match.index].parentID + if (isChild) break + } errorPlayer.play() - const session = event.properties.sessionID ?? "global" setStore("list", store.list.length, { ...base, type: "error", - session, + session: sessionID ?? "global", error: "error" in event.properties ? event.properties.error : undefined, }) break diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 6632abe3a..d10eadea9 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -44,6 +44,16 @@ export default function Layout(props: ParentProps) { activeDraggable: undefined as string | undefined, }) + let scrollContainerRef: HTMLDivElement | undefined + + function scrollToSession(sessionId: string) { + if (!scrollContainerRef) return + const element = scrollContainerRef.querySelector(`[data-session-id="${sessionId}"]`) + if (element) { + element.scrollIntoView({ block: "center", behavior: "smooth" }) + } + } + const params = useParams() const globalSDK = useGlobalSDK() const globalSync = useGlobalSync() @@ -111,7 +121,9 @@ export default function Layout(props: ParentProps) { // If target is within bounds, navigate to that session if (targetIndex >= 0 && targetIndex < sessions.length) { - navigateToSession(sessions[targetIndex]) + const session = sessions[targetIndex] + navigateToSession(session) + queueMicrotask(() => scrollToSession(session.id)) return } @@ -130,6 +142,7 @@ export default function Layout(props: ParentProps) { // If going down (offset > 0), go to first session; if going up (offset < 0), go to last session const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1] navigate(`/${base64Encode(nextProject.worktree)}/session/${targetSession.id}`) + queueMicrotask(() => scrollToSession(targetSession.id)) } async function archiveSession(session: Session) { @@ -418,6 +431,7 @@ export default function Layout(props: ParentProps) { return ( <> <div + data-session-id={props.session.id} class="group/session relative w-full pr-2 py-1 rounded-md cursor-default transition-colors hover:bg-surface-raised-base-hover focus-within:bg-surface-raised-base-hover has-[.active]:bg-surface-raised-base-hover" style={{ "padding-left": `${16 + depth * 12}px` }} @@ -670,7 +684,10 @@ export default function Layout(props: ParentProps) { > <DragDropSensors /> <ConstrainDragXAxis /> - <div class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar"> + <div + ref={scrollContainerRef} + class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar" + > <SortableProvider ids={layout.projects.list().map((p) => p.worktree)}> <For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For> </SortableProvider> |
