Skip to content

perf: reduce long session streaming render cost#450

Open
hsteude wants to merge 3 commits into
mainfrom
fix/long-session-streaming-render-cost
Open

perf: reduce long session streaming render cost#450
hsteude wants to merge 3 commits into
mainfrom
fix/long-session-streaming-render-cost

Conversation

@hsteude

@hsteude hsteude commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • update streaming part deltas in place instead of recreating full message arrays
  • cache display message wrappers so long active sessions avoid rebuilding every message object per token
  • reuse timeline turn refs when only text changes and make visible turn content reactive

Verification

  • bun run typecheck
  • bun run build

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces UI rendering overhead during long, streaming sessions by minimizing allocations and avoiding rebuilding message/turn structures on every token update.

Changes:

  • Cache DisplayMessage wrappers in the session page so message objects aren’t recreated on each reactive update.
  • Apply streaming part deltas/updates in-place within the sync store to avoid rebuilding full arrays.
  • Make turn/timeline and per-message derived UI data more reactive while aiming to reuse stable references.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
app-prefixable/src/pages/session.tsx Adds DisplayMessage wrapper caching and cache eviction for non-visible messages.
app-prefixable/src/context/sync.tsx Switches part update/delta handling to in-place store mutations via produce().
app-prefixable/src/components/message-turn.tsx Uses memos for derived assistant message content (text/tools/meta/subtasks) to react to streaming updates.
app-prefixable/src/components/message-timeline.tsx Introduces structure comparison logic intended to reuse turn refs when only text changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +147 to 154
const turnRefState = createMemo<TurnRefState>((prev) => {
const messages = props.messages
const structure = messageStructure(messages)
if (prev && sameTimelineStructure(prev.structure, structure)) {
return { structure, refs: prev.refs }
}
return { structure, refs: messagesToTurnRefs(messages.filter(hasStructuredContent)) }
})
Comment on lines +277 to +280
get time() {
if (msg.info.role === "assistant") return { created: msg.info.time.created, completed: msg.info.time.completed };
return { created: msg.info.time.created };
},

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +143 to 150
const turnRefState = createMemo<TurnRefState>((prev) => {
const messages = props.messages
const structure = messageStructure(messages)
if (prev && sameTimelineStructure(prev.structure, structure)) {
return { structure, refs: prev.refs }
}
return { structure, refs: messagesToTurnRefs(messages.filter(hasStructuredContent)) }
})

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +23 to +28
type MessageStructure = {
id: string
role: DisplayMessage["role"]
error: boolean
partCount: number
}
Comment on lines +86 to +93
function messageStructure(messages: DisplayMessage[]) {
return messages.map((msg) => ({
id: msg.id,
role: msg.role,
error: !!msg.error,
partCount: msg.parts.length,
}))
}
Comment on lines +95 to 106
function sameTimelineStructure(prev: MessageStructure[], next: MessageStructure[]) {
if (prev.length !== next.length) return false

for (let i = 0; i < next.length; i++) {
const a = prev[i]
const b = next[i]
if (a.id !== b.id || a.role !== b.role || a.error !== b.error) return false
if (a.partCount !== b.partCount) return false
}

return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants