feat(pins): pin messages to a channel
Pinned state lives on the message rather than a join table: a message is pinned in exactly one channel, its own, so a separate table would add a join to every lookup and buy nothing. Every message now carries its pin state, so the timeline can mark a pin without a second request and the panel and the timeline cannot disagree. Toggling is deliberately not optimistic — the server refuses past the channel's limit, and showing it pinned before confirmation would lie in exactly that case. The pins list reuses the same assembly the channel history uses, extracted into one helper, so the two cannot drift apart in what they include. The migration also adds the (channel, user, created) index the filtered search will need, since both touch the same table and one migration is cheaper than two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -519,6 +519,23 @@ function handleEvent(origin: string, event: ServerEvent): void {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'message_pinned': {
|
||||
// Atualiza a mensagem já carregada em vez de recarregar o canal: fixar é
|
||||
// uma mudança de um campo, e recarregar jogaria fora a posição de leitura.
|
||||
// updateMessage chaveia por message.channelId, então basta achar a
|
||||
// mensagem na lista daquele canal.
|
||||
const cs = useChatStore.getState();
|
||||
const list = cs.messages.get(event.channelId);
|
||||
const found = list?.find((m) => m.id === event.messageId);
|
||||
if (found) {
|
||||
cs.updateMessage({
|
||||
...found,
|
||||
pinnedAt: event.pinned ? Date.now() : null,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'message_updated':
|
||||
if (!isHome) {
|
||||
normalizeMessageAssets(event.message, origin);
|
||||
|
||||
Reference in New Issue
Block a user