diff --git a/modules/rsocials/components/folk-campaign-manager.ts b/modules/rsocials/components/folk-campaign-manager.ts index 5ef0806..c836e98 100644 --- a/modules/rsocials/components/folk-campaign-manager.ts +++ b/modules/rsocials/components/folk-campaign-manager.ts @@ -18,7 +18,7 @@ export class FolkCampaignManager extends HTMLElement { static get observedAttributes() { return ['space']; } connectedCallback() { - this.attachShadow({ mode: 'open' }); + if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); this._space = this.getAttribute('space') || 'demo'; // Start with demo campaign this._campaigns = [{ ...MYCOFI_CAMPAIGN, createdAt: Date.now(), updatedAt: Date.now() }]; diff --git a/modules/rsocials/components/folk-thread-builder.ts b/modules/rsocials/components/folk-thread-builder.ts index 65a0a55..b8bc318 100644 --- a/modules/rsocials/components/folk-thread-builder.ts +++ b/modules/rsocials/components/folk-thread-builder.ts @@ -30,6 +30,7 @@ export class FolkThreadBuilder extends HTMLElement { private _tweetImages: Record = {}; private _autoSaveTimer: ReturnType | null = null; private _offlineUnsub: (() => void) | null = null; + private _offlineReady: Promise | null = null; private _tweetImageUploadIdx: string | null = null; // SVG icons @@ -44,7 +45,9 @@ export class FolkThreadBuilder extends HTMLElement { static get observedAttributes() { return ['space', 'thread-id', 'mode']; } connectedCallback() { - this.attachShadow({ mode: 'open' }); + if (!this.shadowRoot) { + this.attachShadow({ mode: 'open' }); + } this._space = this.getAttribute('space') || 'demo'; this._threadId = this.getAttribute('thread-id') || null; this._mode = (this.getAttribute('mode') as any) || 'new'; @@ -60,7 +63,7 @@ export class FolkThreadBuilder extends HTMLElement { this.render(); if (this._space !== 'demo') { - this.subscribeOffline(); + this._offlineReady = this.subscribeOffline(); } } @@ -112,22 +115,35 @@ export class FolkThreadBuilder extends HTMLElement { return (window as any).__rspaceOfflineRuntime; } - private saveToAutomerge(thread: ThreadData) { + private async saveToAutomerge(thread: ThreadData) { const runtime = this.getRuntime(); if (!runtime?.isInitialized) return; - const docId = socialsDocId(this._space) as DocumentId; - runtime.change(docId, `Save thread ${thread.title || thread.id}`, (d: SocialsDoc) => { - if (!d.threads) d.threads = {} as any; - thread.updatedAt = Date.now(); - d.threads[thread.id] = thread; - }); + // Ensure the document is open before writing + if (this._offlineReady) { + await this._offlineReady; + } + + try { + const docId = socialsDocId(this._space) as DocumentId; + runtime.change(docId, `Save thread ${thread.title || thread.id}`, (d: SocialsDoc) => { + if (!d.threads) d.threads = {} as any; + thread.updatedAt = Date.now(); + d.threads[thread.id] = thread; + }); + } catch (e: any) { + console.error('[thread-builder] Failed to save:', e.message); + } } - private deleteFromAutomerge(id: string) { + private async deleteFromAutomerge(id: string) { const runtime = this.getRuntime(); if (!runtime?.isInitialized) return; + if (this._offlineReady) { + await this._offlineReady; + } + const docId = socialsDocId(this._space) as DocumentId; runtime.change(docId, `Delete thread ${id}`, (d: SocialsDoc) => { if (d.threads?.[id]) delete d.threads[id]; diff --git a/modules/rsocials/components/folk-thread-gallery.ts b/modules/rsocials/components/folk-thread-gallery.ts index 38c9789..5682f32 100644 --- a/modules/rsocials/components/folk-thread-gallery.ts +++ b/modules/rsocials/components/folk-thread-gallery.ts @@ -18,7 +18,7 @@ export class FolkThreadGallery extends HTMLElement { static get observedAttributes() { return ['space']; } connectedCallback() { - this.attachShadow({ mode: 'open' }); + if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); this._space = this.getAttribute('space') || 'demo'; this.render(); if (this._space === 'demo') {