clay-server 3.8.0-beta.1 → 3.8.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/lib/builtin-mates.js +14 -14
  2. package/lib/mates.js +2 -6
  3. package/lib/project-mate-interaction.js +7 -7
  4. package/lib/project-sessions.js +1 -1
  5. package/lib/project-user-mention.js +4 -4
  6. package/lib/project.js +4 -3
  7. package/lib/public/css/avatar-imprints.css +46 -0
  8. package/lib/public/css/icon-strip.css +3 -3
  9. package/lib/public/css/profile.css +123 -95
  10. package/lib/public/css/session-actions.css +40 -9
  11. package/lib/public/css/user-settings.css +1 -1
  12. package/lib/public/index.html +2 -2
  13. package/lib/public/modules/app-cursors.js +2 -2
  14. package/lib/public/modules/app-dm.js +1 -8
  15. package/lib/public/modules/app-messages.js +2 -2
  16. package/lib/public/modules/app-projects.js +7 -6
  17. package/lib/public/modules/app-rate-limit.js +0 -1
  18. package/lib/public/modules/app-rendering.js +0 -3
  19. package/lib/public/modules/avatar-imprint.js +134 -0
  20. package/lib/public/modules/avatar.js +25 -19
  21. package/lib/public/modules/debate.js +10 -5
  22. package/lib/public/modules/mate-sidebar.js +2 -2
  23. package/lib/public/modules/mention.js +26 -16
  24. package/lib/public/modules/profile.js +73 -190
  25. package/lib/public/modules/session-actions.js +5 -3
  26. package/lib/public/modules/sidebar-mates.js +5 -4
  27. package/lib/public/modules/sidebar-sessions.js +1 -1
  28. package/lib/public/style.css +1 -0
  29. package/lib/server-auth.js +2 -2
  30. package/lib/server-dm.js +8 -8
  31. package/lib/server-mates.js +3 -3
  32. package/lib/server-settings.js +2 -2
  33. package/lib/server.js +5 -4
  34. package/lib/users.js +4 -4
  35. package/package.json +1 -1
@@ -72,8 +72,8 @@ function createCursorElement(userId, displayName, color, avatarStyle, avatarSeed
72
72
  // Avatar
73
73
  var avatarImg = document.createElement("img");
74
74
  avatarImg.className = "remote-cursor-avatar";
75
- avatarImg.src = avatarCustom ? avatarCustom : avatarUrl(avatarStyle || "thumbs", avatarSeed || userId, 16);
76
- avatarImg.style.cssText = "width:14px;height:14px;border-radius:50%;background:#fff;flex-shrink:0;";
75
+ avatarImg.src = avatarCustom ? avatarCustom : avatarUrl(avatarStyle || "imprint", avatarSeed || userId, 16);
76
+ avatarImg.style.cssText = "width:14px;height:14px;border-radius:4px;background:#fff;flex-shrink:0;";
77
77
  tag.appendChild(avatarImg);
78
78
 
79
79
  // Name label
@@ -215,7 +215,7 @@ export function enterDmMode(key, targetUser, messages) {
215
215
  var termBtn = document.getElementById("terminal-toggle-btn");
216
216
  if (termBtn) termBtn.style.display = "none";
217
217
  // Apply mate color to chat title bar and panels
218
- var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#7c3aed";
218
+ var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#5857fc";
219
219
  document.body.style.setProperty("--mate-color", mateColor);
220
220
  document.body.style.setProperty("--mate-color-tint", mateColor + "0a");
221
221
  document.body.classList.add("mate-dm-active");
@@ -223,19 +223,12 @@ export function enterDmMode(key, targetUser, messages) {
223
223
  var mp = targetUser.profile || {};
224
224
  var mateAvUrlDm = mateAvatarUrl(targetUser, 36);
225
225
  var myUser = store.get('cachedAllUsers').find(function (u) { return u.id === store.get('myUserId'); });
226
- if (!myUser) {
227
- try { var cached = JSON.parse(localStorage.getItem("clay_my_user") || "null"); if (cached) myUser = cached; } catch(e) {}
228
- }
229
226
  var myAvatarUrl = userAvatarUrl(myUser || { id: store.get('myUserId') }, 36);
230
227
  var myDisplayName = (myUser && myUser.displayName) || "";
231
228
  document.body.dataset.mateAvatarUrl = mateAvUrlDm;
232
229
  document.body.dataset.mateName = mp.displayName || targetUser.displayName || targetUser.name || "";
233
230
  document.body.dataset.myAvatarUrl = myAvatarUrl;
234
231
  document.body.dataset.myDisplayName = myDisplayName;
235
- // Cache my info for restore after hard refresh
236
- if (myUser) {
237
- try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: myUser.displayName, avatarStyle: myUser.avatarStyle, avatarSeed: myUser.avatarSeed, avatarCustom: myUser.avatarCustom, username: myUser.username })); } catch(e) {}
238
- }
239
232
  var titleBarContent = document.querySelector(".title-bar-content");
