Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 6bc2ca7ee3133766de4e8427de01a2285f4460a9)
+++ src/services/ActivityPubService.js	(revision 7cea8736d7b290abee1d49d29d04723e3be1913e)
@@ -228,4 +228,5 @@
   // shift+enter uses <br> and has no \n → this is a no-op there).
   body = body.replace(/\r?\n/g, '<br>');
+  body = linkHashtags(base, body); // link inline #hashtags in the post body too
   const seen = new Set();
   const attachment = urls.filter(Boolean)
@@ -244,5 +245,5 @@
     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, '') })) : [],
+    tag: buildHashtagList(base, post.tags, body),
     replies: `${id}/replies`,
     // NSFW → Mastodon-style content warning: sensitive (blurs media) + a summary/spoiler
@@ -914,4 +915,20 @@
 }
 
+// Merge a post's tags field + the #hashtags linked inline in its body into one deduped
+// Hashtag tag list (with hrefs to our /tag page).
+function buildHashtagList(base, tagsField, content) {
+  const out = [], seen = new Set();
+  for (const t of (Array.isArray(tagsField) ? tagsField : [])) {
+    const name = String(t).replace(/\s+/g, ''); if (!name) continue;
+    const k = name.toLowerCase(); if (seen.has(k)) continue; seen.add(k);
+    out.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(k)}`, name: '#' + name });
+  }
+  for (const h of hashtagTags(base, content)) {
+    const k = h.name.slice(1).toLowerCase(); if (seen.has(k)) continue; seen.add(k);
+    out.push(h);
+  }
+  return out;
+}
+
 export function buildReplyNote(base, site, row) {
   const me = actorId(base, site.slug);
