Index: src/assets/css/style.css
===================================================================
--- src/assets/css/style.css	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/assets/css/style.css	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -4400,2 +4400,11 @@
     }
 }
+
+/* === Fediverse interactions (inbound AP replies/likes/boosts) === */
+.post-fediverse { margin: 2rem 0; }
+.post-fediverse .fedi-heading { font-size: 1.15rem; margin: 0 0 .5rem; }
+.post-fediverse .fedi-stats { display: flex; gap: 1.1rem; color: var(--ink-soft, #777); font-size: .95rem; margin: 0 0 1rem; }
+.post-fediverse .fedi-replies { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 1rem; }
+.post-fediverse .fedi-reply { display: flex; gap: .75rem; }
+.post-fediverse .fedi-handle { color: var(--ink-soft, #888); font-size: .85rem; }
+.post-fediverse .comment-content { margin-top: .15rem; }
Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/config/database.js	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -316,4 +316,20 @@
     );
     CREATE INDEX IF NOT EXISTS idx_ap_followers_slug ON ap_followers(slug);
+    CREATE TABLE IF NOT EXISTS ap_interactions (
+      id INTEGER PRIMARY KEY AUTOINCREMENT,
+      kind TEXT NOT NULL,                   -- 'reply' | 'like' | 'announce'
+      post_id TEXT NOT NULL,
+      object_uri TEXT NOT NULL DEFAULT '',  -- remote note id (reply) or '' (like/announce)
+      actor_uri TEXT NOT NULL,
+      actor_name TEXT,
+      actor_handle TEXT,
+      actor_url TEXT,
+      actor_icon TEXT,
+      content TEXT,                         -- sanitized HTML (reply)
+      published TEXT,
+      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+      UNIQUE(kind, post_id, actor_uri, object_uri)
+    );
+    CREATE INDEX IF NOT EXISTS idx_ap_inter_post ON ap_interactions(post_id, kind);
   `);
 }
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/routes/posts.js	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -748,4 +748,8 @@
     db.prepare('SELECT 1 FROM post_likes WHERE post_id = ? AND user_id = ?').get(post.id, req.session.user.id));
 
+  // Inbound fediverse activity (replies/likes/boosts) for this post.
+  let fediverse = { replies: [], likeCount: 0, announceCount: 0, total: 0 };
+  try { fediverse = ActivityPubService.getInteractions(post.id); } catch { /* non-fatal */ }
+
   renderPage(req, res, 'pages/post', {
     post,
@@ -755,4 +759,5 @@
     comments: topLevel,
     totalComments,
+    fediverse,
     likeCount,
     likedByMe,
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/services/ActivityPubService.js	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -18,4 +18,5 @@
 import crypto from 'crypto';
 import db from '../config/database.js';
+import HtmlSanitizerService from './HtmlSanitizerService.js';
 
 const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
@@ -190,4 +191,50 @@
 export function followerCount(slug) { return fStmts().cnt.get(slug).n; }
 
+// ── inbound interactions store (replies / likes / boosts), lazy stmts ──
+let _insI, _delLA, _delReply, _listI;
+function iStmts() {
+  if (!_insI) {
+    _insI = db.prepare('INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
+    _delLA = db.prepare('DELETE FROM ap_interactions WHERE kind = ? AND post_id = ? AND actor_uri = ?');
+    _delReply = db.prepare("DELETE FROM ap_interactions WHERE kind = 'reply' AND object_uri = ?");
+    _listI = db.prepare('SELECT kind, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');
+  }
+  return { ins: _insI, delLA: _delLA, delReply: _delReply, list: _listI };
+}
+
+const localPostExists = (id) => { try { return !!db.prepare('SELECT 1 FROM posts WHERE id = ?').get(id); } catch { return false; } };
+// Extract our local post id from a note URL, but only if it's ours (base match).
+function postIdFromNoteUrl(url, base) {
+  const s = String(url || '');
+  if (base && !s.startsWith(base)) return null;
+  const m = s.match(/\/ap\/notes\/([^/?#]+)/);
+  return m ? decodeURIComponent(m[1]) : null;
+}
+function deriveHandle(actorUri) {
+  try { const u = new URL(actorUri); const seg = u.pathname.split('/').filter(Boolean).pop() || ''; return `@${seg}@${u.host}`; } catch { return String(actorUri || ''); }
+}
+function actorInfo(doc, actorUri) {
+  let host = ''; try { host = new URL(actorUri).host; } catch { /* keep empty */ }
+  const handle = doc && doc.preferredUsername ? `@${doc.preferredUsername}@${host}` : deriveHandle(actorUri);
+  const icon = doc && doc.icon ? (doc.icon.url || (Array.isArray(doc.icon) && doc.icon[0] && doc.icon[0].url)) : null;
+  return {
+    name: (doc && (doc.name || doc.preferredUsername)) || handle,
+    handle,
+    url: (doc && (doc.url || doc.id)) || actorUri,
+    icon: icon || null,
+  };
+}
+
+// Stored, view-ready summary of a post's inbound fediverse activity.
+export function getInteractions(postId) {
+  const rows = iStmts().list.all(postId);
+  return {
+    replies: rows.filter((r) => r.kind === 'reply'),
+    likeCount: rows.filter((r) => r.kind === 'like').length,
+    announceCount: rows.filter((r) => r.kind === 'announce').length,
+    total: rows.length,
+  };
+}
+
 // ── HTTP Signatures + delivery ────────────────────────────────────
 const slugFromActorUrl = (url) => { const m = String(url || '').match(/\/ap\/users\/([^/?#]+)/); return m ? decodeURIComponent(m[1]) : null; };
@@ -263,11 +310,54 @@
     return 202;
   }
-  if (type === 'Undo' && act.object && act.object.type === 'Follow') {
+  if (type === 'Undo' && act.object) {
     const who = typeof act.actor === 'string' ? act.actor : (act.actor && act.actor.id);
-    const obj = act.object.object;
-    const slug = slugParam || slugFromActorUrl(typeof obj === 'string' ? obj : (obj && obj.id));
-    if (who && slug) { fStmts().del.run(slug, who); console.log('[AP] Unfollow', who, '→', slug); }
+    const ot = act.object.type;
+    if (ot === 'Follow') {
+      const obj = act.object.object;
+      const slug = slugParam || slugFromActorUrl(typeof obj === 'string' ? obj : (obj && obj.id));
+      if (who && slug) { fStmts().del.run(slug, who); console.log('[AP] Unfollow', who, '→', slug); }
+      return 202;
+    }
+    if (ot === 'Like' || ot === 'Announce') {
+      const tgt = act.object.object;
+      const pid = postIdFromNoteUrl(typeof tgt === 'string' ? tgt : (tgt && tgt.id), base);
+      if (who && pid) { iStmts().delLA.run(ot.toLowerCase(), pid, who); console.log('[AP] Undo', ot, who, '→', pid); }
+      return 202;
+    }
     return 202;
   }
+
+  const actorUri = typeof act.actor === 'string' ? act.actor : (act.actor && act.actor.id);
+  const resolveActor = async (uri) => ((verified && verified.id === uri) ? verified : await fetchActor(uri).catch(() => null));
+
+  // Inbound reply: a Create whose object replies to one of our notes.
+  if (type === 'Create' && act.object && (act.object.type === 'Note' || act.object.type === 'Article')) {
+    const o = act.object;
+    const pid = postIdFromNoteUrl(o.inReplyTo, base);
+    if (pid && actorUri && localPostExists(pid)) {
+      const ai = actorInfo(await resolveActor(actorUri), actorUri);
+      const html = HtmlSanitizerService.sanitize(o.content || '');
+      iStmts().ins.run('reply', pid, o.id || '', actorUri, ai.name, ai.handle, ai.url, ai.icon, html, o.published || null);
+      console.log('[AP] reply', actorUri, '→', pid);
+    }
+    return 202;
+  }
+  if (type === 'Like' || type === 'Announce') {
+    const tgt = act.object;
+    const pid = postIdFromNoteUrl(typeof tgt === 'string' ? tgt : (tgt && tgt.id), base);
+    if (pid && actorUri && localPostExists(pid)) {
+      const ai = actorInfo(await resolveActor(actorUri), actorUri);
+      iStmts().ins.run(type.toLowerCase(), pid, '', actorUri, ai.name, ai.handle, ai.url, ai.icon, null, null);
+      console.log('[AP]', type === 'Like' ? 'like' : 'boost', actorUri, '→', pid);
+    }
+    return 202;
+  }
+  if (type === 'Delete') {
+    // A remote reply was deleted upstream → drop it if we stored it.
+    const oid = typeof act.object === 'string' ? act.object : (act.object && act.object.id);
+    if (oid) iStmts().delReply.run(oid);
+    return 202;
+  }
+
   console.log('[AP] inbox', type || 'unknown', '→', slugParam || 'shared', '(ignored)');
   return 202;
@@ -313,3 +403,4 @@
   buildActor, buildNote, buildCreate, buildOutbox, buildFollowers,
   followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete,
+  getInteractions,
 };
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/services/i18n.js	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -107,4 +107,5 @@
     'comments.heading_one': '{n} reactie', 'comments.heading_other': '{n} reacties',
     'comments.empty': 'Nog geen reacties.',
+    'fedi.heading': 'Vanuit de fediverse', 'fedi.likes': 'sterren', 'fedi.boosts': 'boosts', 'fedi.replies': 'Reacties uit de fediverse',
     'comments.to_start': 'om de conversatie te starten.',
     'comments.reply': 'Reageer', 'comments.delete': 'Verwijder', 'comments.cancel': 'Annuleren',
@@ -1099,4 +1100,5 @@
     'comments.heading_one': '{n} comment', 'comments.heading_other': '{n} comments',
     'comments.empty': 'No comments yet.',
+    'fedi.heading': 'From the fediverse', 'fedi.likes': 'favourites', 'fedi.boosts': 'boosts', 'fedi.replies': 'Replies from the fediverse',
     'comments.to_start': 'to start the conversation.',
     'comments.reply': 'Reply', 'comments.delete': 'Delete', 'comments.cancel': 'Cancel',
@@ -2089,4 +2091,5 @@
     'comments.heading_one': '{n} Kommentar', 'comments.heading_other': '{n} Kommentare',
     'comments.empty': 'Noch keine Kommentare.',
+    'fedi.heading': 'Aus dem Fediverse', 'fedi.likes': 'Favoriten', 'fedi.boosts': 'Boosts', 'fedi.replies': 'Antworten aus dem Fediverse',
     'comments.to_start': 'um das Gespräch zu starten.',
     'comments.reply': 'Antworten', 'comments.delete': 'Löschen', 'comments.cancel': 'Abbrechen',
Index: src/views/pages/post.ejs
===================================================================
--- src/views/pages/post.ejs	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/views/pages/post.ejs	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -70,4 +70,36 @@
       <% } %>
     </aside>
+  <% } %>
+
+  <!-- Fediverse interactions (inbound replies / likes / boosts via ActivityPub) -->
+  <% if (typeof fediverse !== 'undefined' && fediverse && fediverse.total > 0) { %>
+    <section class="post-fediverse" id="fediverse">
+      <h2 class="fedi-heading"><%= t('fedi.heading') %></h2>
+      <p class="fedi-stats">
+        <span title="<%= t('fedi.likes') %>">⭐ <%= fediverse.likeCount %></span>
+        <span title="<%= t('fedi.boosts') %>">🔁 <%= fediverse.announceCount %></span>
+        <span title="<%= t('fedi.replies') %>">💬 <%= fediverse.replies.length %></span>
+      </p>
+      <% if (fediverse.replies.length) { %>
+        <ol class="fedi-replies">
+          <% fediverse.replies.forEach(function(r) { %>
+            <li class="fedi-reply">
+              <div class="comment-avatar">
+                <% if (r.actor_icon) { %><img src="<%= r.actor_icon %>" alt="" loading="lazy">
+                <% } else { %><span class="comment-avatar-fallback"><%= (r.actor_name || '?').charAt(0).toUpperCase() %></span><% } %>
+              </div>
+              <div class="comment-body">
+                <div class="comment-meta">
+                  <a class="comment-author" href="<%= r.actor_url %>" rel="nofollow noopener" target="_blank"><%= r.actor_name %></a>
+                  <span class="fedi-handle"><%= r.actor_handle %></span>
+                  <% if (r.published || r.created_at) { %><span class="comment-time"><%= formatDateTime(r.published || r.created_at) %></span><% } %>
+                </div>
+                <div class="comment-content"><%- r.content %></div>
+              </div>
+            </li>
+          <% }); %>
+        </ol>
+      <% } %>
+    </section>
   <% } %>
 
Index: src/views/shell.ejs
===================================================================
--- src/views/shell.ejs	(revision eb852c5856f54a4e8690534406540d8a33e0bfcc)
+++ src/views/shell.ejs	(revision c16e0a5a2c2c866ae09afb2b0996809746bc8e4b)
@@ -175,5 +175,5 @@
 
 <!-- v9 stylesheet (full palette system) -->
-<link rel="stylesheet" href="/assets/css/style.css?v=34">
+<link rel="stylesheet" href="/assets/css/style.css?v=35">
 
 <!-- Audio player styles: loaded on every page so the mini-player works
