summaryrefslogtreecommitdiffhomepage
path: root/packages/ui
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 10:42:08 -0600
committerAdam <[email protected]>2025-12-29 10:42:48 -0600
commit5f074edc3a2ca80db4fd51360304bc070279a218 (patch)
tree135342ae17241e33bc4d40e625b876fb8d3ad54c /packages/ui
parent56b5cdf88316af25a58215de899f90eea23db9b9 (diff)
downloadopencode-5f074edc3a2ca80db4fd51360304bc070279a218.tar.gz
opencode-5f074edc3a2ca80db4fd51360304bc070279a218.zip
fix(desktop): performance/jankiness
Diffstat (limited to 'packages/ui')
-rw-r--r--packages/ui/src/components/list.tsx55
-rw-r--r--packages/ui/src/components/message-part.tsx21
-rw-r--r--packages/ui/src/components/session-turn.tsx35
-rw-r--r--packages/ui/src/hooks/use-filtered-list.tsx10
4 files changed, 56 insertions, 65 deletions
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index 808c9b032..a549c19e9 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, type JSX, on, Show } from "solid-js"
+import { createEffect, createSignal, For, onCleanup, 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,6 +116,33 @@ 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}>
@@ -157,31 +184,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
{(group) => (
<div data-slot="list-group">
<Show when={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>
- )
- })()}
+ <GroupHeader category={group.category} />
</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 e43ffc322..6fee7cd26 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -364,16 +364,10 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
const permission = createMemo(() => {
const sessionID = props.message.sessionID
const permissions = data.store.permission?.[sessionID] ?? []
- 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,
- )
+ const next = permissions[0]
if (!next) return undefined
- return next.callID === part.callID ? next : undefined
+ if (next.callID !== part.callID) return undefined
+ return next
})
const [forceOpen, setForceOpen] = createSignal(false)
@@ -632,14 +626,7 @@ ToolRegistry.register({
const sessionId = childSessionId()
if (!sessionId) return undefined
const permissions = data.store.permission?.[sessionId] ?? []
- return permissions.reduce(
- (result, perm) => {
- if (!result) return perm
- if (perm.id < result.id) return perm
- return result
- },
- undefined as (typeof permissions)[number] | undefined,
- )
+ return permissions[0]
})
const childToolPart = createMemo(() => {
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index a3ac5b0b4..4a6b8b612 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -111,13 +111,23 @@ export function SessionTurn(
const allMessages = createMemo(() => data.store.message[props.sessionID] ?? [])
- const message = createMemo(() => {
+ const messageIndex = createMemo(() => {
const messages = allMessages()
const result = Binary.search(messages, props.messageID, (m) => m.id)
- if (!result.found) return undefined
+ if (!result.found) return -1
const msg = messages[result.index]
- if (msg.role !== "user") return undefined
+ 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
return msg
})
@@ -141,13 +151,6 @@ 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[]
@@ -195,17 +198,7 @@ export function SessionTurn(
const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? [])
const permissionCount = createMemo(() => permissions().length)
- const nextPermission = createMemo(() => {
- const items = permissions()
- return items.reduce(
- (result, perm) => {
- if (!result) return perm
- if (perm.id < result.id) return perm
- return result
- },
- undefined as ReturnType<typeof permissions>[number] | undefined,
- )
- })
+ const nextPermission = createMemo(() => permissions()[0])
const permissionParts = createMemo(() => {
if (props.stepsExpanded) return [] as { part: ToolPart; message: AssistantMessage }[]
diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx
index 76a5ae84f..416f030ef 100644
--- a/packages/ui/src/hooks/use-filtered-list.tsx
+++ b/packages/ui/src/hooks/use-filtered-list.tsx
@@ -51,9 +51,17 @@ 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: props.current ? props.key(props.current) : props.key(flat()[0]),
+ initialActive: initialActive(),
loop: true,
})