diff --git a/packages/app/src/new-session/composer-adapter.ts b/packages/app/src/new-session/composer-adapter.ts
index 548669aa9fc8..7e9ca3dba4dc 100644
--- a/packages/app/src/new-session/composer-adapter.ts
+++ b/packages/app/src/new-session/composer-adapter.ts
@@ -1,6 +1,5 @@
import { base64Encode } from "@opencode-ai/util/encode"
import { getDirectory } from "@opencode-ai/util/path"
-import { startTransition } from "solid-js"
import type { NewSessionComposerAdapter } from "@/composer/adapter"
import { useComposerState } from "@/composer/persistence"
import { createComposerControls, createComposerModelSelection } from "@/composer/selection"
@@ -72,24 +71,21 @@ export function createNewSessionComposerAdapter(props: {
if (!created) return
data.session.remember(created)
- await startTransition(() => {
- tabs.updateDraft(props.draftID, { worktree: undefined })
- if (permission.isAutoAcceptingDirectory(projectDirectory)) {
- permission.enableAutoAccept(created.id, sessionDirectory)
- }
- local.session.promote(sessionDirectory, created.id, {
- agent: selection.agent,
- model: selection.model,
- variant: selection.variant ?? null,
- })
- tabs.promoteDraft(props.draftID, { server: server.key, sessionId: created.id })
- submission.retarget(
- prompt.capture(
- { dir: base64Encode(sessionDirectory), id: created.id },
- { server: server.key, scope: serverSDK.scope },
- ),
- )
+ if (permission.isAutoAcceptingDirectory(projectDirectory)) {
+ permission.enableAutoAccept(created.id, sessionDirectory)
+ }
+ local.session.promote(sessionDirectory, created.id, {
+ agent: selection.agent,
+ model: selection.model,
+ variant: selection.variant ?? null,
})
+ await tabs.promoteDraft(props.draftID, { server: server.key, sessionId: created.id })
+ submission.retarget(
+ prompt.capture(
+ { dir: base64Encode(sessionDirectory), id: created.id },
+ { server: server.key, scope: serverSDK.scope },
+ ),
+ )
return {
id: created.id,
diff --git a/packages/app/src/shell/routes/routes.tsx b/packages/app/src/shell/routes/routes.tsx
index 42851b622346..7b94b828158f 100644
--- a/packages/app/src/shell/routes/routes.tsx
+++ b/packages/app/src/shell/routes/routes.tsx
@@ -1,4 +1,4 @@
-import { Route, useParams } from "@solidjs/router"
+import { Navigate, Route, useParams } from "@solidjs/router"
import { createMemo, lazy, Show, type ParentProps } from "solid-js"
import { Home } from "@/home/route"
import { ServerProvider } from "@/runtime/server/current"
@@ -6,7 +6,7 @@ import { useGlobal } from "@/runtime/server/runtime"
import { ServerConnection } from "@/runtime/server/registry"
import { LayoutProvider } from "@/shell/state/layout"
import Shell from "@/shell/shell"
-import { requireServerKey } from "./session"
+import { parseServerKey } from "./session"
export const File = lazy(() => import("@opencode-ai/session-ui/file").then((module) => ({ default: module.File })))
const loadDraftRoute = () => Promise.all([import("@/new-session/route"), File.preload()]).then(([module]) => module)
@@ -44,12 +44,14 @@ export function AppRoutes() {
function TargetServerRoute(props: ParentProps) {
const params = useParams<{ serverKey: string }>()
const global = useGlobal()
- const connection = createMemo(() =>
- global.servers.list().find((item) => ServerConnection.key(item) === requireServerKey(params.serverKey)),
- )
+ const connection = createMemo(() => {
+ const key = parseServerKey(params.serverKey)
+ if (!key) return
+ return global.servers.list().find((item) => ServerConnection.key(item) === key)
+ })
return (
-
+ }>
{(connection) => {props.children}}
)
diff --git a/packages/app/src/shell/routes/session.test.ts b/packages/app/src/shell/routes/session.test.ts
index de3101866539..3b79fe390c3a 100644
--- a/packages/app/src/shell/routes/session.test.ts
+++ b/packages/app/src/shell/routes/session.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { ServerConnection } from "@/runtime/server/registry"
-import { requireServerKey, rootSession, sessionHref } from "./session"
+import { parseServerKey, requireServerKey, rootSession, sessionHref } from "./session"
describe("session routes", () => {
test("builds and decodes a server-keyed session route", () => {
@@ -12,6 +12,8 @@ describe("session routes", () => {
})
test("rejects malformed server keys", () => {
+ expect(parseServerKey(undefined)).toBeUndefined()
+ expect(parseServerKey("not-base64")).toBeUndefined()
expect(() => requireServerKey("not-base64")).toThrow("Invalid server route")
})
diff --git a/packages/app/src/shell/routes/session.ts b/packages/app/src/shell/routes/session.ts
index 01c8b9e47ae5..8d3cab37e7e8 100644
--- a/packages/app/src/shell/routes/session.ts
+++ b/packages/app/src/shell/routes/session.ts
@@ -7,8 +7,14 @@ export function sessionHref(server: ServerConnection.Key, sessionID: string) {
}
export function requireServerKey(segment: string | undefined) {
+ const key = parseServerKey(segment)
+ if (!key) throw new Error("Invalid server route")
+ return key
+}
+
+export function parseServerKey(segment: string | undefined) {
const key = decode64(segment)
- if (!key || base64Encode(key) !== segment) throw new Error("Invalid server route")
+ if (!key || base64Encode(key) !== segment) return
return ServerConnection.Key.make(key)
}
diff --git a/packages/app/src/shell/tabs/tabs.tsx b/packages/app/src/shell/tabs/tabs.tsx
index 7767ce17efdf..7e3da89a3f06 100644
--- a/packages/app/src/shell/tabs/tabs.tsx
+++ b/packages/app/src/shell/tabs/tabs.tsx
@@ -229,12 +229,12 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
)
})
},
- promoteDraft(draftID: string, session: Omit) {
+ async promoteDraft(draftID: string, session: Omit) {
// Keep the replacement and navigation atomic so /new-session never renders
// after its backing draft tab has been removed from the store.
const active = location.pathname === "/new-session" && location.query.draftId === draftID
const next = { type: "session" as const, ...session }
- void startTransition(() => {
+ await startTransition(() => {
setStore(
produce((tabs) => {
const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID)