Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision e390844613107c851b9321e3916b173afc3ebf4e)
+++ src/routes/posts.js	(revision 80c36a11780668405aae6004e333b68963a6b9db)
@@ -232,10 +232,11 @@
     } catch (e) { /* FTS index issues are non-fatal */ }
 
-    // ActivityPub: federate a freshly published public post to followers.
-    if (!fanOnly) {
+    // ActivityPub: federate a freshly published post to followers. fan_only → delivered
+    // to followers but addressed followers-only (option A: "fans" = your fedi followers).
+    if (status === 'published') {
       ActivityPubService.deliverCreate(site, {
         id: postId, slug: finalSlug, title: title || finalSlug,
         content: cleanContent, cover_image_url: cover_image_url || null,
-        published_at: publishedAt, created_at: now,
+        published_at: publishedAt, created_at: now, fan_only: fanOnly,
       }).catch(() => { /* best-effort */ });
     }
@@ -356,10 +357,10 @@
   // ActivityPub: federate edits to followers. A post that BECOMES published →
   // Create (new post); an already-published post that's edited → Update (so
-  // Mastodon refreshes its cached copy). fan_only posts don't federate.
-  if (finalStatus === 'published' && !fanOnly) {
+  // Mastodon refreshes its cached copy). fan_only → followers-only (option A).
+  if (finalStatus === 'published') {
     const apPost = {
       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,
+      published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly,
     };
     if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ });
@@ -391,8 +392,8 @@
   }
 
-  // ActivityPub: tell followers the post is gone (Delete + Tombstone), but only
-  // if it was actually federated (published + not fan-only). Fire before the row
-  // is removed — we still have post.id (= the Note id).
-  if (post.status === 'published' && !post.fan_only) {
+  // ActivityPub: tell followers the post is gone (Delete + Tombstone) if it was
+  // federated (any published post now federates — fan_only goes followers-only).
+  // Fire before the row is removed — we still have post.id (= the Note id).
+  if (post.status === 'published') {
     ActivityPubService.deliverDelete(site, post).catch(() => { /* best-effort */ });
   }
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision e390844613107c851b9321e3916b173afc3ebf4e)
+++ src/services/ActivityPubService.js	(revision 80c36a11780668405aae6004e333b68963a6b9db)
@@ -240,6 +240,8 @@
     url: human,
     published: new Date(post.published_at || post.created_at || Date.now()).toISOString(),
-    to: [PUBLIC],
-    cc: [`${aId}/followers`],
+    // fan_only = "fans only" → followers-only visibility (delivered to your followers
+    // but not addressed to Public, so Mastodon shows it only to them and can't boost it).
+    to: post.fan_only ? [`${aId}/followers`] : [PUBLIC],
+    cc: post.fan_only ? [] : [`${aId}/followers`],
     tag: Array.isArray(post.tags) ? post.tags.map((t) => ({ type: 'Hashtag', name: '#' + String(t).replace(/\s+/g, '') })) : [],
     replies: `${id}/replies`,
Index: src/services/Scheduler.js
===================================================================
--- src/services/Scheduler.js	(revision e390844613107c851b9321e3916b173afc3ebf4e)
+++ src/services/Scheduler.js	(revision 80c36a11780668405aae6004e333b68963a6b9db)
@@ -32,17 +32,15 @@
       // get a duplicate FTS row → duplicate search hits.
       try { ftsDel.run(p.id); fts.run(HtmlSanitizerService.toPlainText(p.content || ''), p.title || '', p.username || '', p.id); } catch { /* FTS failure is non-fatal */ }
-      // ActivityPub: federate the now-published post to followers.
-      if (!p.fan_only) {
-        try {
-          const site = siteStmt.get(p.site_id);
-          if (site) {
-            ActivityPubService.deliverCreate(site, {
-              id: p.id, slug: p.slug, title: p.title || p.slug,
-              content: p.content, cover_image_url: p.cover_image_url || null,
-              published_at: p.published_at || p.publish_at, created_at: p.created_at,
-            }).catch(() => { /* best-effort */ });
-          }
-        } catch { /* non-fatal */ }
-      }
+      // ActivityPub: federate the now-published post to followers (fan_only → followers-only).
+      try {
+        const site = siteStmt.get(p.site_id);
+        if (site) {
+          ActivityPubService.deliverCreate(site, {
+            id: p.id, slug: p.slug, title: p.title || p.slug,
+            content: p.content, cover_image_url: p.cover_image_url || null,
+            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only,
+          }).catch(() => { /* best-effort */ });
+        }
+      } catch { /* non-fatal */ }
     }
     return due.length;
