React
React is a separate model with its own self-contained handler, following the same pattern as Reply. Kowloon uses a one reaction per user per target model: a React activity sets it (creating or replacing whatever was there), and an emoji-less payload clears it. There is no separate "unreact" activity type -- clearing is just React with no emoji.
Set or replace a reaction
Section titled “Set or replace a reaction”{ "type": "React", "objectType": "React", "to": "post:64f0...@kwln.org", "object": { "type": "React", "emoji": "thumbsup", "name": "thumbsup" }}Clear a reaction
Section titled “Clear a reaction”{ "type": "React", "objectType": "React", "to": "post:64f0...@kwln.org", "object": { "emoji": "" }}Required: actorId, objectType (a string), object (an object), to (the target ID -- despite the schema calling it to, semantically this is the reaction target, not an audience).
Targets supported: Post, Reply, Page, Bookmark, Group, Circle.
Only gated on new/changed reactions -- clearing your own past reaction is never blocked, even if you've since been blocked (you can always clean up your own data). The gate (authorizeInteraction(..., capability: "canReact")) is currently only enforced for Post/Reply targets, checked against the root post for Reply targets. Page/Bookmark/Group/Circle react targets aren't FeedItems-backed and skip this gate -- a tracked follow-up, kowloon#40.
Side effects
Section titled “Side effects”reactCount/reactPreview(top emoji)/reactSummary(all distinct emoji, concatenated) are recomputed from a live aggregate over theReactcollection -- never incremented/decremented by delta -- and written to whichever target model matches, plus itsFeedItemscache entry. Recompute-not-delta is what keeps counts correct across add/replace/clear without needing to track prior state.User.reactCount(the actor's own tally of things they've reacted to) is incremented on a brand-new reaction, decremented on clear, and left untouched on a replace.- A notification fires only on a brand-new reaction -- never on replace or clear -- respecting
prefs.notifications.react. - Federation goes to the target's host, the target author's home, and -- for Reply targets -- the parent's host too.
Response
Section titled “Response”{ activity, created: { status, react }, result: same, federation }, where status is one of reacted / replaced / unreacted / already_reacted / no_change.
Client mapping
Section titled “Client mapping”react({ postId, emoji, name }) matches exactly, including the deliberate object.type omission on clear. There is no separate deleteReact() method; react({ postId, emoji: '' }) is the only (and correct) way to clear a reaction.