Skip to content

Show own reactions from other devices live; fix mis-styling of others' reactions#68

Merged
gammons merged 3 commits into
gammons:mainfrom
Frodotus:fix/live-reaction-updates
Jun 11, 2026
Merged

Show own reactions from other devices live; fix mis-styling of others' reactions#68
gammons merged 3 commits into
gammons:mainfrom
Frodotus:fix/live-reaction-updates

Conversation

@Frodotus

Copy link
Copy Markdown
Contributor

Closes #67.

Problem

A reaction you add from another device (Slack web/mobile) didn't appear live in slk on the channel you were viewing — it only showed after switching channels and back. The WS reaction_added event arrived fine (verified via SLK_DEBUG); the live UI update was being dropped.

Root cause

reduceReactions filtered out every reaction by your own account (if m.UserID != a.currentUserID) to avoid double-counting the WS echo of your own optimistic update. But that also discarded reactions you made elsewhere, which had no optimistic update in slk — so they never rendered until a cache reload. The underlying reason a filter was needed at all: UpdateReaction incremented Count even when the userID was already present.

A related secondary bug: UpdateReaction set HasReacted = true for any reactor, so another user's reaction (which already showed live) was mis-styled as your own.

Fix

  • Drop the blanket self-filter; apply every reaction_added / reaction_removed event.
  • Make UpdateReaction idempotent per (emoji, userID)Count changes only on real membership change — so an optimistic update and its WS echo collapse to one count. This is what makes dropping the self-filter safe.
  • Set HasReacted only for the current user via a new SetCurrentUser on the message and thread panes, fixing the mis-styling of other users' reactions.

Testing

  • go test ./... passes; added TestUpdateReactionIdempotentAndHasReacted covering double-apply (optimistic + echo → count 1), other-user merge, and HasReacted correctness.
  • Verified end-to-end in a kitty terminal: reactions made from web/mobile now appear live on the active channel without a channel switch.

@Frodotus Frodotus force-pushed the fix/live-reaction-updates branch from 533cd88 to 58f2075 Compare June 10, 2026 17:36
@gammons

gammons commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Nice fix — the idempotency-per-(emoji, userID) approach is the right way to drop the self-filter. One blocking issue though:

The HasReacted half won't take effect in production. The new messagepane.SetCurrentUser / threadPanel.SetCurrentUser wiring lives inside App.SetCurrentUserID(), but production never calls that setter — a.currentUserID is assigned directly in the workspace reducer (internal/ui/reducer_workspace.go:196 for WorkspaceReadyMsg and :270 for WorkspaceSwitchedMsg). So in a real session both panes' currentUserID stay "".

Consequences:

  • userID == m.currentUserID is realID == "" → false, so a reaction you add live never gets HasReacted = true and renders as not-yours.
  • toggleReactionOnSelectedMessage (app.go:786) keys add-vs-remove off r.HasReacted, so you can't toggle your own reaction off until a channel-switch reload re-derives it from r.Users.

Initial-load styling is fine (computed from r.Users in main.go), which is why it hides in quick manual testing — only live self-reactions regress. The tests pass because they call a.SetCurrentUserID("ME"), which exercises wiring production doesn't use.

Suggested fix: route the reducer through the setter (a.SetCurrentUserID(m.UserID) at both spots instead of the raw field assignment) and add a test that drives the real WorkspaceReadyMsg path before asserting a live own-device add sets HasReacted.

Minor follow-up: the length-based dedup assumes Count == len(UserIDs). Slack truncates reactions[].users for popular reactions, so a reaction_removed for a user truncated out of UserIDs will hit the len(newIDs) == len(r.UserIDs) guard and skip the Count--, leaving a stuck count. Edge case, but worth a comment or guard since the old unconditional Count-- covered it.

…tUserID (wires panes) + decrement truncated-list reaction removals
@Frodotus

Copy link
Copy Markdown
Contributor Author

Good catch on both — fixed in 9044917.

Blocking (HasReacted in production): routed both reducer spots through the setter — a.SetCurrentUserID(m.UserID) at reducer_workspace.go in reduceWorkspaceReady and reduceWorkspaceSwitched — so messagepane/threadPanel/threadsView actually learn the current user in a real session. Added TestLiveOwnReactionStyledViaWorkspaceReady, which drives the real WorkspaceReadyMsg path (not SetCurrentUserID directly) and asserts a live own-device add gets HasReacted=true. It fails against the old raw-assignment code.

Minor (truncated reactor lists): made the dedup guard truncation-aware instead of unconditional. The skip now only applies when the user is absent from the list and Count <= len(UserIDs) (a genuine duplicate echo). When Count > len(UserIDs) Slack has truncated reactions[].users, so a removal of an un-listed user is real and still decrements — no more stuck count. Applied in both messages and thread models; covered by TestUpdateReactionTruncatedRemoval (truncated removal decrements; non-truncated duplicate echo does not under-count).

@gammons

gammons commented Jun 11, 2026

Copy link
Copy Markdown
Owner

🍨

@gammons gammons merged commit d966c3d into gammons:main Jun 11, 2026
3 checks passed
Sign up for free to 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.

Own reactions made from another device don't appear live (only after channel switch)

2 participants