mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-18 13:53:36 +00:00
fix(tui): suppress transient reconnect overlay flashes (#34924)
This commit is contained in:
parent
f016392368
commit
7ec7413fdb
4 changed files with 98 additions and 2 deletions
|
|
@ -1116,6 +1116,34 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
return render({ params: route.data.data })
|
||||
})
|
||||
|
||||
// Suppress the full-screen reconnecting overlay for transient disconnects (initial startup, host
|
||||
// reload, sub-second event-stream blips). After the first successful connect, show it only once the
|
||||
// connection has been lost for a full second; before the first connect give a longer grace period so
|
||||
// startup never flashes it, but a server that dies before ever connecting still surfaces instead of
|
||||
// leaving a silent empty app. Hide it immediately the moment status leaves "connecting".
|
||||
const [showReconnecting, setShowReconnecting] = createSignal(false)
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
|
||||
createEffect(() => {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = undefined
|
||||
}
|
||||
if (sdk.connection.status() !== "connecting") {
|
||||
setShowReconnecting(false)
|
||||
return
|
||||
}
|
||||
reconnectTimer = setTimeout(
|
||||
() => {
|
||||
reconnectTimer = undefined
|
||||
setShowReconnecting(true)
|
||||
},
|
||||
sdk.connection.connectedOnce() ? 1000 : 5000,
|
||||
).unref()
|
||||
})
|
||||
onCleanup(() => {
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer)
|
||||
})
|
||||
|
||||
return (
|
||||
<box
|
||||
width={dimensions().width}
|
||||
|
|
@ -1161,7 +1189,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
<Show when={!startup.skipInitialLoading}>
|
||||
<StartupLoading ready={ready} />
|
||||
</Show>
|
||||
<Show when={sdk.connection.status() === "connecting"}>
|
||||
<Show when={showReconnecting()}>
|
||||
<Reconnecting attempt={sdk.connection.attempt()} error={sdk.connection.error()} />
|
||||
</Show>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -636,6 +636,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
error() {
|
||||
return sdk.connection.error()
|
||||
},
|
||||
connectedOnce() {
|
||||
return sdk.connection.connectedOnce()
|
||||
},
|
||||
},
|
||||
session: {
|
||||
list() {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||
status: SDKConnectionStatus
|
||||
attempt: number
|
||||
error?: string
|
||||
connectedOnce: boolean
|
||||
}>({
|
||||
status: "connecting",
|
||||
attempt: 0,
|
||||
connectedOnce: false,
|
||||
})
|
||||
let stream: AbortController | undefined
|
||||
let pending: Promise<void> | undefined
|
||||
|
|
@ -68,7 +70,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||
clearTimeout(timeout)
|
||||
attempt = 0
|
||||
events.emit(first.value.type, first.value)
|
||||
setConnection({ status: "connected", attempt: 0, error: undefined })
|
||||
setConnection({ status: "connected", attempt: 0, error: undefined, connectedOnce: true })
|
||||
connected()
|
||||
while (!abort.signal.aborted && !controller.signal.aborted) {
|
||||
const event = await iterator.next()
|
||||
|
|
@ -140,6 +142,9 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||
error() {
|
||||
return connection.error
|
||||
},
|
||||
connectedOnce() {
|
||||
return connection.connectedOnce
|
||||
},
|
||||
},
|
||||
reload,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,66 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("connectedOnce is false until first connect and persists across disconnect", async () => {
|
||||
const encoder = new TextEncoder()
|
||||
let stream: ReadableStreamDefaultController<Uint8Array> | undefined
|
||||
const eventResponse = () =>
|
||||
new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
stream = controller
|
||||
},
|
||||
}),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
const connect = () =>
|
||||
stream?.enqueue(
|
||||
encoder.encode(`data: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n`),
|
||||
)
|
||||
const disconnect = () => {
|
||||
stream?.close()
|
||||
stream = undefined
|
||||
}
|
||||
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === "/api/event") return eventResponse()
|
||||
})
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
||||
function Probe() {
|
||||
data = useData()
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
</DataProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await wait(() => stream !== undefined)
|
||||
expect(data.connection.status()).toBe("connecting")
|
||||
expect(data.connection.connectedOnce()).toBe(false)
|
||||
|
||||
connect()
|
||||
await wait(() => data.connection.status() === "connected")
|
||||
expect(data.connection.connectedOnce()).toBe(true)
|
||||
|
||||
disconnect()
|
||||
await wait(() => data.connection.status() === "connecting")
|
||||
expect(data.connection.connectedOnce()).toBe(true)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("tracks session status from active sessions and execution events", async () => {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch((url) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue