diff options
| author | adamelmore <[email protected]> | 2026-01-26 06:06:10 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-26 06:08:09 -0600 |
| commit | c87232d5dfee75d117f450181457ebf328c6e5c6 (patch) | |
| tree | e60105f57d4e9c67d56c1534fae398f0787d8557 | |
| parent | d03c5f6b3f0ef9996776e082788cbc0889a75800 (diff) | |
| download | opencode-c87232d5dfee75d117f450181457ebf328c6e5c6.tar.gz opencode-c87232d5dfee75d117f450181457ebf328c6e5c6.zip | |
perf(app): performance improvements
| -rw-r--r-- | packages/app/src/context/notification.tsx | 71 | ||||
| -rw-r--r-- | packages/app/src/pages/home.tsx | 11 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 59 |
3 files changed, 101 insertions, 40 deletions
diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 1c5953b3f..5e35f6ac0 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -1,5 +1,5 @@ import { createStore } from "solid-js/store" -import { createEffect, onCleanup } from "solid-js" +import { createEffect, createMemo, onCleanup } from "solid-js" import { useParams } from "@solidjs/router" import { createSimpleContext } from "@opencode-ai/ui/context" import { useGlobalSDK } from "./global-sdk" @@ -52,6 +52,15 @@ export const { use: useNotification, provider: NotificationProvider } = createSi const settings = useSettings() const language = useLanguage() + const empty: Notification[] = [] + + const currentDirectory = createMemo(() => { + if (!params.dir) return + return base64Decode(params.dir) + }) + + const currentSession = createMemo(() => params.id) + const [store, setStore, _, ready] = persisted( Persist.global("notification", ["notification.v1"]), createStore({ @@ -72,13 +81,59 @@ export const { use: useNotification, provider: NotificationProvider } = createSi setStore("list", (list) => pruneNotifications([...list, notification])) } + const index = createMemo(() => { + const sessionAll = new Map<string, Notification[]>() + const sessionUnseen = new Map<string, Notification[]>() + const projectAll = new Map<string, Notification[]>() + const projectUnseen = new Map<string, Notification[]>() + + for (const notification of store.list) { + const session = notification.session + if (session) { + const list = sessionAll.get(session) + if (list) list.push(notification) + else sessionAll.set(session, [notification]) + if (!notification.viewed) { + const unseen = sessionUnseen.get(session) + if (unseen) unseen.push(notification) + else sessionUnseen.set(session, [notification]) + } + } + + const directory = notification.directory + if (directory) { + const list = projectAll.get(directory) + if (list) list.push(notification) + else projectAll.set(directory, [notification]) + if (!notification.viewed) { + const unseen = projectUnseen.get(directory) + if (unseen) unseen.push(notification) + else projectUnseen.set(directory, [notification]) + } + } + } + + return { + session: { + all: sessionAll, + unseen: sessionUnseen, + }, + project: { + all: projectAll, + unseen: projectUnseen, + }, + } + }) + const unsub = globalSDK.event.listen((e) => { - const directory = e.name const event = e.details + if (event.type !== "session.idle" && event.type !== "session.error") return + + const directory = e.name const time = Date.now() - const activeDirectory = params.dir ? base64Decode(params.dir) : undefined - const activeSession = params.id const viewed = (sessionID?: string) => { + const activeDirectory = currentDirectory() + const activeSession = currentSession() if (!activeDirectory) return false if (!activeSession) return false if (!sessionID) return false @@ -148,10 +203,10 @@ export const { use: useNotification, provider: NotificationProvider } = createSi ready, session: { all(session: string) { - return store.list.filter((n) => n.session === session) + return index().session.all.get(session) ?? empty }, unseen(session: string) { - return store.list.filter((n) => n.session === session && !n.viewed) + return index().session.unseen.get(session) ?? empty }, markViewed(session: string) { setStore("list", (n) => n.session === session, "viewed", true) @@ -159,10 +214,10 @@ export const { use: useNotification, provider: NotificationProvider } = createSi }, project: { all(directory: string) { - return store.list.filter((n) => n.directory === directory) + return index().project.all.get(directory) ?? empty }, unseen(directory: string) { - return store.list.filter((n) => n.directory === directory && !n.viewed) + return index().project.unseen.get(directory) ?? empty }, markViewed(directory: string) { setStore("list", (n) => n.directory === directory, "viewed", true) diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 0c04c0767..10f7dac53 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -23,6 +23,11 @@ export default function Home() { const server = useServer() const language = useLanguage() const homedir = createMemo(() => sync.data.path.home) + const recent = createMemo(() => { + return sync.data.project + .toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created)) + .slice(0, 5) + }) function openProject(directory: string) { layout.projects.open(directory) @@ -84,11 +89,7 @@ export default function Home() { </Button> </div> <ul class="flex flex-col gap-2"> - <For - each={sync.data.project - .toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created)) - .slice(0, 5)} - > + <For each={recent()}> {(project) => ( <Button size="large" diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 485deae99..2702f119b 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -350,7 +350,7 @@ export default function Layout(props: ParentProps) { const props = e.details.properties if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return - const [store] = globalSync.child(directory) + const [store] = globalSync.child(directory, { bootstrap: false }) const session = store.session.find((s) => s.id === props.sessionID) const sessionKey = `${directory}:${props.sessionID}` @@ -419,7 +419,7 @@ export default function Layout(props: ParentProps) { toastBySession.delete(sessionKey) alertedAtBySession.delete(sessionKey) } - const [store] = globalSync.child(currentDir) + const [store] = globalSync.child(currentDir, { bootstrap: false }) const childSessions = store.session.filter((s) => s.parentID === currentSession) for (const child of childSessions) { const childKey = `${currentDir}:${child.id}` @@ -433,17 +433,18 @@ export default function Layout(props: ParentProps) { }) }) - function sortSessions(a: Session, b: Session) { - const now = Date.now() + function sortSessions(now: number) { const oneMinuteAgo = now - 60 * 1000 - const aUpdated = a.time.updated ?? a.time.created - const bUpdated = b.time.updated ?? b.time.created - const aRecent = aUpdated > oneMinuteAgo - const bRecent = bUpdated > oneMinuteAgo - if (aRecent && bRecent) return a.id.localeCompare(b.id) - if (aRecent && !bRecent) return -1 - if (!aRecent && bRecent) return 1 - return bUpdated - aUpdated + return (a: Session, b: Session) => { + const aUpdated = a.time.updated ?? a.time.created + const bUpdated = b.time.updated ?? b.time.created + const aRecent = aUpdated > oneMinuteAgo + const bRecent = bUpdated > oneMinuteAgo + if (aRecent && bRecent) return a.id.localeCompare(b.id) + if (aRecent && !bRecent) return -1 + if (!aRecent && bRecent) return 1 + return bUpdated - aUpdated + } } const [scrollSessionKey, setScrollSessionKey] = createSignal<string | undefined>(undefined) @@ -475,7 +476,7 @@ export default function Layout(props: ParentProps) { const direct = projects.find((p) => p.worktree === directory) if (direct) return direct - const [child] = globalSync.child(directory) + const [child] = globalSync.child(directory, { bootstrap: false }) const id = child.project if (!id) return @@ -596,6 +597,7 @@ export default function Layout(props: ParentProps) { const currentSessions = createMemo(() => { const project = currentProject() if (!project) return [] as Session[] + const compare = sortSessions(Date.now()) if (workspaceSetting()) { const dirs = workspaceIds(project) const activeDir = params.dir ? base64Decode(params.dir) : "" @@ -608,7 +610,7 @@ export default function Layout(props: ParentProps) { const dirSessions = dirStore.session .filter((session) => session.directory === dirStore.path.directory) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions) + .toSorted(compare) result.push(...dirSessions) } return result @@ -617,7 +619,7 @@ export default function Layout(props: ParentProps) { return projectStore.session .filter((session) => session.directory === projectStore.path.directory) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions) + .toSorted(compare) }) type PrefetchQueue = { @@ -659,7 +661,7 @@ export default function Layout(props: ParentProps) { } async function prefetchMessages(directory: string, sessionID: string, token: number) { - const [, setStore] = globalSync.child(directory) + const [, setStore] = globalSync.child(directory, { bootstrap: false }) return retry(() => globalSDK.client.session.messages({ directory, sessionID, limit: prefetchChunk })) .then((messages) => { @@ -717,7 +719,7 @@ export default function Layout(props: ParentProps) { const directory = session.directory if (!directory) return - const [store] = globalSync.child(directory) + const [store] = globalSync.child(directory, { bootstrap: false }) const cached = untrack(() => store.message[session.id] !== undefined) if (cached) return @@ -1817,7 +1819,7 @@ export default function Layout(props: ParentProps) { const directory = store.activeWorkspace if (!directory) return - const [workspaceStore] = globalSync.child(directory) + const [workspaceStore] = globalSync.child(directory, { bootstrap: false }) const kind = directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox") const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id) @@ -1843,7 +1845,7 @@ export default function Layout(props: ParentProps) { workspaceStore.session .filter((session) => session.directory === workspaceStore.path.directory) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions), + .toSorted(sortSessions(Date.now())), ) const local = createMemo(() => props.directory === props.project.worktree) const active = createMemo(() => { @@ -2048,7 +2050,7 @@ export default function Layout(props: ParentProps) { const [open, setOpen] = createSignal(false) const label = (directory: string) => { - const [data] = globalSync.child(directory) + const [data] = globalSync.child(directory, { bootstrap: false }) const kind = directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox") const name = workspaceLabel(directory, data.vcs?.branch, props.project.id) @@ -2056,20 +2058,23 @@ export default function Layout(props: ParentProps) { } const sessions = (directory: string) => { - const [data] = globalSync.child(directory) + const [data] = globalSync.child(directory, { bootstrap: false }) + const root = workspaceKey(directory) return data.session - .filter((session) => session.directory === data.path.directory) + .filter((session) => workspaceKey(session.directory) === root) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions) + .toSorted(sortSessions(Date.now())) .slice(0, 2) } const projectSessions = () => { - const [data] = globalSync.child(props.project.worktree) + const directory = props.project.worktree + const [data] = globalSync.child(directory, { bootstrap: false }) + const root = workspaceKey(directory) return data.session - .filter((session) => session.directory === data.path.directory) + .filter((session) => workspaceKey(session.directory) === root) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions) + .toSorted(sortSessions(Date.now())) .slice(0, 2) } @@ -2196,7 +2201,7 @@ export default function Layout(props: ParentProps) { workspaceStore.session .filter((session) => session.directory === workspaceStore.path.directory) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions), + .toSorted(sortSessions(Date.now())), ) const loading = createMemo(() => workspaceStore.status !== "complete" && sessions().length === 0) const hasMore = createMemo(() => workspaceStore.sessionTotal > workspaceStore.session.length) |
