Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 236e11bad44420ff62d6eb5067bebd9bee14ba7e)
+++ src/services/ActivityPubService.js	(revision 6dd1a3f0ef839a8ce97dec8cc96e5b00ae93a8bc)
@@ -1839,7 +1839,14 @@
   const media = JSON.stringify((note.images || []).map((u) => ({ url: u, type: 'image/jpeg' })));
   try {
-    tlStmts().ins.run(id, slug, note.actor_uri || '', note.actor_name || '', note.actor_handle || '',
+    const r = tlStmts().ins.run(id, slug, note.actor_uri || '', note.actor_name || '', note.actor_handle || '',
       note.actor_icon || '', note.actor_url || '', note.content || '', note.url || null,
       new Date().toISOString(), media, note.sensitive ? 1 : 0, note.cw || null);
+    if (!r.changes) {
+      // Row already cached (INSERT OR IGNORE) → refresh it with the freshly
+      // resolved note. Without this a row cached without its cover (or with
+      // stale content) stayed stale forever — even boosting again didn't heal it.
+      db.prepare('UPDATE ap_timeline SET content = ?, media_json = ?, nsfw = ?, cw = ?, url = COALESCE(?, url) WHERE slug = ? AND id = ?')
+        .run(note.content || '', media, note.sensitive ? 1 : 0, note.cw || null, note.url || null, slug, id);
+    }
   } catch { /* ignore */ }
   markBoosted(slug, id);
