Index: CHANGELOG.de.md
===================================================================
--- CHANGELOG.de.md	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ CHANGELOG.de.md	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -7,4 +7,7 @@
 
 ### Hinzugefügt
+- **Eine Erwähnung ist jetzt eine Benachrichtigung.** Wenn dich jemand im Fediverse in einem
+  Beitrag erwähnt — auch in einem, der keine Antwort an dich ist — erscheint das in deinen
+  Fediverse-Benachrichtigungen mit einem Link zum Original.
 - **Cover-Art bei offen geteiltem Audio.** Ein offen im Fediverse geteilter Track trägt jetzt sein
   Cover (oder das Beitragscover) mit, sodass Audio-Player, die Artwork unterstützen, es statt einer
Index: CHANGELOG.md
===================================================================
--- CHANGELOG.md	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ CHANGELOG.md	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -7,4 +7,7 @@
 
 ### Added
+- **A mention is now a notification.** When someone on the fediverse mentions you in a post —
+  even one that isn't a reply to you — it shows up in your fediverse notifications with a link
+  to the original.
 - **Cover art on openly shared audio.** A track shared openly on the fediverse now carries its
   cover art (or the post cover), so audio players that support artwork show it instead of a blank tile.
Index: CHANGELOG.nl.md
===================================================================
--- CHANGELOG.nl.md	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ CHANGELOG.nl.md	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -7,4 +7,7 @@
 
 ### Toegevoegd
+- **Een vermelding is nu een melding.** Als iemand op de fediverse je noemt in een post — ook
+  eentje die geen reactie op jou is — verschijnt dat in je fediverse-meldingen met een link naar
+  het origineel.
 - **Cover-art op openbaar gedeelde audio.** Een track die je openbaar op de fediverse deelt draagt
   nu z'n cover-art mee (of de post-cover), zodat audiospelers die artwork ondersteunen die tonen in
Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ src/config/database.js	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -347,4 +347,16 @@
     );
     CREATE INDEX IF NOT EXISTS idx_poll_votes_post ON poll_votes(post_id);
+    CREATE TABLE IF NOT EXISTS ap_mentions (
+      id INTEGER PRIMARY KEY AUTOINCREMENT,
+      slug TEXT NOT NULL,           -- our mentioned site/actor
+      object_uri TEXT NOT NULL,     -- the remote note that mentions us
+      note_url TEXT,                -- its human URL (open/interact)
+      actor_uri TEXT, actor_name TEXT, actor_handle TEXT, actor_icon TEXT, actor_url TEXT,
+      content TEXT,                 -- sanitized HTML snippet of the mentioning note
+      published TEXT,
+      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+      UNIQUE(slug, object_uri)
+    );
+    CREATE INDEX IF NOT EXISTS idx_ap_mentions_slug ON ap_mentions(slug, created_at);
     CREATE TABLE IF NOT EXISTS ap_reports (
       id INTEGER PRIMARY KEY AUTOINCREMENT,
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ src/services/ActivityPubService.js	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -660,4 +660,19 @@
 // ── HTTP Signatures + delivery ────────────────────────────────────
 const slugFromActorUrl = (url) => { const m = String(url || '').match(/\/ap\/users\/([^/?#]+)/); return m ? decodeURIComponent(m[1]) : null; };
+// Which of OUR sites are named in a note's Mention tags? Only hrefs on our own base count
+// (an /ap/users/<slug> path on a remote host is someone else's actor), and the slug must be
+// an existing site. Deduped.
+export function localMentionSlugs(tags, base) {
+  if (!base) return [];
+  const out = [], seen = new Set();
+  for (const t of (Array.isArray(tags) ? tags : (tags ? [tags] : []))) {
+    if (!t || t.type !== 'Mention' || typeof t.href !== 'string') continue;
+    if (!t.href.startsWith(base + '/ap/users/')) continue;
+    const slug = slugFromActorUrl(t.href);
+    if (!slug || seen.has(slug)) continue; seen.add(slug);
+    try { if (db.prepare('SELECT 1 FROM sites WHERE slug = ?').get(slug)) out.push(slug); } catch { /* ignore */ }
+  }
+  return out;
+}
 
 // Sign + POST an activity to a remote inbox (draft-cavage HTTP Signatures, RSA-SHA256).
@@ -1073,4 +1088,22 @@
         }
         console.log('[AP] timeline +', actorUri, 'x' + subs.length);
+      }
+    }
+    // Mentioned in a post that is NOT a reply to our content (a reply to us already returned
+    // above): store a mention notification for each of our actors named in the Mention tags.
+    // Requires our own base prefix on the tag href — /ap/users/<slug> on a REMOTE host is
+    // someone else's actor, not ours.
+    if (actorUri && !isLocalActor && o.id) {
+      const slugs = localMentionSlugs(o.tag, base);
+      if (slugs.length) {
+        const ai = actorInfo(await resolveActor(actorUri), actorUri);
+        const html = HtmlSanitizerService.sanitize(o.content || '');
+        for (const slug of slugs) {
+          try {
+            const r = db.prepare('INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, published, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)')
+              .run(slug, o.id, safeUrl(o.url) || null, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.published || null);
+            if (r.changes) console.log('[AP] mention', actorUri, '→', slug);
+          } catch { /* ignore */ }
+        }
       }
     }
@@ -2161,4 +2194,9 @@
     }
   } catch { /* ignore */ }
