Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision ae05747989c0db698829b0eccb94de8a33d7399f)
+++ src/services/ActivityPubService.js	(revision 4424af7fc82df76bcf2fcaf1e1f373a4eb94a526)
@@ -1422,4 +1422,17 @@
   return parts.join('');
 }
+// Linkify inline #hashtags and bare URLs in BODY html for on-site DISPLAY, using the
+// EXACT same rules as the federated copy (linkHashtags/linkUrls), so the website and the
+// Mastodon copy agree instead of the website showing raw text. Idempotent: existing
+// <a>…</a> (editor links, embeds, shortcode buttons) are split out and left untouched, so
+// nothing is double-wrapped. Pass base='' → root-relative /tag/<slug> links.
+export function linkifyBody(base, html) {
+  const withTags = String(html || '')
+    .split(/(<a\b[^>]*>[\s\S]*?<\/a>)/gi)
+    .map((seg) => (/^<a\b/i.test(seg) ? seg : linkHashtags(base, seg)))
+    .join('');
+  return linkUrls(withTags);
+}
+
 // Extract the AP Hashtag tag objects from already-linked reply content.
 function hashtagTags(base, content) {
@@ -2421,3 +2434,4 @@
   deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
   getReplyUris, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio,
+  linkifyBody,
 };
