diff --git a/.playwright-mcp/workspace-working.png b/.playwright-mcp/workspace-working.png new file mode 100644 index 0000000..6aab723 Binary files /dev/null and b/.playwright-mcp/workspace-working.png differ diff --git a/changelog.md b/changelog.md index df768f3..d50c481 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-12-30 - 3.13.1 - fix(webcontainer) +prevent double initialization and race conditions when booting WebContainer and loading editor workspace/file tree + +- Add loadTreeStarted flag in dees-editor-filetree to avoid double-loading the file tree and reset it on refresh or on error to allow retries. +- Add initializationStarted flag in dees-editor-workspace to prevent duplicate workspace initialization and reset it on initialization failure to allow retry. +- Make WebContainerEnvironment use a shared singleton container and a bootPromise so only one WebContainer boot runs per page; instances wait for an ongoing boot instead of booting again. +- Reset bootPromise/sharedContainer on boot failure and clear them on teardown so subsequent attempts can retry cleanly. + ## 2025-12-30 - 3.13.0 - feat(editor/runtime) Replace bare editor with Monaco-based editor and add runtime + workspace/filetree integration diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 1678fd2..ac4f29d 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.13.0', + version: '3.13.1', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-editor/dees-editor-filetree/dees-editor-filetree.ts b/ts_web/elements/00group-editor/dees-editor-filetree/dees-editor-filetree.ts index 9980685..714a8fa 100644 --- a/ts_web/elements/00group-editor/dees-editor-filetree/dees-editor-filetree.ts +++ b/ts_web/elements/00group-editor/dees-editor-filetree/dees-editor-filetree.ts @@ -55,6 +55,7 @@ export class DeesEditorFiletree extends DeesElement { accessor errorMessage: string = ''; private expandedPaths: Set = new Set(); + private loadTreeStarted: boolean = false; public static styles = [ themeDefaultStyles, @@ -484,6 +485,10 @@ export class DeesEditorFiletree extends DeesElement { private async loadTree() { if (!this.executionEnvironment) return; + // Prevent double loading on initial render + if (this.loadTreeStarted) return; + this.loadTreeStarted = true; + this.isLoading = true; this.errorMessage = ''; @@ -503,6 +508,8 @@ export class DeesEditorFiletree extends DeesElement { } catch (error) { this.errorMessage = `Failed to load files: ${error}`; console.error('Failed to load file tree:', error); + // Reset flag to allow retry + this.loadTreeStarted = false; } finally { this.isLoading = false; } @@ -521,6 +528,7 @@ export class DeesEditorFiletree extends DeesElement { public async refresh() { this.expandedPaths.clear(); + this.loadTreeStarted = false; // Reset to allow loading await this.loadTree(); } diff --git a/ts_web/elements/00group-editor/dees-editor-workspace/dees-editor-workspace.ts b/ts_web/elements/00group-editor/dees-editor-workspace/dees-editor-workspace.ts index 818a171..b7743a7 100644 --- a/ts_web/elements/00group-editor/dees-editor-workspace/dees-editor-workspace.ts +++ b/ts_web/elements/00group-editor/dees-editor-workspace/dees-editor-workspace.ts @@ -74,6 +74,7 @@ export class DeesEditorWorkspace extends DeesElement { accessor isInitializing: boolean = true; private editorElement: DeesEditorMonaco | null = null; + private initializationStarted: boolean = false; public static styles = [ themeDefaultStyles, @@ -442,6 +443,10 @@ export class DeesEditorWorkspace extends DeesElement { private async initializeWorkspace() { if (!this.executionEnvironment) return; + // Prevent double initialization + if (this.initializationStarted) return; + this.initializationStarted = true; + this.isInitializing = true; try { @@ -450,6 +455,8 @@ export class DeesEditorWorkspace extends DeesElement { } } catch (error) { console.error('Failed to initialize workspace:', error); + // Reset flag to allow retry + this.initializationStarted = false; } finally { this.isInitializing = false; } diff --git a/ts_web/elements/00group-runtime/environments/WebContainerEnvironment.ts b/ts_web/elements/00group-runtime/environments/WebContainerEnvironment.ts index f348321..c73e9c7 100644 --- a/ts_web/elements/00group-runtime/environments/WebContainerEnvironment.ts +++ b/ts_web/elements/00group-runtime/environments/WebContainerEnvironment.ts @@ -6,7 +6,10 @@ import type { IExecutionEnvironment, IFileEntry, IProcessHandle } from '../inter * Runs Node.js and shell commands in the browser using WebContainer API. */ export class WebContainerEnvironment implements IExecutionEnvironment { - private container: webcontainer.WebContainer | null = null; + // Static shared state - WebContainer only allows ONE boot per page + private static sharedContainer: webcontainer.WebContainer | null = null; + private static bootPromise: Promise | null = null; + private _ready: boolean = false; public readonly type = 'webcontainer' as const; @@ -15,11 +18,29 @@ export class WebContainerEnvironment implements IExecutionEnvironment { return this._ready; } + private get container(): webcontainer.WebContainer | null { + return WebContainerEnvironment.sharedContainer; + } + // ============ Lifecycle ============ public async init(): Promise { - if (this._ready && this.container) { - return; // Already initialized + // Already initialized (this instance) + if (this._ready && WebContainerEnvironment.sharedContainer) { + return; + } + + // If boot is in progress (by any instance), wait for it + if (WebContainerEnvironment.bootPromise) { + await WebContainerEnvironment.bootPromise; + this._ready = true; + return; + } + + // If already booted by another instance, just mark ready + if (WebContainerEnvironment.sharedContainer) { + this._ready = true; + return; } // Check if SharedArrayBuffer is available (required for WebContainer) @@ -32,14 +53,24 @@ export class WebContainerEnvironment implements IExecutionEnvironment { ); } - this.container = await webcontainer.WebContainer.boot(); - this._ready = true; + // Start boot process + WebContainerEnvironment.bootPromise = webcontainer.WebContainer.boot(); + + try { + WebContainerEnvironment.sharedContainer = await WebContainerEnvironment.bootPromise; + this._ready = true; + } catch (error) { + // Reset promise on failure so retry is possible + WebContainerEnvironment.bootPromise = null; + throw error; + } } public async destroy(): Promise { - if (this.container) { - this.container.teardown(); - this.container = null; + if (WebContainerEnvironment.sharedContainer) { + WebContainerEnvironment.sharedContainer.teardown(); + WebContainerEnvironment.sharedContainer = null; + WebContainerEnvironment.bootPromise = null; this._ready = false; } }