Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 837fc9c1dc50db6b6a042e84da63ed9989edf2fa)
+++ src/routes/posts.js	(revision b7d4458e21f8eef0c969874d5d571662936d8702)
@@ -177,4 +177,5 @@
   const fanOnly = req.body.fan_only ? 1 : 0;
   const nsfw = req.body.nsfw ? 1 : 0;
+  const cw = (req.body.content_warning || '').trim().slice(0, 200);
 
   // Content arrives as user-authored HTML from the WYSIWYG editor — sanitize
@@ -214,7 +215,7 @@
     INSERT INTO posts (
       id, site_id, slug, author_id, title, content, excerpt,
-      status, cover_image_url, pinned, tags, type, noindex, fan_only, nsfw, publish_at,
+      status, cover_image_url, pinned, tags, type, noindex, fan_only, nsfw, content_warning, publish_at,
       created_at, updated_at, published_at
-    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
   `).run(
     postId, site.id, finalSlug, req.session.user.id,
@@ -222,5 +223,5 @@
     finalStatus, cover_image_url || null, parsePinnedRank(pinned),
     JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)),
-    finalType, noindex ? 1 : 0, fanOnly, nsfw, publishAt,
+    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, publishAt,
     now, now, publishedAt
   );
@@ -239,5 +240,5 @@
         id: postId, slug: finalSlug, title: title || finalSlug,
         content: cleanContent, cover_image_url: cover_image_url || null,
-        published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw,
+        published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw, content_warning: cw,
       }).catch(() => { /* best-effort */ });
     }
@@ -298,4 +299,5 @@
   const fanOnly = req.body.fan_only ? 1 : 0;
   const nsfw = req.body.nsfw ? 1 : 0;
+  const cw = (req.body.content_warning || '').trim().slice(0, 200);
   const newSlug = req.body.slug;
   const action = req.body.action || 'save';
@@ -336,5 +338,5 @@
       title = ?, content = ?, excerpt = ?, status = ?,
       cover_image_url = ?, pinned = ?, tags = ?,
-      type = ?, noindex = ?, fan_only = ?, nsfw = ?, publish_at = ?,
+      type = ?, noindex = ?, fan_only = ?, nsfw = ?, content_warning = ?, publish_at = ?,
       slug = ?, published_at = ?, updated_at = ?
     WHERE id = ?
@@ -343,5 +345,5 @@
     cover_image_url || null, parsePinnedRank(pinned),
     JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)),
-    finalType, noindex ? 1 : 0, fanOnly, nsfw, publishAt,
+    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, publishAt,
     finalSlug, publishedAt, now, post.id
   );
@@ -364,5 +366,5 @@
       id: post.id, slug: finalSlug, title: title || finalSlug,
       content: cleanContent, cover_image_url: cover_image_url || null,
-      published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw,
+      published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw, content_warning: cw,
     };
     if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ });
