فهرست منبع

Revert "fix(desktop): jankiness"

This reverts commit 831e9bce51c035ec22ce9562bf0257d6b59b2fe4.
Adam 1 ماه پیش
والد
کامیت
fb0e1e4d8d

+ 1 - 1
packages/app/src/app.tsx

@@ -69,7 +69,7 @@ export function App() {
                               <Route
                                 path="/session/:id?"
                                 component={(p) => (
-                                  <Show when={p.params.id ?? "new"} keyed>
+                                  <Show when={p.params.id || true} keyed>
                                     <TerminalProvider>
                                       <PromptProvider>
                                         <Session />

+ 2 - 2
packages/app/src/context/global-sync.tsx

@@ -124,7 +124,7 @@ function createGlobalSync() {
           const updated = new Date(s.time.updated).getTime()
           return updated > fourHoursAgo
         })
-        setStore("session", reconcile(sessions, { key: "id" }))
+        setStore("session", sessions)
       })
       .catch((err) => {
         console.error("Failed to load sessions", err)
@@ -263,7 +263,7 @@ function createGlobalSync() {
         setStore("session_diff", event.properties.sessionID, reconcile(event.properties.diff, { key: "file" }))
         break
       case "todo.updated":
-        setStore("todo", event.properties.sessionID, reconcile(event.properties.todos, { key: "id" }))
+        setStore("todo", event.properties.sessionID, reconcile(event.properties.todos))
         break
       case "session.status": {
         setStore("session_status", event.properties.sessionID, reconcile(event.properties.status))

+ 2 - 2
packages/app/src/context/sync.tsx

@@ -81,7 +81,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
               }),
             )
 
-            setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
+            setStore("todo", sessionID, reconcile(todo.data ?? []))
             setStore(
               "message",
               sessionID,
@@ -115,7 +115,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
               .slice()
               .sort((a, b) => a.id.localeCompare(b.id))
               .slice(0, store.limit)
-            setStore("session", reconcile(sessions, { key: "id" }))
+            setStore("session", sessions)
           })
         },
         more: createMemo(() => store.session.length >= store.limit),

+ 8 - 12
packages/app/src/pages/session.tsx

@@ -167,18 +167,14 @@ export default function Page() {
     ),
   )
 
-  createEffect(
-    on(
-      () => params.id,
-      (id) => {
-        const status = sync.data.session_status[id ?? ""] ?? { type: "idle" }
-        batch(() => {
-          setStore("userInteracted", false)
-          setStore("stepsExpanded", status.type !== "idle")
-        })
-      },
-    ),
-  )
+  createEffect(() => {
+    params.id
+    const status = sync.data.session_status[params.id ?? ""] ?? { type: "idle" }
+    batch(() => {
+      setStore("userInteracted", false)
+      setStore("stepsExpanded", status.type !== "idle")
+    })
+  })
 
   const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? { type: "idle" })
   const working = createMemo(() => status().type !== "idle" && activeMessage()?.id === lastUserMessage()?.id)

+ 26 - 29
packages/ui/src/components/list.tsx

@@ -1,5 +1,5 @@
 import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
-import { createEffect, createSignal, For, onCleanup, type JSX, on, Show } from "solid-js"
+import { createEffect, createSignal, For, type JSX, on, Show } from "solid-js"
 import { createStore } from "solid-js/store"
 import { Icon, type IconProps } from "./icon"
 import { IconButton } from "./icon-button"
@@ -116,33 +116,6 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
     setScrollRef,
   })
 