+  try {
+    for (const r of db.prepare('SELECT object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, created_at FROM ap_mentions WHERE slug = ? ORDER BY created_at DESC LIMIT 50').all(slug)) {
+      out.push({ type: 'mention', name: r.actor_name, handle: r.actor_handle, url: r.actor_url || r.actor_uri, icon: r.actor_icon, content: stripLeadingMentions(r.content), note_url: r.note_url || r.object_uri, created_at: r.created_at });
+    }
+  } catch { /* ignore */ }
   out.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
   return out.slice(0, limit || 60);
@@ -2335,5 +2373,5 @@
   listOutbox, deliverOutboxDelete, deliverOutboxUpdate,
   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, sendInteraction, voteOnPoll, voteOnRemotePoll,
-  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport,
+  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
   autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
   getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ src/services/i18n.js	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -28,5 +28,5 @@
     'nav.language': 'Taal',
     'nav.notifications': 'Meldingen',
-    'notif.title': 'Meldingen', 'notif.empty': 'Nog geen meldingen.', 'notif.someone': 'Iemand', 'notif.followed': 'volgt je nu', 'notif.liked': 'likete je post', 'notif.boosted': 'boostte je post', 'notif.replied': 'reageerde op', 'notif.reported': 'rapporteerde je bij hun server', 'blk.title': 'Blokkeren', 'blk.lead': 'Blokkeer een account of een heel domein — hun reacties, likes en posts verdwijnen en nieuwe worden geweigerd.', 'blk.block_btn': 'Blokkeren', 'blk.empty': 'Niks geblokkeerd.', 'blk.unblock': 'Deblokkeren', 'tl.block': 'Blokkeer',
+    'notif.title': 'Meldingen', 'notif.empty': 'Nog geen meldingen.', 'notif.someone': 'Iemand', 'notif.followed': 'volgt je nu', 'notif.liked': 'likete je post', 'notif.boosted': 'boostte je post', 'notif.replied': 'reageerde op', 'notif.reported': 'rapporteerde je bij hun server', 'notif.mentioned': 'noemde je in een post', 'blk.title': 'Blokkeren', 'blk.lead': 'Blokkeer een account of een heel domein — hun reacties, likes en posts verdwijnen en nieuwe worden geweigerd.', 'blk.block_btn': 'Blokkeren', 'blk.empty': 'Niks geblokkeerd.', 'blk.unblock': 'Deblokkeren', 'tl.block': 'Blokkeer',
     'notif.reply': '{actor} reageerde op je reactie', 'notif.comment': '{actor} reageerde op je post', 'notif.like': '{actor} vindt je post leuk',
     'switch.agenda': 'Agenda',
@@ -959,5 +959,5 @@
     'nav.language': 'Language',
     'nav.notifications': 'Notifications',
