fix(app): hydrate timeline message parents (#35269)

This commit is contained in:
Luke Parker
2026-07-05 10:49:46 +10:00
committed by GitHub
parent 1b9b260458
commit a12d50e15a
12 changed files with 984 additions and 42 deletions
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import type { AssistantMessage, Message, UserMessage } from "@opencode-ai/sdk/v2"
import { loadOlderTimeline, selectUserMessages, selectVisibleUserMessages } from "./model"
import { isTimelineReady, loadOlderTimeline, selectUserMessages, selectVisibleUserMessages } from "./model"
const user = (id: string) => ({ id, role: "user" }) as UserMessage
const assistant = (id: string) => ({ id, role: "assistant" }) as AssistantMessage
@@ -15,6 +15,12 @@ describe("timeline model", () => {
expect(selectVisibleUserMessages(users)).toBe(users)
})
test("waits for an assistant-only load to hydrate its user root", () => {
expect(isTimelineReady([assistant("msg_2")], true)).toBe(false)
expect(isTimelineReady([user("msg_1"), assistant("msg_2")], true)).toBe(true)
expect(isTimelineReady([], false)).toBe(true)
})
test("loads exactly one opaque cursor page", async () => {
let calls = 0
const anchors: Array<string | boolean> = []
@@ -45,7 +45,7 @@ export function createTimelineModel(input: {
})
const ready = createMemo(() => {
const id = input.sessionID()
return !id || sync().data.message[id] !== undefined
return !id || isTimelineReady(sync().data.message[id], serverSync().session.history.loading(id))
})
const userMessages = createMemo(() => selectUserMessages(messages()), emptyUserMessages, { equals: same })
const visibleUserMessages = createMemo(
@@ -98,6 +98,10 @@ export function selectUserMessages(messages: Message[]) {
return messages.filter((message): message is UserMessage => message.role === "user")
}
export function isTimelineReady(messages: Message[] | undefined, loading: boolean) {
return messages !== undefined && (messages.some((message) => message.role === "user") || !loading)
}
export function selectVisibleUserMessages(messages: UserMessage[], revertMessageID?: string) {
if (!revertMessageID) return messages
return messages.filter((message) => message.id < revertMessageID)