Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Emoji reactions across messaging connectors by KrisBraun · Pull Request #151 · plotday/plot · GitHub
Skip to content

Emoji reactions across messaging connectors - #151

Merged
KrisBraun merged 8 commits into
mainfrom
emoji-reactions
May 27, 2026
Merged

Emoji reactions across messaging connectors#151
KrisBraun merged 8 commits into
mainfrom
emoji-reactions

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Summary

  • Add a first-class Reaction type alongside Tag. Threads and notes carry a reactions map keyed by Unicode emoji (or a provider:workspaceId/name custom-emoji ref); connectors declare what they support via Connector.reactionCapabilities (open-unicode / unicode-subset / fixed).
  • Slack: bidirectional Unicode reactions via note.reactions. Inbound replaces the 21-entry SLACK_REACTION_TO_TAG table with a 70+ shortcode→Unicode map, wires reaction_added / reaction_removed events, and refreshes the parent thread on each event. Outbound diffs Plot's reactions against conversations.history and dispatches reactions.add / reactions.remove.
  • MS Teams: inbound Unicode reactions via note.reactions, normalizing Graph's legacy reactionType enum (like/heart/laugh/surprised/sad/angry) into the corresponding Unicode emoji. Outbound setChannelReaction / unsetChannelReaction Graph 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-actor actAs() write-back tokens to attribute reactions correctly.
  • Google Chat: drop the 15-emoji EMOJI_TO_TAG / TAG_TO_EMOJI lookup and pass the raw Unicode grapheme through note.reactions in both directions, so any emoji a Chat user picks now round-trips into Plot instead of being silently dropped.
  • Tag cleanup: 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 the LinkTypeConfig.statuses[].tag mechanism that was the last server-side user; connectors already signalled completion via done: true / active: true, which was doing the real work. Tag.Twist moves from 109 to 12 to match the server's compute-range placement.
  • Connector status copy: 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).

Custom workspace emoji (Slack / Google Chat / Teams) are skipped both directions until image caching for custom_emoji lands; the relevant connectors declare customEmoji: 'none' for now.

Test plan

  • Slack: react in Slack → emoji appears on the Plot note; react in Plot → emoji appears in Slack; remove from either side propagates.
  • Slack: reaction-only events don't disturb message content or status (idempotency).
  • Google Chat: pick a non-curated emoji in Chat (e.g. 🦄) → appears in Plot; previously it was silently dropped.
  • MS Teams: react in Teams with a Graph-legacy reactionType and with a Unicode emoji → both surface on the note.
  • No connector still writes note.tags[Tag.Yes..Dismayed] or note.tags[Tag.Pinned..Idea].
  • Status flows: completing a Slack later item or Gmail starred item no longer auto-adds Tag.Star; Todoist priority-4 tasks no longer auto-tag Urgent.
  • Gmail / Granola: thread / agenda titles render "Email" → "Thread" and "Meeting" → "Notes".

🤖 Generated with Claude Code

KrisBraunand others added 8 commits May 27, 2026 10:01
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>
@KrisBraun
KrisBraun merged commit ccc7489 into mainMay 27, 2026
1 check passed
@KrisBraun
KrisBraun deleted the emoji-reactions branch May 27, 2026 17:16
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun