Skip to content

Commit 0c963ac

Browse files
BrendonovichBenGu3
authored andcommitted
fix(app): use tab-scoped servers in sessions (anomalyco#33946)
1 parent 36bc4ce commit 0c963ac

9 files changed

Lines changed: 77 additions & 58 deletions

File tree

packages/app/src/app.tsx

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { CommentsProvider } from "@/context/comments"
3232
import { FileProvider } from "@/context/file"
3333
import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk"
3434
import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
35-
import { GlobalProvider } from "@/context/global"
35+
import { GlobalProvider, useGlobal } from "@/context/global"
3636
import { HighlightsProvider } from "@/context/highlights"
3737
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
3838
import { LayoutProvider } from "@/context/layout"
@@ -103,10 +103,10 @@ const SessionRoute = () => {
103103

104104
const TargetSessionRoute = () => {
105105
const params = useParams<{ serverKey: string; id: string }>()
106-
const server = useServer()
106+
const global = useGlobal()
107107
const conn = createMemo(() => {
108108
const key = requireServerKey(params.serverKey)
109-
return server.list.find((item) => ServerConnection.key(item) === key)
109+
return global.servers.list().find((item) => ServerConnection.key(item) === key)
110110
})
111111

112112
return (
@@ -221,25 +221,27 @@ function DraftRoute() {
221221
}
222222

223223
function ResolvedDraftRoute(props: { draft: DraftTab }) {
224-
const server = useServer()
225-
const conn = createMemo(() => server.list.find((item) => ServerConnection.key(item) === props.draft.server))
224+
const global = useGlobal()
225+
const conn = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === props.draft.server))
226226
const directory = () => props.draft.directory
227227
const serverKey = () => props.draft.server
228228

229229
return (
230-
<ServerSDKProvider server={conn}>
231-
<ServerSyncProvider server={conn}>
232-
<TargetServerScopedProviders directory={directory}>
233-
<SDKProvider directory={directory}>
234-
<DirectoryDataProvider directory={directory} server={serverKey}>
235-
<DraftProviders>
236-
<NewSession />
237-
</DraftProviders>
238-
</DirectoryDataProvider>
239-
</SDKProvider>
240-
</TargetServerScopedProviders>
241-
</ServerSyncProvider>
242-
</ServerSDKProvider>
230+
<Show when={`${props.draft.server}\0${props.draft.directory}`} keyed>
231+
<ServerSDKProvider server={conn}>
232+
<ServerSyncProvider server={conn}>
233+
<TargetServerScopedProviders directory={directory}>
234+
<SDKProvider directory={directory}>
235+
<DirectoryDataProvider directory={directory} server={serverKey}>
236+
<DraftProviders>
237+
<NewSession />
238+
</DraftProviders>
239+
</DirectoryDataProvider>
240+
</SDKProvider>
241+
</TargetServerScopedProviders>
242+
</ServerSyncProvider>
243+
</ServerSDKProvider>
244+
</Show>
243245
)
244246
}
245247

@@ -588,18 +590,31 @@ function Routes() {
588590
</Route>
589591
<Show when={settings.general.newLayoutDesigns()}>
590592
<Route path="/" component={NewHome} />
591-
<Route
592-
path="/:dir/session/:id"
593-
component={() => {
594-
const server = useServer()
595-
const { id } = useParams()
596-
597-
return <Navigate href={`/server/${server.key}/session/${id}`} />
598-
}}
599-
/>
593+
<Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} />
600594
</Show>
601595
<Route path="/new-session" component={DraftRoute} />
602596
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
603597
</>
604598
)
605599
}
600+
601+
function LegacyTargetSessionRoute() {
602+
const server = useServer()
603+
const tabs = useTabs()
604+
const params = useParams<{ id: string }>()
605+
606+
return (
607+
<Show when={tabs.ready()}>
608+
<Navigate
609+
href={sessionHref(
610+
legacySessionServer(
611+
tabs.store.filter((item) => item.type === "session"),
612+
params.id,
613+
server.key,
614+
),
615+
params.id,
616+
)}
617+
/>
618+
</Show>
619+
)
620+
}

packages/app/src/components/dialog-select-file.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { useLayout } from "@/context/layout"
1515
import { useFile } from "@/context/file"
1616
import { useLanguage } from "@/context/language"
1717
import { usePlatform } from "@/context/platform"
18-
import { useServer } from "@/context/server"
1918
import { useSettings } from "@/context/settings"
2019
import { useSessionLayout } from "@/pages/session/session-layout"
2120
import { createSessionTabs } from "@/pages/session/helpers"
@@ -272,7 +271,6 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
272271
const command = useCommand()
273272
const language = useLanguage()
274273
const platform = usePlatform()
275-
const server = useServer()
276274
const settings = useSettings()
277275
const layout = useLayout()
278276
const file = useFile()
@@ -393,10 +391,10 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
393391
state.cleanup?.()
394392
})
395393

