Skip to content
Open
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
34 changes: 20 additions & 14 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -147,7 +147,7 @@ export function tui(input: {
input.args.continue
? {
type: "session",
sessionID: "dummy",
sessionID: CONTINUE_PLACEHOLDER_ID,
}
: undefined
}
Expand Down Expand Up @@ -352,20 +352,26 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
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
Expand Down
7 changes: 7 additions & 0 deletions packages/opencode/src/cli/cmd/tui/context/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
Loading