-  function GroupHeader(props: { category: string }): JSX.Element {
-    const [stuck, setStuck] = createSignal(false)
-    const [header, setHeader] = createSignal<HTMLDivElement | undefined>(undefined)
-
-    createEffect(() => {
-      const scroll = scrollRef()
-      const node = header()
-      if (!scroll || !node) return
-
-      const handler = () => {
-        const rect = node.getBoundingClientRect()
-        const scrollRect = scroll.getBoundingClientRect()
-        setStuck(rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
-      }
-
-      scroll.addEventListener("scroll", handler, { passive: true })
-      handler()
-      onCleanup(() => scroll.removeEventListener("scroll", handler))
-    })
-
-    return (
-      <div data-slot="list-header" data-stuck={stuck()} ref={setHeader}>
-        {props.category}
-      </div>
-    )
-  }
-
   return (
     <div data-component="list" classList={{ [props.class ?? ""]: !!props.class }}>
       <Show when={!!props.search}>
@@ -184,7 +157,31 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
             {(group) => (
               <div data-slot="list-group">
                 <Show when={group.category}>
-                  <GroupHeader category={group.category} />
+                  {(() => {
+                    const [stuck, setStuck] = createSignal(false)
+                    return (
+                      <div
+                        data-slot="list-header"
+                        data-stuck={stuck()}
+                        ref={(el) => {
+                          createEffect(() => {
+                            const scroll = scrollRef()
+                            if (!scroll) return
+                            const handler = () => {
+                              const rect = el.getBoundingClientRect()
+                              const scrollRect = scroll.getBoundingClientRect()
+                              setStuck(rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
+                            }
+                            scroll.addEventListener("scroll", handler, { passive: true })
+                            handler()
+                            return () => scroll.removeEventListener("scroll", handler)
+                          })
+                        }}
+                      >
+                        {group.category}
+                      </div>
+                    )
+                  })()}
                 </Show>
                 <div data-slot="list-items">
                   <For each={group.items}>

+ 10 - 1
packages/ui/src/components/message-part.tsx

@@ -364,7 +364,16 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
   const permission = createMemo(() => {
     const sessionID = props.message.sessionID
     const permissions = data.store.permission?.[sessionID] ?? []
-    return permissions.find((perm) => perm.callID === part.callID)
+    const next = permissions.reduce(
+      (result, perm) => {
+        if (!result) return perm
+        if (perm.id < result.id) return perm
+        return result
+      },
+      undefined as (typeof permissions)[number] | undefined,
+    )
+    if (!next) return undefined
+    return next.callID === part.callID ? next : undefined
   })
 
   const [forceOpen, setForceOpen] = createSignal(false)

+ 10 - 13
packages/ui/src/components/session-turn.tsx

@@ -111,23 +111,13 @@ export function SessionTurn(
 
   const allMessages = createMemo(() => data.store.message[props.sessionID] ?? [])
 
-  const messageIndex = createMemo(() => {
+  const message = createMemo(() => {
     const messages = allMessages()
     const result = Binary.search(messages, props.messageID, (m) => m.id)
-    if (!result.found) return -1
+    if (!result.found) return undefined
 
     const msg = messages[result.index]
-    if (msg.role !== "user") return -1
-
-    return result.index
-  })
-
-  const message = createMemo(() => {
-    const index = messageIndex()
-    if (index < 0) return undefined
-
-    const msg = allMessages()[index]
-    if (!msg || msg.role !== "user") return undefined
+    if (msg.role !== "user") return undefined
 
     return msg
   })
@@ -151,6 +141,13 @@ export function SessionTurn(
     return data.store.part[msg.id] ?? []
   })
 
+  const messageIndex = createMemo(() => {
+    const messages = allMessages()
+    const result = Binary.search(messages, props.messageID, (m) => m.id)
+    if (!result.found) return -1
+    return result.index
+  })
+
   const assistantMessages = createMemo(() => {
     const msg = message()
     if (!msg) return [] as AssistantMessage[]

+ 1 - 9
packages/ui/src/hooks/use-filtered-list.tsx

@@ -51,17 +51,9 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
     )
   })
 
-  function initialActive() {
-    if (props.current) return props.key(props.current)
-
-    const items = flat()
-    if (items.length === 0) return ""
-    return props.key(items[0])
-  }
-
   const list = createList({
     items: () => flat().map(props.key),
-    initialActive: initialActive(),
+    initialActive: props.current ? props.key(props.current) : props.key(flat()[0]),
     loop: true,
   })