Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision a677616cd3a4564e824f55e278142df81152336c)
+++ src/config/database.js	(revision f3caf194e5257640279bb1d13500358ff4965402)
@@ -551,4 +551,5 @@
   ensureColumn('ap_timeline', 'quote_json', 'TEXT');         // FEP-044f resolved quoted-post snapshot (author + content), for the embedded quote card
   ensureColumn('ap_timeline', 'author_emoji_json', 'TEXT');  // FEP-9098 custom emojis in the author's display name (shaer:author.emojis)
+  ensureColumn('ap_timeline', 'reblog_emoji_json', 'TEXT');  // FEP-9098 custom emojis in the booster's display name (shaer:booster.emojis)
   ensureColumn('ap_timeline', 'reblog_name', 'TEXT');        // a followed account boosted this → "X boosted"
   ensureColumn('ap_timeline', 'reblog_handle', 'TEXT');      //   the booster's @handle
Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision a677616cd3a4564e824f55e278142df81152336c)
+++ src/routes/activitypub.js	(revision f3caf194e5257640279bb1d13500358ff4965402)
@@ -188,4 +188,6 @@
         name: t.reblog_name || undefined, handle: t.reblog_handle || undefined,
         icon: t.reblog_icon || undefined,
+        // FEP-9098: emojis in the booster's display name (":shortcode:"), if any.
+        emojis: (() => { try { return t.reblog_emoji_json ? JSON.parse(t.reblog_emoji_json) : undefined; } catch { return undefined; } })(),
       } : undefined,
     },
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision a677616cd3a4564e824f55e278142df81152336c)
+++ src/services/ActivityPubService.js	(revision f3caf194e5257640279bb1d13500358ff4965402)
@@ -1690,5 +1690,5 @@
             let inserted = false;
             try { const r = tlStmts().ins.run(bn.id, s.slug, origUri || '', oai.name, oai.handle, oai.icon, oai.url, html, bn.url || null, new Date().toISOString(), media, bn.sensitive ? 1 : 0, bn.summary || null); inserted = r.changes > 0; } catch { /* ignore */ }
-            if (inserted) { try { db.prepare('UPDATE ap_timeline SET reblog_name = ?, reblog_handle = ?, reblog_icon = ? WHERE slug = ? AND id = ?').run(booster.name, booster.handle, booster.icon, s.slug, bn.id); } catch { /* ignore */ } }
+            if (inserted) { try { db.prepare('UPDATE ap_timeline SET reblog_name = ?, reblog_handle = ?, reblog_icon = ?, reblog_emoji_json = ? WHERE slug = ? AND id = ?').run(booster.name, booster.handle, booster.icon, (booster.emojis && Object.keys(booster.emojis).length) ? JSON.stringify(booster.emojis) : null, s.slug, bn.id); } catch { /* ignore */ } }
             storeAuthorEmoji(bn.id, s.slug, oai);   // custom-emoji display name for the byline
           }
@@ -2789,5 +2789,5 @@
 // during a flux window, e.g. a fleet-wide update), and drops notes that are gone
 // (404/410). Bump SELFHEAL_VERSION only on a release that warrants a re-sync.
-const SELFHEAL_VERSION = 13; // v13: capture custom-emoji display names (author_emoji_json) onto already-cached posts
+const SELFHEAL_VERSION = 14; // v14: capture custom-emoji booster names (reblog_emoji_json) onto already-cached boosts
 async function fetchNoteAP(url) {
   try {
@@ -3009,5 +3009,5 @@
     if (cur >= SELFHEAL_VERSION) return; // already healed for this version — skip on normal boots
     let rows = [];
-    try { rows = db.prepare('SELECT id, content, media_json, nsfw, cw, url, emoji_json, link_json, quote_json, author_uri, author_name, author_emoji_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ }
+    try { rows = db.prepare('SELECT id, slug, content, media_json, nsfw, cw, url, emoji_json, link_json, quote_json, author_uri, author_name, author_emoji_json, reblog_name, reblog_handle, reblog_emoji_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ }
     let healed = 0, failed = 0;
     for (const r of rows) {
@@ -3035,4 +3035,12 @@
           const ai = actorInfo(await fetchActor(r.author_uri), r.author_uri);
           if (ai.emojis) { try { db.prepare('UPDATE ap_timeline SET author_emoji_json = ? WHERE id = ?').run(JSON.stringify(ai.emojis), r.id); } catch { /* ignore */ } }
+        }
+        // v14: same for the booster's display name ("X boosted"). The row stores
+        // no booster URI, so resolve it from the handle via webfinger. Scoped to
+        // this exact row (slug) since a note can be boosted by different people.
+        if (/:[A-Za-z0-9_+-]+:/.test(r.reblog_name || '') && !r.reblog_emoji_json && r.reblog_handle) {
+          const bUri = await webfingerResolve(r.reblog_handle);
+          const em = bUri ? actorNameEmojis(await fetchActor(bUri)) : undefined;
+          if (em) { try { db.prepare('UPDATE ap_timeline SET reblog_emoji_json = ? WHERE id = ? AND slug = ?').run(JSON.stringify(em), r.id, r.slug); } catch { /* ignore */ } }
         }
       } catch { failed++; /* per-note best-effort */ }
