Skip to content

Commit f0542fa

Browse files
committed
fix(app): optimistic revert/restore
1 parent 02c7582 commit f0542fa

4 files changed

Lines changed: 99 additions & 29 deletions

File tree

packages/app/src/pages/session.tsx

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
22
import { useDialog } from "@opencode-ai/ui/context/dialog"
33
import {
4+
batch,
45
onCleanup,
56
Show,
67
Match,
@@ -291,6 +292,7 @@ export default function Page() {
291292
git: false,
292293
pendingMessage: undefined as string | undefined,
293294
restoring: undefined as string | undefined,
295+
reverting: false,
294296
reviewSnap: false,
295297
scrollGesture: 0,
296298
scroll: {
@@ -1243,6 +1245,24 @@ export default function Page() {
12431245
})
12441246
}
12451247

1248+
const merge = (next: NonNullable<ReturnType<typeof info>>) =>
1249+
sync.set("session", (list) => {
1250+
const idx = list.findIndex((item) => item.id === next.id)
1251+
if (idx < 0) return list
1252+
const out = list.slice()
1253+
out[idx] = next
1254+
return out
1255+
})
1256+
1257+
const roll = (sessionID: string, next: NonNullable<ReturnType<typeof info>>["revert"]) =>
1258+
sync.set("session", (list) => {
1259+
const idx = list.findIndex((item) => item.id === sessionID)
1260+
if (idx < 0) return list
1261+
const out = list.slice()
1262+
out[idx] = { ...out[idx], revert: next }
1263+
return out
1264+
})
1265+
12461266
const busy = (sessionID: string) => {
12471267
if (sync.data.session_status[sessionID]?.type !== "idle") return true
12481268
return (sync.data.message[sessionID] ?? []).some(
@@ -1275,42 +1295,77 @@ export default function Page() {
12751295
}
12761296

12771297
const revert = (input: { sessionID: string; messageID: string }) => {
1298+
if (ui.reverting || ui.restoring) return
1299+
const prev = prompt.current().slice()
1300+
const last = info()?.revert
12781301
const value = draft(input.messageID)
1302+
batch(() => {
1303+
setUi("reverting", true)
1304+
roll(input.sessionID, { messageID: input.messageID })
1305+
prompt.set(value)
1306+
})
12791307
return halt(input.sessionID)
12801308
.then(() => sdk.client.session.revert(input))
1281-
.then(() => {
1282-
prompt.set(value)
1309+
.then((result) => {
1310+
if (result.data) merge(result.data)
1311+
})
1312+
.catch((err) => {
1313+
batch(() => {
1314+
roll(input.sessionID, last)
1315+
prompt.set(prev)
1316+
})
1317+
fail(err)
1318+
})
1319+
.finally(() => {
1320+
setUi("reverting", false)
12831321
})
1284-
.catch(fail)
12851322
}
12861323

12871324
const restore = (id: string) => {
12881325
const sessionID = params.id
1289-
if (!sessionID || ui.restoring) return
1326+
if (!sessionID || ui.restoring || ui.reverting) return
12901327

12911328
const next = userMessages().find((item) => item.id > id)
1292-
setUi("restoring", id)
1329+
const prev = prompt.current().slice()
1330+
const last = info()?.revert
1331+
1332+
batch(() => {
1333+
setUi("restoring", id)
1334+
setUi("reverting", true)
1335+
roll(sessionID, next ? { messageID: next.id } : undefined)
1336+
if (next) {
1337+
prompt.set(draft(next.id))
1338+
return
1339+
}
1340+
prompt.reset()
1341+
})
12931342

12941343
const task = !next
1295-
? halt(sessionID)
1296-
.then(() => sdk.client.session.unrevert({ sessionID }))
1297-
.then(() => {
1298-
prompt.reset()
1299-
})
1300-
: halt(sessionID)
1301-
.then(() =>
1302-
sdk.client.session.revert({
1303-
sessionID,
1304-
messageID: next.id,
1305-
}),
1306-
)
1307-
.then(() => {
1308-
prompt.set(draft(next.id))
1309-
})
1310-
1311-
return task.catch(fail).finally(() => {
1312-
setUi("restoring", (value) => (value === id ? undefined : value))
1313-
})
1344+
? halt(sessionID).then(() => sdk.client.session.unrevert({ sessionID }))
1345+
: halt(sessionID).then(() =>
1346+
sdk.client.session.revert({
1347+
sessionID,
1348+
messageID: next.id,
1349+
}),
1350+
)
1351+
1352+
return task
1353+
.then((result) => {
1354+
if (result.data) merge(result.data)
1355+
})
1356+
.catch((err) => {
1357+
batch(() => {
1358+
roll(sessionID, last)
1359+
prompt.set(prev)
1360+
})
1361+
fail(err)
1362+
})
1363+
.finally(() => {
1364+
batch(() => {
1365+
setUi("restoring", (value) => (value === id ? undefined : value))
1366+
setUi("reverting", false)
1367+
})
1368+
})
13141369
}
13151370

13161371
const rolled = createMemo(() => {
@@ -1487,6 +1542,7 @@ export default function Page() {
14871542
? {
14881543
items: rolled(),
14891544
restoring: ui.restoring,
1545+
disabled: ui.reverting,
14901546
onRestore: restore,
14911547
}
14921548
: undefined

packages/app/src/pages/session/composer/session-composer-region.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function SessionComposerRegion(props: {
2424
revert?: {
2525
items: { id: string; text: string }[]
2626
restoring?: string
27+
disabled?: boolean
2728
onRestore: (id: string) => void
2829
}
2930
setPromptDockRef: (el: HTMLDivElement) => void
@@ -156,6 +157,7 @@ export function SessionComposerRegion(props: {
156157
<SessionRevertDock
157158
items={revert.items}
158159
restoring={revert.restoring}
160+
disabled={revert.disabled}
159161
onRestore={revert.onRestore}
160162
/>
161163
</div>
@@ -195,7 +197,12 @@ export function SessionComposerRegion(props: {
195197
"margin-top": `${-36 * value()}px`,
196198
}}
197199
>
198-
<SessionRevertDock items={revert.items} restoring={revert.restoring} onRestore={revert.onRestore} />
200+
<SessionRevertDock
201+
items={revert.items}
202+
restoring={revert.restoring}
203+
disabled={revert.disabled}
204+
onRestore={revert.onRestore}
205+
/>
199206
</div>
200207
)}
201208
</Show>

packages/app/src/pages/session/composer/session-revert-dock.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { For, Show, createMemo } from "solid-js"
1+
import { For, Show, createEffect, createMemo } from "solid-js"
22
import { createStore } from "solid-js/store"
33
import { Button } from "@opencode-ai/ui/button"
44
import { DockTray } from "@opencode-ai/ui/dock-surface"
@@ -8,11 +8,18 @@ import { useLanguage } from "@/context/language"
88
export function SessionRevertDock(props: {
99
items: { id: string; text: string }[]
1010
restoring?: string
11+
disabled?: boolean
1112
onRestore: (id: string) => void
1213
}) {
1314
const language = useLanguage()
1415
const [store, setStore] = createStore({
15-
collapsed: false,
16+
collapsed: true,
17+
})
18+
19+
createEffect(() => {
20+
props.items.length
21+
props.items[0]?.id
22+
setStore("collapsed", true)
1623
})
1724

1825
const toggle = () => setStore("collapsed", (value) => !value)
@@ -77,7 +84,7 @@ export function SessionRevertDock(props: {
7784
size="small"
7885
variant="secondary"
7986
class="shrink-0"
80-
disabled={!!props.restoring}
87+
disabled={props.disabled || !!props.restoring}
8188
onClick={() => props.onRestore(item.id)}
8289
>
8390
{language.t("session.revertDock.restore")}

packages/ui/src/i18n/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const dict: Record<string, string> = {
128128
"ui.message.copy": "Copy",
129129
"ui.message.copyMessage": "Copy message",
130130
"ui.message.forkMessage": "Fork to new session",
131-
"ui.message.revertMessage": "Reset to this point",
131+
"ui.message.revertMessage": "Revert message",
132132
"ui.message.copyResponse": "Copy response",
133133
"ui.message.copied": "Copied",
134134
"ui.message.interrupted": "Interrupted",

0 commit comments

Comments
 (0)