-    'notif.title': 'Notifications', 'notif.empty': 'No notifications yet.', 'notif.someone': 'Someone', 'notif.followed': 'followed you', 'notif.liked': 'liked your post', 'notif.boosted': 'boosted your post', 'notif.replied': 'replied to', 'notif.reported': 'reported you to their server', 'blk.title': 'Blocking', 'blk.lead': 'Block an account or a whole domain — their replies, likes and posts disappear and new ones are refused.', 'blk.block_btn': 'Block', 'blk.empty': 'Nothing blocked.', 'blk.unblock': 'Unblock', 'tl.block': 'Block',
+    'notif.title': 'Notifications', 'notif.empty': 'No notifications yet.', 'notif.someone': 'Someone', 'notif.followed': 'followed you', 'notif.liked': 'liked your post', 'notif.boosted': 'boosted your post', 'notif.replied': 'replied to', 'notif.reported': 'reported you to their server', 'notif.mentioned': 'mentioned you in a post', 'blk.title': 'Blocking', 'blk.lead': 'Block an account or a whole domain — their replies, likes and posts disappear and new ones are refused.', 'blk.block_btn': 'Block', 'blk.empty': 'Nothing blocked.', 'blk.unblock': 'Unblock', 'tl.block': 'Block',
     'notif.reply': '{actor} replied to your comment', 'notif.comment': '{actor} commented on your post', 'notif.like': '{actor} liked your post',
     'switch.agenda': 'Agenda',
@@ -1881,5 +1881,5 @@
     'nav.language': 'Sprache',
     'nav.notifications': 'Benachrichtigungen',
-    'notif.title': 'Benachrichtigungen', 'notif.empty': 'Noch keine Benachrichtigungen.', 'notif.someone': 'Jemand', 'notif.followed': 'folgt dir jetzt', 'notif.liked': 'gefällt dein Beitrag', 'notif.boosted': 'teilte deinen Beitrag', 'notif.replied': 'antwortete auf', 'notif.reported': 'hat dich bei ihrem Server gemeldet', 'blk.title': 'Blockieren', 'blk.lead': 'Blockiere ein Konto oder eine ganze Domain — ihre Antworten, Likes und Beiträge verschwinden und neue werden abgelehnt.', 'blk.block_btn': 'Blockieren', 'blk.empty': 'Nichts blockiert.', 'blk.unblock': 'Entsperren', 'tl.block': 'Blockieren',
+    'notif.title': 'Benachrichtigungen', 'notif.empty': 'Noch keine Benachrichtigungen.', 'notif.someone': 'Jemand', 'notif.followed': 'folgt dir jetzt', 'notif.liked': 'gefällt dein Beitrag', 'notif.boosted': 'teilte deinen Beitrag', 'notif.replied': 'antwortete auf', 'notif.reported': 'hat dich bei ihrem Server gemeldet', 'notif.mentioned': 'hat dich in einem Beitrag erwähnt', 'blk.title': 'Blockieren', 'blk.lead': 'Blockiere ein Konto oder eine ganze Domain — ihre Antworten, Likes und Beiträge verschwinden und neue werden abgelehnt.', 'blk.block_btn': 'Blockieren', 'blk.empty': 'Nichts blockiert.', 'blk.unblock': 'Entsperren', 'tl.block': 'Blockieren',
     'notif.reply': '{actor} hat auf deinen Kommentar geantwortet', 'notif.comment': '{actor} hat deinen Beitrag kommentiert', 'notif.like': '{actor} gefällt dein Beitrag',
     'switch.agenda': 'Termine',
Index: src/views/pages/fedi-notifications.ejs
===================================================================
--- src/views/pages/fedi-notifications.ejs	(revision 254939cf68e57bbc5fedd907d4a13a0c5f823669)
+++ src/views/pages/fedi-notifications.ejs	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -7,5 +7,5 @@
     <ul class="nt-list">
       <% items.forEach(function(n){
-           var _ic = n.type === 'follow' ? 'follow' : (n.type === 'like' ? 'like' : (n.type === 'announce' ? 'boost' : (n.type === 'report' ? 'report' : 'reply')));
+           var _ic = n.type === 'follow' ? 'follow' : (n.type === 'like' ? 'like' : (n.type === 'announce' ? 'boost' : (n.type === 'report' ? 'report' : (n.type === 'mention' ? 'mention' : 'reply'))));
       %>
         <li class="nt-item nt-<%= _ic %>">
@@ -15,4 +15,5 @@
             <% } else if (_ic === 'follow') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
             <% } else if (_ic === 'report') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>
