diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 5da2740cce4c..8bc608628d14 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -2,7 +2,7 @@ import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimension import * as Clipboard from "@tui/util/clipboard" import * as Selection from "@tui/util/selection" import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core" -import { RouteProvider, useRoute } from "@tui/context/route" +import { CONTINUE_PLACEHOLDER_ID, RouteProvider, useRoute } from "@tui/context/route" import { Switch, Match, @@ -147,7 +147,7 @@ export function tui(input: { input.args.continue ? { type: "session", - sessionID: "dummy", + sessionID: CONTINUE_PLACEHOLDER_ID, } : undefined } @@ -352,20 +352,26 @@ function App(props: { onSnapshot?: () => Promise }) { const match = sync.data.session .toSorted((a, b) => b.time.updated - a.time.updated) .find((x) => x.parentID === undefined)?.id - if (match) { + if (!match) { + // Only give up once the full session list has loaded + if (sync.status !== "complete") return continued = true - if (args.fork) { - void sdk.client.session.fork({ sessionID: match }).then((result) => { - if (result.data?.id) { - route.navigate({ type: "session", sessionID: result.data.id }) - } else { - toast.show({ message: "Failed to fork session", variant: "error" }) - } - }) - } else { - route.navigate({ type: "session", sessionID: match }) - } + route.navigate({ type: "home" }) + return + } + continued = true + if (args.fork) { + void sdk.client.session.fork({ sessionID: match }).then((result) => { + if (result.data?.id) { + route.navigate({ type: "session", sessionID: result.data.id }) + return + } + toast.show({ message: "Failed to fork session", variant: "error" }) + route.navigate({ type: "home" }) + }) + return } + route.navigate({ type: "session", sessionID: match }) }) // Handle --session with --fork: wait for sync to be fully complete before forking diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 35be17801b1f..4d5f389431e1 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -21,6 +21,13 @@ export type PluginRoute = { export type Route = HomeRoute | SessionRoute | PluginRoute +/** + * Placeholder session ID used as the initial route when `--continue` is passed. + * Prevents a visual flash to the home screen before the real session is resolved. + * Must never be fetched from the server — consumers should guard against it. + */ +export const CONTINUE_PLACEHOLDER_ID = "dummy" + export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ name: "Route", init: (props: { initialRoute?: Route }) => { diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 06be5dfbefbf..0390ae1c392e 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -14,7 +14,7 @@ import { } from "solid-js" import { Dynamic } from "solid-js/web" import path from "path" -import { useRoute, useRouteData } from "@tui/context/route" +import { CONTINUE_PLACEHOLDER_ID, useRoute, useRouteData } from "@tui/context/route" import { useProject } from "@tui/context/project" import { useSync } from "@tui/context/sync" import { useEvent } from "@tui/context/event" @@ -181,6 +181,9 @@ export function Session() { const sdk = useSDK() createEffect(async () => { + // Skip fetching while the route still holds the placeholder used by --continue. + // The continue effect in App() will replace this route with a real session or home. + if (route.sessionID === CONTINUE_PLACEHOLDER_ID) return const previousWorkspace = project.workspace.current() const result = await sdk.client.session.get({ sessionID: route.sessionID }, { throwOnError: true }) if (!result.data) {