396-
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns() && server.current) {
394+
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
397395
return (
398396
<DialogSelectFileV2
399-
server={server.current}
397+
server={serverSDK().server}
400398
mode="file"
401399
start={projectDirectory()}
402400
title={language.t("session.header.searchFiles")}

packages/app/src/components/status-popover.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
55
import { Popover } from "@opencode-ai/ui/popover"
66
import { Suspense, createMemo, createSignal, lazy, Show, type JSX } from "solid-js"
77
import { useLanguage } from "@/context/language"
8-
import { useServer } from "@/context/server"
8+
import { ServerConnection, useServer } from "@/context/server"
9+
import { useServerSDK } from "@/context/server-sdk"
910
import { useSync } from "@/context/sync"
1011
import { useGlobal } from "@/context/global"
1112

@@ -81,11 +82,11 @@ export function StatusPopoverV2(props: { scope?: "server" }) {
8182

8283
function DirectoryStatusPopover() {
8384
const language = useLanguage()
84-
const server = useServer()
85+
const server = useServerSDK()
8586
const global = useGlobal()
8687
const sync = useSync()
8788
const [shown, setShown] = createSignal(false)
88-
const serverHealth = () => global.servers.health[server.key]?.healthy
89+
const serverHealth = () => global.servers.health[ServerConnection.key(server().server)]?.healthy
8990
const ready = createMemo(() => serverHealth() === false || sync().data.mcp_ready)
9091
const mcpIssue = createMemo(() => {
9192
const mcp = Object.values(sync().data.mcp ?? {})

packages/app/src/components/terminal.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { matchKeybind, parseKeybind } from "@/context/command"
1010
import { useLanguage } from "@/context/language"
1111
import { usePlatform } from "@/context/platform"
1212
import { useSDK } from "@/context/sdk"
13-
import { useServer } from "@/context/server"
13+
import { useServerSDK } from "@/context/server-sdk"
1414
import { terminalFontFamily, useSettings } from "@/context/settings"
1515
import type { LocalPTY } from "@/context/terminal"
1616
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
@@ -160,13 +160,15 @@ export const Terminal = (props: TerminalProps) => {
160160
const settings = useSettings()
161161
const theme = useTheme()
162162
const language = useLanguage()
163-
const server = useServer()
163+
// Terminal captures its connection for the PTY lifetime, so callers must key it per server/session.
164+
const connection = useServerSDK()().server
164165
const directory = sdk().directory
165166
const client = sdk().client
166167
const url = sdk().url
167-
const auth = server.current?.http
168+
const auth = connection.http
168169
const username = auth?.username ?? "opencode"
169170
const password = auth?.password ?? ""
171+
const authToken = connection.type === "http" ? connection.authToken : false
170172
const sameOrigin = new URL(url, location.href).origin === location.origin
171173
let container!: HTMLDivElement
172174
const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"])
@@ -540,7 +542,7 @@ export const Terminal = (props: TerminalProps) => {
540542
sameOrigin,
541543
username,
542544
password,
543-
authToken: server.current?.type === "http" ? server.current.authToken : false,
545+
authToken,
544546
}),
545547
)
546548
socket.binaryType = "arraybuffer"

packages/app/src/components/titlebar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
319319
return
320320
}
321321

322+
const activeTab = currentTab()
323+
if (activeTab?.type === "draft") {
324+
tabs.newDraft({ server: activeTab.server, directory: activeTab.directory }, "")
325+
return
326+
}
327+
322328
const current = layout.projects.list()[0]
323329
if (current) {
324330
tabs.newDraft({ server: server.key, directory: current.worktree }, "")

packages/app/src/context/layout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { ProjectAvatarVariant } from "@opencode-ai/ui/v2/project-avatar-v2"
1717
import { migrateLegacySessionStateKeys, ServerScope, SessionStateKey } from "@/utils/server-scope"
1818
import { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } from "./layout-helpers"
1919
import { requireServerKey } from "@/utils/session-route"
20+
import { type DraftTab, useTabs } from "./tabs"
2021

2122
export { createSessionKeyReader, ensureSessionKey, pruneSessionKeys }
2223

@@ -159,12 +160,17 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
159160
const serverSdk = useServerSDK()
160161
const serverSync = useServerSync()
161162
const server = useServer()
163+
const tabs = useTabs()
162164
const platform = usePlatform()
163165
const location = useLocation()
164166
const route = createMemo(() => {
165167
const value = currentRoute(location.pathname, location.search)
166168
if (value.type === "home") return value
167169
if (value.server) return value
170+
if (value.type === "draft") {
171+
const draft = tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === value.draftID)
172+
if (draft) return { ...value, server: draft.server }
173+
}
168174
return { ...value, server: server.key }
169175
})
170176

packages/app/src/context/prompt.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useServerSDK } from "./server-sdk"
99
import type { ServerScope } from "@/utils/server-scope"
1010
import { useSDK } from "./sdk"
1111
import { useTabs, type Tab } from "./tabs"
12-
import { ServerConnection, useServer } from "./server"
12+
import { ServerConnection } from "./server"
1313
import { requireServerKey } from "@/utils/session-route"
1414
import { useSettings } from "./settings"
1515

@@ -287,7 +287,6 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
287287
const sdk = useSDK()
288288
const [search] = useSearchParams<{ draftId?: string }>()
289289
const serverSDK = useServerSDK()
290-
const server = useServer()
291290
const tabs = useTabs()
292291
const settings = useSettings()
293292
const cache = new Map<string, PromptCacheEntry>()
@@ -312,7 +311,8 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
312311
}
313312

314313
const owner = getOwner()
315-
const serverKey = () => (params.serverKey ? requireServerKey(params.serverKey) : server.key)
314+
const serverKey = () =>
315+
params.serverKey ? requireServerKey(params.serverKey) : ServerConnection.key(serverSDK().server)
316316
const scope = () =>
317317
search.draftId ? { draftID: search.draftId } : { dir: base64Encode(sdk().directory), id: params.id }
318318
const load = (scope: Scope) => {

packages/app/src/context/server-sdk.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerS
269269
})
270270

271271
return {
272+
server,
272273
scope,
273274
url: server.http.url,
274275
client: sdk,

packages/app/src/pages/session/composer/session-composer-controls.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import { useGlobal } from "@/context/global"
88
import { useLayout } from "@/context/layout"
99
import { useLocal } from "@/context/local"
1010
import type { QueryOptionsApi } from "@/context/server-sync"
11+
import { useServerSDK } from "@/context/server-sdk"
1112
import { serverName, ServerConnection, useServer } from "@/context/server"
1213
import { useSDK } from "@/context/sdk"
1314
import { useSettings } from "@/context/settings"
1415
import { useSync } from "@/context/sync"
15-
import { type DraftTab, useTabs } from "@/context/tabs"
16+
import { useTabs } from "@/context/tabs"
1617
import { useProviders } from "@/hooks/use-providers"
1718
import { pathKey } from "@/utils/path-key"
1819

@@ -27,6 +28,7 @@ export function createSessionComposerControls(input: {
2728
const providers = useProviders()
2829
const settings = useSettings()
2930
const server = useServer()
31+
const serverSDK = useServerSDK()
3032
const sync = useSync()
3133
const sdk = useSDK()
3234
const tabs = useTabs()
@@ -35,23 +37,11 @@ export function createSessionComposerControls(input: {
3537
const [search] = useSearchParams<{ draftId?: string }>()
3638
const view = layout.view(input.sessionKey)
3739

38-
const draft = createMemo(() => {
39-
if (!search.draftId) return
40-
return tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)
41-
})
42-
const projectServer = createMemo(() => {
43-
if (!search.draftId) return server.current
44-
const target = draft()?.server
45-
if (!target) return
46-
return server.list.find((conn) => ServerConnection.key(conn) === target)
47-
})
48-
const projectServerCtx = createMemo(() => {
49-
const conn = projectServer()
50-
if (conn) return global.ensureServerCtx(conn)
51-
})
40+
const projectServer = () => serverSDK().server
41+
const projectServerCtx = createMemo(() => global.ensureServerCtx(projectServer()))
5242
const projects = createMemo(() => {
5343
if (server.list.length <= 1) {
54-
return search.draftId ? (projectServerCtx()?.projects.list() ?? []) : layout.projects.list()
44+
return search.draftId ? projectServerCtx().projects.list() : layout.projects.list()
5545
}
5646
return server.list.flatMap((conn) => {
5747
const server = { key: ServerConnection.key(conn), name: serverName(conn) }
@@ -121,7 +111,7 @@ export function createSessionComposerControls(input: {
121111
projects: {
122112
available: projects(),
123113
directory: sdk().directory,
124-
server: server.list.length > 1 && projectServer() ? ServerConnection.key(projectServer()!) : undefined,
114+
server: server.list.length > 1 ? ServerConnection.key(projectServer()) : undefined,
125115
select: selectProject,
126116
add: addProject,
127117
},

0 commit comments

Comments
 (0)