Browse Source

perf(app): performance improvements

adamelmore 3 weeks ago
parent
commit
c87232d5df

+ 63 - 8
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)

+ 6 - 5
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"

+ 32 - 27
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)