perf(ui): resync only after reconnects

The first connected status from a newly started OpenCode stream waited for initial hydration and then repeated the same session, pending-request, catalog, metadata, and filesystem loads. This made every normal startup pay for a reconnect recovery pass.

Track only genuine error or unexpected-disconnect transitions as requiring recovery. Initial connecting-to-connected startup and deliberate workspace shutdown no longer resync, while failed streams still receive one coalesced authoritative recovery after reconnection.

Validated with 10 targeted connection and readiness tests, the UI typecheck, 537 UI tests, and git diff checks.
This commit is contained in:
Pascal André 2026-08-18 12:53:57 +02:00
parent cfb970dc56
commit b2a97a84eb
No known key found for this signature in database
3 changed files with 48 additions and 2 deletions

View file

@ -0,0 +1,23 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { ConnectionResyncGate } from "./connection-resync-gate.ts"
describe("connection resync gate", () => {
it("skips initial connection and resyncs once after a stream failure", () => {
const gate = new ConnectionResyncGate()
assert.equal(gate.observe("instance", "connecting"), false)
assert.equal(gate.observe("instance", "connected"), false)
assert.equal(gate.observe("instance", "error"), false)
assert.equal(gate.observe("instance", "connected"), true)
assert.equal(gate.observe("instance", "connected"), false)
})
it("does not resync a deliberately stopped or cleared workspace", () => {
const gate = new ConnectionResyncGate()
assert.equal(gate.observe("instance", "disconnected", "workspace stopped"), false)
assert.equal(gate.observe("instance", "connected"), false)
gate.observe("instance", "disconnected")
gate.clear("instance")
assert.equal(gate.observe("instance", "connected"), false)
})
})

View file

@ -0,0 +1,18 @@
import type { InstanceStreamStatus } from "../../../server/src/api-types"
export class ConnectionResyncGate {
private readonly pending = new Set<string>()
observe(instanceId: string, status: InstanceStreamStatus, reason?: string): boolean {
if (status === "error" || (status === "disconnected" && reason !== "workspace stopped")) {
this.pending.add(instanceId)
return false
}
if (status !== "connected") return false
return this.pending.delete(instanceId)
}
clear(instanceId: string): void {
this.pending.delete(instanceId)
}
}

View file

@ -32,6 +32,7 @@ import { getRootClient } from "./opencode-client"
import { buildV2RequestLocations } from "./request-locations"
import { fetchCommands, clearCommands } from "./commands"
import { getInstanceRefreshTargets, type InstanceRefreshTarget } from "./instance-invalidation"
import { ConnectionResyncGate } from "./connection-resync-gate"
import { serverSettings } from "./preferences"
import {
reconcileSessionPendingState,
@ -271,6 +272,7 @@ const connectionResyncs = new TrailingResyncCoordinator(
log.warn("Failed to resync sessions after instance connection", { instanceId, error })
},
)
const connectionResyncGate = new ConnectionResyncGate()
function resyncConnectedInstance(instanceId: string): void {
void connectionResyncs.request(instanceId)
@ -313,11 +315,13 @@ function refreshVolatileInstanceState(
}
serverEvents.on("instance.eventStatus", (event) => {
if (event.type !== "instance.eventStatus" || event.status !== "connected") return
if (event.type !== "instance.eventStatus") return
const shouldResync = connectionResyncGate.observe(event.instanceId, event.status, event.reason)
if (event.status !== "connected") return
if (disconnectedInstance()?.id === event.instanceId) {
setDisconnectedInstance(null)
}
resyncConnectedInstance(event.instanceId)
if (shouldResync) resyncConnectedInstance(event.instanceId)
})
function createRestoreCreationRequestId(): string {
@ -1064,6 +1068,7 @@ function updateInstance(id: string, updates: Partial<Instance>) {
}
function removeInstance(id: string, options: { authoritative?: boolean } = {}) {
connectionResyncGate.clear(id)
const removedInstance = instances().get(id)
const removedOccurrence = removedInstance
? Array.from(instances().values())