summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 09:56:21 -0600
committerAdam <[email protected]>2025-12-29 09:56:33 -0600
commitfb0e1e4d8d3adc54b382c9daa5cdae7435d82e35 (patch)
treeb7ea9c0c05a5f323990a8ee16853f675bfa8486b
parentb745b1593f3ac25302ab7e8fc375f5fbe956e403 (diff)
downloadopencode-fb0e1e4d8d3adc54b382c9daa5cdae7435d82e35.tar.gz
opencode-fb0e1e4d8d3adc54b382c9daa5cdae7435d82e35.zip
Revert "fix(desktop): jankiness"
This reverts commit 831e9bce51c035ec22ce9562bf0257d6b59b2fe4.
-rw-r--r--packages/app/src/app.tsx2
-rw-r--r--packages/app/src/context/global-sync.tsx4
-rw-r--r--packages/app/src/context/sync.tsx4
-rw-r--r--packages/app/src/pages/session.tsx20
-rw-r--r--packages/ui/src/components/list.tsx55
-rw-r--r--packages/ui/src/components/message-part.tsx11
-rw-r--r--packages/ui/src/components/session-turn.tsx23
-rw-r--r--packages/ui/src/hooks/use-filtered-list.tsx10
8 files changed, 60 insertions, 69 deletions
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index 9e38d5e98..bf5ba9566 100644
--- a/packages/app/src/app.tsx
+++ b/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 />
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index a6851aecb..e1dcf15fc 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/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))
diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx
index 05156613a..dad035fb3 100644
--- a/packages/app/src/context/sync.tsx
+++ b/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),
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index a6dc804d1..6bc39daca 100644
--- a/packages/app/src/pages/session.tsx
+++ b/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)
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index a549c19e9..808c9b032 100644
--- a/packages/ui/src/components/list.tsx
+++ b/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}>
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index ea865d7ce..e43ffc322 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/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)
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 0a1ee5c4f..a3ac5b0b4 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/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[]
diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx
index 416f030ef..76a5ae84f 100644
--- a/packages/ui/src/hooks/use-filtered-list.tsx
+++ b/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,
})