Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision b7d4458e21f8eef0c969874d5d571662936d8702)
+++ src/services/ActivityPubService.js	(revision 9e4e4ff6bb278ba6345cdd8e21bb9c6fd0d8f0c6)
@@ -1174,5 +1174,5 @@
 // during a flux window, e.g. a fleet-wide update), and drops notes that are gone
 // (404/410). Bump SELFHEAL_VERSION only on a release that warrants a re-sync.
-const SELFHEAL_VERSION = 1;
+const SELFHEAL_VERSION = 2;
 async function fetchNoteAP(url) {
   try {
@@ -1200,5 +1200,5 @@
     if (cur >= SELFHEAL_VERSION) return; // already healed for this version — skip on normal boots
     let rows = [];
-    try { rows = db.prepare('SELECT id, content, media_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ }
+    try { rows = db.prepare('SELECT id, content, media_json, nsfw, cw FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ }
     let healed = 0;
     for (const r of rows) {
@@ -1209,6 +1209,8 @@
         const html = HtmlSanitizerService.sanitize(note.content || '');
         const media = mediaFromNote(note);
-        if ((html && html !== r.content) || media !== (r.media_json || '[]')) {
-          db.prepare('UPDATE ap_timeline SET content = ?, media_json = ? WHERE id = ?').run(html || r.content, media, r.id);
+        const nsfw = note.sensitive ? 1 : 0;   // re-sync NSFW/sensitive + CW onto already-cached posts
+        const cw = note.summary || null;
+        if ((html && html !== r.content) || media !== (r.media_json || '[]') || nsfw !== (r.nsfw || 0) || (cw || '') !== (r.cw || '')) {
+          db.prepare('UPDATE ap_timeline SET content = ?, media_json = ?, nsfw = ?, cw = ? WHERE id = ?').run(html || r.content, media, nsfw, cw, r.id);
           healed++;
         }
