Tooltip is title={r.emoji} (MessageRow.tsx:983) - raw <:name:id> for custom emoji.
ReactionEmoji accepts animated but ignores it (1147-1151).
Who reacted isn't available: Reaction has no reactor list (wire.ts:410-417). Discord's tooltip reads "X, Y and Z reacted with :name:".
Fix: resolve custom emoji names; animate animated reactions; daemon: a listReactors RPC fetched on hover (Discord GET /channels/{c}/messages/{m}/reactions/{emoji}, Matrix annotation senders).
Part of the v1.0 UI pass.
- Tooltip is `title={r.emoji}` (MessageRow.tsx:983) - raw `<:name:id>` for custom emoji.
- `ReactionEmoji` accepts `animated` but ignores it (1147-1151).
- Who reacted isn't available: `Reaction` has no reactor list (wire.ts:410-417). Discord's tooltip reads "X, Y and Z reacted with :name:".
**Fix:** resolve custom emoji names; animate animated reactions; **daemon:** a `listReactors` RPC fetched on hover (Discord `GET /channels/{c}/messages/{m}/reactions/{emoji}`, Matrix annotation senders).
_Part of the v1.0 UI pass._
Salastil
added this to the v1.0 milestone 2026-10-07 20:43:14 -04:00
Pointing at a reaction now tells you what it is and who gave it.
Names: the tooltip was the raw token (<:name:id> for a custom emoji). It is now Discord's sentence - "Ann, Bob and Cat reacted with :name:", "Ann, Bob, Cat and 4 others…", or "5 people reacted with :name:" while the names haven't arrived or the service can't say. Custom emoji are named from their own token, Unicode ones from the picker's table (the bare emoji where there is no name).
Who reacted (daemon): new listReactors RPC (nobilis 1da3ad8). Discord: GET /channels/{c}/messages/{m}/reactions/{emoji}?limit=10. Matrix: the senders the session has seen for that message and emoji - matrix_reaction_targets now records the sender (as a ReactionEvent), a redaction removes just that person, names come from the room roster. Every other service answers an empty list (the count is all there is). Asked once the pointer has stayed 250 ms, never per message, and kept 30 s per (message, emoji, count).
Animated: nothing was being ignored - the CDN address already asks for the WebP with animated=true, which animates the ones that move; the dead animated prop is gone and the comment says so.
Tests: reactions.test.ts (names and sentences), reactor_tests in nobilis (who is seen, a redaction loses only that person). Clippy and the daemon suite pass.
Not exercised live: the Discord request, since the test account is receive-only and not to be used for this. The picture is the staged state, so it shows the fallback sentence; the names fill the same slot.
Before (top: only the pointer, with no tooltip a screenshot can capture), after (bottom):
Finished: [506140d](https://git.salastil.com/Salastil/moho/commit/506140db866ee707da289f4b02e4055a6b0cb9f1).
Pointing at a reaction now tells you what it is and who gave it.
- **Names:** the tooltip was the raw token (`<:name:id>` for a custom emoji). It is now Discord's sentence - "Ann, Bob and Cat reacted with :name:", "Ann, Bob, Cat and 4 others…", or "5 people reacted with :name:" while the names haven't arrived or the service can't say. Custom emoji are named from their own token, Unicode ones from the picker's table (the bare emoji where there is no name).
- **Who reacted (daemon):** new `listReactors` RPC (nobilis 1da3ad8). Discord: `GET /channels/{c}/messages/{m}/reactions/{emoji}?limit=10`. Matrix: the senders the session has seen for that message and emoji - `matrix_reaction_targets` now records the sender (as a `ReactionEvent`), a redaction removes just that person, names come from the room roster. Every other service answers an empty list (the count is all there is). Asked once the pointer has stayed 250 ms, never per message, and kept 30 s per (message, emoji, count).
- **Animated:** nothing was being ignored - the CDN address already asks for the WebP with `animated=true`, which animates the ones that move; the dead `animated` prop is gone and the comment says so.
- Tests: `reactions.test.ts` (names and sentences), `reactor_tests` in nobilis (who is seen, a redaction loses only that person). Clippy and the daemon suite pass.
- Not exercised live: the Discord request, since the test account is receive-only and not to be used for this. The picture is the staged state, so it shows the fallback sentence; the names fill the same slot.
Before (top: only the pointer, with no tooltip a screenshot can capture), after (bottom):

Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
title={r.emoji}(MessageRow.tsx:983) - raw<:name:id>for custom emoji.ReactionEmojiacceptsanimatedbut ignores it (1147-1151).Reactionhas no reactor list (wire.ts:410-417). Discord's tooltip reads "X, Y and Z reacted with :name:".Fix: resolve custom emoji names; animate animated reactions; daemon: a
listReactorsRPC fetched on hover (DiscordGET /channels/{c}/messages/{m}/reactions/{emoji}, Matrix annotation senders).Part of the v1.0 UI pass.
Finished: 506140d.
Pointing at a reaction now tells you what it is and who gave it.
<:name:id>for a custom emoji). It is now Discord's sentence - "Ann, Bob and Cat reacted with :name:", "Ann, Bob, Cat and 4 others…", or "5 people reacted with :name:" while the names haven't arrived or the service can't say. Custom emoji are named from their own token, Unicode ones from the picker's table (the bare emoji where there is no name).listReactorsRPC (nobilis 1da3ad8). Discord:GET /channels/{c}/messages/{m}/reactions/{emoji}?limit=10. Matrix: the senders the session has seen for that message and emoji -matrix_reaction_targetsnow records the sender (as aReactionEvent), a redaction removes just that person, names come from the room roster. Every other service answers an empty list (the count is all there is). Asked once the pointer has stayed 250 ms, never per message, and kept 30 s per (message, emoji, count).animated=true, which animates the ones that move; the deadanimatedprop is gone and the comment says so.reactions.test.ts(names and sentences),reactor_testsin nobilis (who is seen, a redaction loses only that person). Clippy and the daemon suite pass.Before (top: only the pointer, with no tooltip a screenshot can capture), after (bottom):