From b7111f01ee0b7b20f007b80d26c5b01917cc6ce4 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 5 Mar 2026 21:14:46 -0800 Subject: [PATCH] fix: deep-clone Automerge proxies to prevent stale image data Automerge proxy objects silently ignore property writes outside change() calls. When this._thread was a proxy, removeTweetImage's assignment to this._thread.tweetImages was silently discarded, causing deleted photos to reappear. Fix by deep-cloning all Automerge reads (subscribeOffline, onChange, loadDraft) so this._thread is always a plain mutable object. Co-Authored-By: Claude Opus 4.6 --- .../rsocials/components/folk-thread-builder.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/rsocials/components/folk-thread-builder.ts b/modules/rsocials/components/folk-thread-builder.ts index 41426a1..ba30169 100644 --- a/modules/rsocials/components/folk-thread-builder.ts +++ b/modules/rsocials/components/folk-thread-builder.ts @@ -113,14 +113,18 @@ export class FolkThreadBuilder extends HTMLElement { const doc = await runtime.subscribe(docId, socialsSchema); if (this._threadId && doc?.threads?.[this._threadId] && !this._thread) { - this._thread = doc.threads[this._threadId]; + // Deep-clone to get a plain object (not an Automerge proxy) + this._thread = JSON.parse(JSON.stringify(doc.threads[this._threadId])); this._tweetImages = this._thread?.tweetImages || {}; this.render(); } this._offlineUnsub = runtime.onChange(docId, (updated: SocialsDoc) => { if (this._threadId && updated?.threads?.[this._threadId]) { - this._thread = updated.threads[this._threadId]; + // Deep-clone to avoid Automerge proxy — direct property writes + // on proxies are silently ignored outside change() calls + this._thread = JSON.parse(JSON.stringify(updated.threads[this._threadId])); + this._tweetImages = this._thread?.tweetImages || {}; } }); } catch (e: any) { @@ -551,9 +555,11 @@ export class FolkThreadBuilder extends HTMLElement { const thread = doc?.threads?.[id]; if (!thread) return; - this._threadId = thread.id; - this._thread = thread; - this._tweetImages = thread.tweetImages || {}; + // Deep-clone to get a plain object (Automerge proxies reject direct writes) + const plain = JSON.parse(JSON.stringify(thread)) as ThreadData; + this._threadId = plain.id; + this._thread = plain; + this._tweetImages = plain.tweetImages || {}; const sr = this.shadowRoot; if (!sr) return;