aboutsummaryrefslogtreecommitdiffstats
path: root/src/public-chat.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-15 22:39:05 -0700
committerAdam Soltys <[email protected]>2026-09-15 22:39:05 -0700
commit3d706acd1edccb5c4eb0f781b52b3d302bdafa2b (patch)
tree3f43b2daf254b87a8f8dec58eeb2481f8b4678d7 /src/public-chat.js
parent12e069fc62bff8715de189aedc777b69d1e56377 (diff)
downloadcoinosv3-3d706acd1edccb5c4eb0f781b52b3d302bdafa2b.tar.gz
coinosv3-3d706acd1edccb5c4eb0f781b52b3d302bdafa2b.zip
A changed reaction stays changed: the fold keeps the newest, not the last to arrive.
One reaction per author was folded as "last one in wins". A reload's backfill hands reactions back in relay order, so a flame swapped for confetti came back as the flame. Rooms, DMs and the public page now keep the newest by the rumor's own clock, and an optimistic send stamps its time so an older one arriving afterwards stays out. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01JuwQqNWtDJcxPWtEm9134z
Diffstat (limited to 'src/public-chat.js')
-rw-r--r--src/public-chat.js11
1 files changed, 10 insertions, 1 deletions
diff --git a/src/public-chat.js b/src/public-chat.js
index 91ded5b..9cce6f5 100644
--- a/src/public-chat.js
+++ b/src/public-chat.js
@@ -84,6 +84,7 @@ export function mountPublicChat() {
const deletes = new Map(); // rumorId -> Set(author)
const reactions = new Map(); // rumorId -> Map(author -> emoji)
const reactEmoji = new Map(); // shortcode -> url, from reactions' own emoji tags
+ const reactAt = new Map(); // `${rumorId}|${author}` -> ms of the reaction that stands
const controlEntries = [];
let folded = null; // foldControl result: banned set + channel renames
let foldT = 0; // fold-once-per-burst debounce, same as the app's rooms
@@ -157,7 +158,15 @@ export function mountPublicChat() {
}
} else if (rumor.kind === 7) {
const target2 = tag('e')?.[1];
- if (target2) (reactions.get(target2) || reactions.set(target2, new Map()).get(target2)).set(author, rumor.content);
+ if (target2) {
+ // newest by the rumor's clock, not last to arrive (see the app)
+ const key = target2 + '|' + author;
+ const at = eventMs(rumor) || rumor.created_at * 1000;
+ if (at >= (reactAt.get(key) || 0)) {
+ reactAt.set(key, at);
+ (reactions.get(target2) || reactions.set(target2, new Map()).get(target2)).set(author, rumor.content);
+ }
+ }
// a :code: reaction names its picture in its own tag; kept by code so
// the chip can show it
for (const [code, url] of emojiTagMap(rumor.tags)) reactEmoji.set(code, url);