Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision d7526bda1a7c629688e73f463847832f685c3381)
+++ src/services/ActivityPubService.js	(revision e951b7c4efb52daa3b8964b528fd3f604de85365)
@@ -169,4 +169,21 @@
   } catch { /* non-fatal */ }
   return out;
+}
+
+// Notifications "seen" tracking → a real bell badge. Stored per site in app_settings.
+export function markNotificationsSeen(slug) {
+  try {
+    db.prepare("INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP")
+      .run(`fedi_notif_seen:${slug}`, new Date().toISOString());
+  } catch { /* non-fatal */ }
+}
+export function countUnseenNotifications(slug) {
+  try {
+    const row = db.prepare('SELECT value FROM app_settings WHERE key = ?').get(`fedi_notif_seen:${slug}`);
+    const seen = row ? Date.parse(row.value) : 0;
+    let n = 0;
+    for (const it of getNotifications(slug, 50)) { if (Date.parse(it.created_at) > seen) n++; }
+    return n;
+  } catch { return 0; }
 }
 
@@ -919,4 +936,4 @@
   getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
   deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
-  getReplyUris,
+  getReplyUris, markNotificationsSeen, countUnseenNotifications,
 };
