From 82101bcb9b16ee2a7c187f4eab310bc8789ed358 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 19 Aug 2021 21:16:49 +0530
Subject: [PATCH 01/20] use two characters for the default avatar

---
 customize.dist/src/less2/include/avatar.less  |  2 +-
 customize.dist/src/less2/include/toolbar.less |  2 +-
 www/common/inner/common-mediatag.js           | 11 ++++++++++-
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less
index 725c7748f..02abc4050 100644
--- a/customize.dist/src/less2/include/avatar.less
+++ b/customize.dist/src/less2/include/avatar.less
@@ -4,7 +4,7 @@
     @width: 30px
 ) {
     @avatar-width: @width;
-    @avatar-font-size: @width / 1.2;
+    @avatar-font-size: @width / 1.8;
 }
 .avatar_main(@width: 30px) {
     --LessLoader_require: LessLoader_currentFile();
diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index b0f9b5e42..28b513095 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -855,7 +855,7 @@
                     span {
                         text-align: center;
                         width: 100%;
-                        font-size: 48px;
+                        font-size: 40px;
                         display: inline-flex;
                         justify-content: center;
                         align-items: center;
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 1d88e1029..03d85fab0 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -82,7 +82,16 @@ define([
     MT.displayAvatar = function (common, $container, href, name, _cb) {
         var cb = Util.once(Util.mkAsync(_cb || function () {}));
         var displayDefault = function () {
-            var text = Util.getFirstCharacter(name || Messages.anonymous);
+            name = (name || "").trim() || Messages.anonymous;
+            var parts = name.split(/\s+/);
+            var text;
+            if (parts.length > 1) {
+                text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
+            } else {
+                text = Util.getFirstCharacter(name);
+                text += Util.getFirstCharacter(name.replace(text, ''));
+            }
+
             var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
             $container.append($avatar);
             if (cb) { cb(); }

From b8c847bccef0001553cfc09724f38939dd8cd74c Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 19 Aug 2021 22:25:51 +0530
Subject: [PATCH 02/20] prototype animal avatars for guests that haven't set a
 custom name

---
 www/common/inner/common-mediatag.js | 34 ++++++++++++++++++++++++++---
 www/common/toolbar.js               |  5 +++--
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 03d85fab0..2dc912e02 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -79,21 +79,48 @@ define([
         });
     };
 
-    MT.displayAvatar = function (common, $container, href, name, _cb) {
+    // https://emojipedia.org/nature/
+    var ANIMALS = [ '🙈', 'đŸĻ€', '🐞', 'đŸĻ‹', 'đŸŦ', '🐋', 'đŸĸ', 'đŸĻ‰', 'đŸĻ†', '🐧', 'đŸĻĄ', 'đŸĻ˜', 'đŸĻ¨', 'đŸĻĻ', 'đŸĻĨ', 'đŸŧ', 'đŸģ', 'đŸĻ', 'đŸĻ„', '🐄', '🐷', '🐐', 'đŸĻ™', 'đŸĻ’', '🐘', 'đŸĻ', '🐁', '🐹', '🐰', 'đŸĻĢ', 'đŸĻ”', '🐨'];
+
+    var getRandomAnimal = function () {
+        return ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
+    };
+
+    var getPseudorandomAnimal = function (seed) {
+        if (typeof(seed) !== 'string') { return getRandomAnimal(); }
+        seed = seed.replace(/\D/g, '').slice(0, 10);
+        seed = parseInt(seed);
+        if (!seed) { return getRandomAnimal(); }
+        return ANIMALS[seed % ANIMALS.length];
+    };
+
+    MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
         var cb = Util.once(Util.mkAsync(_cb || function () {}));
         var displayDefault = function () {
+            if (avatars[uid]) {
+                var nodes = $.parseHTML(avatars[uid]);
+                var $el = $(nodes[0]);
+                $container.append($el);
+                return void cb($el);
+            }
+            var animal = false;
+
             name = (name || "").trim() || Messages.anonymous;
             var parts = name.split(/\s+/);
             var text;
-            if (parts.length > 1) {
+            if (name === Messages.anonymous) {
+                text =  getPseudorandomAnimal(uid);
+                animal = true;
+            } else if (parts.length > 1) {
                 text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
             } else {
                 text = Util.getFirstCharacter(name);
                 text += Util.getFirstCharacter(name.replace(text, ''));
             }
 
-            var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
+            var $avatar = $('<span>', {'class': 'cp-avatar-default' + (animal? ' animal': '')}).text(text);
             $container.append($avatar);
+            avatars[uid] = $avatar[0].outerHTML;
             if (cb) { cb(); }
         };
         if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
@@ -106,6 +133,7 @@ define([
             return void cb($el);
         }
 
+
         var centerImage = function ($img, $image) {
             var img = $image[0];
             var w = img.width;
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index d103fe410..791857049 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -361,9 +361,10 @@ MessengerUI, Messages, Pages) {
                     Common.openURL(origin+'/profile/#' + data.profile);
                 });
             }
-            Common.displayAvatar($span, data.avatar, name, function () {
+            console.error("AVATAR", $span, data.uid);
+            Common.displayAvatar($span, data.avatar, name, function () { // XXX pass a little more info so we can display better (pseudo-random) defaults
                 $span.append($rightCol);
-            });
+            }, data.uid);
             $span.data('uid', data.uid);
             $editUsersList.append($span);
         });

From c5e6ca646eb0ab2868f3c2c31602537e6cbfed19 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Fri, 20 Aug 2021 15:57:14 +0530
Subject: [PATCH 03/20] adjust animal avatar caching system and adjust size in
 the toolbar

---
 customize.dist/src/less2/include/toolbar.less |  3 +++
 www/common/inner/common-mediatag.js           | 21 ++++++++++++-------
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index 28b513095..d8029f0d2 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -200,6 +200,9 @@
                 .avatar_main(30px);
                 .cp-avatar-default, media-tag {
                     margin-right: 5px;
+                    &.animal {
+                        font-size: 20px;
+                    }
                 }
                 &.cp-userlist-clickable {
                     cursor: pointer;
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 2dc912e02..ece9e3024 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -80,7 +80,7 @@ define([
     };
 
     // https://emojipedia.org/nature/
-    var ANIMALS = [ '🙈', 'đŸĻ€', '🐞', 'đŸĻ‹', 'đŸŦ', '🐋', 'đŸĸ', 'đŸĻ‰', 'đŸĻ†', '🐧', 'đŸĻĄ', 'đŸĻ˜', 'đŸĻ¨', 'đŸĻĻ', 'đŸĻĨ', 'đŸŧ', 'đŸģ', 'đŸĻ', 'đŸĻ„', '🐄', '🐷', '🐐', 'đŸĻ™', 'đŸĻ’', '🐘', 'đŸĻ', '🐁', '🐹', '🐰', 'đŸĻĢ', 'đŸĻ”', '🐨'];
+    var ANIMALS = '🙈 đŸĻ€ 🐞 đŸĻ‹ đŸŦ 🐋 đŸĸ đŸĻ‰ đŸĻ† 🐧 đŸĻĄ đŸĻ˜ đŸĻ¨ đŸĻĻ đŸĻĨ đŸŧ đŸģ đŸĻ đŸĻ“ 🐄 🐷 🐐 đŸĻ™ đŸĻ’ 🐘 đŸĻ 🐁 🐹 🐰 đŸĻĢ đŸĻ” 🐨 🐱 đŸē đŸ‘ē 👹 đŸ‘Ŋ 👾 🤖'.split(/\s+/);
 
     var getRandomAnimal = function () {
         return ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
@@ -94,14 +94,13 @@ define([
         return ANIMALS[seed % ANIMALS.length];
     };
 
+    var animal_avatars = {};
     MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
         var cb = Util.once(Util.mkAsync(_cb || function () {}));
         var displayDefault = function () {
-            if (avatars[uid]) {
-                var nodes = $.parseHTML(avatars[uid]);
-                var $el = $(nodes[0]);
-                $container.append($el);
-                return void cb($el);
+            var animal_avatar;
+            if (uid && animal_avatars[uid]) {
+                animal_avatar = animal_avatars[uid]
             }
             var animal = false;
 
@@ -109,7 +108,11 @@ define([
             var parts = name.split(/\s+/);
             var text;
             if (name === Messages.anonymous) {
-                text =  getPseudorandomAnimal(uid);
+                if (animal_avatar) {
+                    text = animal_avatar;
+                } else {
+                    text = animal_avatar = getPseudorandomAnimal(uid);
+                }
                 animal = true;
             } else if (parts.length > 1) {
                 text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
@@ -120,7 +123,9 @@ define([
 
             var $avatar = $('<span>', {'class': 'cp-avatar-default' + (animal? ' animal': '')}).text(text);
             $container.append($avatar);
-            avatars[uid] = $avatar[0].outerHTML;
+            if (uid && animal) {
+                animal_avatars[uid] = animal_avatar;
+            }
             if (cb) { cb(); }
         };
         if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol

From 46e545a976ef9312f9aa0018aaca7dca614cedf1 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Mon, 23 Aug 2021 16:34:51 +0530
Subject: [PATCH 04/20] lint compliance

---
 www/common/inner/common-mediatag.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index ece9e3024..3d4e82caf 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -100,7 +100,7 @@ define([
         var displayDefault = function () {
             var animal_avatar;
             if (uid && animal_avatars[uid]) {
-                animal_avatar = animal_avatars[uid]
+                animal_avatar = animal_avatars[uid];
             }
             var animal = false;
 

From 385cd4e947aaa0494c21502fa7d92460a5b8ab2a Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 12:05:25 +0530
Subject: [PATCH 05/20] handle single-character usernames when deriving
 initials from usernames

and use emoji avatar in user admin button
---
 www/common/common-ui-elements.js    |  7 +++++--
 www/common/inner/common-mediatag.js | 23 +++++++++++++++++------
 www/common/toolbar.js               |  2 +-
 3 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 8315768f1..a4d84796d 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -1993,9 +1993,11 @@ define([
         var loadingAvatar;
         var to;
         var oldUrl = '';
+        var oldUid = undefined;
         var updateButton = function () {
             var myData = metadataMgr.getUserData();
             var privateData = metadataMgr.getPrivateData();
+            var uid = myData.uid;
             if (!priv.plan && privateData.plan) {
                 config.$initBlock.empty();
                 metadataMgr.off('change', updateButton);
@@ -2013,15 +2015,16 @@ define([
             var newName = myData.name;
             var url = myData.avatar;
             $displayName.text(newName || Messages.anonymous);
-            if (accountName && oldUrl !== url) {
+            if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid) {
                 $avatar.html('');
                 Common.displayAvatar($avatar, url,
                         newName || Messages.anonymous, function ($img) {
                     oldUrl = url;
+                    oldUid = uid;
                     $userAdmin.find('> button').removeClass('cp-avatar');
                     if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
                     loadingAvatar = false;
-                });
+                }, uid);
                 return;
             }
             loadingAvatar = false;
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 3d4e82caf..bceb314b8 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -94,6 +94,21 @@ define([
         return ANIMALS[seed % ANIMALS.length];
     };
 
+    var getPrettyInitials = function (name) {
+        var parts = name.split(/\s+/);
+        var text;
+        if (parts.length > 1) {
+            text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
+        } else {
+            text = Util.getFirstCharacter(name);
+            var second = Util.getFirstCharacter(name.replace(text, ''));
+            if (second && second !== '?') {
+                text += second;
+            }
+        }
+        return text;
+    };
+
     var animal_avatars = {};
     MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
         var cb = Util.once(Util.mkAsync(_cb || function () {}));
@@ -105,20 +120,16 @@ define([
             var animal = false;
 
             name = (name || "").trim() || Messages.anonymous;
-            var parts = name.split(/\s+/);
             var text;
-            if (name === Messages.anonymous) {
+            if (name === Messages.anonymous && uid) {
                 if (animal_avatar) {
                     text = animal_avatar;
                 } else {
                     text = animal_avatar = getPseudorandomAnimal(uid);
                 }
                 animal = true;
-            } else if (parts.length > 1) {
-                text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
             } else {
-                text = Util.getFirstCharacter(name);
-                text += Util.getFirstCharacter(name.replace(text, ''));
+                text = getPrettyInitials(name);
             }
 
             var $avatar = $('<span>', {'class': 'cp-avatar-default' + (animal? ' animal': '')}).text(text);
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index 791857049..7493d9996 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -362,7 +362,7 @@ MessengerUI, Messages, Pages) {
                 });
             }
             console.error("AVATAR", $span, data.uid);
-            Common.displayAvatar($span, data.avatar, name, function () { // XXX pass a little more info so we can display better (pseudo-random) defaults
+            Common.displayAvatar($span, data.avatar, name, function () {
                 $span.append($rightCol);
             }, data.uid);
             $span.data('uid', data.uid);

From 8d579c037645ff3b33bdb4c059b8ba1a90f7ba66 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 12:57:24 +0530
Subject: [PATCH 06/20] Add avatars to rich text comments and mentions

---
 www/common/common-ui-elements.js | 21 ++++++++++++--------
 www/common/sframe-common.js      | 13 ++++++++++---
 www/pad/comments.js              | 33 +++++++++++++++++++++-----------
 3 files changed, 45 insertions(+), 22 deletions(-)

diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index a4d84796d..943050e62 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -3456,7 +3456,8 @@ define([
                 name: f.displayName,
                 curvePublic: f.curvePublic,
                 profile: f.profile,
-                notifications: f.notifications
+                notifications: f.notifications,
+                uid: f.uid,
             };
         });
     };
@@ -3555,7 +3556,7 @@ define([
         };
         // Set the value to receive from the autocomplete
         var toInsert = function (data, key) {
-            var name = data.name.replace(/[^a-zA-Z0-9]+/g, "-");
+            var name = (data.name.replace(/[^a-zA-Z0-9]+/g, "-") || "").trim() || Messages.anonymous; // XXX
             return "[@"+name+"|"+key+"]";
         };
 
@@ -3608,18 +3609,20 @@ define([
                     var avatar = h('span.cp-avatar', {
                         contenteditable: false
                     });
-                    common.displayAvatar($(avatar), data.avatar, data.name);
+
+                    var displayName = (data.name || "").trim() || Messages.anonymous;
+                    common.displayAvatar($(avatar), data.avatar, displayName); // XXX
                     return h('span.cp-mentions', {
                         'data-curve': data.curvePublic,
                         'data-notifications': data.notifications,
                         'data-profile': data.profile,
-                        'data-name': Util.fixHTML(data.name),
+                        'data-name': Util.fixHTML(displayName),
                         'data-avatar': data.avatar || "",
                     }, [
                         avatar,
                         h('span.cp-mentions-name', {
                             contenteditable: false
-                        }, data.name)
+                        }, displayName)
                     ]);
                 };
             }
@@ -3651,7 +3654,7 @@ define([
                     }).map(function (key) {
                         var data = sources[key];
                         return {
-                            label: data.name,
+                            label: (data.name || "").trim() || Messages.anonymous,
                             value: key
                         };
                     });
@@ -3686,10 +3689,12 @@ define([
             var obj = sources[key];
             if (!obj) { return; }
             var avatar = h('span.cp-avatar');
-            common.displayAvatar($(avatar), obj.avatar, obj.name);
+            var displayName = (obj.name || "").trim() || Messages.anonymous;
+
+            common.displayAvatar($(avatar), obj.avatar, displayName, Util.noop, obj.uid); // XXX
             var li = h('li.cp-autocomplete-value', [
                 avatar,
-                h('span', obj.name)
+                h('span', displayName),
             ]);
             return $(li).appendTo(ul);
         };
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index 206137c86..01c48b540 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -249,11 +249,18 @@ define([
         if (existing.indexOf(n) !== -1) { n = 0; }
         return n;
     };
-    funcs.getAuthorId = function(authors, curve) {
+    funcs.getAuthorId = function(authors, curve, tokenId) {
         var existing = Object.keys(authors || {}).map(Number);
-        if (!funcs.isLoggedIn()) { return authorUid(existing); }
-
         var uid;
+        if (!funcs.isLoggedIn()) {
+            existing.some(function (id) {
+                var author = authors[id] || {};
+                if (author.uid !== tokenId) { return; }
+                uid = Number(id);
+                return true;
+            });
+            return uid || authorUid(existing);
+        }
         existing.some(function(id) {
             var author = authors[id] || {};
             if (author.curvePublic !== curve) { return; }
diff --git a/www/pad/comments.js b/www/pad/comments.js
index 6069aa1d0..6280a72bf 100644
--- a/www/pad/comments.js
+++ b/www/pad/comments.js
@@ -43,18 +43,21 @@ define([
 
     var canonicalize = function(t) { return t.replace(/\r\n/g, '\n'); };
 
-    var getAuthorId = function(Env, curve) {
-        return Env.common.getAuthorId(Env.comments.authors, curve);
+    var getAuthorId = function(Env, curve, uid) {
+        return Env.common.getAuthorId(Env.comments.authors, curve, uid);
     };
 
-    // Return the author ID  and add/update the data for registered users
-    // Return the username for unregistered users
+    // Return the author ID  and add/update user data
+    // associate data with a curvePublic for registered users and the uid otherwise
     var updateAuthorData = function(Env, onChange) {
         var userData = Env.metadataMgr.getUserData();
+        var myAuthorId;
         if (!Env.common.isLoggedIn()) {
-            return userData.name;
+            myAuthorId = getAuthorId(Env, undefined, userData.uid);
+        } else {
+            myAuthorId = getAuthorId(Env, userData.curvePublic);
         }
-        var myAuthorId = getAuthorId(Env, userData.curvePublic);
+
         var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {};
         var old = Sortify(data);
         data.name = userData.name;
@@ -62,6 +65,8 @@ define([
         data.profile = userData.profile;
         data.curvePublic = userData.curvePublic;
         data.notifications = userData.notifications;
+        data.uid = userData.uid;
+
         if (typeof(onChange) === "function" && Sortify(data) !== old) {
             onChange();
         }
@@ -82,6 +87,9 @@ define([
         var userData = Env.metadataMgr.getUserData();
         var privateData = Env.metadataMgr.getPrivateData();
         var others = {};
+
+
+        // XXX mentioned users should be excluded from the list of notified recipients to avoid notifying them twice
         // Get all the other registered users with a mailbox
         thread.m.forEach(function(obj) {
             var u = obj.u;
@@ -93,7 +101,8 @@ define([
                 curvePublic: author.curvePublic,
                 comment: obj.m,
                 content: obj.v,
-                notifications: author.notifications
+                notifications: author.notifications,
+                uid: author.uid,
             };
         });
         // Send the notification
@@ -146,7 +155,7 @@ define([
             'aria-required': true,
             contenteditable: true,
         });
-        Env.common.displayAvatar($(avatar), userData.avatar, name);
+        Env.common.displayAvatar($(avatar), userData.avatar, name, Util.noop, userData.uid);
 
         var cancel = h('button.btn.btn-cancel', {
             tabindex: 1
@@ -224,7 +233,9 @@ define([
 
         if (Env.common.isLoggedIn()) {
             var authors = {};
-            Object.keys((Env.comments && Env.comments.authors) ||  {}).forEach(function(id) {
+            Object.keys((Env.comments && Env.comments.authors) ||  {})
+            .filter(function (id) { return Util.find(Env, ['commments', 'authors', id, 'curvePublic']); })
+            .forEach(function(id) {
                 var obj = Util.clone(Env.comments.authors[id]);
                 authors[obj.curvePublic] = obj;
             });
@@ -369,7 +380,7 @@ define([
                 var name = Util.fixHTML(author.name || Messages.anonymous);
                 var date = new Date(msg.t);
                 var avatar = h('span.cp-avatar');
-                Env.common.displayAvatar($(avatar), author.avatar, name);
+                Env.common.displayAvatar($(avatar), author.avatar, name, Util.noop, author.uid);
                 if (author.profile) {
                     $(avatar).click(function(e) {
                         Env.common.openURL(Hash.hashToHref(author.profile, 'profile'));
@@ -393,7 +404,7 @@ define([
                     }
                     cleanMentions($el);
                     var avatar = h('span.cp-avatar');
-                    Env.common.displayAvatar($(avatar), avatarUrl, name);
+                    Env.common.displayAvatar($(avatar), avatarUrl, name, Util.noop, author.uid);
                     $el.append([
                         avatar,
                         h('span.cp-mentions-name', name)

From c630abb3c56abd451c2968800d49e9245557533a Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 12:59:37 +0530
Subject: [PATCH 07/20] refactor animal avatar font-size to automatically scale
 with parents

---
 customize.dist/src/less2/include/avatar.less  | 8 +++++++-
 customize.dist/src/less2/include/toolbar.less | 3 ---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less
index 02abc4050..079ceb60d 100644
--- a/customize.dist/src/less2/include/avatar.less
+++ b/customize.dist/src/less2/include/avatar.less
@@ -40,7 +40,13 @@
             color: @cp_avatar-fg;
             font-size: @avatar-font-size;
             font-size: var(--avatar-font-size);
-            text-transform: capitalize;
+            .animal {
+                font-size: 20px;
+                // scale animal avatar to be somewhat larger, because:
+                // 1. emojis are wider than most latin characters
+                // 2. they should occupy the width of two average characters
+                font-size: calc(var(--avatar-width) * (6/5));
+            }
         }
         media-tag {
             min-height: @avatar-width;
diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index d8029f0d2..28b513095 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -200,9 +200,6 @@
                 .avatar_main(30px);
                 .cp-avatar-default, media-tag {
                     margin-right: 5px;
-                    &.animal {
-                        font-size: 20px;
-                    }
                 }
                 &.cp-userlist-clickable {
                     cursor: pointer;

From c4fcc9f732ad3eb2b95c6291dc128d1305707c58 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 13:03:27 +0530
Subject: [PATCH 08/20] WIP comments and non-functional changes

---
 www/common/common-ui-elements.js    | 14 +++++++++-----
 www/common/inner/common-mediatag.js |  9 +++++++--
 www/common/media-tag.js             |  1 +
 www/common/toolbar.js               |  3 ++-
 www/kanban/inner.js                 | 20 ++++++++++----------
 www/profile/inner.js                |  4 ++--
 www/slide/inner.js                  |  2 +-
 7 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 943050e62..6bb409222 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -157,8 +157,10 @@ define([
         var icons = Object.keys(users).map(function (key, i) {
             var data = users[key];
             var name = data.displayName || data.name || Messages.anonymous;
-            var avatar = h('span.cp-usergrid-avatar.cp-avatar');
-            common.displayAvatar($(avatar), data.avatar, name);
+            var avatar = h('span.cp-usergrid-avatar.cp-avatar', {
+                'aria-hidden': true, // XXX aria
+            });
+            common.displayAvatar($(avatar), data.avatar, name); // XXX
             var removeBtn, el;
             if (config.remove) {
                 removeBtn = h('span.fa.fa-times');
@@ -1989,11 +1991,11 @@ define([
 
         var $displayName = $userAdmin.find('.'+displayNameCls);
 
-        var $avatar = $userAdmin.find('> button .cp-dropdown-button-title');
+        var $avatar = $userAdmin.find('> button .cp-dropdown-button-title'); // XXX alt="User menu"
         var loadingAvatar;
         var to;
         var oldUrl = '';
-        var oldUid = undefined;
+        var oldUid;
         var updateButton = function () {
             var myData = metadataMgr.getUserData();
             var privateData = metadataMgr.getPrivateData();
@@ -2024,6 +2026,8 @@ define([
                     $userAdmin.find('> button').removeClass('cp-avatar');
                     if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
                     loadingAvatar = false;
+
+                    // XXX alt="User menu"
                 }, uid);
                 return;
             }
@@ -2306,7 +2310,7 @@ define([
             var teams = Object.keys(privateData.teams).map(function (id) {
                 var data = privateData.teams[id];
                 var avatar = h('span.cp-creation-team-avatar.cp-avatar');
-                common.displayAvatar($(avatar), data.avatar, data.name);
+                common.displayAvatar($(avatar), data.avatar, data.name); // XXX
                 return h('div.cp-creation-team', {
                     'data-id': id,
                     title: data.name,
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index bceb314b8..54b20784a 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -132,7 +132,12 @@ define([
                 text = getPrettyInitials(name);
             }
 
-            var $avatar = $('<span>', {'class': 'cp-avatar-default' + (animal? ' animal': '')}).text(text);
+            var $avatar = $('<span>', {
+                'class': 'cp-avatar-default' + (animal? ' animal': ''),
+                // XXX prevents screenreaders from trying to describe this
+                alt: '',
+                'aria-hidden': true,
+            }).text(text);
             $container.append($avatar);
             if (uid && animal) {
                 animal_avatars[uid] = animal_avatar;
@@ -184,7 +189,7 @@ define([
                 var $img = $(mt).appendTo($container);
                 MT.displayMediatagImage(common, $img, function (err, $image) {
                     if (err) { return void console.error(err); }
-                    centerImage($img, $image);
+                    centerImage($img, $image); // XXX add alt="" (unless the media-tag has an alt attr)
                 });
             });
         }
diff --git a/www/common/media-tag.js b/www/common/media-tag.js
index 15b038724..d1a5ebcda 100644
--- a/www/common/media-tag.js
+++ b/www/common/media-tag.js
@@ -73,6 +73,7 @@ var factory = function () {
              * @param {object}   cfg     Object {Plugins, allowed, download, pdf} containing infos about plugins
              * @param {function} cb      Callback function: (err, pluginElement) => {}
              */
+             // XXX add alt attributes if present in metadata
             text: function (metadata, url, content, cfg, cb) {
                 var plainText = document.createElement('div');
                 plainText.className = "plain-text-reader";
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index 7493d9996..da8b1dad0 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -356,12 +356,13 @@ MessengerUI, Messages, Pages) {
                 });
             }
             if (data.profile) {
+                // XXX title to visit their profile "Visit {0}'s profile"
+                // Messages.contacts_info3 "Double-click their icon to view their profile",
                 $span.addClass('cp-userlist-clickable');
                 $span.click(function () {
                     Common.openURL(origin+'/profile/#' + data.profile);
                 });
             }
-            console.error("AVATAR", $span, data.uid);
             Common.displayAvatar($span, data.avatar, name, function () {
                 $span.append($rightCol);
             }, data.uid);
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 38e933cf1..42b1e39fa 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -59,7 +59,7 @@ define([
     verbose = function () {}; // comment out to enable verbose logging
     var onRedraw = Util.mkEvent();
     var onCursorUpdate = Util.mkEvent();
-    var remoteCursors = {};
+    var remoteCursors = {}; // XXX
 
     var setValueAndCursor = function (input, val, _cursor) {
         if (!input) { return; }
@@ -95,7 +95,7 @@ define([
 
     var getAvatar = function (cursor, noClear) {
         // Tippy
-        var html = MT.getCursorAvatar(cursor);
+        var html = MT.getCursorAvatar(cursor); // XXX
 
         var l = Util.getFirstCharacter(cursor.name || Messages.anonymous);
 
@@ -103,10 +103,10 @@ define([
         if (cursor.color) {
             text = 'color:'+getTextColor(cursor.color)+';';
         }
-        var avatar = h('span.cp-cursor.cp-tippy-html', {
+        var avatar = h('span.cp-cursor.cp-tippy-html', { // XXX
             style: "background-color: " + (cursor.color || 'red') + ";"+text,
             'data-cptippy-html': true,
-            title: html
+            title: html, // XXX "{0} is editing"
         }, l);
         if (!noClear) {
             cursor.clear = function () {
@@ -852,7 +852,7 @@ define([
             getAvatar: getAvatar,
             openLink: openLink,
             getTags: getExistingTags,
-            cursors: remoteCursors,
+            cursors: remoteCursors, // XXX
             boards: boards,
             _boards: Util.clone(boards),
         });
@@ -1101,7 +1101,7 @@ define([
             $container.find('.kanban-edit-item').remove();
         });
 
-        var getCursor = function () {
+        var getCursor = function () { // XXX
             if (!kanban || !kanban.inEditMode) { return; }
             try {
                 var id = kanban.inEditMode;
@@ -1204,7 +1204,7 @@ define([
             var remoteContent = newContent.content;
 
             if (Sortify(currentContent) !== Sortify(remoteContent)) {
-                var cursor = getCursor();
+                var cursor = getCursor(); // XXX
                 verbose("Content is different.. Applying content");
                 kanban.options.boards = remoteContent;
                 updateBoards(framework, kanban, remoteContent);
@@ -1261,11 +1261,11 @@ define([
         });
 
         var myCursor = {};
-        onCursorUpdate.reg(function (data) {
+        onCursorUpdate.reg(function (data) { // XXX
             myCursor = data;
             framework.updateCursor();
         });
-        framework.onCursorUpdate(function (data) {
+        framework.onCursorUpdate(function (data) { // XXX
             if (!data) { return; }
             if (data.reset) {
                 Object.keys(remoteCursors).forEach(function (id) {
@@ -1293,7 +1293,7 @@ define([
             if (!cursor.item && !cursor.board) { return; }
 
             // Add new cursor
-            var avatar = getAvatar(cursor);
+            var avatar = getAvatar(cursor); // XXX
             var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
             var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
             if ($item.length) {
diff --git a/www/profile/inner.js b/www/profile/inner.js
index 11abd6ae8..c63b72e99 100644
--- a/www/profile/inner.js
+++ b/www/profile/inner.js
@@ -349,7 +349,7 @@ define([
             $('<img>', {
                 src: '/customize/images/avatar.png',
                 title: Messages.profile_avatar,
-                alt: 'Avatar'
+                alt: 'Avatar' // XXX translate this "Default profile picture"
             }).appendTo($span);
             return;
         }
@@ -391,7 +391,7 @@ define([
                 }, function () {
                     sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) {
                         if (err || err2) { return void UI.log(err || err2); }
-                        displayAvatar(data.url);
+                        displayAvatar(data.url); // XXX add "Profile picture"
                     });
                 });
             };
diff --git a/www/slide/inner.js b/www/slide/inner.js
index 9c9b9c70b..143165176 100644
--- a/www/slide/inner.js
+++ b/www/slide/inner.js
@@ -511,7 +511,7 @@ define([
                 framework.updateCursor();
             }, 500); // 500ms to make sure it is sent after chainpad sync
         };
-        framework.onCursorUpdate(CodeMirror.setRemoteCursor);
+        framework.onCursorUpdate(CodeMirror.setRemoteCursor); // XXX
         framework.setCursorGetter(CodeMirror.getCursor);
         editor.on('cursorActivity', updateCursor);
 

From 4b0cebb0fd46f0d0339320160407b2859caa42ff Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 13:19:51 +0530
Subject: [PATCH 09/20] animal emojis in the team roster

and fall back to default username in teams when members are unnamed
in various places where it was not handled
---
 www/teams/inner.js | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/www/teams/inner.js b/www/teams/inner.js
index 373a27998..d1e383e44 100644
--- a/www/teams/inner.js
+++ b/www/teams/inner.js
@@ -693,6 +693,10 @@ define([
             redrawRoster(common);
         });
     };
+
+    var getDisplayName = function (name) {
+        return (typeof(name) === 'string'? name: "").trim() || Messages.anonymous;
+    };
     var makeMember = function (common, data, me, roster) {
         if (!data.curvePublic) { return; }
 
@@ -701,11 +705,12 @@ define([
             return user.role === "OWNER" && user.curvePublic !== me.curvePublic && !user.pendingOwner;
         });
 
+        var displayName = getDisplayName(data.displayName);
         // Avatar
         var avatar = h('span.cp-avatar.cp-team-member-avatar');
-        common.displayAvatar($(avatar), data.avatar, data.displayName);
+        common.displayAvatar($(avatar), data.avatar, displayName, Util.noop, data.uid);
         // Name
-        var name = h('span.cp-team-member-name', data.displayName);
+        var name = h('span.cp-team-member-name', displayName);
         if (data.pendingOwner) {
             $(name).append(h('em', {
                 title: Messages.team_pendingOwnerTitle
@@ -789,7 +794,7 @@ define([
                 title: Messages.team_rosterKick
             });
             $(remove).click(function () {
-                UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(data.displayName)]), function (yes) {
+                UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(displayName)]), function (yes) {
                     if (!yes) { return; }
                     APP.module.execCommand('REMOVE_USER', {
                         pending: data.pending,
@@ -1073,6 +1078,9 @@ define([
                     metadata: obj
                 }, function () {
                     $avatar.empty();
+                    // the UI is not supposed to allow admins to remove team names
+                    // so we expect that it will be there. Failing that the initials
+                    // from the default name will be displayed
                     common.displayAvatar($avatar, data.url);
                 });
             });
@@ -1191,10 +1199,11 @@ define([
 
     var displayUser = function (common, data) {
         var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar');
-        common.displayAvatar($(avatar), data.avatar, data.displayName);
+        var name = getDisplayName(data.displayName);
+        common.displayAvatar($(avatar), data.avatar, name);
         return h('div.cp-teams-invite-from-author', [
             avatar,
-            h('span.cp-teams-invite-from-name', data.displayName)
+            h('span.cp-teams-invite-from-name', name)
         ]);
     };
 
@@ -1319,20 +1328,21 @@ define([
         nThen(function (waitFor) {
             // Get preview content.
             sframeChan.query('Q_ANON_GET_PREVIEW_CONTENT', { seeds: seeds }, waitFor(function (err, json) {
-                if (json && (json.error || !Object.keys(json).length)) {
+                if (json && (json.error || !Object.keys(json).length)) { // XXX team invite links are triggering this every time for me?
                     $(errorBlock).text(Messages.team_inviteInvalidLinkError).show();
                     waitFor.abort();
                     $div.empty();
                     return;
                 }
+                // XXX nothing guarantees that author, teamName, or message exist in json
                 $div.empty();
                 $div.append(h('div.cp-teams-invite-from', [
-                    Messages.team_inviteFrom || 'From:',
+                    Messages.team_inviteFrom,
                     displayUser(common, json.author)
                 ]));
                 $div.append(UI.setHTML(h('p.cp-teams-invite-to'),
                     Messages._getKey('team_inviteFromMsg',
-                    [Util.fixHTML(json.author.displayName),
+                    [Util.fixHTML(getDisplayName(json.author.displayName)),
                     Util.fixHTML(json.teamName)])));
                 if (json.message) {
                     $div.append(h('div.cp-teams-invite-message', json.message));
@@ -1449,10 +1459,10 @@ define([
             // Update the name in the user menu
             var $displayName = $bar.find('.' + Toolbar.constants.username);
             metadataMgr.onChange(function () {
-                var name = metadataMgr.getUserData().name || Messages.anonymous;
+                var name = getDisplayName(metadataMgr.getUserData().name);
                 $displayName.text(name);
             });
-            $displayName.text(user.name || Messages.anonymous);
+            $displayName.text(getDisplayName(user.name));
 
             // Load the Team module
             var onEvent = function (obj) {

From 95869b84c901ce48f69d1ad0fa49e9a6cfe31915 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 13:38:05 +0530
Subject: [PATCH 10/20] emoji avatars in the contacts app

and handling of empty display names
---
 www/common/common-interface.js |  4 ++++
 www/common/common-messaging.js |  3 ++-
 www/common/messenger-ui.js     | 30 ++++++++++++++++++++----------
 www/teams/inner.js             |  4 +---
 4 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index 71a1ed8af..5c2431efd 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -41,6 +41,10 @@ define([
         return e;
     };
 
+    UI.getDisplayName = function (name) {
+        return (typeof(name) === 'string'? name: "").trim() || Messages.anonymous;
+    };
+
     // FIXME almost everywhere this is used would also be
     // a good candidate for sframe-common's getMediatagFromHref
     UI.mediaTag = function (src, key) {
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index 65f05961e..4a3eb2a91 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -17,7 +17,8 @@ define([
             edPublic: proxy.edPublic,
             curvePublic: proxy.curvePublic,
             notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
-            avatar: proxy.profile && proxy.profile.avatar
+            avatar: proxy.profile && proxy.profile.avatar,
+            uid: proxy.uid,
         };
         if (hash === false) { delete data.channel; }
         return data;
diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js
index 76a756a11..494001624 100644
--- a/www/common/messenger-ui.js
+++ b/www/common/messenger-ui.js
@@ -190,9 +190,11 @@ define([
         markup.message = function (msg) {
             if (msg.type !== 'MSG') { return; }
             var curvePublic = msg.author;
+            // FIXME this assignment looks like it has some holes in its logic
+            // but I'm scared to touch it because it looks like it was hacked to fix some bugs
             var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ?
                             (msg.name || Messages.anonymous) :
-                            contactsData[msg.author].displayName;
+                            contactsData[msg.author].displayName || Messages.anonymous;
             var d = msg.time ? new Date(msg.time) : undefined;
             var day = d ? d.toLocaleDateString() : '';
             var hour = d ? d.toLocaleTimeString() : '';
@@ -239,7 +241,7 @@ define([
             });
 
             var chan = state.channels[id];
-            var displayName = chan.name;
+            var displayName = UI.getDisplayName(chan.name || chan.displayName);
 
             var fetching = false;
             var $moreHistory = $(moreHistory).click(function () {
@@ -364,7 +366,7 @@ define([
                         avatars[friend.avatar] = $img[0].outerHTML;
                     }
                     $(rightCol).insertAfter($avatar);
-                });
+                }, friend.uid);
             }
 
             var sending = false;
@@ -544,7 +546,7 @@ define([
                 title: Messages.contacts_online
             });
             var rightCol = h('span.cp-app-contacts-right-col', [
-                h('span.cp-app-contacts-name', [room.name]),
+                h('span.cp-app-contacts-name', [room.isFriendChat? UI.getDisplayName(room.name): room.name]),
                 h('span.cp-app-contacts-icons', [
                     room.isFriendChat ? mute : undefined,
                     room.isFriendChat ? unmute : undefined,
@@ -609,7 +611,7 @@ define([
                         avatars[friendData.avatar] = $img[0].outerHTML;
                     }
                     $room.append(rightCol);
-                });
+                }, friendData.uid);
             }
             $room.append(status);
             return $room;
@@ -631,9 +633,9 @@ define([
             var el_message = markup.message(message);
 
             if (message.type === 'MSG') {
-                var name = typeof message.name !== "undefined" ?
-                        (message.name || Messages.anonymous) :
-                        contactsData[message.author].displayName;
+                var name = UI.getDisplayName(typeof message.name !== "undefined" ?
+                        message.name:
+                        contactsData[message.author].displayName);
                 common.notify({
                     title: name,
                     msg: message.text,
@@ -826,6 +828,11 @@ define([
             }
         };
 
+/*  The following block is for a disabled feature which allows users to switch
+    between pad chat (when in the context of a pad) and direct chats with their
+    contacts.
+*/
+/*
         common.getMetadataMgr().onTitleChange(function () {
             var padChat = common.getPadChat();
             var md = common.getMetadataMgr().getMetadata();
@@ -839,11 +846,14 @@ define([
             $lAvatar.find('.cp-avatar-default, media-tag').remove();
 
             var $div = $('<div>');
+            // There should always be a title here (defaultTitle if nothing else)
+            // so we don't ever need to supply a uid for an animal avatar
             common.displayAvatar($div, null, name, function () {
                 $mAvatar.html($div.html());
                 $lAvatar.find('.cp-app-contacts-right-col').before($div.html());
             });
         });
+*/
 
         // TODO room
         // var onJoinRoom
@@ -878,7 +888,7 @@ define([
                         h('i.fa.fa-bell'),
                         Messages.contacts_unmute || 'unmute'
                     ]);
-                    common.displayAvatar($(avatar), data.avatar, data.name);
+                    common.displayAvatar($(avatar), data.avatar, data.name, Util.noop, data.uid);
                     $(button).click(function () {
                         unmuteUser(curve, button);
                         execCommand('UNMUTE_USER', curve, function (e, data) {
@@ -894,7 +904,7 @@ define([
                     });
                     return h('div.cp-contacts-muted-user', [
                         h('span', avatar),
-                        h('span', data.name),
+                        h('span', UI.getDisplayName(data.name)),
                         button
                     ]);
                 });
diff --git a/www/teams/inner.js b/www/teams/inner.js
index d1e383e44..bd7fca5ed 100644
--- a/www/teams/inner.js
+++ b/www/teams/inner.js
@@ -694,9 +694,7 @@ define([
         });
     };
 
-    var getDisplayName = function (name) {
-        return (typeof(name) === 'string'? name: "").trim() || Messages.anonymous;
-    };
+    var getDisplayName = UI.getDisplayName;
     var makeMember = function (common, data, me, roster) {
         if (!data.curvePublic) { return; }
 

From 7bb3bc167cdd340ab9bb192031e39fb011b8f247 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 15:12:28 +0530
Subject: [PATCH 11/20] use emoji avatars in share and access modals

---
 www/common/common-ui-elements.js | 36 +++++++++++++++++---------------
 www/common/inner/access.js       |  9 ++++++--
 2 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 6bb409222..d0ef0af3f 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -156,11 +156,11 @@ define([
 
         var icons = Object.keys(users).map(function (key, i) {
             var data = users[key];
-            var name = data.displayName || data.name || Messages.anonymous;
+            var name = UI.getDisplayName(data.displayName || data.name);
             var avatar = h('span.cp-usergrid-avatar.cp-avatar', {
                 'aria-hidden': true, // XXX aria
             });
-            common.displayAvatar($(avatar), data.avatar, name); // XXX
+            common.displayAvatar($(avatar), data.avatar, name, Util.noop, data.uid);
             var removeBtn, el;
             if (config.remove) {
                 removeBtn = h('span.fa.fa-times');
@@ -2014,13 +2014,12 @@ define([
                 return;
             }
             loadingAvatar = true;
-            var newName = myData.name;
+            var newName = UI.getDisplayName(myData.name);
             var url = myData.avatar;
-            $displayName.text(newName || Messages.anonymous);
+            $displayName.text(newName);
             if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid) {
                 $avatar.html('');
-                Common.displayAvatar($avatar, url,
-                        newName || Messages.anonymous, function ($img) {
+                Common.displayAvatar($avatar, url, newName, function ($img) {
                     oldUrl = url;
                     oldUid = uid;
                     $userAdmin.find('> button').removeClass('cp-avatar');
@@ -2310,7 +2309,8 @@ define([
             var teams = Object.keys(privateData.teams).map(function (id) {
                 var data = privateData.teams[id];
                 var avatar = h('span.cp-creation-team-avatar.cp-avatar');
-                common.displayAvatar($(avatar), data.avatar, data.name); // XXX
+                // We assume that teams always have a non-empty name, so we don't need a UID
+                common.displayAvatar($(avatar), data.avatar, data.name);
                 return h('div.cp-creation-team', {
                     'data-id': id,
                     title: data.name,
@@ -3113,7 +3113,7 @@ define([
         var sframeChan = common.getSframeChannel();
         var msg = data.content.msg;
 
-        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+        var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
         var title = Util.fixHTML(msg.content.title);
 
         var text = Messages._getKey('owner_add', [name, title]);
@@ -3245,7 +3245,7 @@ define([
         var sframeChan = common.getSframeChannel();
         var msg = data.content.msg;
 
-        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+        var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
         var title = Util.fixHTML(msg.content.title);
 
         var text = Messages._getKey('owner_team_add', [name, title]);
@@ -3360,13 +3360,15 @@ define([
         var verified = h('p');
         var $verified = $(verified);
 
+        name = UI.getDisplayName(name);
         if (priv.friends && priv.friends[curve]) {
             $verified.addClass('cp-notifications-requestedit-verified');
             var f = priv.friends[curve];
             $verified.append(h('span.fa.fa-certificate'));
             var $avatar = $(h('span.cp-avatar')).appendTo($verified);
-            $verified.append(h('p', Messages._getKey('isContact', [f.displayName])));
-            common.displayAvatar($avatar, f.avatar, f.displayName);
+            name = UI.getDisplayName(f.displayName);
+            $verified.append(h('p', Messages._getKey('isContact', [name])));
+            common.displayAvatar($avatar, f.avatar, name, Util.noop, f.uid);
         } else {
             $verified.append(Messages._getKey('isNotContact', [name]));
         }
@@ -3376,7 +3378,7 @@ define([
     UIElements.displayInviteTeamModal = function (common, data) {
         var msg = data.content.msg;
 
-        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+        var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
         var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');
 
         var verified = UIElements.getVerifiedFriend(common, msg.author, name);
@@ -3560,7 +3562,7 @@ define([
         };
         // Set the value to receive from the autocomplete
         var toInsert = function (data, key) {
-            var name = (data.name.replace(/[^a-zA-Z0-9]+/g, "-") || "").trim() || Messages.anonymous; // XXX
+            var name = UI.getDisplayName(data.name.replace(/[^a-zA-Z0-9]+/g, "-"));
             return "[@"+name+"|"+key+"]";
         };
 
@@ -3614,7 +3616,7 @@ define([
                         contenteditable: false
                     });
 
-                    var displayName = (data.name || "").trim() || Messages.anonymous;
+                    var displayName = UI.getDisplayName(data.name);
                     common.displayAvatar($(avatar), data.avatar, displayName); // XXX
                     return h('span.cp-mentions', {
                         'data-curve': data.curvePublic,
@@ -3658,7 +3660,7 @@ define([
                     }).map(function (key) {
                         var data = sources[key];
                         return {
-                            label: (data.name || "").trim() || Messages.anonymous,
+                            label: UI.getDisplayName(data.name),
                             value: key
                         };
                     });
@@ -3693,9 +3695,9 @@ define([
             var obj = sources[key];
             if (!obj) { return; }
             var avatar = h('span.cp-avatar');
-            var displayName = (obj.name || "").trim() || Messages.anonymous;
+            var displayName = UI.getDisplayName(obj.name);
 
-            common.displayAvatar($(avatar), obj.avatar, displayName, Util.noop, obj.uid); // XXX
+            common.displayAvatar($(avatar), obj.avatar, displayName, Util.noop, obj.uid);
             var li = h('li.cp-autocomplete-value', [
                 avatar,
                 h('span', displayName),
diff --git a/www/common/inner/access.js b/www/common/inner/access.js
index 3f281dc47..119714de3 100644
--- a/www/common/inner/access.js
+++ b/www/common/inner/access.js
@@ -171,7 +171,7 @@ define([
             if (!Object.keys(_friends).length) {
                 var friendText;
                 if (!friendKeys.length) {
-                    console.error(UIElements.noContactsMessage(common));
+                    //console.error(UIElements.noContactsMessage(common));
                     var findContacts = UIElements.noContactsMessage(common);
                     friendText = h('span.cp-app-prop-content',
                         findContacts.content
@@ -772,7 +772,8 @@ define([
                 if (friend.edPublic !== ed || c === 'me') { return; }
                 _owners[friend.edPublic] = {
                     name: friend.displayName,
-                    avatar: friend.avatar
+                    avatar: friend.avatar,
+                    uid: friend.uid,
                 };
                 return true;
             })) {
@@ -782,6 +783,10 @@ define([
             _owners[ed] = {
                 avatar: '?',
                 name: Messages.owner_unknownUser,
+                // TODO a possible enhancement is to use data from the context
+                // ie. if you have opened the access modal from within the pad
+                // its owner might be present or they might have left some data
+                // in the pad itself (as is the case of the uid in rich text comments)
             };
             strangers++;
         });

From 68efd549177bb5041dabce9170fc0bf8463182ee Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Wed, 25 Aug 2021 16:18:09 +0530
Subject: [PATCH 12/20] include uid in cursor data for animal avatars

---
 www/code/inner.js                      | 4 +++-
 www/common/inner/common-mediatag.js    | 9 +++++++--
 www/common/sframe-common-codemirror.js | 1 +
 www/slide/inner.js                     | 4 +++-
 4 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/www/code/inner.js b/www/code/inner.js
index 4f6c0308e..4328f0d8f 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -388,7 +388,9 @@ define([
     var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
 
         var common = framework._.sfCommon;
-        var privateData = common.getMetadataMgr().getPrivateData();
+        var metadataMgr = common.getMetadataMgr();
+        var privateData = metadataMgr.getPrivateData();
+        CodeMirror.uid = metadataMgr.getUserData().uid;
 
         var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode);
         var markdownTb = mkMarkdownTb(editor, framework);
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 54b20784a..5e091a83c 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -43,9 +43,15 @@ define([
         });
     };
 
+    var animal_avatars = {};
     MT.getCursorAvatar = function (cursor) {
+        var uid = cursor.uid;
         var html = '<span class="cp-cursor-avatar">';
-        html += (cursor.avatar && avatars[cursor.avatar]) || '';
+        if (cursor.avatar && avatars[cursor.avatar]) {
+            html += (cursor.avatar && avatars[cursor.avatar]) || '';
+        } else if (animal_avatars[uid]) {
+            html += animal_avatars[uid] + ' ';
+        }
         html += Util.fixHTML(cursor.name) + '</span>';
         return html;
     };
@@ -109,7 +115,6 @@ define([
         return text;
     };
 
-    var animal_avatars = {};
     MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
         var cb = Util.once(Util.mkAsync(_cb || function () {}));
         var displayDefault = function () {
diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js
index 58475bbed..4e490288d 100644
--- a/www/common/sframe-common-codemirror.js
+++ b/www/common/sframe-common-codemirror.js
@@ -509,6 +509,7 @@ define([
             var cursor = {};
             cursor.selectionStart = cursorToPos(editor.getCursor('from'), doc);
             cursor.selectionEnd = cursorToPos(editor.getCursor('to'), doc);
+            cursor.uid = exp.uid; // FIXME this is inefficient for the network but it's unlikely to trigger errors
             return cursor;
         };
 
diff --git a/www/slide/inner.js b/www/slide/inner.js
index 143165176..9bbfc6e4d 100644
--- a/www/slide/inner.js
+++ b/www/slide/inner.js
@@ -459,7 +459,9 @@ define([
     var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
 
         var common = framework._.sfCommon;
-        var privateData = common.getMetadataMgr().getPrivateData();
+        var metadataMgr = common.getMetadataMgr();
+        var privateData = metadataMgr.getPrivateData();
+        CodeMirror.uid = metadataMgr.getUserData().uid;
 
         var $contentContainer = $('#cp-app-slide-editor');
         var $modal = $('#cp-app-slide-modal');

From dd8f70d6f457e2a2afa30a710139ddb16fe26417 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 14:07:03 +0530
Subject: [PATCH 13/20] fix a bug I introduced by not preserving the base case
 of 'getAuthorId'

---
 www/common/sframe-common.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index 01c48b540..000d049b3 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -252,10 +252,12 @@ define([
     funcs.getAuthorId = function(authors, curve, tokenId) {
         var existing = Object.keys(authors || {}).map(Number);
         var uid;
-        if (!funcs.isLoggedIn()) {
+        var loggedIn = funcs.isLoggedIn();
+        if (!loggedIn && !tokenId) { return authorUid(existing); }
+        if (!loggedIn) {
             existing.some(function (id) {
-                var author = authors[id] || {};
-                if (author.uid !== tokenId) { return; }
+                var author = authors[id];
+                if (!author || author.uid !== tokenId) { return; }
                 uid = Number(id);
                 return true;
             });

From bf02ec7359bb6dc048bcac928ee47b8d230f8a73 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 14:30:51 +0530
Subject: [PATCH 14/20] animal emojis in color-by-author 'written by' tooltips

---
 www/code/markers.js                 | 18 +++++++++++++++---
 www/common/inner/common-mediatag.js |  2 +-
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/www/code/markers.js b/www/code/markers.js
index b070e68fc..b8a53bdd9 100644
--- a/www/code/markers.js
+++ b/www/code/markers.js
@@ -3,7 +3,8 @@ define([
     '/common/sframe-common-codemirror.js',
     '/customize/messages.js',
     '/bower_components/chainpad/chainpad.dist.js',
-], function (Util, SFCodeMirror, Messages, ChainPad) {
+    '/common/inner/common-mediatag.js',
+], function (Util, SFCodeMirror, Messages, ChainPad, MT) {
     var Markers = {};
 
     /* TODO Known Issues
@@ -38,7 +39,17 @@ define([
             });
         }
         uid = Number(uid);
-        var name = Util.fixHTML(author.name || Messages.anonymous);
+        var name = Util.fixHTML((author.name || "").trim());
+        var animal;
+        if ((!name || name === Messages.anonymous) && typeof(author.uid) === 'string') {
+            animal = MT.getPseudorandomAnimal(author.uid);
+            if (animal) {
+                name = animal + ' ' + Messages.anonymous;
+            } else {
+                name = Messages.anonymous;
+            }
+        }
+
         var col = Util.hexToRGB(author.color);
         var rgba = 'rgba('+col[0]+','+col[1]+','+col[2]+','+Env.opacity+');';
         return Env.editor.markText(from, to, {
@@ -520,7 +531,8 @@ define([
         Env.authormarks.authors[Env.myAuthorId] = {
             name: userData.name,
             curvePublic: userData.curvePublic,
-            color: userData.color
+            color: userData.color,
+            uid: userData.uid,
         };
         if (!old || (old.name === userData.name && old.color === userData.color)) { return; }
         return true;
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 5e091a83c..db15d83d4 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -92,7 +92,7 @@ define([
         return ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
     };
 
-    var getPseudorandomAnimal = function (seed) {
+    var getPseudorandomAnimal = MT.getPseudorandomAnimal = function (seed) {
         if (typeof(seed) !== 'string') { return getRandomAnimal(); }
         seed = seed.replace(/\D/g, '').slice(0, 10);
         seed = parseInt(seed);

From a701522c61b822bef3238ae2a0d6be57fed976b2 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 15:03:07 +0530
Subject: [PATCH 15/20] animal avatars for guests in join/part messages

---
 www/common/toolbar.js | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index da8b1dad0..5b359a323 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -1217,18 +1217,31 @@ MessengerUI, Messages, Pages) {
         }
     };
 
+    var getFancyGuestName = function (name, uid) {
+        name = UI.getDisplayName(name);
+        if (name === Messages.anonymous && uid) {
+            var animal = MT.getPseudorandomAnimal(uid);
+            if (animal) {
+                name = animal + ' ' + name;
+            }
+        }
+        return name;
+    };
+
     // Notifications
     var initNotifications = function (toolbar, config) {
         // Display notifications when users are joining/leaving the session
         var oldUserData;
         if (!config.metadataMgr) { return; }
         var metadataMgr = config.metadataMgr;
-        var notify = function(type, name, oldname) {
+        var notify = function(type, name, oldname, uid) {
             if (toolbar.isAlone) { return; }
             // type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
             if (typeof name === "undefined") { return; }
-            name = name || Messages.anonymous;
             if (Config.disableUserlistNotifications) { return; }
+            name = getFancyGuestName(name, uid);
+            oldname = getFancyGuestName(oldname, uid);
+
             switch(type) {
                 case 1:
                     UI.log(Messages._getKey("notifyJoined", [name]));
@@ -1277,7 +1290,7 @@ MessengerUI, Messages, Pages) {
                         delete oldUserData[u];
                         if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; }
                         if (userPresent(u, temp, newdata || oldUserData) < 1) {
-                            notify(-1, temp.name);
+                            notify(-1, temp.name, undefined, temp.uid);
                         }
                     }
                 }
@@ -1297,10 +1310,10 @@ MessengerUI, Messages, Pages) {
                     if (typeof oldUserData[k] === "undefined") {
                         // if the same uid is already present in the userdata, don't notify
                         if (!userPresent(k, newdata[k], oldUserData)) {
-                            notify(1, newdata[k].name);
+                            notify(1, newdata[k].name, undefined, newdata[k].uid);
                         }
                     } else if (oldUserData[k].name !== newdata[k].name) {
-                        notify(0, newdata[k].name, oldUserData[k].name);
+                        notify(0, newdata[k].name, oldUserData[k].name, newdata[k].uid);
                     }
                 }
             }

From 8a1f6d66cf3e7a12d7ab8b1b3142ecfd7dd1ab82 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 17:04:38 +0530
Subject: [PATCH 16/20] animal avatars in kanban cursors

---
 www/common/inner/common-mediatag.js |  3 ++-
 www/kanban/app-kanban.less          |  8 ++++++
 www/kanban/inner.js                 | 39 ++++++++++++++++++-----------
 3 files changed, 35 insertions(+), 15 deletions(-)

diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index db15d83d4..e061667ce 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -46,6 +46,7 @@ define([
     var animal_avatars = {};
     MT.getCursorAvatar = function (cursor) {
         var uid = cursor.uid;
+        // TODO it would be nice to have "{0} is editing" instead of just their name
         var html = '<span class="cp-cursor-avatar">';
         if (cursor.avatar && avatars[cursor.avatar]) {
             html += (cursor.avatar && avatars[cursor.avatar]) || '';
@@ -100,7 +101,7 @@ define([
         return ANIMALS[seed % ANIMALS.length];
     };
 
-    var getPrettyInitials = function (name) {
+    var getPrettyInitials = MT.getPrettyInitials = function (name) {
         var parts = name.split(/\s+/);
         var text;
         if (parts.length > 1) {
diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less
index 69924ef77..098560548 100644
--- a/www/kanban/app-kanban.less
+++ b/www/kanban/app-kanban.less
@@ -159,6 +159,14 @@
             margin-right: 5px;
             .tools_unselectable();
             cursor: default;
+            &.cp-cursor.cp-tippy-html {
+                background-color: var(--red);
+                // XXX figure out how to inherit this from avatar.less
+                font-size: 11px; // 20px / 1.8 as per avatar.less..
+                &.animal {
+                    font-size: 14px; // 20px / 1.8 * (6/5)...
+                }
+            }
         }
     }
     .kanban-item {
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 42b1e39fa..fe80b1007 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -59,7 +59,7 @@ define([
     verbose = function () {}; // comment out to enable verbose logging
     var onRedraw = Util.mkEvent();
     var onCursorUpdate = Util.mkEvent();
-    var remoteCursors = {}; // XXX
+    var remoteCursors = {};
 
     var setValueAndCursor = function (input, val, _cursor) {
         if (!input) { return; }
@@ -95,18 +95,27 @@ define([
 
     var getAvatar = function (cursor, noClear) {
         // Tippy
-        var html = MT.getCursorAvatar(cursor); // XXX
+        var html = MT.getCursorAvatar(cursor);
 
-        var l = Util.getFirstCharacter(cursor.name || Messages.anonymous);
+        var name = UI.getDisplayName(cursor.name);
+
+        var l;
+        var animal = '';
+        if (cursor.name === Messages.anonymous && typeof(cursor.uid) === 'string') {
+            l = MT.getPseudorandomAnimal(cursor.uid);
+            animal = '.animal';
+        } else {
+            l = MT.getPrettyInitials(name);
+        }
 
         var text = '';
         if (cursor.color) {
-            text = 'color:'+getTextColor(cursor.color)+';';
+            text = 'background-color:' + cursor.color + '; color:'+getTextColor(cursor.color)+';';
         }
-        var avatar = h('span.cp-cursor.cp-tippy-html', { // XXX
-            style: "background-color: " + (cursor.color || 'red') + ";"+text,
+        var avatar = h('span.cp-cursor.cp-tippy-html' + animal, {
+            style: text,
             'data-cptippy-html': true,
-            title: html, // XXX "{0} is editing"
+            title: html,
         }, l);
         if (!noClear) {
             cursor.clear = function () {
@@ -852,7 +861,7 @@ define([
             getAvatar: getAvatar,
             openLink: openLink,
             getTags: getExistingTags,
-            cursors: remoteCursors, // XXX
+            cursors: remoteCursors,
             boards: boards,
             _boards: Util.clone(boards),
         });
@@ -1062,6 +1071,7 @@ define([
         var kanban;
         var $container = $('#cp-app-kanban-content');
 
+        var myData = framework._.cpNfInner.metadataMgr.getUserData();
         var privateData = framework._.cpNfInner.metadataMgr.getPrivateData();
         if (!privateData.isEmbed) {
             mkHelpMenu(framework);
@@ -1101,7 +1111,7 @@ define([
             $container.find('.kanban-edit-item').remove();
         });
 
-        var getCursor = function () { // XXX
+        var getCursor = function () {
             if (!kanban || !kanban.inEditMode) { return; }
             try {
                 var id = kanban.inEditMode;
@@ -1204,7 +1214,7 @@ define([
             var remoteContent = newContent.content;
 
             if (Sortify(currentContent) !== Sortify(remoteContent)) {
-                var cursor = getCursor(); // XXX
+                var cursor = getCursor();
                 verbose("Content is different.. Applying content");
                 kanban.options.boards = remoteContent;
                 updateBoards(framework, kanban, remoteContent);
@@ -1261,11 +1271,13 @@ define([
         });
 
         var myCursor = {};
-        onCursorUpdate.reg(function (data) { // XXX
+        onCursorUpdate.reg(function (data) {
+            console.log('onCursorUpdate', data);
             myCursor = data;
+            myCursor.uid = myData.uid;
             framework.updateCursor();
         });
-        framework.onCursorUpdate(function (data) { // XXX
+        framework.onCursorUpdate(function (data) {
             if (!data) { return; }
             if (data.reset) {
                 Object.keys(remoteCursors).forEach(function (id) {
@@ -1293,9 +1305,8 @@ define([
             if (!cursor.item && !cursor.board) { return; }
 
             // Add new cursor
-            var avatar = getAvatar(cursor); // XXX
+            var avatar = getAvatar(cursor);
             var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
-            var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
             if ($item.length) {
                 remoteCursors[id] = cursor;
                 $item.find('.cp-kanban-cursors').append(avatar);

From 840a16a563c2c1b96c6449db8b2adee398d70f7a Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 17:39:56 +0530
Subject: [PATCH 17/20] add animal emojis to rich text cursors' tooltips

---
 www/pad/cursor.js | 21 ++++++++++++++++++---
 www/pad/inner.js  |  3 ++-
 2 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/www/pad/cursor.js b/www/pad/cursor.js
index 42569838f..fbca1a9cf 100644
--- a/www/pad/cursor.js
+++ b/www/pad/cursor.js
@@ -3,7 +3,9 @@ define([
     '/common/common-ui-elements.js',
     '/common/common-interface.js',
     '/bower_components/chainpad/chainpad.dist.js',
-], function ($, UIElements, UI, ChainPad) {
+    '/customize/messages.js',
+    '/common/inner/common-mediatag.js',
+], function ($, UIElements, UI, ChainPad, Messages, MT) {
     var Cursor = {};
 
     Cursor.isCursor = function (el) {
@@ -35,13 +37,20 @@ define([
         $(el).remove();
     };
 
-    Cursor.create = function (inner, hjsonToDom, cursorModule) {
+    Cursor.create = function (inner, hjsonToDom, cursorModule, uid) {
         var exp = {};
 
         var cursors = {};
 
+    // XXX despite the name of this function this doesn't actually render as a tippy tooltip
+    // that means that emojis will use the system font that shows up in native tooltips
+    // so this might be of limited value/aesthetic appeal compared to other apps' cursors
         var makeTippy = function (cursor) {
-            return cursor.name;
+            //return cursor.name;
+            if (typeof(cursor.uid) === 'string' && (!cursor.name || cursor.name === Messages.anonymous)) {
+                return MT.getPseudorandomAnimal(cursor.uid) + ' ' + Messages.anonymous;
+            }
+            return cursor.name || Messages.anonymous;
         };
 
         var makeCursor = function (id, cursor) {
@@ -138,6 +147,12 @@ define([
             var cursorObj = data.cursor;
 
             if (!cursorObj.selectionStart) { return; }
+            if (cursorObj.name === Messages.anonymous) {
+                // save a little bit of data from going over the wire...
+                // remote clients will interpret this as Messages.anonymous (in their UI language)
+                cursorObj.name = '';
+                cursorObj.uid = uid;
+            }
 
             // 1. Transform the cursor to get the offset relative to our doc
             // 2. Turn it into a range
diff --git a/www/pad/inner.js b/www/pad/inner.js
index 95e7c1bff..e89cd9a0a 100644
--- a/www/pad/inner.js
+++ b/www/pad/inner.js
@@ -678,6 +678,7 @@ define([
 
         var metadataMgr = framework._.sfCommon.getMetadataMgr();
         var privateData = metadataMgr.getPrivateData();
+        var myData = metadataMgr.getUserData();
         var common = framework._.sfCommon;
         var APP = window.APP;
 
@@ -704,7 +705,7 @@ define([
         var cursor = module.cursor = Cursor(inner);
 
         // Display other users cursor
-        var cursors = Cursors.create(inner, hjsonToDom, cursor);
+        var cursors = Cursors.create(inner, hjsonToDom, cursor, myData.uid);
 
         var openLink = function(e) {
             var el = e.currentTarget;

From 1e1890dbe4a4dcaba56c703488ca9dec11c713fb Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Thu, 26 Aug 2021 17:42:22 +0530
Subject: [PATCH 18/20] replace a line that I accidentally removed

---
 www/kanban/inner.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index fe80b1007..6934dd210 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -1312,6 +1312,7 @@ define([
                 $item.find('.cp-kanban-cursors').append(avatar);
                 return;
             }
+            var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
             if ($board.length) {
                 remoteCursors[id] = cursor;
                 $board.find('header .cp-kanban-cursors').append(avatar);

From c416303e1d3f9d9fb74821abedf23141e23b8b44 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Fri, 27 Aug 2021 18:23:07 +0530
Subject: [PATCH 19/20] set uid in cursor object in outer scope

rather than within each app that uses cursors
---
 www/code/inner.js                      | 4 +---
 www/code/markers.js                    | 5 +++--
 www/common/outer/cursor.js             | 1 +
 www/common/sframe-common-codemirror.js | 1 -
 www/kanban/inner.js                    | 2 --
 www/pad/cursor.js                      | 8 +-------
 www/pad/inner.js                       | 3 +--
 www/slide/inner.js                     | 6 ++----
 8 files changed, 9 insertions(+), 21 deletions(-)

diff --git a/www/code/inner.js b/www/code/inner.js
index 4328f0d8f..4f6c0308e 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -388,9 +388,7 @@ define([
     var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
 
         var common = framework._.sfCommon;
-        var metadataMgr = common.getMetadataMgr();
-        var privateData = metadataMgr.getPrivateData();
-        CodeMirror.uid = metadataMgr.getUserData().uid;
+        var privateData = common.getMetadataMgr().getPrivateData();
 
         var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode);
         var markdownTb = mkMarkdownTb(editor, framework);
diff --git a/www/code/markers.js b/www/code/markers.js
index b8a53bdd9..48e5a25fb 100644
--- a/www/code/markers.js
+++ b/www/code/markers.js
@@ -4,7 +4,8 @@ define([
     '/customize/messages.js',
     '/bower_components/chainpad/chainpad.dist.js',
     '/common/inner/common-mediatag.js',
-], function (Util, SFCodeMirror, Messages, ChainPad, MT) {
+    '/common/common-interface.js',
+], function (Util, SFCodeMirror, Messages, ChainPad, MT, UI) {
     var Markers = {};
 
     /* TODO Known Issues
@@ -39,7 +40,7 @@ define([
             });
         }
         uid = Number(uid);
-        var name = Util.fixHTML((author.name || "").trim());
+        var name = Util.fixHTML(UI.getDisplayName(author.name));
         var animal;
         if ((!name || name === Messages.anonymous) && typeof(author.uid) === 'string') {
             animal = MT.getPseudorandomAnimal(author.uid);
diff --git a/www/common/outer/cursor.js b/www/common/outer/cursor.js
index 95b2b34c6..b1a5e7cd0 100644
--- a/www/common/outer/cursor.js
+++ b/www/common/outer/cursor.js
@@ -187,6 +187,7 @@ define([
         data.color = Util.find(proxy, ['settings', 'general', 'cursor', 'color']);
         data.name = proxy[Constants.displayNameKey] || ctx.store.noDriveName || Messages.anonymous;
         data.avatar = Util.find(proxy, ['profile', 'avatar']);
+        data.uid = Util.find(proxy, ['uid']) || ctx.store.noDriveUid;
         c.cursor = data;
         sendMyCursor(ctx, client);
         cb();
diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js
index 4e490288d..58475bbed 100644
--- a/www/common/sframe-common-codemirror.js
+++ b/www/common/sframe-common-codemirror.js
@@ -509,7 +509,6 @@ define([
             var cursor = {};
             cursor.selectionStart = cursorToPos(editor.getCursor('from'), doc);
             cursor.selectionEnd = cursorToPos(editor.getCursor('to'), doc);
-            cursor.uid = exp.uid; // FIXME this is inefficient for the network but it's unlikely to trigger errors
             return cursor;
         };
 
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 6934dd210..368d18139 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -1272,9 +1272,7 @@ define([
 
         var myCursor = {};
         onCursorUpdate.reg(function (data) {
-            console.log('onCursorUpdate', data);
             myCursor = data;
-            myCursor.uid = myData.uid;
             framework.updateCursor();
         });
         framework.onCursorUpdate(function (data) {
diff --git a/www/pad/cursor.js b/www/pad/cursor.js
index fbca1a9cf..5b4c60ad7 100644
--- a/www/pad/cursor.js
+++ b/www/pad/cursor.js
@@ -37,7 +37,7 @@ define([
         $(el).remove();
     };
 
-    Cursor.create = function (inner, hjsonToDom, cursorModule, uid) {
+    Cursor.create = function (inner, hjsonToDom, cursorModule) {
         var exp = {};
 
         var cursors = {};
@@ -147,12 +147,6 @@ define([
             var cursorObj = data.cursor;
 
             if (!cursorObj.selectionStart) { return; }
-            if (cursorObj.name === Messages.anonymous) {
-                // save a little bit of data from going over the wire...
-                // remote clients will interpret this as Messages.anonymous (in their UI language)
-                cursorObj.name = '';
-                cursorObj.uid = uid;
-            }
 
             // 1. Transform the cursor to get the offset relative to our doc
             // 2. Turn it into a range
diff --git a/www/pad/inner.js b/www/pad/inner.js
index e89cd9a0a..95e7c1bff 100644
--- a/www/pad/inner.js
+++ b/www/pad/inner.js
@@ -678,7 +678,6 @@ define([
 
         var metadataMgr = framework._.sfCommon.getMetadataMgr();
         var privateData = metadataMgr.getPrivateData();
-        var myData = metadataMgr.getUserData();
         var common = framework._.sfCommon;
         var APP = window.APP;
 
@@ -705,7 +704,7 @@ define([
         var cursor = module.cursor = Cursor(inner);
 
         // Display other users cursor
-        var cursors = Cursors.create(inner, hjsonToDom, cursor, myData.uid);
+        var cursors = Cursors.create(inner, hjsonToDom, cursor);
 
         var openLink = function(e) {
             var el = e.currentTarget;
diff --git a/www/slide/inner.js b/www/slide/inner.js
index 9bbfc6e4d..9c9b9c70b 100644
--- a/www/slide/inner.js
+++ b/www/slide/inner.js
@@ -459,9 +459,7 @@ define([
     var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
 
         var common = framework._.sfCommon;
-        var metadataMgr = common.getMetadataMgr();
-        var privateData = metadataMgr.getPrivateData();
-        CodeMirror.uid = metadataMgr.getUserData().uid;
+        var privateData = common.getMetadataMgr().getPrivateData();
 
         var $contentContainer = $('#cp-app-slide-editor');
         var $modal = $('#cp-app-slide-modal');
@@ -513,7 +511,7 @@ define([
                 framework.updateCursor();
             }, 500); // 500ms to make sure it is sent after chainpad sync
         };
-        framework.onCursorUpdate(CodeMirror.setRemoteCursor); // XXX
+        framework.onCursorUpdate(CodeMirror.setRemoteCursor);
         framework.setCursorGetter(CodeMirror.getCursor);
         editor.on('cursorActivity', updateCursor);
 

From cd613f10377c655340d7534c61ee9868f1c81fd2 Mon Sep 17 00:00:00 2001
From: ansuz <ansuz@transitiontech.ca>
Date: Fri, 27 Aug 2021 19:38:50 +0530
Subject: [PATCH 20/20] many small improvements for animal avatars

* more consistent scaling for animal avatars relative to the font-size of username's initials
* configurable emoji lists via AppConfig.emojiAvatars
* various comments from code review
* fixed a bug that caused the user admin menu button to not be redrawn on name changes
* guard against empty animal emojis in case the admin sets the list to []
---
 customize.dist/src/less2/include/avatar.less  | 10 ++++----
 customize.dist/src/less2/include/toolbar.less |  7 +++++-
 www/common/application_config_internal.js     |  2 ++
 www/common/common-messaging.js                |  2 +-
 www/common/common-ui-elements.js              |  4 ++-
 www/common/inner/access.js                    |  1 +
 www/common/inner/common-mediatag.js           | 25 ++++++++++---------
 www/kanban/app-kanban.less                    |  5 ++--
 www/kanban/inner.js                           | 10 +++++---
 www/pad/cursor.js                             |  6 +++--
 10 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less
index 079ceb60d..acaa13351 100644
--- a/customize.dist/src/less2/include/avatar.less
+++ b/customize.dist/src/less2/include/avatar.less
@@ -5,6 +5,10 @@
 ) {
     @avatar-width: @width;
     @avatar-font-size: @width / 1.8;
+    // scale animal avatar to be somewhat larger, because:
+    // 1. emojis are wider than most latin characters
+    // 2. they should occupy the width of two average characters
+    @avatar-font-size-animal: @avatar-font-size * (6/5);
 }
 .avatar_main(@width: 30px) {
     --LessLoader_require: LessLoader_currentFile();
@@ -41,11 +45,7 @@
             font-size: @avatar-font-size;
             font-size: var(--avatar-font-size);
             .animal {
-                font-size: 20px;
-                // scale animal avatar to be somewhat larger, because:
-                // 1. emojis are wider than most latin characters
-                // 2. they should occupy the width of two average characters
-                font-size: calc(var(--avatar-width) * (6/5));
+                font-size: @avatar-font-size-animal;
             }
         }
         media-tag {
diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index 28b513095..2092d72cc 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -855,10 +855,15 @@
                     span {
                         text-align: center;
                         width: 100%;
-                        font-size: 40px;
+                        .avatar_vars(72px);
+                        font-size: @avatar-font-size;
                         display: inline-flex;
                         justify-content: center;
                         align-items: center;
+                        .animal {
+                            font-size: @avatar-font-size-animal;
+
+                        }
                     }
                     &.cp-avatar {
                         .avatar_main(64px);
diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js
index 3be979f17..2993edc52 100644
--- a/www/common/application_config_internal.js
+++ b/www/common/application_config_internal.js
@@ -208,5 +208,7 @@ define(function() {
     // the driveless mode by changing the following value to "false"
     AppConfig.allowDrivelessMode = true;
 
+    AppConfig.emojiAvatars = '🙈 đŸĻ€ 🐞 đŸĻ‹ đŸŦ 🐋 đŸĸ đŸĻ‰ đŸĻ† 🐧 đŸĻĄ đŸĻ˜ đŸĻ¨ đŸĻĻ đŸĻĨ đŸŧ đŸģ đŸĻ đŸĻ“ 🐄 🐷 🐐 đŸĻ™ đŸĻ’ 🐘 đŸĻ 🐁 🐹 🐰 đŸĻĢ đŸĻ” 🐨 🐱 đŸē đŸ‘ē 👹 đŸ‘Ŋ 👾 🤖'.split(/\s+/);
+
     return AppConfig;
 });
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index 4a3eb2a91..1e6e26ce3 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -18,7 +18,7 @@ define([
             curvePublic: proxy.curvePublic,
             notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
             avatar: proxy.profile && proxy.profile.avatar,
-            uid: proxy.uid,
+            uid: proxy.uid, // XXX test without this and see if it breaks things
         };
         if (hash === false) { delete data.channel; }
         return data;
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index d0ef0af3f..abf45b0bd 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -1996,6 +1996,7 @@ define([
         var to;
         var oldUrl = '';
         var oldUid;
+        var oldName;
         var updateButton = function () {
             var myData = metadataMgr.getUserData();
             var privateData = metadataMgr.getPrivateData();
@@ -2017,11 +2018,12 @@ define([
             var newName = UI.getDisplayName(myData.name);
             var url = myData.avatar;
             $displayName.text(newName);
-            if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid) {
+            if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid || oldName !== newName) {
                 $avatar.html('');
                 Common.displayAvatar($avatar, url, newName, function ($img) {
                     oldUrl = url;
                     oldUid = uid;
+                    oldName = newName;
                     $userAdmin.find('> button').removeClass('cp-avatar');
                     if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
                     loadingAvatar = false;
diff --git a/www/common/inner/access.js b/www/common/inner/access.js
index 119714de3..0ba857a0a 100644
--- a/www/common/inner/access.js
+++ b/www/common/inner/access.js
@@ -787,6 +787,7 @@ define([
                 // ie. if you have opened the access modal from within the pad
                 // its owner might be present or they might have left some data
                 // in the pad itself (as is the case of the uid in rich text comments)
+                // TODO or just implement "Acquaintances"
             };
             strangers++;
         });
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index e061667ce..21cb25e82 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -6,12 +6,13 @@ define([
     '/common/hyperscript.js',
     '/common/media-tag.js',
     '/customize/messages.js',
+    '/customize/application_config.js',
 
     '/bower_components/tweetnacl/nacl-fast.min.js',
     '/bower_components/croppie/croppie.min.js',
     '/bower_components/file-saver/FileSaver.min.js',
     'css!/bower_components/croppie/croppie.css',
-], function ($, Util, Hash, UI, h, MediaTag, Messages) {
+], function ($, Util, Hash, UI, h, MediaTag, Messages, AppConfig) {
     var MT = {};
 
     var Nacl = window.nacl;
@@ -49,7 +50,7 @@ define([
         // TODO it would be nice to have "{0} is editing" instead of just their name
         var html = '<span class="cp-cursor-avatar">';
         if (cursor.avatar && avatars[cursor.avatar]) {
-            html += (cursor.avatar && avatars[cursor.avatar]) || '';
+            html += avatars[cursor.avatar];
         } else if (animal_avatars[uid]) {
             html += animal_avatars[uid] + ' ';
         }
@@ -87,18 +88,20 @@ define([
     };
 
     // https://emojipedia.org/nature/
-    var ANIMALS = '🙈 đŸĻ€ 🐞 đŸĻ‹ đŸŦ 🐋 đŸĸ đŸĻ‰ đŸĻ† 🐧 đŸĻĄ đŸĻ˜ đŸĻ¨ đŸĻĻ đŸĻĨ đŸŧ đŸģ đŸĻ đŸĻ“ 🐄 🐷 🐐 đŸĻ™ đŸĻ’ 🐘 đŸĻ 🐁 🐹 🐰 đŸĻĢ đŸĻ” 🐨 🐱 đŸē đŸ‘ē 👹 đŸ‘Ŋ 👾 🤖'.split(/\s+/);
+    var ANIMALS = AppConfig.emojiAvatars || [];
 
-    var getRandomAnimal = function () {
+    var getRandomAnimal = function () { // XXX should never actually happen?
+        if (!ANIMALS.length) { return ''; }
         return ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
     };
 
     var getPseudorandomAnimal = MT.getPseudorandomAnimal = function (seed) {
+        if (!ANIMALS.length) { return ''; }
         if (typeof(seed) !== 'string') { return getRandomAnimal(); }
-        seed = seed.replace(/\D/g, '').slice(0, 10);
+        seed = seed.replace(/\D/g, '').slice(0, 10); // XXX possible optimization for on-wire uid
         seed = parseInt(seed);
         if (!seed) { return getRandomAnimal(); }
-        return ANIMALS[seed % ANIMALS.length];
+        return ANIMALS[seed % ANIMALS.length] || '';
     };
 
     var getPrettyInitials = MT.getPrettyInitials = function (name) {
@@ -123,29 +126,27 @@ define([
             if (uid && animal_avatars[uid]) {
                 animal_avatar = animal_avatars[uid];
             }
-            var animal = false;
 
-            name = (name || "").trim() || Messages.anonymous;
+            name = UI.getDisplayName(name);
             var text;
-            if (name === Messages.anonymous && uid) {
+            if (ANIMALS.length && name === Messages.anonymous && uid) {
                 if (animal_avatar) {
                     text = animal_avatar;
                 } else {
                     text = animal_avatar = getPseudorandomAnimal(uid);
                 }
-                animal = true;
             } else {
                 text = getPrettyInitials(name);
             }
 
             var $avatar = $('<span>', {
-                'class': 'cp-avatar-default' + (animal? ' animal': ''),
+                'class': 'cp-avatar-default' + (animal_avatar? ' animal': ''),
                 // XXX prevents screenreaders from trying to describe this
                 alt: '',
                 'aria-hidden': true,
             }).text(text);
             $container.append($avatar);
-            if (uid && animal) {
+            if (uid && animal_avatar) {
                 animal_avatars[uid] = animal_avatar;
             }
             if (cb) { cb(); }
diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less
index 098560548..5996940e4 100644
--- a/www/kanban/app-kanban.less
+++ b/www/kanban/app-kanban.less
@@ -160,11 +160,12 @@
             .tools_unselectable();
             cursor: default;
             &.cp-cursor.cp-tippy-html {
+                .avatar_vars(20px);
                 background-color: var(--red);
                 // XXX figure out how to inherit this from avatar.less
-                font-size: 11px; // 20px / 1.8 as per avatar.less..
+                font-size: @avatar-font-size; //var(11px; // 20px / 1.8 as per avatar.less..
                 &.animal {
-                    font-size: 14px; // 20px / 1.8 * (6/5)...
+                    font-size: @avatar-font-size-animal; //14px; // 20px / 1.8 * (6/5)...
                 }
             }
         }
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 368d18139..add0ed693 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -99,12 +99,15 @@ define([
 
         var name = UI.getDisplayName(cursor.name);
 
-        var l;
+        var l; // label?
         var animal = '';
         if (cursor.name === Messages.anonymous && typeof(cursor.uid) === 'string') {
             l = MT.getPseudorandomAnimal(cursor.uid);
-            animal = '.animal';
-        } else {
+            if (l) {
+                animal = '.animal';
+            }
+        }
+        if (!l) {
             l = MT.getPrettyInitials(name);
         }
 
@@ -1071,7 +1074,6 @@ define([
         var kanban;
         var $container = $('#cp-app-kanban-content');
 
-        var myData = framework._.cpNfInner.metadataMgr.getUserData();
         var privateData = framework._.cpNfInner.metadataMgr.getPrivateData();
         if (!privateData.isEmbed) {
             mkHelpMenu(framework);
diff --git a/www/pad/cursor.js b/www/pad/cursor.js
index 5b4c60ad7..ae91c80f1 100644
--- a/www/pad/cursor.js
+++ b/www/pad/cursor.js
@@ -46,9 +46,11 @@ define([
     // that means that emojis will use the system font that shows up in native tooltips
     // so this might be of limited value/aesthetic appeal compared to other apps' cursors
         var makeTippy = function (cursor) {
-            //return cursor.name;
             if (typeof(cursor.uid) === 'string' && (!cursor.name || cursor.name === Messages.anonymous)) {
-                return MT.getPseudorandomAnimal(cursor.uid) + ' ' + Messages.anonymous;
+                var animal = MT.getPseudorandomAnimal(cursor.uid);
+                if (animal) {
+                    return animal + ' ' + Messages.anonymous;
+                }
             }
             return cursor.name || Messages.anonymous;
         };