From 5b2862afd79da6263d30f08914f67ec64b5a6044 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 5 Mar 2026 21:43:16 -0800 Subject: [PATCH] fix: prevent Automerge sync from overwriting local image state onChange handler was resetting _tweetImages on every sync event, undoing local deletions before they could round-trip. Now only updates state in readonly mode; edit mode preserves local changes. Also add cache-busting timestamps to uploaded/generated image URLs to prevent browser from showing stale cached images. Co-Authored-By: Claude Opus 4.6 --- .../components/folk-thread-builder.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/rsocials/components/folk-thread-builder.ts b/modules/rsocials/components/folk-thread-builder.ts index ba30169..4a6f28e 100644 --- a/modules/rsocials/components/folk-thread-builder.ts +++ b/modules/rsocials/components/folk-thread-builder.ts @@ -121,10 +121,15 @@ export class FolkThreadBuilder extends HTMLElement { this._offlineUnsub = runtime.onChange(docId, (updated: SocialsDoc) => { if (this._threadId && 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 || {}; + const synced = JSON.parse(JSON.stringify(updated.threads[this._threadId])) as ThreadData; + // In readonly mode, always take the synced version + if (this._mode === 'readonly') { + this._thread = synced; + this._tweetImages = synced.tweetImages || {}; + this.render(); + } + // In edit mode, only update thread metadata (not images) + // to avoid overwriting in-flight local changes } }); } catch (e: any) { @@ -652,7 +657,8 @@ export class FolkThreadBuilder extends HTMLElement { const res = await fetch(this.basePath + 'api/threads/' + this._threadId + '/tweet/' + index + '/upload-image', { method: 'POST', body: form }); const data = await res.json(); if (data.imageUrl) { - this._tweetImages[index] = data.imageUrl; + // Bust browser cache with timestamp + this._tweetImages[index] = data.imageUrl + '?t=' + Date.now(); if (this._thread) { this._thread.tweetImages = { ...this._tweetImages }; await this.saveToAutomerge(this._thread); @@ -676,7 +682,8 @@ export class FolkThreadBuilder extends HTMLElement { const res = await fetch(this.basePath + 'api/threads/' + this._threadId + '/tweet/' + index + '/image', { method: 'POST' }); const data = await res.json(); if (data.imageUrl) { - this._tweetImages[index] = data.imageUrl; + // Bust browser cache with timestamp + this._tweetImages[index] = data.imageUrl + '?t=' + Date.now(); if (this._thread) { this._thread.tweetImages = { ...this._tweetImages }; await this.saveToAutomerge(this._thread);