From eab1970d5ce822360b2661530560c51908df2dac Mon Sep 17 00:00:00 2001
From: Luke Policinski
Date: Mon, 28 Sep 2026 18:39:23 -0400
Subject: [PATCH 1/4] feature: chat message reactions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Anyone who can post in a room can react to any line in it, website or
game, with one of six reactions: 👍 ❤️ 😂 🔥 😮 😢. Reactions show as
pills under the message with a count, in the api's fixed order, and the
ones you hold are picked out in amber. Clicking a pill toggles it, and
hovering it names who reacted from what this browser already knows
(the room's participants and whoever has spoken), counting the rest.
The message menu gains Add Reaction above Edit, which opens a picker of
all six hung off the trigger. The menu, the picker and the confirm all
hold the right hub open. A gagged player in a group room can only take
back what they hold: the picker disables the rest and says why.
The socket sends lobby:react through the chat request plumbing and
listens for the room's `reaction` event, whose whole-state payload
replaces the message's reactions and never creates or revives one. A
history snapshot keeps reactions that changed live after the room was
joined, since the api sends no version to compare. Refusals toast
through chatErrors, with copy for rate_limited and gagged.
---
components/chat/ChatLobby.vue | 2 +
components/chat/ChatMessage.vue | 28 ++-
components/chat/ChatMessageActions.vue | 199 ++++++++++++---
components/chat/ChatMessageReactions.vue | 178 ++++++++++++++
components/chat/ChatMessages.vue | 5 +
constants/chat.ts | 13 +
i18n/locales/ar_SA.json | 11 +
i18n/locales/da_DK.json | 11 +
i18n/locales/de_DE.json | 11 +
i18n/locales/en.json | 11 +
i18n/locales/es_ES.json | 11 +
i18n/locales/fr_FR.json | 11 +
i18n/locales/it_IT.json | 11 +
i18n/locales/ja_JP.json | 11 +
i18n/locales/ko_KR.json | 11 +
i18n/locales/pl_PL.json | 11 +
i18n/locales/pt_BR.json | 11 +
i18n/locales/ru_RU.json | 11 +
i18n/locales/sv_SE.json | 11 +
i18n/locales/tr_TR.json | 11 +
i18n/locales/uk_UA.json | 11 +
i18n/locales/zh_Hans.json | 11 +
i18n/locales/zh_Hant.json | 11 +
tests/components/ChatLobby.spec.ts | 29 ++-
tests/components/ChatMessage.spec.ts | 139 +++++++++++
tests/components/ChatMessageActions.spec.ts | 180 ++++++++++++++
tests/components/ChatMessageReactions.spec.ts | 178 ++++++++++++++
tests/utilities/chatErrors.spec.ts | 30 +++
tests/utilities/chatLobbyMessages.spec.ts | 109 +++++++++
tests/utilities/chatMessageActions.spec.ts | 185 ++++++++++++--
tests/web-sockets/SocketChatError.spec.ts | 227 ++++++++++++++++++
utilities/chatErrors.ts | 17 +-
utilities/chatLobbyMessages.ts | 74 +++++-
utilities/chatMessageActions.ts | 53 +++-
web-sockets/Socket.ts | 66 +++++
35 files changed, 1834 insertions(+), 65 deletions(-)
create mode 100644 components/chat/ChatMessageReactions.vue
create mode 100644 tests/components/ChatMessageReactions.spec.ts
diff --git a/components/chat/ChatLobby.vue b/components/chat/ChatLobby.vue
index b0175e4b..5396f062 100644
--- a/components/chat/ChatLobby.vue
+++ b/components/chat/ChatLobby.vue
@@ -110,6 +110,7 @@ import Empty from "~/components/ui/empty/Empty.vue";
:last-read-count="lastReadMessageCount"
:message-room="messageRoom"
:can-moderate="canModerate"
+ :can-post="canSend"
@bottom-state-change="handleBottomStateChange"
/>
@@ -282,6 +283,7 @@ import Empty from "~/components/ui/empty/Empty.vue";
:last-read-count="tracksReadPosition ? lastReadMessageCount : 0"
:message-room="messageRoom"
:can-moderate="canModerate"
+ :can-post="canSend"
@bottom-state-change="handleBottomStateChange"
/>
diff --git a/components/chat/ChatMessage.vue b/components/chat/ChatMessage.vue
index debc247d..8176babc 100644
--- a/components/chat/ChatMessage.vue
+++ b/components/chat/ChatMessage.vue
@@ -4,6 +4,7 @@ import TimeAgo from "~/components/TimeAgo.vue";
import PlayerDisplay from "~/components/PlayerDisplay.vue";
import ChatMessageActions from "~/components/chat/ChatMessageActions.vue";
import ChatMessageEditor from "~/components/chat/ChatMessageEditor.vue";
+import ChatMessageReactions from "~/components/chat/ChatMessageReactions.vue";
import FiveStackToolTip from "~/components/FiveStackToolTip.vue";
@@ -105,6 +106,13 @@ import FiveStackToolTip from "~/components/FiveStackToolTip.vue";
{{ $t("chat.edited_at", { time: editedAtLabel }) }}