From a1b50eb0da95a18e9254ba94c9d8a8b6089266cc Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 5 Mar 2026 17:14:06 -0800 Subject: [PATCH] fix: Automerge undefined rejection + thread not synced to server - tweetImages and imageUrl set to null instead of undefined when empty (Automerge rejects undefined as invalid JSON) - Updated ThreadData schema to allow null for optional fields - This was the root cause of the 404 on image generation: saveDraft threw on undefined, so the thread never synced to the server, and the server-side route returned "Thread not found" Co-Authored-By: Claude Opus 4.6 --- modules/rsocials/components/folk-thread-builder.ts | 6 +++--- modules/rsocials/mod.ts | 2 +- modules/rsocials/schemas.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/rsocials/components/folk-thread-builder.ts b/modules/rsocials/components/folk-thread-builder.ts index 89df386..3b988df 100644 --- a/modules/rsocials/components/folk-thread-builder.ts +++ b/modules/rsocials/components/folk-thread-builder.ts @@ -441,8 +441,8 @@ export class FolkThreadBuilder extends HTMLElement { handle: handleInput.value || '@yourhandle', title: titleInput.value || tweets[0].substring(0, 60), tweets, - tweetImages: Object.keys(this._tweetImages).length ? { ...this._tweetImages } : undefined, - imageUrl: this._thread?.imageUrl, + tweetImages: Object.keys(this._tweetImages).length ? { ...this._tweetImages } : null, + imageUrl: this._thread?.imageUrl || null, createdAt: this._thread?.createdAt || Date.now(), updatedAt: Date.now(), }; @@ -716,7 +716,7 @@ export class FolkThreadBuilder extends HTMLElement { await fetch(this.basePath + 'api/threads/' + this._threadId + '/tweet/' + index + '/image', { method: 'DELETE' }); delete this._tweetImages[index]; if (this._thread) { - this._thread.tweetImages = Object.keys(this._tweetImages).length ? { ...this._tweetImages } : undefined; + this._thread.tweetImages = Object.keys(this._tweetImages).length ? { ...this._tweetImages } : null; await this.saveToAutomerge(this._thread); } this.renderPreview(); diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index 04b3492..c086d2f 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -216,7 +216,7 @@ routes.post("/api/threads/:id/upload-image", async (c) => { const ext = safeExtension(file.name); const filename = `thread-${id}.${ext}`; - await deleteOldImage(thread.imageUrl, filename); + if (thread.imageUrl) await deleteOldImage(thread.imageUrl, filename); const buffer = Buffer.from(await file.arrayBuffer()); const imageUrl = await saveUploadedFile(buffer, filename); diff --git a/modules/rsocials/schemas.ts b/modules/rsocials/schemas.ts index 7f0b190..c7656e8 100644 --- a/modules/rsocials/schemas.ts +++ b/modules/rsocials/schemas.ts @@ -17,8 +17,8 @@ export interface ThreadData { handle: string; title: string; tweets: string[]; - imageUrl?: string; - tweetImages?: Record; + imageUrl?: string | null; + tweetImages?: Record | null; createdAt: number; updatedAt: number; }