240
233
  if (titleBarContent) {
241
234
  titleBarContent.style.background = mateColor;
@@ -95,7 +95,7 @@ export function processMessage(msg) {
95
95
  if (headerTitleEl) headerTitleEl.textContent = _mdn;
96
96
  var _tbpn = document.getElementById("title-bar-project-name");
97
97
  if (_tbpn) _tbpn.textContent = _mdn;
98
- var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
98
+ var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#5857fc";
99
99
  var _tbc2 = document.querySelector(".title-bar-content");
100
100
  if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
101
101
  document.body.classList.add("mate-dm-active");
@@ -299,7 +299,7 @@ export function processMessage(msg) {
299
299
  var tbProjectName = document.getElementById("title-bar-project-name");
300
300
  if (tbProjectName) tbProjectName.textContent = _mateDN;
301
301
  // Re-apply mate title bar styling (may be lost during project switch)
302
- var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
302
+ var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#5857fc";
303
303
  var _tbc = document.querySelector(".title-bar-content");
304
304
  if (_tbc) { _tbc.style.background = _mc; _tbc.classList.add("mate-dm-active"); }
305
305
  document.body.classList.add("mate-dm-active");
@@ -257,12 +257,13 @@ export function updateProjectList(msg) {
257
257
  if (msg.dmConversations) store.set({ cachedDmConversations: msg.dmConversations });
258
258
  // renderUserStrip is handled by the store subscriber
259
259
  var st2 = store.snap();
260
- if (document.body.classList.contains("mate-dm-active") || document.body.classList.contains("wide-view")) {
261
- var refreshedMyUser = st2.cachedAllUsers.find(function (u) { return u.id === st2.myUserId; });
262
- if (refreshedMyUser) {
263
- document.body.dataset.myDisplayName = refreshedMyUser.displayName || refreshedMyUser.username || "";
264
- document.body.dataset.myAvatarUrl = userAvatarUrl(refreshedMyUser, 36);
265
- try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: refreshedMyUser.displayName, username: refreshedMyUser.username, avatarStyle: refreshedMyUser.avatarStyle, avatarSeed: refreshedMyUser.avatarSeed, avatarCustom: refreshedMyUser.avatarCustom })); } catch(e) {}
260
+ var refreshedMyUser = st2.cachedAllUsers.find(function (u) { return u.id === st2.myUserId; });
261
+ if (refreshedMyUser) {
262
+ document.body.dataset.myDisplayName = refreshedMyUser.displayName || refreshedMyUser.username || "";
263
+ document.body.dataset.myAvatarUrl = userAvatarUrl(refreshedMyUser, 36);
264
+ var myMessageAvatars = document.querySelectorAll('.dm-bubble-avatar-me');
265
+ for (var avatarIndex = 0; avatarIndex < myMessageAvatars.length; avatarIndex++) {
266
+ myMessageAvatars[avatarIndex].src = document.body.dataset.myAvatarUrl;
266
267
  }
267
268
  }
268
269
  // Render my avatar (always present, hidden behind user-island)
@@ -311,7 +311,6 @@ export function addScheduledMessageBubble(text, resetsAt) {
311
311
  if (isChannel) {
312
312
  // Channel mode: avatar + header with scheduled badge + message
313
313
  var _me = store.get('cachedAllUsers').find(function (u) { return u.id === store.get('myUserId'); });
314
- if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
315
314
  var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
316
315
 
317
316
  var avi = document.createElement("img");
@@ -552,9 +552,6 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName, d
552
552
  _displayName = fromUserName || (_targetUser && (_targetUser.displayName || _targetUser.username)) || "User";
553
553
  } else {
554
554
  _targetUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
555
- if (!_targetUser) {
556
- try { _targetUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {}
557
- }
558
555
  _displayName = document.body.dataset.myDisplayName || "";
559
556
  if (!_displayName) {
560
557
  _displayName = (_targetUser && (_targetUser.displayName || _targetUser.username)) || "Me";
@@ -0,0 +1,134 @@
1
+ // Clay Imprints: deterministic SVG identities derived from a stable seed.
2
+
3
+ var PAPER = "#f1efe8";
4
+ var INK = "#1b1b1a";
5
+ var LIGHT_BASE = "#e7e5de";
6
+ var LIGHT_FIELD = "#333330";
7
+ var REGISTRATION_INDIGO = "#5857fc";
8
+ var BRAND_GREEN = "#07e5a3";
9
+
10
+ function hashString(value) {
11
+ var text = String(value || "anonymous");
12
+ var hash = 2166136261;
13
+ for (var index = 0; index < text.length; index++) {
14
+ hash ^= text.charCodeAt(index);
15
+ hash = Math.imul(hash, 16777619);
16
+ }
17
+ return hash >>> 0;
18
+ }
19
+
20
+ function makeRandom(seed) {
21
+ var state = seed >>> 0;
22
+ return function nextRandom() {
23
+ state += 0x6d2b79f5;
24
+ var value = state;
25
+ value = Math.imul(value ^ (value >>> 15), value | 1);
26
+ value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
27
+ return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
28
+ };
29
+ }
30
+
31
+ function between(random, min, max) {
32
+ return min + random() * (max - min);
33
+ }
34
+
35
+ function number(value) {
36
+ return Math.round(value * 100) / 100;
37
+ }
38
+
39
+ function xmlText(value) {
40
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
41
+ }
42
+
43
+ function legacyIdentity(style, seed) {
44
+ var sourceStyle = String(style || "imprint").toLowerCase();
45
+ var sourceSeed = String(seed || "anonymous");
46
+ if (sourceStyle === "imprint") return sourceSeed;
47
+ return "legacy:" + sourceStyle + ":" + sourceSeed;
48
+ }
49
+
50
+ function curvePath(y, bow, endOffset) {
51
+ return "M -28 " + number(y) +
52
+ " C 17 " + number(y + bow) +
53
+ ", 71 " + number(y - bow) +
54
+ ", 124 " + number(y + endOffset);
55
+ }
56
+
57
+ function cutPath(random) {
58
+ var start = between(random, 37, 52);
59
+ var first = between(random, 14, 84);
60
+ var second = between(random, 12, 86);
61
+ var end = between(random, 40, 64);
62
+ return "M -22 " + number(start) + " C 21 " + number(first) + ", 73 " + number(second) + ", 118 " + number(end);
63
+ }
64
+
65
+ export function imprintSvg(options) {
66
+ var settings = options || {};
67
+ var size = Math.max(12, Math.min(256, Number(settings.size) || 64));
68
+ var identity = legacyIdentity(settings.style, settings.seed);
69
+ var hash = hashString(identity);
70
+ var random = makeRandom(hash);
71
+ var darkBase = random() > 0.52;
72
+ var base = darkBase ? INK : LIGHT_BASE;
73
+ var field = darkBase ? PAPER : LIGHT_FIELD;
74
+ var accent = REGISTRATION_INDIGO;
75
+ var rotation = Math.floor(random() * 4) * 90;
76
+ var bandWidth = between(random, 37, 56);
77
+ var secondaryWidth = between(random, 14, 24);
78
+ var cutWidth = between(random, 15, 25);
79
+ var firstY = between(random, 24, 42);
80
+ var secondY = between(random, 58, 78);
81
+ var firstBow = between(random, -34, 34);
82
+ var secondBow = between(random, -32, 32);
83
+ var firstPath = curvePath(firstY, firstBow, 6);
84
+ var secondPath = curvePath(secondY, -secondBow, -5);
85
+ var contours = "";
86
+
87
+ if (size >= 40) {
88
+ for (var contour = -1; contour <= 1; contour++) {
89
+ var offset = contour * 5.2;
90
+ contours += '<path d="' + curvePath(firstY + offset, firstBow, 0) + '" fill="none" stroke="' +
91
+ (darkBase ? PAPER : INK) + '" stroke-opacity="0.2" stroke-width="0.8"/>';
92
+ }
93
+ }
94
+
95
+ var clipId = "imprint-" + hash.toString(16);
96
+ return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
97
+ '" viewBox="0 0 96 96"><defs><clipPath id="' + clipId +
98
+ '"><rect width="96" height="96" rx="24"/></clipPath></defs><g clip-path="url(#' + clipId +
99
+ ')"><rect width="96" height="96" fill="' + base + '"/><g transform="rotate(' + rotation +
100
+ ' 48 48)"><path d="' + firstPath + '" fill="none" stroke="' + field + '" stroke-width="' +
101
+ number(bandWidth) + '" stroke-linecap="square"/><path d="' + secondPath + '" fill="none" stroke="' +
102
+ accent + '" stroke-width="' + number(secondaryWidth) + '" stroke-linecap="square"/><path d="' +
103
+ cutPath(random) + '" fill="none" stroke="' + base + '" stroke-width="' + number(cutWidth) +
104
+ '" stroke-linecap="square"/>' + contours + '</g></g><rect x="0.5" y="0.5" width="95" height="95" rx="23.5" fill="none" stroke="' +
105
+ (darkBase ? "#000000" : INK) + '" stroke-opacity="0.16"/></svg>';
106
+ }
107
+
108
+ export function imprintDataUrl(options) {
109
+ return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(imprintSvg(options));
110
+ }
111
+
112
+ export function mateMarkSvg(options) {
113
+ var settings = options || {};
114
+ var size = Math.max(12, Math.min(256, Number(settings.size) || 64));
115
+ var identity = String(settings.seed || "M").trim();
116
+ var initial = Array.from(identity || "M")[0].toUpperCase();
117
+ var palette = [
118
+ { background: REGISTRATION_INDIGO, foreground: PAPER },
119
+ { background: BRAND_GREEN, foreground: INK },
120
+ { background: PAPER, foreground: REGISTRATION_INDIGO },
121
+ ][initial.codePointAt(0) % 3];
122
+ var clipId = "mate-mark";
123
+
124
+ return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
125
+ '" viewBox="0 0 96 96"><defs><clipPath id="' + clipId +
126
+ '"><rect width="96" height="96" rx="24"/></clipPath></defs><g clip-path="url(#' + clipId +
127
+ ')"><rect width="96" height="96" fill="' + palette.background + '"/><text x="48" y="50" fill="' + palette.foreground +
128
+ '" font-family="Arial, Helvetica, sans-serif" font-size="43" font-weight="600" text-anchor="middle" dominant-baseline="middle">' +
129
+ xmlText(initial) + '</text></g><rect x="0.5" y="0.5" width="95" height="95" rx="23.5" fill="none" stroke="#000000" stroke-opacity="0.18"/></svg>';
130
+ }
131
+
132
+ export function mateMarkDataUrl(options) {
133
+ return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(mateMarkSvg(options));
134
+ }
@@ -1,36 +1,42 @@
1
- // Centralized avatar URL builder and style definitions
2
- // All DiceBear avatar URLs should be constructed through this module.
1
+ // Centralized Clay Imprint URL builder for people and Mates.
2
+ import { imprintDataUrl, mateMarkDataUrl } from './avatar-imprint.js';
3
3
 
4
- export var AVATAR_STYLES = [
5
- { id: 'thumbs', name: 'Thumbs' },
6
- { id: 'bottts', name: 'Bots' },
7
- { id: 'pixel-art', name: 'Pixel' },
8
- { id: 'adventurer', name: 'Adventurer' },
9
- { id: 'micah', name: 'Micah' },
10
- { id: 'fun-emoji', name: 'Emoji' },
11
- { id: 'icons', name: 'Icons' },
12
- ];
4
+ var imprintCache = new Map();
5
+ var mateMarkCache = new Map();
13
6
 
14
- // Build a DiceBear avatar URL from style, seed, and optional size.
7
+ // Legacy style remains part of the identity hash so existing selections map
8
+ // deterministically into the new Clay Imprint system.
15
9
  export function avatarUrl(style, seed, size) {
16
- var s = encodeURIComponent(seed || 'anonymous');
17
- return 'https://api.dicebear.com/9.x/' + (style || 'thumbs') + '/svg?seed=' + s + '&size=' + (size || 64);
10
+ var cacheKey = [style || 'imprint', seed || 'anonymous', size || 64].join('|');
11
+ if (imprintCache.has(cacheKey)) return imprintCache.get(cacheKey);
12
+ var url = imprintDataUrl({ style: style || 'imprint', seed: seed || 'anonymous', size: size || 64 });
13
+ if (imprintCache.size >= 512) imprintCache.delete(imprintCache.keys().next().value);
14
+ imprintCache.set(cacheKey, url);
15
+ return url;
18
16
  }
19
17
 
20
18
  // Build avatar URL for a user object, preferring custom avatar if set.
21
19
  export function userAvatarUrl(user, size) {
22
20
  if (user && user.avatarCustom) return user.avatarCustom;
23
- var style = (user && user.avatarStyle) || 'thumbs';
21
+ var style = (user && user.avatarStyle) || 'imprint';
24
22
  var seed = (user && (user.avatarSeed || user.username || user.id)) || 'anonymous';
25
23
  return avatarUrl(style, seed, size);
26
24
  }
27
25
 
26
+ export function mateMarkUrl(seed, size) {
27
+ var cacheKey = [seed || 'mate', size || 64].join('|');
28
+ if (mateMarkCache.has(cacheKey)) return mateMarkCache.get(cacheKey);
29
+ var url = mateMarkDataUrl({ seed: seed || 'mate', size: size || 64 });
30
+ if (mateMarkCache.size >= 256) mateMarkCache.delete(mateMarkCache.keys().next().value);
31
+ mateMarkCache.set(cacheKey, url);
32
+ return url;
33
+ }
34
+
28
35
  // Build avatar URL for a mate object, preferring custom avatar if set.
29
36
  export function mateAvatarUrl(mate, size) {
30
- if (!mate) return avatarUrl('bottts', 'mate', size);
37
+ if (!mate) return mateMarkUrl('mate', size);
31
38
  var p = mate.profile || mate;
32
39
  if (p.avatarCustom || mate.avatarCustom) return p.avatarCustom || mate.avatarCustom;
33
- var style = p.avatarStyle || mate.avatarStyle || 'bottts';
34
- var seed = p.avatarSeed || mate.avatarSeed || mate.id || 'mate';
35
- return avatarUrl(style, seed, size);
40
+ var identity = p.displayName || mate.name || mate.displayName || 'Mate';
41
+ return mateMarkUrl(identity, size);
36
42
  }
@@ -53,7 +53,15 @@ export function resetDebateState() {
53
53
  }
54
54
 
55
55
  function buildAvatarUrl(meta) {
56
- return "https://api.dicebear.com/7.x/" + (meta.avatarStyle || "bottts") + "/svg?seed=" + encodeURIComponent(meta.avatarSeed || meta.mateId || "mate");
56
+ return mateAvatarUrl({
57
+ id: meta.mateId,
58
+ profile: {
59
+ avatarStyle: meta.avatarStyle || "imprint",
60
+ avatarSeed: meta.avatarSeed || meta.mateId || "mate",
61
+ avatarColor: meta.avatarColor || "#5857fc",
62
+ avatarCustom: meta.avatarCustom || "",
63
+ },
64
+ }, 32);
57
65
  }
58
66
 
59
67
  // --- Float info panel ---
@@ -943,12 +951,9 @@ function createPanelItem(mate) {
943
951
  cb.type = "checkbox";
944
952
  item.appendChild(cb);
945
953
 
946
- var avatarSrc = "https://api.dicebear.com/7.x/" +
947
- ((mate.profile && mate.profile.avatarStyle) || "bottts") +
948
- "/svg?seed=" + encodeURIComponent((mate.profile && mate.profile.avatarSeed) || mate.id);
949
954
  var avi = document.createElement("img");
950
955
  avi.className = "debate-panel-item-avatar";
951
- avi.src = avatarSrc;
956
+ avi.src = mateAvatarUrl(mate, 32);
952
957
  item.appendChild(avi);
953
958
 
954
959
  var info = document.createElement("div");
@@ -139,7 +139,7 @@ export function showMateSidebar(mateId, mateData) {
139
139
  var profile = mateData.profile || mateData || {};
140
140
  var displayName = profile.displayName || mateData.displayName || mateData.name || "Mate";
141
141
 
142
- var mateColor = profile.avatarColor || mateData.avatarColor || "#7c3aed";
142
+ var mateColor = profile.avatarColor || mateData.avatarColor || "#5857fc";
143
143
 
144
144
  var mateAvUrl = mateAvatarUrl(mateData, 32);
145
145
  if (avatarEl) avatarEl.src = mateAvUrl;
@@ -267,7 +267,7 @@ export function updateMateSidebarProfile(mateData) {
267
267
  if (!columnEl || !mateData) return;
268
268
  var profile = mateData.profile || mateData || {};
269
269
  var displayName = profile.displayName || mateData.displayName || mateData.name || "Mate";
270
- var mateColor = profile.avatarColor || mateData.avatarColor || "#7c3aed";
270
+ var mateColor = profile.avatarColor || mateData.avatarColor || "#5857fc";
271
271
 
272
272
  if (avatarEl) {
273
273
  avatarEl.src = mateAvatarUrl(mateData, 32);
@@ -112,7 +112,7 @@ function buildMentionCandidates() {
112
112
  kind: "mate",
113
113
  id: m.id,
114
114
  name: (m.profile && m.profile.displayName) || m.name || "Mate",
115
- color: (m.profile && m.profile.avatarColor) || "#6c5ce7",
115
+ color: (m.profile && m.profile.avatarColor) || "#5857fc",
116
116
  avatarSrc: mateAvatarUrl(m, 24),
117
117
  vendor: m.vendor || "claude",
118
118
  bio: m.bio || (m.profile && m.profile.bio) || "",
@@ -134,9 +134,8 @@ function buildMentionCandidates() {
134
134
  kind: "user",
135
135
  id: u.id,
136
136
  name: u.displayName || u.username || "User",
137
- color: u.avatarColor || "#7c3aed",
138
- // Build dicebear url from style+seed; matches userAvatarUrl shape.
139
- avatarSrc: "https://api.dicebear.com/7.x/" + (u.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(u.avatarSeed || u.username || u.id),
137
+ color: u.avatarColor || "#5857fc",
138
+ avatarSrc: userAvatarUrl(u, 24),
140
139
  vendor: null,
141
140
  bio: u.username ? "@" + u.username : "",
142
141
  primary: false,
@@ -424,7 +423,7 @@ export function stickyReapplyMention() {
424
423
  var kind = selectedMateKind;
425
424
  var id = selectedMateId;
426
425
  var name = selectedMateName;
427
- var color = selectedMateColor || "#6c5ce7";
426
+ var color = selectedMateColor || "#5857fc";
428
427
  var avatarSrc = selectedMateAvatar || "";
429
428
  // Reset index but keep selection
430
429
  mentionAtIdx = -1;
@@ -516,7 +515,7 @@ export function handleMentionStart(msg) {
516
515
  header.className = "dm-bubble-header";
517
516
  var nameSpan = document.createElement("span");
518
517
  nameSpan.className = "dm-bubble-name";
519
- nameSpan.style.color = msg.avatarColor || "#6c5ce7";
518
+ nameSpan.style.color = msg.avatarColor || "#5857fc";
520
519
  nameSpan.textContent = msg.mateName || "Mate";
521
520
  header.appendChild(nameSpan);
522
521
 
@@ -556,7 +555,7 @@ export function handleMentionStart(msg) {
556
555
  // Project chat: mention block style
557
556
  currentMentionEl = document.createElement("div");
558
557
  currentMentionEl.className = "msg-mention";
559
- currentMentionEl.style.setProperty("--mention-color", msg.avatarColor || "#6c5ce7");
558
+ currentMentionEl.style.setProperty("--mention-color", msg.avatarColor || "#5857fc");
560
559
 
561
560
  var header = document.createElement("div");
562
561
  header.className = "mention-header";
@@ -754,15 +753,13 @@ function isDmLayout() {
754
753
 
755
754
  function getMyAvatarSrc() {
756
755
  if (document.body.dataset.myAvatarUrl) return document.body.dataset.myAvatarUrl;
757
- var myUser = null;
758
- try { myUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch (e) {}
756
+ var myUser = (store.get('cachedAllUsers') || []).find(function (u) { return u.id === store.get('myUserId'); });
759
757
  return userAvatarUrl(myUser || {}, 36);
760
758
  }
761
759
 
762
760
  function getMyDisplayName() {
763
761
  if (document.body.dataset.myDisplayName) return document.body.dataset.myDisplayName;
764
- var myUser = null;
765
- try { myUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch (e) {}
762
+ var myUser = (store.get('cachedAllUsers') || []).find(function (u) { return u.id === store.get('myUserId'); });
766
763
  return (myUser && (myUser.displayName || myUser.username)) || "Me";
767
764
  }
768
765
 
@@ -772,14 +769,27 @@ function timeStr() {
772
769
  }
773
770
 
774
771
  function buildMentionAvatarUrl(meta) {
775
- return "https://api.dicebear.com/7.x/" + (meta.avatarStyle || "bottts") + "/svg?seed=" + encodeURIComponent(meta.avatarSeed || meta.mateId);
772
+ return mateAvatarUrl({
773
+ id: meta.mateId,
774
+ profile: {
775
+ avatarStyle: meta.avatarStyle || "imprint",
776
+ avatarSeed: meta.avatarSeed || meta.mateId,
777
+ avatarColor: meta.avatarColor || "#5857fc",
778
+ avatarCustom: meta.avatarCustom || "",
779
+ },
780
+ }, 36);
776
781
  }
777
782
 
778
783
  function buildUserAvatarUrlFromEntry(entry, who) {
779
784
  // who = "from" | "target"
780
- var style = entry[who + "AvatarStyle"] || "thumbs";
781
785
  var seed = entry[who + "AvatarSeed"] || entry[who === "from" ? "fromName" : "targetName"] || entry[who === "from" ? "from" : "targetUserId"];
782
- return "https://api.dicebear.com/7.x/" + style + "/svg?seed=" + encodeURIComponent(seed || "user");
786
+ return userAvatarUrl({
787
+ id: entry[who === "from" ? "from" : "targetUserId"],
788
+ avatarStyle: entry[who + "AvatarStyle"] || "imprint",
789
+ avatarSeed: seed || "user",
790
+ avatarColor: entry[who + "AvatarColor"] || "#5857fc",
791
+ avatarCustom: entry[who + "AvatarCustom"] || "",
792
+ }, 36);
783
793
  }
784
794
 
785
795
  // Render a user-to-user @mention bubble (live receive AND history replay).
@@ -1021,7 +1031,7 @@ export function renderMentionResponse(entry) {
1021
1031
  header.className = "dm-bubble-header";
1022
1032
  var nameSpan = document.createElement("span");
1023
1033
  nameSpan.className = "dm-bubble-name";
1024
- nameSpan.style.color = entry.avatarColor || "#6c5ce7";
1034
+ nameSpan.style.color = entry.avatarColor || "#5857fc";
1025
1035
  nameSpan.textContent = entry.mateName || "Mate";
1026
1036
  header.appendChild(nameSpan);
1027
1037
 
@@ -1050,7 +1060,7 @@ export function renderMentionResponse(entry) {
1050
1060
  // Project chat: use mention block style
1051
1061
  var el = document.createElement("div");
1052
1062
  el.className = "msg-mention";
1053
- el.style.setProperty("--mention-color", entry.avatarColor || "#6c5ce7");
1063
+ el.style.setProperty("--mention-color", entry.avatarColor || "#5857fc");
1054
1064
 
1055
1065
  var mheader = document.createElement("div");
1056
1066
  mheader.className = "mention-header";