summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorBurak Yigit Kaya <[email protected]>2026-03-24 14:02:22 +0000
committerGitHub <[email protected]>2026-03-24 09:02:22 -0500
commitc9c93eac00bda356f4cf2b03e011d0b19e535952 (patch)
tree0ec10acf5730d9457a292cc8cc7c61d0af9f60ff /packages
parent3f1a4abe6dc72b4d24b916436d3dd95393aeb650 (diff)
downloadopencode-c9c93eac00bda356f4cf2b03e011d0b19e535952.tar.gz
opencode-c9c93eac00bda356f4cf2b03e011d0b19e535952.zip
fix(ui): eliminate N+1 reactive subscriptions in SessionTurn (#18924)
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/pages/session/message-timeline.tsx11
-rw-r--r--packages/ui/src/components/session-turn.tsx49
2 files changed, 34 insertions, 26 deletions
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index fe61f1685..5fef41a55 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -923,7 +923,15 @@ export function MessageTimeline(props: {
{(messageID) => {
const active = createMemo(() => activeMessageID() === messageID)
const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], {
- equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
+ equals: (a, b) =>
+ a.length === b.length &&
+ a.every(
+ (c, i) =>
+ c.path === b[i].path &&
+ c.comment === b[i].comment &&
+ c.selection?.startLine === b[i].selection?.startLine &&
+ c.selection?.endLine === b[i].selection?.endLine,
+ ),
})
const commentCount = createMemo(() => comments().length)
return (
@@ -979,6 +987,7 @@ export function MessageTimeline(props: {
<SessionTurn
sessionID={sessionID() ?? ""}
messageID={messageID}
+ messages={sessionMessages()}
actions={props.actions}
active={active()}
status={active() ? sessionStatus() : undefined}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 8c9c1ffe4..f7ba20af5 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -142,6 +142,7 @@ export function SessionTurn(
props: ParentProps<{
sessionID: string
messageID: string
+ messages?: MessageType[]
actions?: UserActions
showReasoningSummaries?: boolean
shellToolDefaultOpen?: boolean
@@ -166,7 +167,7 @@ export function SessionTurn(
const emptyDiffs: FileDiff[] = []
const idle = { type: "idle" as const }
- const allMessages = createMemo(() => list(data.store.message?.[props.sessionID], emptyMessages))
+ const allMessages = createMemo(() => props.messages ?? list(data.store.message?.[props.sessionID], emptyMessages))
const messageIndex = createMemo(() => {
const messages = allMessages() ?? emptyMessages
@@ -340,30 +341,28 @@ export function SessionTurn(
if (end < start) return undefined
return end - start
})
- const assistantVisible = createMemo(() =>
- assistantMessages().reduce((count, message) => {
- const parts = list(data.store.part?.[message.id], emptyParts)
- return count + parts.filter((part) => partState(part, showReasoningSummaries()) === "visible").length
- }, 0),
- )
- const assistantTailVisible = createMemo(() =>
- assistantMessages()
- .flatMap((message) => list(data.store.part?.[message.id], emptyParts))
- .flatMap((part) => {
- if (partState(part, showReasoningSummaries()) !== "visible") return []
- if (part.type === "text") return ["text" as const]
- return ["other" as const]
- })
- .at(-1),
- )
- const reasoningHeading = createMemo(() =>
- assistantMessages()
- .flatMap((message) => list(data.store.part?.[message.id], emptyParts))
- .filter((part): part is PartType & { type: "reasoning"; text: string } => part.type === "reasoning")
- .map((part) => heading(part.text))
- .filter((text): text is string => !!text)
- .at(-1),
- )
+ const assistantDerived = createMemo(() => {
+ let visible = 0
+ let tail: "text" | "other" | undefined
+ let reason: string | undefined
+ const show = showReasoningSummaries()
+ for (const message of assistantMessages()) {
+ for (const part of list(data.store.part?.[message.id], emptyParts)) {
+ if (partState(part, show) === "visible") {
+ visible++
+ tail = part.type === "text" ? "text" : "other"
+ }
+ if (part.type === "reasoning" && part.text) {
+ const h = heading(part.text)
+ if (h) reason = h
+ }
+ }
+ }
+ return { visible, tail, reason }
+ })
+ const assistantVisible = createMemo(() => assistantDerived().visible)
+ const assistantTailVisible = createMemo(() => assistantDerived().tail)
+ const reasoningHeading = createMemo(() => assistantDerived().reason)
const showThinking = createMemo(() => {
if (!working() || !!error()) return false
if (status().type === "retry") return false