+            <% } else if (_ic === 'mention') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"/></svg>
             <% } else { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><% } %>
           </span>
@@ -28,9 +29,11 @@
               <% } else if (n.type === 'announce') { %><%= t('notif.boosted') %>
               <% } else if (n.type === 'report') { %><%= t('notif.reported') %>
+              <% } else if (n.type === 'mention') { %><%= t('notif.mentioned') %>
               <% } else { %><%= t('notif.replied') %><% } %>
               <% if (n.post_slug) { %><a class="nt-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
+              <% if (n.type === 'mention' && n.note_url) { %><a class="nt-post" href="<%= n.note_url %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
             </div>
             <% if (n.type === 'report' && n.content) { %><div class="nt-content"><%= n.content %></div>
-            <% } else if (n.type === 'reply' && n.content) { %><div class="nt-content"><%- n.content %></div><% } %>
+            <% } else if ((n.type === 'reply' || n.type === 'mention') && n.content) { %><div class="nt-content"><%- n.content %></div><% } %>
           </div>
         </li>
@@ -59,4 +62,5 @@
   .nt-follow .nt-icon, .nt-reply .nt-icon { background: color-mix(in srgb, var(--accent, #888) 16%, transparent); color: var(--accent, #06c); }
   .nt-report .nt-icon { background: color-mix(in srgb, #c0392b 16%, transparent); color: #c0392b; }
+  .nt-mention .nt-icon { background: color-mix(in srgb, #8e6cf0 16%, transparent); color: #8e6cf0; }
   .nt-body { flex: 1; min-width: 0; }
   .nt-line { display: flex; align-items: baseline; gap: .4rem; }
Index: test/inbound-mentions.test.js
===================================================================
--- test/inbound-mentions.test.js	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
+++ test/inbound-mentions.test.js	(revision fe97cc38d423c6b67d75cc07bab36c9ef2dd4f91)
@@ -0,0 +1,49 @@
+// Inbound mentions — a remote note whose Mention tag targets one of our actors becomes a
+// notification, even when it isn't a reply to our content. localMentionSlugs is the detection
+// core: only OUR base counts, the slug must exist, deduped. In-memory SQLite. Run: npm test
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://test.example';
+
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+dbMod.initializeDatabase();
+const AP = (await import('../src/services/ActivityPubService.js')).default;
+
+const BASE = 'https://test.example';
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@test', 'x', 'god');
+db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,?)').run('s1', 'demo', 'Demo', 'u1', 1);
+
+test('a Mention tag with our actor href resolves to the site slug', () => {
+  const tags = [
+    { type: 'Mention', href: `${BASE}/ap/users/demo`, name: '@demo@test.example' },
+    { type: 'Hashtag', href: `${BASE}/tag/x`, name: '#x' },
+  ];
+  assert.deepEqual(AP.localMentionSlugs(tags, BASE), ['demo']);
+});
+
+test('a remote host with the same /ap/users path is NOT ours', () => {
+  const tags = [{ type: 'Mention', href: 'https://other.example/ap/users/demo', name: '@demo@other.example' }];
+  assert.deepEqual(AP.localMentionSlugs(tags, BASE), []);
+});
+
+test('a mention of a non-existent slug is ignored; duplicates dedupe', () => {
+  const tags = [
+    { type: 'Mention', href: `${BASE}/ap/users/ghost`, name: '@ghost' },
+    { type: 'Mention', href: `${BASE}/ap/users/demo`, name: '@demo' },
+    { type: 'Mention', href: `${BASE}/ap/users/demo`, name: '@demo' },
+  ];
+  assert.deepEqual(AP.localMentionSlugs(tags, BASE), ['demo']);
+});
+
+test('a stored mention shows up in the notifications', () => {
+  db.prepare('INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, content) VALUES (?,?,?,?,?,?,?)')
+    .run('demo', 'https://m.example/notes/1', 'https://m.example/@a/1', 'https://m.example/users/a', 'Anna', '@a@m.example', '<p>hi @demo</p>');
+  const items = AP.getNotifications('demo', 20);
+  const m = items.find((n) => n.type === 'mention');
+  assert.ok(m, 'mention notification present');
+  assert.equal(m.handle, '@a@m.example');
+  assert.equal(m.note_url, 'https://m.example/@a/1');
+});
