From 0e39cfb79a8b60f6c15b72738ca4ec71873cab65 Mon Sep 17 00:00:00 2001 From: vinciyan Date: Wed, 19 Aug 2026 01:46:46 +0000 Subject: [PATCH] fix: resend connectionStatus at staggered delays to prevent stuck loading screen on slow networks The webview only leaves its initial loading screen once it receives a connectionStatus=connected postMessage. VS Code drops postMessage calls made before the webview's acquireVsCodeApi bridge is ready, so on slow/remote networks the single send in _sendCachedState() can be lost forever, leaving the sidebar stuck on #initial-loading. Re-send connectionStatus at staggered delays (0.5s-20s) when connected, in ChatViewProvider, AgentManagerPanelProvider and SessionEditorPanelProvider. Stops automatically when the view/panel is disposed or replaced. Message is idempotent. Fixes #2996 --- packages/vscode/package.json | 4 +- .../vscode/src/AgentManagerPanelProvider.ts | 37 +++++++++++++++++++ packages/vscode/src/ChatViewProvider.ts | 37 +++++++++++++++++++ .../vscode/src/SessionEditorPanelProvider.ts | 35 ++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 1eb579fa..e7e7ce77 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -2,7 +2,7 @@ "name": "openchamber", "displayName": "OpenChamber", "description": "%extension.description%", - "version": "1.19.0", + "version": "1.19.1", "publisher": "fedaykindev", "private": true, "repository": { @@ -252,4 +252,4 @@ "react-dom": "^19.1.1", "yaml": "^2.8.1" } -} +} \ No newline at end of file diff --git a/packages/vscode/src/AgentManagerPanelProvider.ts b/packages/vscode/src/AgentManagerPanelProvider.ts index 566f9679..e1b068c6 100644 --- a/packages/vscode/src/AgentManagerPanelProvider.ts +++ b/packages/vscode/src/AgentManagerPanelProvider.ts @@ -23,6 +23,33 @@ export class AgentManagerPanelProvider { private _sseStreams = new Map(); private readonly _webviewDevServerUrl: string | null; + /** + * The webview only leaves its initial loading screen once it receives a + * `connectionStatus: connected` message. VS Code drops postMessage calls + * made before the webview's acquireVsCodeApi bridge is ready (common in + * code-server / slow or flaky networks), so a single send can be lost + * forever. Re-send at staggered delays until the target panel is replaced. + */ + private _scheduleCachedStateRetries(targetPanel: vscode.WebviewPanel | undefined): void { + if (this._cachedStatus !== 'connected') { + return; + } + const panel = targetPanel ?? this._panel; + if (!panel) { + return; + } + const delaysMs = [500, 1500, 3500, 7000, 12000, 20000]; + for (const delayMs of delaysMs) { + setTimeout(() => { + // Only re-send if this exact panel is still the active one. + if (this._panel !== panel) { + return; + } + this._sendCachedState(); + }, delayMs); + } + } + constructor( private readonly _context: vscode.ExtensionContext, private readonly _extensionUri: vscode.Uri, @@ -64,6 +91,9 @@ export class AgentManagerPanelProvider { // Send cached connection status this._sendCachedState(); + // The webview bridge may not be ready yet; keep re-sending so a dropped + // `connectionStatus` can never leave the webview stuck on its loading screen. + this._scheduleCachedStateRetries(this._panel); // Handle panel disposal this._panel.onDidDispose(() => { @@ -126,6 +156,13 @@ export class AgentManagerPanelProvider { // Send to webview if it exists this._sendCachedState(); + + // When we become connected, keep re-sending at staggered delays so the + // webview cannot miss the transition (postMessage is dropped if the + // webview bridge is not ready yet). + if (status === 'connected') { + this._scheduleCachedStateRetries(this._panel); + } } public notifySettingsSynced(settings: unknown): void { diff --git a/packages/vscode/src/ChatViewProvider.ts b/packages/vscode/src/ChatViewProvider.ts index e6e7852c..c55d2563 100644 --- a/packages/vscode/src/ChatViewProvider.ts +++ b/packages/vscode/src/ChatViewProvider.ts @@ -58,6 +58,33 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { private readonly _MESSAGE_TIMEOUT = 5000; // 5 seconds private readonly _MAX_RETRIES = 3; + /** + * The webview only leaves its initial loading screen once it receives a + * `connectionStatus: connected` message. VS Code drops postMessage calls + * made before the webview's acquireVsCodeApi bridge is ready (common in + * code-server / slow or flaky networks), so a single send can be lost + * forever. Re-send at staggered delays until the target view is replaced. + */ + private _scheduleCachedStateRetries(targetView: vscode.WebviewView | undefined): void { + if (this._cachedStatus !== 'connected') { + return; + } + const view = targetView ?? this._view; + if (!view) { + return; + } + const delaysMs = [500, 1500, 3500, 7000, 12000, 20000]; + for (const delayMs of delaysMs) { + setTimeout(() => { + // Only re-send if this exact view is still the active one. + if (this._view !== view) { + return; + } + this._sendCachedState(); + }, delayMs); + } + } + private _createMessageId(): string { return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; } @@ -102,6 +129,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { // Send cached connection status and API URL (may have been set before webview was resolved) this._sendCachedState(); + // The webview bridge may not be ready yet; keep re-sending so a dropped + // `connectionStatus` can never leave the webview stuck on its loading screen. + this._scheduleCachedStateRetries(webviewView); // Send current active editor file state to the new webview this._lastActiveEditorFilePayload = null; @@ -185,6 +215,13 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { // Send to webview if it exists this._sendCachedState(); + + // When we become connected, keep re-sending at staggered delays so the + // webview cannot miss the transition (postMessage is dropped if the + // webview bridge is not ready yet). + if (status === 'connected') { + this._scheduleCachedStateRetries(this._view); + } } public addTextToInput(text: string) { diff --git a/packages/vscode/src/SessionEditorPanelProvider.ts b/packages/vscode/src/SessionEditorPanelProvider.ts index 76a82b02..8c55e401 100644 --- a/packages/vscode/src/SessionEditorPanelProvider.ts +++ b/packages/vscode/src/SessionEditorPanelProvider.ts @@ -49,6 +49,29 @@ export class SessionEditorPanelProvider { private _lastActiveEditorFilePayload: ActiveEditorFilePayload | null = null; private readonly _webviewDevServerUrl: string | null; + /** + * The webview only leaves its initial loading screen once it receives a + * `connectionStatus: connected` message. VS Code drops postMessage calls + * made before the webview's acquireVsCodeApi bridge is ready (common in + * code-server / slow or flaky networks), so a single send can be lost + * forever. Re-send at staggered delays until the target panel is replaced. + */ + private _scheduleCachedStateRetries(panelId: string, entry: SessionPanelState): void { + if (this._cachedStatus !== 'connected') { + return; + } + const delaysMs = [500, 1500, 3500, 7000, 12000, 20000]; + for (const delayMs of delaysMs) { + setTimeout(() => { + // Only re-send if this exact panel is still registered. + if (this._panels.get(panelId)?.panel !== entry.panel) { + return; + } + this._sendCachedStateToPanel(entry); + }, delayMs); + } + } + constructor( private readonly _context: vscode.ExtensionContext, private readonly _extensionUri: vscode.Uri, @@ -116,6 +139,9 @@ export class SessionEditorPanelProvider { void this.updateTheme(vscode.window.activeColorTheme.kind); this._sendCachedStateToPanel(state); + // The webview bridge may not be ready yet; keep re-sending so a dropped + // `connectionStatus` can never leave the webview stuck on its loading screen. + this._scheduleCachedStateRetries(panelId, state); void this._broadcastActiveEditorFile(); panel.onDidDispose(() => { @@ -187,6 +213,15 @@ export class SessionEditorPanelProvider { for (const entry of this._panels.values()) { this._sendCachedStateToPanel(entry); } + + // When we become connected, keep re-sending at staggered delays so the + // webview cannot miss the transition (postMessage is dropped if the + // webview bridge is not ready yet). + if (status === 'connected') { + for (const [panelId, entry] of this._panels.entries()) { + this._scheduleCachedStateRetries(panelId, entry); + } + } } public notifySettingsSynced(settings: unknown): void {