diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index ec85e4c4c18..7aaae278908 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1883,46 +1883,75 @@ function ChatViewContent(props: ChatViewProps) { ); const systemComposerBannerItems = useMemo(() => { const items: ComposerBannerStackItem[] = []; - const resumingServerUpdate = - serverUpdateState.status === "running" && serverUpdateState.stage === "resuming"; - if (activeEnvironmentUnavailableState && !resumingServerUpdate) { - const connection = activeEnvironmentUnavailableState.connection; - const isReconnecting = - connection.phase === "connecting" || connection.phase === "reconnecting"; - items.push({ - id: `environment-unavailable:${activeEnvironmentUnavailableState.environmentId}`, - variant: connection.phase === "error" ? "error" : "warning", - icon: , - title: `${activeEnvironmentUnavailableState.label}: ${connectionStatusTitle(connection)}`, - description: - connection.error ?? - "Reconnect this environment before sending messages or running actions.", - actions: ( - <> - - - - ), - }); + const updateRunning = serverUpdateState.status === "running"; + const unavailableConnection = activeEnvironmentUnavailableState?.connection ?? null; + const environmentReconnecting = + unavailableConnection !== null && + (unavailableConnection.phase === "connecting" || + unavailableConnection.phase === "reconnecting"); + // Reconnecting to a version-skewed server with no update in flight + // usually means the server is restarting mid-update and a refresh wiped + // the in-memory update state. Fold the reconnect and version banners + // into one calm line instead of stacking "Failed to connect" on + // "versions differ". A failed update never folds: its error and retry + // action must stay visible. + const reconnectingThroughVersionSkew = + serverUpdateState.status === "idle" && environmentReconnecting && versionMismatch !== null; + // While an update runs, transient connect blips are expected (the server + // restarts) and the update banner already shows progress. Hard failure + // phases still surface so the Reconnect action stays reachable. + const suppressUnavailableBanner = updateRunning && environmentReconnecting; + if (activeEnvironmentUnavailableState && unavailableConnection && !suppressUnavailableBanner) { + if (reconnectingThroughVersionSkew) { + items.push({ + id: `environment-unavailable:${activeEnvironmentUnavailableState.environmentId}`, + variant: "default", + icon: ( +