diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 26ad36db955e..f98129c2d1e0 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -646,7 +646,10 @@ function App(props: { pair?: DialogPairCredentials }) { run: () => { route.navigate({ type: "home", - location: route.data.type === "session" ? data.session.get(route.data.sessionID)?.location : undefined, + location: + route.data.type === "session" + ? (data.session.get(route.data.sessionID)?.location ?? location.ref) + : undefined, }) dialog.clear() }, diff --git a/packages/tui/src/component/dialog-open.tsx b/packages/tui/src/component/dialog-open.tsx index 2ee57aa03bda..bfef6d54c784 100644 --- a/packages/tui/src/component/dialog-open.tsx +++ b/packages/tui/src/component/dialog-open.tsx @@ -84,6 +84,7 @@ export function DialogOpen() { value: { type: "session", sessionID: session.id } as OpenTarget, category: "Sessions", footer: `${name ? `${Locale.truncate(name, 20)} ยท ` : ""}${timeAgo(session.time.updated)}`, + onSelect: () => location.set(session.location), gutter: running ? () => : tabs.has(session.id) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 79c36ee4c8fe..bffa7cf15c1f 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -143,8 +143,8 @@ export function Session() { const promptRef = usePromptRef() const session = createMemo(() => data.session.get(route.sessionID)) const messages = () => data.session.message.list(route.sessionID) - const location = createMemo(() => session()?.location) const currentLocation = useLocation() + const location = createMemo(() => session()?.location ?? currentLocation.ref) createEffect(() => currentLocation.set(location())) diff --git a/packages/tui/test/cli/tui/dialog-open.test.tsx b/packages/tui/test/cli/tui/dialog-open.test.tsx new file mode 100644 index 000000000000..14ec4c7ec88d --- /dev/null +++ b/packages/tui/test/cli/tui/dialog-open.test.tsx @@ -0,0 +1,106 @@ +/** @jsxImportSource @opentui/solid */ +import { expect, test } from "bun:test" +import { testRender } from "@opentui/solid" +import { mkdtempSync, rmSync } from "fs" +import { tmpdir } from "os" +import path from "path" +import { onMount } from "solid-js" +import { DialogOpen } from "../../../src/component/dialog-open" +import { ConfigProvider } from "../../../src/config" +import { ClientProvider } from "../../../src/context/client" +import { DataProvider, useData } from "../../../src/context/data" +import { Keymap } from "../../../src/context/keymap" +import { LocationProvider, useLocation } from "../../../src/context/location" +import { RouteProvider, useRoute } from "../../../src/context/route" +import { TuiAppProvider } from "../../../src/context/runtime" +import { SessionTabsProvider } from "../../../src/context/session-tabs" +import { StorageProvider } from "../../../src/context/storage" +import { ThemeProvider } from "../../../src/context/theme" +import { DialogProvider, useDialog } from "../../../src/ui/dialog" +import { ToastProvider } from "../../../src/ui/toast" +import { createApi, createEventStream, createFetch, json } from "../../fixture/tui-client" +import { TestTuiContexts } from "../../fixture/tui-environment" +import { createTuiResolvedConfig } from "../../fixture/tui-runtime" + +test("selecting an unhydrated session preserves its location", async () => { + const state = mkdtempSync(path.join(tmpdir(), "opencode-dialog-open-")) + const events = createEventStream() + const remote = { directory: "/tmp/opencode/remote", workspaceID: "ws_remote" } + const calls = createFetch((url) => { + if (url.pathname !== "/api/session") return + return json({ + data: [ + { + id: "ses_remote", + projectID: "proj_remote", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + time: { created: 1, updated: 2 }, + title: "Remote session", + location: remote, + }, + ], + cursor: {}, + }) + }, events) + let route!: ReturnType + let location!: ReturnType + let data!: ReturnType + + function Probe() { + const dialog = useDialog() + route = useRoute() + location = useLocation() + data = useData() + onMount(() => dialog.replace(() => )) + return null + } + + const app = await testRender( + () => ( + + + + + + + + + + + + Promise.resolve({}) }}> + + + + + + + + + + + + + + + + ), + { width: 100, height: 30, kittyKeyboard: true }, + ) + app.renderer.start() + + try { + await app.waitForFrame((frame) => frame.includes("Remote session")) + expect(data.session.get("ses_remote")).toBeUndefined() + + app.mockInput.pressEnter() + await app.waitFor(() => route.data.type === "session") + + expect(route.data).toEqual({ type: "session", sessionID: "ses_remote" }) + expect(location.ref).toEqual(remote) + } finally { + app.renderer.destroy() + rmSync(state, { recursive: true, force: true }) + } +})