fix(app): timeline jank

This commit is contained in:
Adam
2026-03-03 05:35:15 -06:00
parent 18850c4f91
commit 5e8742f431
4 changed files with 114 additions and 69 deletions
+3 -1
View File
@@ -1091,7 +1091,9 @@ export default function Page() {
const el = scroller const el = scroller
const delta = next - dockHeight const delta = next - dockHeight
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 + Math.max(0, delta) : false const stick = el
? !autoScroll.userScrolled() || el.scrollHeight - el.clientHeight - el.scrollTop < 10 + Math.max(0, delta)
: false
dockHeight = next dockHeight = next
@@ -689,7 +689,9 @@ export function MessageTimeline(props: {
if (!item || active()) return false if (!item || active()) return false
return messageID > item.id return messageID > item.id
}) })
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),
})
const commentCount = createMemo(() => comments().length) const commentCount = createMemo(() => comments().length)
return ( return (
<div <div
+104 -66
View File
@@ -9,6 +9,7 @@ import {
Show, Show,
Switch, Switch,
onCleanup, onCleanup,
Index,
type JSX, type JSX,
} from "solid-js" } from "solid-js"
import stripAnsi from "strip-ansi" import stripAnsi from "strip-ansi"
@@ -458,50 +459,67 @@ export function AssistantParts(props: {
const last = createMemo(() => grouped().at(-1)?.key) const last = createMemo(() => grouped().at(-1)?.key)
return ( return (
<For each={grouped()}> <Index each={grouped()}>
{(entry) => { {(entryAccessor) => {
if (entry.type === "context") { const entryType = createMemo(() => entryAccessor().type)
const parts = createMemo(
() =>
entry.refs
.map((ref) => partByID(list(data.store.part?.[ref.messageID], emptyParts), ref.partID))
.filter((part): part is ToolPart => !!part && isContextGroupTool(part)),
emptyTools,
{ equals: same },
)
const busy = createMemo(() => props.working && last() === entry.key)
return (
<Show when={parts().length > 0}>
<ContextToolGroup parts={parts()} busy={busy()} />
</Show>
)
}
const message = createMemo(() => props.messages.find((item) => item.id === entry.ref.messageID))
const part = createMemo(() =>
partByID(list(data.store.part?.[entry.ref.messageID], emptyParts), entry.ref.partID),
)
return ( return (
<Show when={message()}> <Switch>
{(message) => ( <Match when={entryType() === "context"}>
<Show when={part()}> {(() => {
{(part) => ( const parts = createMemo(
<Part () => {
part={part()} const entry = entryAccessor() as { type: "context"; refs: PartRef[] }
message={message()} return entry.refs
showAssistantCopyPartID={props.showAssistantCopyPartID} .map((ref) => partByID(list(data.store.part?.[ref.messageID], emptyParts), ref.partID))
turnDurationMs={props.turnDurationMs} .filter((part): part is ToolPart => !!part && isContextGroupTool(part))
defaultOpen={partDefaultOpen(part(), props.shellToolDefaultOpen, props.editToolDefaultOpen)} },
/> emptyTools,
)} { equals: same },
</Show> )
)} const busy = createMemo(() => props.working && last() === entryAccessor().key)
</Show>
return (
<Show when={parts().length > 0}>
<ContextToolGroup parts={parts()} busy={busy()} />
</Show>
)
})()}
</Match>
<Match when={entryType() === "part"}>
{(() => {
const message = createMemo(() => {
const entry = entryAccessor() as { type: "part"; ref: PartRef }
return props.messages.find((item) => item.id === entry.ref.messageID)
})
const part = createMemo(() => {
const entry = entryAccessor() as { type: "part"; ref: PartRef }
return partByID(list(data.store.part?.[entry.ref.messageID], emptyParts), entry.ref.partID)
})
return (
<Show when={message()}>
{(msg) => (
<Show when={part()}>
{(p) => (
<Part
part={p()}
message={msg()}
showAssistantCopyPartID={props.showAssistantCopyPartID}
turnDurationMs={props.turnDurationMs}
defaultOpen={partDefaultOpen(p(), props.shellToolDefaultOpen, props.editToolDefaultOpen)}
/>
)}
</Show>
)}
</Show>
)
})()}
</Match>
</Switch>
) )
}} }}
</For> </Index>
) )
} }
@@ -632,36 +650,56 @@ export function AssistantMessageDisplay(props: {
) )
return ( return (
<For each={grouped()}> <Index each={grouped()}>
{(entry) => { {(entryAccessor) => {
if (entry.type === "context") { const entryType = createMemo(() => entryAccessor().type)
const parts = createMemo(
() =>
entry.refs
.map((ref) => partByID(props.parts, ref.partID))
.filter((part): part is ToolPart => !!part && isContextGroupTool(part)),
emptyTools,
{ equals: same },
)
return (
<Show when={parts().length > 0}>
<ContextToolGroup parts={parts()} />
</Show>
)
}
const part = createMemo(() => partByID(props.parts, entry.ref.partID))
return ( return (
<Show when={part()}> <Switch>
{(part) => ( <Match when={entryType() === "context"}>
<Part part={part()} message={props.message} showAssistantCopyPartID={props.showAssistantCopyPartID} /> {(() => {
)} const parts = createMemo(
</Show> () => {
const entry = entryAccessor() as { type: "context"; refs: PartRef[] }
return entry.refs
.map((ref) => partByID(props.parts, ref.partID))
.filter((part): part is ToolPart => !!part && isContextGroupTool(part))
},
emptyTools,
{ equals: same },
)
return (
<Show when={parts().length > 0}>
<ContextToolGroup parts={parts()} />
</Show>
)
})()}
</Match>
<Match when={entryType() === "part"}>
{(() => {
const part = createMemo(() => {
const entry = entryAccessor() as { type: "part"; ref: PartRef }
return partByID(props.parts, entry.ref.partID)
})
return (
<Show when={part()}>
{(p) => (
<Part
part={p()}
message={props.message}
showAssistantCopyPartID={props.showAssistantCopyPartID}
/>
)}
</Show>
)
})()}
</Match>
</Switch>
) )
}} }}
</For> </Index>
) )
} }
+4 -1
View File
@@ -142,7 +142,10 @@ export function createAutoScroll(options: AutoScrollOptions) {
const handleInteraction = () => { const handleInteraction = () => {
if (!active()) return if (!active()) return
stop() const selection = window.getSelection()
if (selection && selection.toString().length > 0) {
stop()
}
} }
const updateOverflowAnchor = (el: HTMLElement) => { const updateOverflowAnchor = (el: HTMLElement) => {