From 9ca726bc99e9e024eecce731f748d825cc422036 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Tue, 7 Jul 2026 10:24:53 -0400
Subject: [PATCH] Admin star on DM message bubbles from admin peers
Extend the admin star to per-message avatars: incoming bubbles from an
admin peer now show the gold star. MessageBubble gains an opt-in adminId
and wraps its avatar img in a non-clipped shell only when the author is an
admin (non-admin bubbles keep the bare
). MessageThread passes the
peer pubkey for incoming bubbles; outgoing bubbles are the current user,
whose admin status is already shown on the profile button.
Co-Authored-By: Claude Opus 4.8
---
css/tailwind.source.css | 8 ++++++++
js/ui/dm/MessageBubble.js | 18 ++++++++++++++++--
js/ui/dm/MessageThread.js | 3 +++
3 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/css/tailwind.source.css b/css/tailwind.source.css
index 45f2c793..4f34dda1 100644
--- a/css/tailwind.source.css
+++ b/css/tailwind.source.css
@@ -5896,6 +5896,14 @@
border: var(--border-width-hairline) solid var(--border-subtle);
}
+ /* Non-clipped relative shell used only when a message avatar needs an admin
+ star; the plain .dm-message-avatar img has no wrapper otherwise. */
+ .dm-message-avatar-wrap {
+ position: relative;
+ display: inline-flex;
+ flex-shrink: 0;
+ }
+
.dm-message-bubble {
max-width: 75%;
display: flex;
diff --git a/js/ui/dm/MessageBubble.js b/js/ui/dm/MessageBubble.js
index 7276a796..53155bbf 100644
--- a/js/ui/dm/MessageBubble.js
+++ b/js/ui/dm/MessageBubble.js
@@ -1,3 +1,5 @@
+import { applyAdminStar, isAdminActor } from "../adminBadge.js";
+
function createElement(doc, tag, className, text) {
const element = doc.createElement(tag);
if (className) {
@@ -14,6 +16,7 @@ export function MessageBubble({
message = {},
variant = "incoming",
avatarSrc = "",
+ adminId = "",
} = {}) {
if (!doc) {
throw new Error("MessageBubble requires a document reference.");
@@ -32,6 +35,17 @@ export function MessageBubble({
avatar.src = avatarSrc || "assets/svg/default-profile.svg";
avatar.alt = variant === "outgoing" ? "Me" : "Sender";
+ // The message avatar is a bare circular
; when its author is an admin,
+ // wrap it in a non-clipped relative shell so the star can sit in the corner.
+ // Non-admin bubbles keep the plain
so existing layout is untouched.
+ let avatarNode = avatar;
+ if (adminId && isAdminActor(adminId)) {
+ const wrap = createElement(doc, "span", "dm-message-avatar-wrap");
+ wrap.appendChild(avatar);
+ applyAdminStar(wrap, adminId, { doc });
+ avatarNode = wrap;
+ }
+
const bubble = createElement(doc, "div", "dm-message-bubble");
bubble.classList.add(`dm-message-bubble--${variant}`);
bubble.dataset.status = status;
@@ -67,9 +81,9 @@ export function MessageBubble({
if (variant === "outgoing") {
row.appendChild(bubble);
- row.appendChild(avatar);
+ row.appendChild(avatarNode);
} else {
- row.appendChild(avatar);
+ row.appendChild(avatarNode);
row.appendChild(bubble);
}
diff --git a/js/ui/dm/MessageThread.js b/js/ui/dm/MessageThread.js
index e03f734d..e1fdd3de 100644
--- a/js/ui/dm/MessageThread.js
+++ b/js/ui/dm/MessageThread.js
@@ -137,6 +137,9 @@ export function MessageThread({
message,
variant: isOutgoing ? "outgoing" : "incoming",
avatarSrc: isOutgoing ? currentUserAvatarUrl : (contact.avatarSrc || ""),
+ // Star incoming bubbles from an admin peer; the current user's own admin
+ // status is already shown on the profile button.
+ adminId: isOutgoing ? "" : (contact.pubkey || contact.id || ""),
});
timeline.appendChild(bubble);
});