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
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"name": "openchamber",
|
"name": "openchamber",
|
||||||
"displayName": "OpenChamber",
|
"displayName": "OpenChamber",
|
||||||
"description": "%extension.description%",
|
"description": "%extension.description%",
|
||||||
"version": "1.19.0",
|
"version": "1.19.1",
|
||||||
"publisher": "fedaykindev",
|
"publisher": "fedaykindev",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -23,6 +23,33 @@ export class AgentManagerPanelProvider {
|
|||||||
private _sseStreams = new Map<string, AbortController>();
|
private _sseStreams = new Map<string, AbortController>();
|
||||||
private readonly _webviewDevServerUrl: string | 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(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(
|
constructor(
|
||||||
private readonly _context: vscode.ExtensionContext,
|
private readonly _context: vscode.ExtensionContext,
|
||||||
private readonly _extensionUri: vscode.Uri,
|
private readonly _extensionUri: vscode.Uri,
|
||||||
@@ -64,6 +91,9 @@ export class AgentManagerPanelProvider {
|
|||||||
|
|
||||||
// Send cached connection status
|
// Send cached connection status
|
||||||
this._sendCachedState();
|
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
|
// Handle panel disposal
|
||||||
this._panel.onDidDispose(() => {
|
this._panel.onDidDispose(() => {
|
||||||
@@ -126,6 +156,13 @@ export class AgentManagerPanelProvider {
|
|||||||
|
|
||||||
// Send to webview if it exists
|
// Send to webview if it exists
|
||||||
this._sendCachedState();
|
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 {
|
public notifySettingsSynced(settings: unknown): void {
|
||||||
|
|||||||
@@ -58,6 +58,33 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
private readonly _MESSAGE_TIMEOUT = 5000; // 5 seconds
|
private readonly _MESSAGE_TIMEOUT = 5000; // 5 seconds
|
||||||
private readonly _MAX_RETRIES = 3;
|
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 {
|
private _createMessageId(): string {
|
||||||
return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
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)
|
// Send cached connection status and API URL (may have been set before webview was resolved)
|
||||||
this._sendCachedState();
|
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
|
// Send current active editor file state to the new webview
|
||||||
this._lastActiveEditorFilePayload = null;
|
this._lastActiveEditorFilePayload = null;
|
||||||
@@ -185,6 +215,13 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// Send to webview if it exists
|
// Send to webview if it exists
|
||||||
this._sendCachedState();
|
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) {
|
public addTextToInput(text: string) {
|
||||||
|
|||||||
@@ -49,6 +49,29 @@ export class SessionEditorPanelProvider {
|
|||||||
private _lastActiveEditorFilePayload: ActiveEditorFilePayload | null = null;
|
private _lastActiveEditorFilePayload: ActiveEditorFilePayload | null = null;
|
||||||
private readonly _webviewDevServerUrl: string | 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(
|
constructor(
|
||||||
private readonly _context: vscode.ExtensionContext,
|
private readonly _context: vscode.ExtensionContext,
|
||||||
private readonly _extensionUri: vscode.Uri,
|
private readonly _extensionUri: vscode.Uri,
|
||||||
@@ -116,6 +139,9 @@ export class SessionEditorPanelProvider {
|
|||||||
|
|
||||||
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
||||||
this._sendCachedStateToPanel(state);
|
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();
|
void this._broadcastActiveEditorFile();
|
||||||
|
|
||||||
panel.onDidDispose(() => {
|
panel.onDidDispose(() => {
|
||||||
@@ -187,6 +213,15 @@ export class SessionEditorPanelProvider {
|
|||||||
for (const entry of this._panels.values()) {
|
for (const entry of this._panels.values()) {
|
||||||
this._sendCachedStateToPanel(entry);
|
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 {
|
public notifySettingsSynced(settings: unknown): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user