fix(ui): eliminate N+1 reactive subscriptions in SessionTurn (#18924)

pull/18859/merge
Burak Yigit Kaya 2026-03-24 14:02:22 +00:00 committed by GitHub
parent 3f1a4abe6d
commit c9c93eac00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 26 deletions

View File

@ -923,7 +923,15 @@ export function MessageTimeline(props: {
{(messageID) => { {(messageID) => {
const active = createMemo(() => activeMessageID() === messageID) const active = createMemo(() => activeMessageID() === messageID)
const comments = createMemo(() => messageComments(sync.data.part[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) const commentCount = createMemo(() => comments().length)
return ( return (
@ -979,6 +987,7 @@ export function MessageTimeline(props: {
<SessionTurn <SessionTurn
sessionID={sessionID() ?? ""} sessionID={sessionID() ?? ""}
messageID={messageID} messageID={messageID}
messages={sessionMessages()}
actions={props.actions} actions={props.actions}
active={active()} active={active()}
status={active() ? sessionStatus() : undefined} status={active() ? sessionStatus() : undefined}

View File

@ -142,6 +142,7 @@ export function SessionTurn(
props: ParentProps<{ props: ParentProps<{
sessionID: string sessionID: string
messageID: string messageID: string
messages?: MessageType[]
actions?: UserActions actions?: UserActions
showReasoningSummaries?: boolean showReasoningSummaries?: boolean
shellToolDefaultOpen?: boolean shellToolDefaultOpen?: boolean
@ -166,7 +167,7 @@ export function SessionTurn(
const emptyDiffs: FileDiff[] = [] const emptyDiffs: FileDiff[] = []
const idle = { type: "idle" as const } 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 messageIndex = createMemo(() => {
const messages = allMessages() ?? emptyMessages const messages = allMessages() ?? emptyMessages
@ -340,30 +341,28 @@ export function SessionTurn(
if (end < start) return undefined if (end < start) return undefined
return end - start return end - start
}) })
const assistantVisible = createMemo(() => const assistantDerived = createMemo(() => {
assistantMessages().reduce((count, message) => { let visible = 0
const parts = list(data.store.part?.[message.id], emptyParts) let tail: "text" | "other" | undefined
return count + parts.filter((part) => partState(part, showReasoningSummaries()) === "visible").length let reason: string | undefined
}, 0), const show = showReasoningSummaries()
) for (const message of assistantMessages()) {
const assistantTailVisible = createMemo(() => for (const part of list(data.store.part?.[message.id], emptyParts)) {
assistantMessages() if (partState(part, show) === "visible") {
.flatMap((message) => list(data.store.part?.[message.id], emptyParts)) visible++
.flatMap((part) => { tail = part.type === "text" ? "text" : "other"
if (partState(part, showReasoningSummaries()) !== "visible") return [] }
if (part.type === "text") return ["text" as const] if (part.type === "reasoning" && part.text) {
return ["other" as const] const h = heading(part.text)
}) if (h) reason = h
.at(-1), }
) }
const reasoningHeading = createMemo(() => }
assistantMessages() return { visible, tail, reason }
.flatMap((message) => list(data.store.part?.[message.id], emptyParts)) })
.filter((part): part is PartType & { type: "reasoning"; text: string } => part.type === "reasoning") const assistantVisible = createMemo(() => assistantDerived().visible)
.map((part) => heading(part.text)) const assistantTailVisible = createMemo(() => assistantDerived().tail)
.filter((text): text is string => !!text) const reasoningHeading = createMemo(() => assistantDerived().reason)
.at(-1),
)
const showThinking = createMemo(() => { const showThinking = createMemo(() => {
if (!working() || !!error()) return false if (!working() || !!error()) return false
if (status().type === "retry") return false if (status().type === "retry") return false