Emoji reactions across messaging connectors#151
Merged
Conversation
Reactions are the open-set counterpart to Tag's count range (1000+).
The emoji string is either a Unicode grapheme cluster or a custom-emoji
ref of the form 'provider:workspaceId/name'. Adds `reactions` to
ThreadCommon (Thread + Note), to NewThread/NewNote, and to
ThreadUpdate/NoteUpdate. Connectors declare reaction support via
`reactionCapabilities` (open-unicode / unicode-subset / fixed) so the
Plot picker and outbound dispatch can filter what the platform accepts.
Foundation for higher-fidelity emoji reactions across Slack, Microsoft
Teams, Google Chat, LinkedIn Messaging, and Plot-native threads. The
runtime currently defaults `Note.reactions` and `Thread.reactions` to
`{}` everywhere; subsequent commits populate them from the new
note_reaction / thread_reaction tables.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup tables. Inbound
sync now writes the raw Unicode grapheme into `note.reactions`, so any
emoji a Google Chat user picks round-trips into Plot rather than being
silently dropped for falling outside the curated set. Outbound
write-back iterates `note.reactions` directly — the emoji is the dict
key — and add/remove diff against the auth user's current Chat
reactions on the message.
Declares reactionCapabilities: { mode: 'open-unicode', customEmoji:
'none' }. Custom workspace emoji are skipped on both directions until
the custom_emoji image cache lands; switch customEmoji to 'workspace'
then.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbound:
- Replace SLACK_REACTION_TO_TAG (21-entry Tag mapping) with
SLACK_SHORTCODE_TO_UNICODE — a 70+ entry curated map from Slack
shortcodes to Unicode. Reactions land on each note's `reactions`
field per-message (was: aggregated as thread-level tags).
- Wire reaction_added / reaction_removed Events API events. The event
payload only carries the reacted message's ts; resolve its parent
thread_ts via conversations.history, then refresh the full thread
via a new private `refreshSlackThread` helper. Updates land via the
existing saveLink upsert (per-note key match) so reaction-only
events don't disturb message content or status.
Outbound:
- onNoteUpdated now reconciles reactions in addition to content.
Diff Plot's reactions vs the message's current Slack reactions via
conversations.history; call reactions.add / reactions.remove for
the delta. Acts as the auth user for every reaction (per-actor
actAs() write-back is a follow-up). Reactions.add/remove treat
already_reacted / no_reaction as idempotent success.
Declares reactionCapabilities: { mode: 'open-unicode', customEmoji:
'none' }. Custom workspace emoji are skipped both directions until
custom_emoji image caching lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbound:
- Add TeamsReaction type + extractTeamsMessageReactions() helper.
Normalizes Graph's `reactionType` (legacy enum like/heart/laugh/
surprised/sad/angry → Unicode; Unicode emoji pass through) and emits
NewReactions keyed by emoji string. Dedupes by source.accountId.
- Wire into both transformChannelThread and transformDmThread so each
note carries its own per-message reactions. Existing change-
notification webhook (onTeamsWebhook → startIncrementalSync) drives
updates automatically: any time a message changes, the
re-fetch + re-upsert path now picks up reaction state too.
Outbound (deferred):
- Add GraphApi.setChannelReaction / unsetChannelReaction and the chat
equivalents for future use. Not yet wired in onNoteUpdated — Teams
allows only one reaction per user per message, so a diff-and-apply
pass needs per-actor actAs() write-back tokens to attribute
reactions correctly. Following that pattern when actAs lands.
Declares reactionCapabilities: { mode: 'open-unicode', customEmoji:
'none' }. Graph API accepts arbitrary Unicode emoji and the legacy
enum strings; custom workspace emoji isn't supported in Teams.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The count-tag range is fully retired in favour of the open Unicode emoji Reaction type. Every connector in this repo (Google Chat, Slack, MS Teams) now routes emoji reactions through note.reactions / NewReactions instead of note.tags; grep across connectors, twists, and workers shows zero remaining references to the count-tag enum values. Compute tags (Todo, Done) and toggle tags (Pinned through Idea) stay — they aren't reactions and still drive system / shared-state behavior. Their retirement is captured in docs/emoji-reactions-followups.md §2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The toggle range (100–999) is gone from the Tag enum. Reactions already replaced its user-facing chips; the per-status `tag` field was the last server-side use. Drop both: - Tag enum keeps Todo (1), Done (3), and Twist — moved from 109 (toggle) to 12 (compute) to match the server's retirement. Twist remains the system marker for "a twist is processing this note"; runtime keeps writing/clearing it and twists may still set it via twistTags. - LinkTypeConfig.statuses[].tag field removed. Every connector pairing it (slack, gmail, linear, jira, github, airtable, attio, asana, todoist, google-tasks) was using it on a status that already carries the done: true / active: true boolean that actually drove behaviour. The propagateLinkStatusTags worker code retires alongside. - Slack/Gmail: 'later'/'starred' statuses no longer auto-add a Tag.Star (per "drop the status-tag mechanism entirely"). - Todoist: priority-4 tasks no longer auto-tag Urgent; users react with 🚨 themselves. All 16 connectors lint clean; @plotday/api lint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Earlier connectors-pass missed a few inline-typed local helpers; this follow-up cleans up the documentation example in twister/docs/BUILDING_CONNECTORS.md that still referenced the dropped field, plus an internal type definition in attio.ts (folded into the previous commit). Mechanical follow-up to the toggle-tag retirement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Gmail "Email" → "Thread" (matches Plot's thread vocabulary and how filter commands now compose "<connector> <link-type>" titles). - Granola "Meeting" → "Notes" (the link points to the meeting notes doc, not the meeting itself). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reactiontype alongsideTag. Threads and notes carry areactionsmap keyed by Unicode emoji (or aprovider:workspaceId/namecustom-emoji ref); connectors declare what they support viaConnector.reactionCapabilities(open-unicode/unicode-subset/fixed).note.reactions. Inbound replaces the 21-entrySLACK_REACTION_TO_TAGtable with a 70+ shortcode→Unicode map, wiresreaction_added/reaction_removedevents, and refreshes the parent thread on each event. Outbound diffs Plot's reactions againstconversations.historyand dispatchesreactions.add/reactions.remove.note.reactions, normalizing Graph's legacyreactionTypeenum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. OutboundsetChannelReaction/unsetChannelReactionGraph endpoints land alongside but are not yet wired — Teams permits only one reaction per user per message, so the diff-and-apply pass needs per-actoractAs()write-back tokens to attribute reactions correctly.EMOJI_TO_TAG/TAG_TO_EMOJIlookup and pass the raw Unicode grapheme throughnote.reactionsin both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.Tagcleanup: retire the count range (Tag.Yes..Tag.Dismayed, 1000–1027) — reactions replace them. Retire the 10 toggle-tag entries (Pinned,Urgent,Goal,Decision,Waiting,Blocked,Warning,Question,Star,Idea) and theLinkTypeConfig.statuses[].tagmechanism that was the last server-side user; connectors already signalled completion viadone: true/active: true, which was doing the real work.Tag.Twistmoves from109to12to match the server's compute-range placement.Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for
custom_emojilands; the relevant connectors declarecustomEmoji: 'none'for now.Test plan
reactionTypeand with a Unicode emoji → both surface on the note.note.tags[Tag.Yes..Dismayed]ornote.tags[Tag.Pinned..Idea].lateritem or Gmailstarreditem no longer auto-addsTag.Star; Todoist priority-4 tasks no longer auto-tagUrgent.🤖 Generated with Claude Code