atagen

macltok

public
Star0
Watch0

Commit 8fa8a7cde89c

tarball

atagen <boss@atagen.co>2026-10-22 09:31 UTC

5 files changed+27-3
timeline: carry msc4027 shortcodes on custom emote reactions

A reaction keyed on an mxc uri is meaningless to a client without the pack, and MSC4027's shortcode is the label. ruma's ReactionEventContent drops the field, so the fork reads and writes it raw in the timeline and send queue, and Reaction says which image a key is so hosts never parse mxc strings.
Modified file: docs/design-decisions.md+7 additions
@@ -119,6 +119,11 @@ edit, redact and react take an `EventRef`, so your own message can be edited or
119119 reacted to before the server has echoed it back. redacting an unsent one just
120120 cancels it
121121
122+a custom emote reaction keys on the image's mxc uri and carries its shortcode
123+(msc4027), so clients without the pack can still label it. `Reaction` says
124+which image it is and the shortcode any sender gave, and `toggle_reaction`
125+takes the shortcode to send
126+
122127 ## warming up to send
123128
124129 the sdk does all its key work on the send path, in the room's send queue before
@@ -383,6 +388,8 @@ which keep their public sans-io apis
383388 - `Room::prepare_encryption`, the send path's encryption prep made public
384389 - `SyncServiceBuilder::with_room_list_extra_required_state`, for state the
385390 sdk's hard-coded list leaves out. image packs use it
391+- msc4027 reaction shortcodes, kept on the timeline's `ReactionInfo` and sent
392+ with `Timeline::toggle_reaction_with_shortcode`
386393
387394 ## public model
388395
Modified file: src/engine/timeline.rs+7 additions−2 deletions
@@ -185,9 +185,14 @@ impl TimelineSession {
185185 .await
186186 .map_err(timeline_error)
187187 }),
188- TimelineCommand::ToggleReaction { target, key, .. } => Box::pin(async move {
188+ TimelineCommand::ToggleReaction {
189+ target,
190+ key,
191+ shortcode,
192+ ..
193+ } => Box::pin(async move {
189194 timeline
190- .toggle_reaction(&item_id(target), &key)
195+ .toggle_reaction_with_shortcode(&item_id(target), &key, shortcode.as_deref())
191196 .await
192197 .map(drop)
193198 .map_err(timeline_error)
Modified file: src/model.rs+6 additions
@@ -236,6 +236,8 @@ pub enum TimelineCommand {
236236 timeline_id: TimelineId,
237237 target: EventRef,
238238 key: String,
239+ /// a custom emote's shortcode, sent along so clients without the pack can label it
240+ shortcode: Option<String>,
239241 },
240242 /// works on a closed room too, it just can't refuse to move the marker backwards
241243 MarkRead {
@@ -1605,6 +1607,10 @@ pub enum ReplyDetails {
16051607 #[non_exhaustive]
16061608 pub struct Reaction {
16071609 pub key: String,
1610+ /// a custom emote's shortcode, from whichever sender gave one
1611+ pub shortcode: Option<String>,
1612+ /// the custom emote itself, when the key is an image
1613+ pub image: Option<MediaId>,
16081614 pub count: usize,
16091615 pub own: bool,
16101616 pub senders: Vec<OwnedUserId>,
Modified file: src/normalise/timeline.rs+5 additions−1 deletions
@@ -11,7 +11,7 @@ use matrix_sdk_ui::{
1111 },
1212 };
1313 use ruma::{
14- OwnedEventId, UserId,
14+ MxcUri, OwnedEventId, UserId,
1515 events::{
1616 StateEventContentChange,
1717 room::message::{MessageFormat, MessageType},
@@ -421,6 +421,10 @@ fn reactions(event: &EventTimelineItem, current_user_id: Option<&UserId>) -> Vec
421421 .flat_map(|reactions| reactions.iter())
422422 .map(|(key, senders)| Reaction {
423423 key: key.clone(),
424+ shortcode: senders.values().find_map(|info| info.shortcode.clone()),
425+ image: <&MxcUri>::from(key.as_str())
426+ .is_valid()
427+ .then(|| MediaId::plain(key)),
424428 count: senders.len(),
425429 own: current_user_id.is_some_and(|user_id| senders.contains_key(user_id)),
426430 senders: senders.keys().cloned().collect(),
Modified file: src/transport.rs+2 additions
@@ -341,11 +341,13 @@ impl ClientHandle {
341341 timeline_id: TimelineId,
342342 target: impl Into<EventRef>,
343343 key: impl Into<String>,
344+ shortcode: Option<String>,
344345 ) -> Result<(), Error> {
345346 self.run(Command::Timeline(TimelineCommand::ToggleReaction {
346347 timeline_id,
347348 target: target.into(),
348349 key: key.into(),
350+ shortcode,
349351 }))
350352 .await
351353 }