Allow sending emojis in the chat

Add an emoji picker to the dashboard reply composer and let generated
replies use emoji.

The picker offers a short hand-picked set rather than a full Unicode
index, and inserts at the caret so the operator can keep typing without
reaching for the mouse again. Insertion respects the Reddit length cap.

The chat reply system prompt now permits at most one or two emoji per
message, only where someone texting casually would use one. Scoped to
ongoing replies — openers build from configurable prompts, so hardcoding
guidance there would fight the operator's own settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Opus 5 committed 2026-08-14 09:45:22 +01:00
1 parent 2447563c60
commit b5304e24e3
5 files changed
+118 -1

No files matched your search

+45
View File
@@ -353,6 +353,51 @@ describe('ConversationsPage', () => {
expect(sendRequests).toBe(2);
});
it('inserts a picked emoji at the caret without disturbing the rest of the draft', async () => {
installConversationList();
const user = userEvent.setup();
renderConversations();
await user.click(await screen.findByRole('button', { name: /u\/alice/ }));
await screen.findByRole('heading', { name: 'u/alice' });
const composer = await screen.findByRole('textbox', { name: 'Reply to u/alice' }) as HTMLTextAreaElement;
await user.type(composer, 'hey there');
composer.setSelectionRange(3, 3);
expect(screen.queryByRole('group', { name: 'Emoji' })).not.toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Add emoji' }));
await user.click(within(screen.getByRole('group', { name: 'Emoji' })).getByRole('button', { name: '😄' }));
expect(composer).toHaveValue('hey😄 there');
await waitFor(() => expect(composer).toHaveFocus());
await user.click(screen.getByRole('button', { name: 'Hide emoji' }));
expect(screen.queryByRole('group', { name: 'Emoji' })).not.toBeInTheDocument();
});
it('sends a reply containing emoji unchanged', async () => {
installConversationList();
let sentBody: unknown = null;
server.use(
http.post('/api/conversations/room-alice/send-reply', async ({ request }) => {
sentBody = await request.json();
return HttpResponse.json({ success: true });
}),
);
const user = userEvent.setup();
renderConversations();
await user.click(await screen.findByRole('button', { name: /u\/alice/ }));
await screen.findByRole('heading', { name: 'u/alice' });
const composer = await screen.findByRole('textbox', { name: 'Reply to u/alice' });
await user.type(composer, 'nice one 🔥');
await user.click(screen.getByRole('button', { name: 'Review and send reply' }));
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Confirm send' }));
await waitFor(() => expect(sentBody).toEqual({ message: 'nice one 🔥' }));
});
it('generates a media ask and looks up durable processed-user history', async () => {
installConversationList();
server.use(