The picker asks the account what it used last

Where the account keeps the list itself - Matrix, in account data - that
copy wins, because it is the one that has seen every client and the
local one is only this machine's view of it. Everything else keeps the
local list unchanged: nothing else here has anywhere to put it, and on
the services whose emoji are guild- or channel-bound a travelling list
would travel to places its entries cannot be sent.

Writing back is fire and forget. The row on screen is already right, and
one pick failing to reach another client is not worth interrupting
somebody mid-sentence about.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-13 13:30:11 -04:00
co-authored by Claude Opus 5
parent 178b2a5c23
commit db65b8b101
2 changed files with 34 additions and 1 deletions
+1 -1
Submodule nobilis updated: 3e522afdc5...ae2e8189b4
@@ -100,6 +100,19 @@ function readRecent(accountId?: string): string[] {
}
}
/**
* Whether this account keeps its recent picks on the account rather than in
* this window.
*
* Matrix does, in account data, so the list follows the person to their
* phone and back. Nothing else here has anywhere to put it - and on the
* services whose emoji are guild- or channel-bound, a list that travelled
* would travel to places the emoji in it cannot be sent.
*/
function travels(accountId?: string): boolean {
return !!accountId?.startsWith('matrix:')
}
/** One image out of a Matrix sticker pack, as the daemon offers it. */
export interface StickerEntry {
name: string
@@ -195,6 +208,19 @@ export function EmojiPicker({
// point the list on screen belongs to somebody else.
useEffect(() => {
setRecent(readRecent(accountId))
// Where the account keeps the list itself, that copy wins: it is the one
// that has seen every client, and the local one is only ever this
// machine's view of it. Asked each time the picker changes account
// rather than held, because the answer changes elsewhere.
if (!travels(accountId)) return
void window.moho
.rpc<string[]>('matrixRecentEmoji', { accountId })
// An older daemon does not answer this, and an account that has never
// picked one has nothing to say. Both leave the local list alone.
.then((list) => {
if (list.length > 0) setRecent(list)
})
.catch(() => {})
}, [accountId])
// Anchored above and right-aligned to the button that opened it, and kept
@@ -355,6 +381,13 @@ export function EmojiPicker({
} catch {
/* a full or disabled store just means recents don't persist */
}
// And on the account, where it has somewhere to go. Fire and forget: the
// list on screen is already right, and a failed write means one pick is
// not carried to another client - not something to interrupt somebody
// mid-sentence about.
if (travels(accountId)) {
void window.moho.rpc('matrixEmojiUsed', { accountId, emoji: text }).catch(() => {})
}
onSelect(text)
}