fix: thread builder save crash — await Automerge doc open before write
Two bugs fixed:
- attachShadow called unconditionally in connectedCallback, crashing on
re-insertion ("Shadow root cannot be created on a host which already
hosts a shadow tree")
- saveToAutomerge/deleteFromAutomerge called runtime.change() before the
async subscribeOffline() had resolved, causing "Document not open" errors
Now tracks the subscribe promise and awaits it before any write operation.
Also guards shadow root creation in gallery and campaign manager components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0318f0a7e1
commit
0abfce2991
|
|
@ -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() }];
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export class FolkThreadBuilder extends HTMLElement {
|
|||
private _tweetImages: Record<string, string> = {};
|
||||
private _autoSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private _offlineUnsub: (() => void) | null = null;
|
||||
private _offlineReady: Promise<void> | 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];
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
Loading…
Reference in New Issue