diff --git a/packages/app/src/new-session/route.tsx b/packages/app/src/new-session/route.tsx index 2f98f92fb28..76f9625baa2 100644 --- a/packages/app/src/new-session/route.tsx +++ b/packages/app/src/new-session/route.tsx @@ -11,7 +11,7 @@ import { ServerConnection } from "@/runtime/server/registry" import { useTabs, type DraftTab } from "@/shell/tabs/tabs" import { SessionUIProvider } from "@/shell/routes/session-ui-provider" import NewSession from "@/new-session/screen" -import { IncompatibleServerPanel } from "@/session/incompatible-server-panel" +import { ServerUnavailablePanel } from "@/session/server-unavailable-panel" import { ErrorPage } from "@/shell/errors/error" import { SessionPanelFrame, SessionRouteFrame } from "@/session/session-frame" @@ -57,7 +57,8 @@ function ResolvedDraftContent(props: { draft: DraftTab }) { fallback={ - { const index = tabs.store.findIndex((tab) => tab.type === "draft" && tab.draftID === props.draft.draftID) diff --git a/packages/app/src/runtime/i18n/en.ts b/packages/app/src/runtime/i18n/en.ts index e7ae4682a9a..7f95fd2d39f 100644 --- a/packages/app/src/runtime/i18n/en.ts +++ b/packages/app/src/runtime/i18n/en.ts @@ -538,6 +538,9 @@ export const dict = { "session.error.incompatible": "This server version isn't supported", "session.error.incompatible.description": "{{server}} is running OpenCode {{version}}, which isn't compatible with this app. Upgrade the server to OpenCode V2 to continue.", + "session.error.unreachable": "Can't reach this server", + "session.error.unreachable.description": + "{{server}} isn't responding, so this session can't load. It will open automatically once the server is reachable again.", "session.background.moveRunning": "Move to background", "session.background.moveInline": "Press {{keybind}} to move running work to the background", "command.session.background": "Move to background", diff --git a/packages/app/src/session/route-error.tsx b/packages/app/src/session/route-error.tsx index 8d4006bd4e8..59e91a0f3c1 100644 --- a/packages/app/src/session/route-error.tsx +++ b/packages/app/src/session/route-error.tsx @@ -6,7 +6,7 @@ import { useServer } from "@/runtime/server/current" import { ServerConnection, serverName, useServers } from "@/runtime/server/registry" import { useTabs } from "@/shell/tabs/tabs" import { isLocalSessionNotFoundError, isSessionNotFoundError } from "@/runtime/server/errors" -import { IncompatibleServerPanel } from "./incompatible-server-panel" +import { ServerUnavailablePanel } from "./server-unavailable-panel" export function SessionErrorFallback(props: { error: unknown; sessionID?: string; serverKey?: ServerConnection.Key }) { const language = useLanguage() @@ -28,10 +28,14 @@ export function SessionErrorFallback(props: { error: unknown; sessionID?: string return ( } + fallback={} > } >
@@ -69,9 +73,3 @@ export function SessionErrorFallback(props: { error: unknown; sessionID?: string ) } - -function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) { - if (!sessionID) return false - - return isSessionNotFoundError(error, sessionID) || isLocalSessionNotFoundError(error, sessionID) -} diff --git a/packages/app/src/session/route.tsx b/packages/app/src/session/route.tsx index cfd75e3fa14..725510d9165 100644 --- a/packages/app/src/session/route.tsx +++ b/packages/app/src/session/route.tsx @@ -23,7 +23,7 @@ import { requireServerKey } from "@/shell/routes/session" import { useSessionModel } from "./model" import { SessionPanelFrame } from "./session-frame" import { SessionIdentityHeader } from "./session-identity-header" -import { IncompatibleServerPanel } from "./incompatible-server-panel" +import { ServerUnavailablePanel } from "./server-unavailable-panel" import { SessionErrorFallback } from "./route-error" import { createSessionResolution } from "./session-resolution" import { SessionScreenView } from "./screen" @@ -128,18 +128,39 @@ function ResolvedTargetSessionRoute() { const directory = createMemo(() => current()?.location.directory) + // Only a failed health check: a version mismatch has its own panel, and a rejected credential is not an outage. + const unreachable = () => { + const health = server.health + + return !!health && !health.healthy && !health.checking && !health.unauthorized && !health.incompatible + } + return ( - tabs.removeSessionTab({ server: server.key, sessionId: params.id })} /> } > - }> + }> + + tabs.removeSessionTab({ server: server.key, sessionId: params.id })} + /> + + + } + > {(value) => ( diff --git a/packages/app/src/session/incompatible-server-panel.tsx b/packages/app/src/session/server-unavailable-panel.tsx similarity index 58% rename from packages/app/src/session/incompatible-server-panel.tsx rename to packages/app/src/session/server-unavailable-panel.tsx index 7dbfc7fd5b8..ecdd7433f2e 100644 --- a/packages/app/src/session/incompatible-server-panel.tsx +++ b/packages/app/src/session/server-unavailable-panel.tsx @@ -5,7 +5,7 @@ import { useLanguage } from "@/runtime/i18n/language" import { useServer } from "@/runtime/server/current" import { serverName } from "@/runtime/server/registry" -export function IncompatibleServerPanel(props: { onClose?: () => void }) { +export function ServerUnavailablePanel(props: { reason: "incompatible" | "unreachable"; onClose?: () => void }) { const language = useLanguage() const server = useServer() @@ -14,12 +14,18 @@ export function IncompatibleServerPanel(props: { onClose?: () => void }) {
-
{language.t("session.error.incompatible")}
+
+ {props.reason === "incompatible" + ? language.t("session.error.incompatible") + : language.t("session.error.unreachable")} +
- {language.t("session.error.incompatible.description", { - server: serverName(server.conn), - version: server.health?.version ?? "1", - })} + {props.reason === "incompatible" + ? language.t("session.error.incompatible.description", { + server: serverName(server.conn), + version: server.health?.version ?? "1", + }) + : language.t("session.error.unreachable.description", { server: serverName(server.conn) })}
diff --git a/packages/app/src/shell/titlebar/tab-nav.tsx b/packages/app/src/shell/titlebar/tab-nav.tsx index 77088bf90de..9a233efde07 100644 --- a/packages/app/src/shell/titlebar/tab-nav.tsx +++ b/packages/app/src/shell/titlebar/tab-nav.tsx @@ -30,6 +30,7 @@ export function TabNavItem(props: { unread: boolean loading: boolean fallbackTitle?: string + fallbackDirectory?: string onRename: (title: string) => Promise onClose: () => void onNavigate: () => void @@ -63,9 +64,11 @@ export function TabNavItem(props: { const project = createMemo(() => { const session = props.session - if (!session) return + if (session) return serverCtx()?.projects.forSession(session) - return serverCtx()?.projects.forSession(session) + if (!props.fallbackDirectory) return + + return serverCtx()?.projects.resolve({ worktree: props.fallbackDirectory, expanded: false }) }) const title = createMemo(() => { @@ -295,7 +298,25 @@ export function TabNavItem(props: {