fix(tui): when diff viewer closes always return to last route (#28903)

This commit is contained in:
James Long 2026-05-22 15:30:31 -04:00 committed by GitHub
parent 8f7a6c4a00
commit bfb2d8dc76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 3 deletions

View file

@ -1,5 +1,5 @@
/** @jsxImportSource @opentui/solid */
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { TextAttributes, type BorderSides, type BoxRenderable, type ScrollBoxRenderable } from "@opentui/core"
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
@ -80,7 +80,12 @@ function DiffViewer(props: { api: TuiPluginApi }) {
const theme = () => props.api.theme.current
const params = () =>
("params" in props.api.route.current ? props.api.route.current.params : undefined) as
| { mode?: DiffMode; sessionID?: string; messageID?: string }
| {
mode?: DiffMode
sessionID?: string
messageID?: string
returnRoute?: TuiRouteCurrent
}
| undefined
const mode = () => params()?.mode ?? "git"
const diffInput = createMemo(() => ({
@ -369,8 +374,13 @@ function DiffViewer(props: { api: TuiPluginApi }) {
title: "Close diff viewer",
category: "VCS",
run() {
const returnRoute = params()?.returnRoute
props.api.ui.dialog.clear()
props.api.route.navigate("home")
props.api.route.navigate(
returnRoute?.name ?? "home",
returnRoute && "params" in returnRoute ? returnRoute.params : undefined,
)
},
},
{
@ -619,6 +629,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
mode: option.value,
sessionID: params()?.sessionID,
messageID: params()?.messageID,
returnRoute: params()?.returnRoute,
})
},
}))}
@ -933,6 +944,7 @@ const tui: TuiPlugin = async (api) => {
api.route.navigate(ROUTE, {
mode: "git",
sessionID: "params" in api.route.current ? api.route.current.params?.sessionID : undefined,
returnRoute: api.route.current,
})
api.ui.dialog.clear()
},

View file

@ -0,0 +1,106 @@
/** @jsxImportSource @opentui/solid */
import { expect, test } from "bun:test"
import path from "path"
import { mkdir } from "fs/promises"
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
import { testRender, useRenderer } from "@opentui/solid"
import { Global } from "@opencode-ai/core/global"
import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
import { KVProvider } from "../../../src/cli/cmd/tui/context/kv"
import { ThemeProvider } from "../../../src/cli/cmd/tui/context/theme"
import { TuiConfigProvider } from "../../../src/cli/cmd/tui/context/tui-config"
import { OpencodeKeymapProvider } from "../../../src/cli/cmd/tui/keymap"
import diffViewerPlugin from "../../../src/cli/cmd/tui/feature-plugins/system/diff-viewer"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
test("closing the diff viewer returns to the route it opened from", async () => {
const startRoute: TuiRouteCurrent = { name: "session", params: { sessionID: "session-1" } }
const commands = new Map<string, NonNullable<Parameters<TuiPluginApi["keymap"]["registerLayer"]>[0]["commands"]>[number]>()
let current = startRoute
let renderDiff: TuiRouteDefinition["render"] | undefined
await mkdir(Global.Path.state, { recursive: true })
await Bun.write(path.join(Global.Path.state, "kv.json"), "{}")
function Harness() {
const renderer = useRenderer()
const keymap = createDefaultOpenTuiKeymap(renderer)
const registerLayer = keymap.registerLayer.bind(keymap)
keymap.registerLayer = (layer) => {
layer.commands?.forEach((command) => commands.set(command.name, command))
return registerLayer(layer)
}
const base = createTuiPluginApi({
keymap,
client: {
vcs: { diff: async () => ({ data: [] }) },
session: { diff: async () => ({ data: [] }) },
} as unknown as TuiPluginApi["client"],
})
const api = {
...base,
route: {
register(routes) {
renderDiff = routes.find((route) => route.name === "diff")?.render
return () => {}
},
navigate(name, params) {
current = params ? { name, params } : { name }
},
get current() {
return current
},
},
} satisfies TuiPluginApi
void diffViewerPlugin.tui(api, undefined, pluginMeta)
commands.get("diff.open")?.run?.({} as never)
return (
<OpencodeKeymapProvider keymap={keymap}>
<TuiConfigProvider config={createTuiResolvedConfig()}>
<KVProvider>
<ThemeProvider mode="dark">{renderDiff?.({ params: "params" in current ? current.params : undefined })}</ThemeProvider>
</KVProvider>
</TuiConfigProvider>
</OpencodeKeymapProvider>
)
}
const app = await testRender(() => <Harness />, { width: 80, height: 20 })
try {
await waitForCommand(app, commands, "diff.close")
expect(current).toEqual({ name: "diff", params: { mode: "git", sessionID: "session-1", returnRoute: startRoute } })
expect(commands.has("diff.close")).toBe(true)
commands.get("diff.close")!.run?.({} as never)
expect(current).toEqual(startRoute)
} finally {
app.renderer.destroy()
}
})
async function waitForCommand(
app: Awaited<ReturnType<typeof testRender>>,
commands: Map<string, unknown>,
command: string,
) {
for (let attempt = 0; attempt < 10; attempt++) {
await app.renderOnce()
if (commands.has(command)) return
await new Promise((resolve) => setTimeout(resolve, 25))
}
}
const pluginMeta = {
id: "diff-viewer",
source: "internal",
spec: "diff-viewer",
target: "diff-viewer",
first_time: 0,
last_time: 0,
time_changed: 0,
load_count: 1,
fingerprint: "test",
state: "same",
} satisfies TuiPluginMeta