mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-22 20:05:23 +00:00
feat(desktop): show splash overlay during server switch
ServerKey's keyed <Show> remount is a multi-second synchronous cascade (dispose + rebuild of the whole app subtree) that used to leave the UI looking frozen. A tiny module-level serverSwitching signal now gates a fullscreen Splash rendered above the ServerKey boundary, and the status-popover click handler setTimeout-defers the batched navigate+setActive so the browser paints the splash before the freeze begins and dismisses it after the new subtree paints.
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
Suspense,
|
||||
} from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { serverSwitching } from "@/utils/server-switch"
|
||||
import { CommandProvider } from "@/context/command"
|
||||
import { CommentsProvider } from "@/context/comments"
|
||||
import { FileProvider } from "@/context/file"
|
||||
@@ -305,6 +306,12 @@ export function AppInterface(props: {
|
||||
router?: Component<BaseRouterProps>
|
||||
disableHealthCheck?: boolean
|
||||
}) {
|
||||
// ServerKey wraps the whole Router so that switching `server.key` throws
|
||||
// away any session / pty state from the previous server. Preserving the
|
||||
// route across servers doesn't work because session ids, pty ids, and
|
||||
// most URL-addressable resources are server-scoped — you'd 404 on every
|
||||
// fetch. The click handler that swaps servers also navigates back to "/"
|
||||
// so the fresh MemoryRouter doesn't try to re-resolve a now-dead URL.
|
||||
return (
|
||||
<ServerProvider
|
||||
defaultServer={props.defaultServer}
|
||||
@@ -312,6 +319,11 @@ export function AppInterface(props: {
|
||||
servers={props.servers}
|
||||
>
|
||||
<ConnectionGate disableHealthCheck={props.disableHealthCheck}>
|
||||
<Show when={serverSwitching()}>
|
||||
<div class="fixed inset-0 z-[2147483647] bg-background-base flex flex-col items-center justify-center pointer-events-auto">
|
||||
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
||||
</div>
|
||||
</Show>
|
||||
<ServerKey>
|
||||
<GlobalSDKProvider>
|
||||
<GlobalSyncProvider>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useSDK } from "@/context/sdk"
|
||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||
import { setServerSwitching } from "@/utils/server-switch"
|
||||
|
||||
const pollMs = 10_000
|
||||
|
||||
@@ -292,13 +293,26 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
||||
aria-disabled={blocked()}
|
||||
onClick={() => {
|
||||
if (blocked()) return
|
||||
// Run navigate + setActive in the same tick so Solid
|
||||
// disposes the old subtree once instead of cascading
|
||||
// the route change disposal into the ServerKey remount.
|
||||
batch(() => {
|
||||
navigate("/")
|
||||
server.setActive(key)
|
||||
})
|
||||
// Paint a full-window splash BEFORE the heavy
|
||||
// ServerKey remount so the user gets visual
|
||||
// feedback during the multi-second synchronous
|
||||
// dispose cascade (xterm + file-tree + providers).
|
||||
// setTimeout(0) yields to the browser so the
|
||||
// splash lands on screen before the cascade
|
||||
// starts; a second setTimeout(0) after the batch
|
||||
// waits for the new subtree to paint, then
|
||||
// dismisses the splash.
|
||||
setServerSwitching(true)
|
||||
setTimeout(() => {
|
||||
try {
|
||||
batch(() => {
|
||||
navigate("/")
|
||||
server.setActive(key)
|
||||
})
|
||||
} finally {
|
||||
setTimeout(() => setServerSwitching(false), 0)
|
||||
}
|
||||
}, 0)
|
||||
}}
|
||||
>
|
||||
<ServerHealthIndicator health={health[key]} />
|
||||
|
||||
9
packages/app/src/utils/server-switch.tsx
Normal file
9
packages/app/src/utils/server-switch.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createSignal } from "solid-js"
|
||||
|
||||
// Global flag used to paint a full-window splash overlay while a server
|
||||
// swap is in progress. ServerKey's keyed <Show> remount is a big
|
||||
// synchronous cascade (dispose + remount of the entire app subtree) that
|
||||
// can freeze the UI for several seconds; setting this true before the
|
||||
// swap and false after lets us render an overlay above the ServerKey
|
||||
// boundary so the freeze has visual feedback instead of looking stuck.
|
||||
export const [serverSwitching, setServerSwitching] = createSignal(false)
|
||||
Reference in New Issue
Block a user