Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ export function SessionContextTab() {
() => {
const revert = info()?.revert?.messageID
if (!revert) return userMessages()
return userMessages().filter((m) => m.id < revert)
const boundary = userMessages().findIndex((message) => message.id === revert)
return boundary < 0 ? userMessages() : userMessages().slice(0, boundary)
},
emptyUserMessages,
{ equals: same },
Expand Down
8 changes: 6 additions & 2 deletions packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1851,7 +1851,9 @@ export default function Page() {

const session = sdk().api.session
const target = sync()
const next = userMessages().find((item) => item.id > id)
const index = userMessages().findIndex((item) => item.id === id)
if (index < 0) return
const next = userMessages()[index + 1]
const last = target.session.get(sessionID)?.revert

await runPromptRollbackMutation({
Expand Down Expand Up @@ -1891,8 +1893,10 @@ export default function Page() {
const rolled = createMemo(() => {
const id = revertMessageID()
if (!id) return []
const index = userMessages().findIndex((item) => item.id === id)
if (index < 0) return []
return userMessages()
.filter((item) => item.id >= id)
.slice(index)
.map((item) => ({ id: item.id, text: line(item.id) }))
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ export function MessageTimeline(props: {
const visible = new Set(props.userMessages.map((message) => message.id))
const boundary = sessionMessages().find((message) => message.role === "user" && !visible.has(message.id))?.id
const messages = sync().data.session_message[id] ?? []
return boundary ? messages.filter((message) => message.id < boundary) : messages
if (!boundary) return messages
const index = messages.findIndex((message) => message.id === boundary)
return index < 0 ? messages : messages.slice(0, index)
})
const info = createMemo(() => {
const id = sessionID()
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/pages/session/timeline/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ const assistant = (id: string) => ({ id, role: "assistant" }) as AssistantMessag

describe("timeline model", () => {
test("selects users and applies the revert boundary", () => {
const messages: Message[] = [user("msg_1"), assistant("msg_2"), user("msg_3"), user("msg_5")]
const messages: Message[] = [user("msg_z"), assistant("msg_a"), user("msg_b"), user("msg_c")]
const users = selectUserMessages(messages)

expect(users.map((message) => message.id)).toEqual(["msg_1", "msg_3", "msg_5"])
expect(selectVisibleUserMessages(users, "msg_5").map((message) => message.id)).toEqual(["msg_1", "msg_3"])
expect(users.map((message) => message.id)).toEqual(["msg_z", "msg_b", "msg_c"])
expect(selectVisibleUserMessages(users, "msg_b").map((message) => message.id)).toEqual(["msg_z"])
expect(selectVisibleUserMessages(users)).toBe(users)
})

Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/pages/session/timeline/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ export function isTimelineReady(messages: Message[] | undefined, loading: boolea

export function selectVisibleUserMessages(messages: UserMessage[], revertMessageID?: string) {
if (!revertMessageID) return messages
return messages.filter((message) => message.id < revertMessageID)
const boundary = messages.findIndex((message) => message.id === revertMessageID)
return boundary < 0 ? messages : messages.slice(0, boundary)
}

export async function loadOlderTimeline(input: {
Expand Down
12 changes: 6 additions & 6 deletions packages/app/src/pages/session/timeline/rows-current.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ describe("current session timeline rows", () => {

test("renders an optimistic user turn and thinking before the protocol message arrives", () => {
const source = [
{ id: "msg_1", type: "user", text: "existing", time: { created: 1 } },
{ id: "msg_z", type: "user", text: "existing", time: { created: 1 } },
] satisfies SessionMessageInfo[]
const normalized = normalizeSessionMessages("ses_1", source)
const optimistic = {
id: "msg_2",
id: "msg_a",
sessionID: "ses_1",
role: "user" as const,
time: { created: 2 },
Expand All @@ -161,10 +161,10 @@ describe("current session timeline rows", () => {

expect(result.activeMessageID).toBe(optimistic.id)
expect(result.rows.map(TimelineRow.key)).toEqual([
"user-message:msg_1",
"turn-gap:msg_2",
"user-message:msg_2",
"thinking:msg_2",
"user-message:msg_z",
"turn-gap:msg_a",
"user-message:msg_a",
"thinking:msg_a",
])
})

Expand Down
7 changes: 4 additions & 3 deletions packages/app/src/pages/session/timeline/rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AssistantMessage, Part, SessionStatus, UserMessage } from "@opencode-ai
import { groupParts, renderable, type PartGroup } from "@opencode-ai/session-ui/message-part"
import { TimelineRow, type SummaryDiff } from "./timeline-row"
import { uniqueSummaryDiffs } from "./summary-diffs"
import { compareMessages } from "@/utils/session-message"

export { TimelineRow, type SummaryDiff } from "./timeline-row"

Expand Down Expand Up @@ -71,12 +72,12 @@ export namespace Timeline {
turns.push(turn)
turnByUserID.set(user.id, turn)
})
const latestUserMessageID = turns.at(-1)?.user.id
projectedUserMessages.forEach((user) => {
if (turnByUserID.has(user.id)) return
if (latestUserMessageID && user.id < latestUserMessageID) return
const turn = { user, assistants: [] }
turns.push(turn)
const index = turns.findIndex((item) => compareMessages(user, item.user) < 0)
if (index < 0) turns.push(turn)
if (index >= 0) turns.splice(index, 0, turn)
turnByUserID.set(user.id, turn)
})
const activeMessageID = turns.at(-1)?.user.id
Expand Down
17 changes: 11 additions & 6 deletions packages/app/src/pages/session/use-session-commands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const visibleUserMessages = () => {
const revert = info()?.revert?.messageID
if (!revert) return userMessages()
return userMessages().filter((m) => m.id < revert)
const boundary = userMessages().findIndex((message) => message.id === revert)
return boundary < 0 ? userMessages() : userMessages().slice(0, boundary)
}

const showAllFiles = () => {
Expand Down Expand Up @@ -337,7 +338,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const promptSession = prompt.capture()
const revert = info()?.revert?.messageID
const messages = userMessages()
const message = findLast(messages, (x) => !revert || x.id < revert)
const boundary = revert ? messages.findIndex((message) => message.id === revert) : messages.length
if (boundary < 0) return
const message = messages[boundary - 1]
if (!message) return
const parts = sync().data.part[message.id]

Expand All @@ -352,7 +355,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
updatePrompt: (promptSession) => {
if (parts) promptSession.set(extractPromptFromParts(parts, { directory }))
},
updateViewport: () => setActiveMessage(findLast(messages, (x) => x.id < message.id)),
updateViewport: () => setActiveMessage(messages[boundary - 2]),
})
}

Expand All @@ -367,14 +370,16 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const revertMessageID = info()?.revert?.messageID
if (!revertMessageID) return

const next = messages.find((x) => x.id > revertMessageID)
const boundary = messages.findIndex((message) => message.id === revertMessageID)
if (boundary < 0) return
const next = messages[boundary + 1]
if (!next) {
await runCommand({
owner,
prompt: promptSession,
request: () => session.revert.clear({ sessionID }),
updatePrompt: (promptSession) => promptSession.reset(),
updateViewport: () => setActiveMessage(findLast(messages, (x) => x.id >= revertMessageID)),
updateViewport: () => setActiveMessage(messages.at(-1)),
})
return
}
Expand All @@ -384,7 +389,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
prompt: promptSession,
request: () => session.revert.stage({ sessionID, messageID: next.id }),
updatePrompt: () => undefined,
updateViewport: () => setActiveMessage(findLast(messages, (x) => x.id < next.id)),
updateViewport: () => setActiveMessage(messages[boundary]),
})
}

Expand Down
Loading