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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-05 21:14:46 -08:00
parent 0eb6e8fe42
commit b7111f01ee
1 changed files with 11 additions and 5 deletions

View File

@ -113,14 +113,18 @@ export class FolkThreadBuilder extends HTMLElement {
const doc = await runtime.subscribe(docId, socialsSchema); const doc = await runtime.subscribe(docId, socialsSchema);
if (this._threadId && doc?.threads?.[this._threadId] && !this._thread) { 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._tweetImages = this._thread?.tweetImages || {};
this.render(); this.render();
} }
this._offlineUnsub = runtime.onChange(docId, (updated: SocialsDoc) => { this._offlineUnsub = runtime.onChange(docId, (updated: SocialsDoc) => {
if (this._threadId && updated?.threads?.[this._threadId]) { 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) { } catch (e: any) {
@ -551,9 +555,11 @@ export class FolkThreadBuilder extends HTMLElement {
const thread = doc?.threads?.[id]; const thread = doc?.threads?.[id];
if (!thread) return; if (!thread) return;
this._threadId = thread.id; // Deep-clone to get a plain object (Automerge proxies reject direct writes)
this._thread = thread; const plain = JSON.parse(JSON.stringify(thread)) as ThreadData;
this._tweetImages = thread.tweetImages || {}; this._threadId = plain.id;
this._thread = plain;
this._tweetImages = plain.tweetImages || {};
const sr = this.shadowRoot; const sr = this.shadowRoot;
if (!sr) return; if (!sr) return;