eleven / messenger Public

web: tapping an @mention opens the member's profile card (#1341)

The one-item mention menu (Message X) is replaced by the existing profile
sheet (viewProfile), which already carries Message them, nickname, mute,
report, and block — the info surface a tapped mention should open.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 49da77a1c7ebf9244d33d865ac377fed941c1ca2 parent 8ca6fb0
3 files changed, +10 −56
  • changelog/2026-07-25-mention-profile.md +3 −0
  • web/static/shell.js +7 −34
  • web/static/style.css +0 −22
diff --git a/changelog/2026-07-25-mention-profile.md b/changelog/2026-07-25-mention-profile.md
new file mode 100644
index 0000000..403cf5c
--- /dev/null
+++ b/changelog/2026-07-25-mention-profile.md
@@ -0,0 +1,3 @@
+🤖 Tapping an @mention in a message now opens that person's profile card —
+with Message, mute, block, and the rest — instead of a single "Message"
+menu.
diff --git a/web/static/shell.js b/web/static/shell.js
index 102f5f4..d376a31 100644
--- a/web/static/shell.js
+++ b/web/static/shell.js
@@ -145,7 +145,7 @@ import { openMemoryPanel } from "/static/apps/memory.js"; // /memory — the AI-
import { setHost as setChatAppHost, setConnected as setChatAppConnected, appendNotice, setNoticeTyping, feedGlideToBottom, scrollToMessage, repaintMentionsOf } from "/static/apps/chat.js";
import { avatarFor, threadAvatarFor, paintAvatar, avatarEl } from "/static/avatars.js";
import { rosterLabel, publishedLabel, rosterMatches, rosterPresence, setPresenceLine, agentTag, pickerName, pickerSection } from "/static/rosterui.js";
-import { editGroup, openOrCreateDM, showMembers, viewProfile, confirmRemoveFromSpace, updateRenameBanner, showSpaceInfo, closeChat, endConversation, toggleMute, togglePin, addPeople, manageGroupApps, leaveGroup } from "/static/groupactions.js";
+import { editGroup, showMembers, viewProfile, confirmRemoveFromSpace, updateRenameBanner, showSpaceInfo, closeChat, endConversation, toggleMute, togglePin, addPeople, manageGroupApps, leaveGroup } from "/static/groupactions.js";
import { existingDM, showNewChat, showMembership, flash, showDiscover, showApps, createThread, pickerApps } from "/static/newchat.js";
import { addDevice, openInApp } from "/static/adddevice.js";
import { loadTiers, applyTiers, setTierShown, subscribeToLevel, cancelLevelSub, resumeLevelSub, changeLevelSub, levelPortal, billingDate, levelPrice, ensureTierKeys } from "/static/billingui.js";
@@ -5222,14 +5222,14 @@ setChatAppHost({
const e = roster.find((x) => x.user_id === uid);
return displayNameFor(nicknames[uid], e && e.label);
},
- // Mentions: the roster's (client-decrypted) display names, and the original
- // tap-a-mention member menu (Message them …). Deliberately the PUBLISHED
- // names, not my nicknames — a mention travels as text every member must
- // resolve, so "@Dermo" would highlight for no one else.
+ // Mentions: the roster's (client-decrypted) display names. Deliberately the
+ // PUBLISHED names, not my nicknames — a mention travels as text every member
+ // must resolve, so "@Dermo" would highlight for no one else. Tapping one
+ // opens the member's profile card (Message them, mute, block, …).
mentionNames: () => roster.map((e) => e.label).filter(Boolean),
- mention: (label, anchor) => {
+ mention: (label) => {
const entry = roster.find((e) => e.label === label);
- if (entry && entry.user_id !== myUserId) openMentionMenu(entry, anchor);
+ if (entry && entry.user_id !== myUserId) viewProfile(entry.user_id);
},
// Renderers gate the tappable @mention affordance on this so a self-mention
// stays a plain bold chip (no link, no menu) — matching the classic feed.
@@ -8648,33 +8648,6 @@ function typeaheadItems(tok) {
}
-// Small menu anchored to a tapped @mention. One action for now — message the
-// person (reopen the existing DM, or start one) — with room to grow.
-function openMentionMenu(entry, anchor) {
- document.querySelector(".mention-menu")?.remove();
- const pop = document.createElement("div");
- pop.className = "mention-menu";
- const msg = document.createElement("button");
- msg.className = "mention-menu-item";
- msg.textContent = `Message ${entry.label}`;
- msg.onclick = () => {
- pop.remove();
- openOrCreateDM(entry.user_id);
- };
- pop.appendChild(msg);
- document.body.appendChild(pop);
- const r = anchor.getBoundingClientRect();
- pop.style.top = `${Math.max(8, r.top - pop.offsetHeight - 8)}px`;
- pop.style.left = `${Math.min(r.left, window.innerWidth - pop.offsetWidth - 8)}px`;
- const close = (e) => {
- if (!pop.contains(e.target)) {
- pop.remove();
- document.removeEventListener("pointerdown", close, true);
- }
- };
- setTimeout(() => document.addEventListener("pointerdown", close, true), 0);
-}
-
// A centered modal sheet. Click the backdrop or Esc to dismiss.
function modal() {
const prevFocus = document.activeElement; // restored when the sheet closes
diff --git a/web/static/style.css b/web/static/style.css
index 493338d..4606e4a 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -1528,28 +1528,6 @@ select:disabled { opacity: 0.5; cursor: default; }
.mention { font-weight: 600; }
.mention-link { cursor: pointer; }
.mention-link:hover { text-decoration: underline; text-underline-offset: 2px; }
-/* the little menu a tapped mention opens (anchored + fixed, like .emoji-picker) */
-.mention-menu {
- position: fixed;
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 12px;
- box-shadow: 0 12px 40px var(--shadow);
- padding: 6px;
- z-index: 60;
-}
-.mention-menu-item {
- display: block;
- width: 100%;
- text-align: left;
- background: transparent;
- color: var(--ink);
- border-radius: 8px;
- padding: 9px 14px;
- font-weight: 500;
- white-space: nowrap;
-}
-.mention-menu-item:hover { background: var(--bubble-them); }
.composer {
display: flex;