perf(app): better session stream rendering

This commit is contained in:
adamelmore
2026-01-23 23:18:54 -06:00
committed by Adam
parent 04b511e1fe
commit da8f3e92a7
3 changed files with 47 additions and 22 deletions

View File

@@ -316,12 +316,22 @@ export default function Page() {
return sync.session.history.loading(id) return sync.session.history.loading(id)
}) })
const emptyUserMessages: UserMessage[] = [] const emptyUserMessages: UserMessage[] = []
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[], emptyUserMessages) const userMessages = createMemo(
const visibleUserMessages = createMemo(() => { () => messages().filter((m) => m.role === "user") as UserMessage[],
const revert = revertMessageID() emptyUserMessages,
if (!revert) return userMessages() { equals: same },
return userMessages().filter((m) => m.id < revert) )
}, emptyUserMessages) const visibleUserMessages = createMemo(
() => {
const revert = revertMessageID()
if (!revert) return userMessages()
return userMessages().filter((m) => m.id < revert)
},
emptyUserMessages,
{
equals: same,
},
)
const lastUserMessage = createMemo(() => visibleUserMessages().at(-1)) const lastUserMessage = createMemo(() => visibleUserMessages().at(-1))
createEffect( createEffect(
@@ -347,13 +357,19 @@ export default function Page() {
promptHeight: 0, promptHeight: 0,
}) })
const renderedUserMessages = createMemo(() => { const renderedUserMessages = createMemo(
const msgs = visibleUserMessages() () => {
const start = store.turnStart const msgs = visibleUserMessages()
if (start <= 0) return msgs const start = store.turnStart
if (start >= msgs.length) return emptyUserMessages if (start <= 0) return msgs
return msgs.slice(start) if (start >= msgs.length) return emptyUserMessages
}, emptyUserMessages) return msgs.slice(start)
},
emptyUserMessages,
{
equals: same,
},
)
const newSessionWorktree = createMemo(() => { const newSessionWorktree = createMemo(() => {
if (store.newSessionWorktree === "create") return "create" if (store.newSessionWorktree === "create") return "create"

View File

@@ -457,9 +457,16 @@ export function SessionTurn(
}) })
createEffect(() => { createEffect(() => {
const timer = setInterval(() => { const update = () => {
setStore("duration", duration()) setStore("duration", duration())
}, 1000) }
update()
// Only keep ticking while the active (in-progress) turn is running.
if (!working()) return
const timer = setInterval(update, 1000)
onCleanup(() => clearInterval(timer)) onCleanup(() => clearInterval(timer))
}) })
@@ -495,6 +502,11 @@ export function SessionTurn(
} }
}) })
onCleanup(() => {
if (!statusTimeout) return
clearTimeout(statusTimeout)
})
return ( return (
<div data-component="session-turn" class={props.classes?.root} ref={setRootRef}> <div data-component="session-turn" class={props.classes?.root} ref={setRootRef}>
<div <div

View File

@@ -15,7 +15,6 @@ export function createAutoScroll(options: AutoScrollOptions) {
let settleTimer: ReturnType<typeof setTimeout> | undefined let settleTimer: ReturnType<typeof setTimeout> | undefined
let autoTimer: ReturnType<typeof setTimeout> | undefined let autoTimer: ReturnType<typeof setTimeout> | undefined
let cleanup: (() => void) | undefined let cleanup: (() => void) | undefined
let resizeFrame: number | undefined
let auto: { top: number; time: number } | undefined let auto: { top: number; time: number } | undefined
const threshold = () => options.bottomThreshold ?? 10 const threshold = () => options.bottomThreshold ?? 10
@@ -152,11 +151,10 @@ export function createAutoScroll(options: AutoScrollOptions) {
() => { () => {
if (!active()) return if (!active()) return
if (store.userScrolled) return if (store.userScrolled) return
if (resizeFrame !== undefined) return // ResizeObserver fires after layout, before paint.
resizeFrame = requestAnimationFrame(() => { // Keep the bottom locked in the same frame to avoid visible
resizeFrame = undefined // "jump up then catch up" artifacts while streaming content.
scrollToBottom(false) scrollToBottom(false)
})
}, },
) )
@@ -190,7 +188,6 @@ export function createAutoScroll(options: AutoScrollOptions) {
onCleanup(() => { onCleanup(() => {
if (settleTimer) clearTimeout(settleTimer) if (settleTimer) clearTimeout(settleTimer)
if (autoTimer) clearTimeout(autoTimer) if (autoTimer) clearTimeout(autoTimer)
if (resizeFrame !== undefined) cancelAnimationFrame(resizeFrame)
if (cleanup) cleanup() if (cleanup) cleanup()
}) })