The picker's delete "x" overlaps the glyph in a tightly packed grid,
which is too easy to hit by accident on a touch screen. Adding and
deleting custom emoji now live in their own modal under the account
menu, with delete gated behind the same confirm() every other
destructive action in the app uses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Site-wide, any user can upload -- usable both as reactions and inline
in message text via :shortcode:, alongside the existing built-in
Unicode picker. A :shortcode: reference is stored/sent as literal
text (same as the built-in shortcode convention) and resolved to an
image at render time, so it degrades to plain text if the emoji is
later deleted.
Backend: new custom_emoji table (shortcode unique, sized to fit
MessageReaction.emoji's existing column alongside its colons), upload/
list/delete endpoints (delete restricted to uploader or site admin).
Frontend: a CustomEmojiProvider context feeds a new "Custom" category
in the emoji picker (inline upload + hover-to-remove), extends the
composer's shortcode autocomplete, and a shared EmojiGlyph resolver
renders custom emoji wherever a value can appear -- message text,
reaction pills, and the picker itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RoomInfoPanel was hard-gated behind !isMobile in ChatShellPage.tsx, but
its trigger button ("Room details" in ChatPane.tsx) still rendered and
toggled state unconditionally -- tapping it on mobile did nothing
visible, with no way to reach room info/members/settings at all. Fixed
by removing the gate and making the panel itself responsive: it renders
as a full-screen fixed overlay below the mobile breakpoint instead of
the desktop resizable aside (which stays exactly as before -- verified
in-browser at both viewport sizes).
Also found and fixed a second real bug during the mobile audit: the
emoji picker's fixed 320px width overflows a 375px-wide viewport by 5px
depending on trigger position (e.g. the composer's emoji button, near
the left edge). Shrunk it to 280px with a proportionally reduced column
count below 480px, rather than attempting dynamic position-aware sizing
for a 5px overflow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A search box in EmojiPicker filters the flat emoji list by name/keyword
instead of only browsing by category, shared by both the composer's
emoji trigger and the message reaction picker.
Names/keywords didn't exist anywhere in this codebase before (emoji.ts is
just raw unicode characters). Rather than hand-typing entries for all 928
unique emoji -- a lot of manual work, and unlike the codepoints themselves
a wrong name only means a bad search result, not a broken emoji, so the
accuracy argument for hand-typing didn't apply here -- generated
emojiNames.ts once from emojibase-data (MIT licensed), matched against
emoji.ts by codepoint (normalizing variation-selector differences between
the two sources). emojibase-data itself was never added to package.json;
it was only ever a one-time generation tool, so this ships with zero new
runtime dependency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The old list was a small hand-picked subset that was missing common
ones (party popper, facepalm, shrug, etc.). Reorganized into ten
categories (Smileys, People & Gestures, Hearts, Animals & Nature,
Food & Drink, Travel & Places, Activities & Sports, Objects, Symbols,
Flags) with much broader coverage in each. Widened the picker panel
and bumped the grid to 9 columns to fit the larger set comfortably.
Composer gains an emoji picker (static curated unicode list, insert at
cursor position) and messages gain Slack/Mattermost-style reactions:
react with any emoji, toggle off by reacting again, see who reacted via
a tooltip on each pill.
Backend: MessageReaction model (unique on message_id+user_id+emoji backs
toggle semantics), WS "reaction" envelope broadcasts the full recomputed
reaction list per message (same approach as message edits), REST message
list embeds reactions so a reload doesn't lose state that only arrived
over WS.
Frontend: shared EmojiPicker component (anchored popover, Escape/outside-
click dismiss via new useEscapeKey hook) used by both the composer and a
new hover-revealed reaction trigger on each message row.