Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 837fc9c1dc50db6b6a042e84da63ed9989edf2fa)
+++ src/services/ActivityPubService.js	(revision c21197aacb345896212bfa3ea63b7e3264c15466)
@@ -250,5 +250,5 @@
     sensitive: !!post.nsfw,
   };
-  if (post.nsfw) note.summary = 'Gevoelige inhoud';
+  if (post.nsfw) note.summary = post.content_warning || 'Gevoelige inhoud';
   if (attachment.length) note.attachment = attachment;
   // Playable-audio posts suppress the cover attachment (player card). Still expose
@@ -708,5 +708,5 @@
         // the timeline).
         for (const s of subs) {
-          tlStmts().ins.run(o.id, s.slug, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.url || null, o.published || null, media);
+          tlStmts().ins.run(o.id, s.slug, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.url || null, o.published || null, media, o.sensitive ? 1 : 0, o.summary || null);
         }
         console.log('[AP] timeline +', actorUri, 'x' + subs.length);
@@ -775,5 +775,5 @@
   if (!site) return;
   const recent = db.prepare(
-    `SELECT id, slug, title, content, cover_image_url, nsfw, published_at, created_at
+    `SELECT id, slug, title, content, cover_image_url, nsfw, content_warning, published_at, created_at
      FROM posts WHERE site_id = ? AND status = 'published' AND (fan_only IS NULL OR fan_only = 0)
      ORDER BY COALESCE(published_at, created_at) DESC LIMIT 20`
@@ -1011,4 +1011,6 @@
     url: note.url || url,
     content: HtmlSanitizerService.sanitize(rawHtml),       // full, sanitized
+    sensitive: !!note.sensitive,                            // remote CW → blur in the Cirkel
+    cw: note.summary || '',
     images,
     threadInboxes,                                          // every ancestor author's inbox
@@ -1083,5 +1085,5 @@
 function tlStmts() {
   if (!_insTl) {
-    _insTl = db.prepare('INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
+    _insTl = db.prepare('INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, nsfw, cw, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     _listTl = db.prepare('SELECT * FROM ap_timeline WHERE slug = ? ORDER BY COALESCE(published, created_at) DESC LIMIT ?');
     _delTl = db.prepare('DELETE FROM ap_timeline WHERE id = ?');
@@ -1102,5 +1104,5 @@
     if (!_cirkelPosts) _cirkelPosts = db.prepare(`
       SELECT t.id, t.author_uri, t.author_name, t.author_handle, t.author_icon, t.author_url,
-             t.content, t.url, t.published, t.media_json, t.boosted
+             t.content, t.url, t.published, t.media_json, t.boosted, t.nsfw, t.cw
       FROM ap_timeline t
       LEFT JOIN ap_following f ON f.slug = t.slug AND f.actor_uri = t.author_uri
@@ -1142,5 +1144,5 @@
     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);
+      new Date().toISOString(), media, note.sensitive ? 1 : 0, note.cw || null);
   } catch { /* ignore */ }
   markBoosted(slug, id);
