Skip to content

Conversation

@deepak0x
Copy link

@deepak0x deepak0x commented Jan 21, 2026

This PR fixes the bug where the pin icon does not appear immediately after a user pins a message. The fix implements optimistic UI updates to provide instant visual feedback too.

Issue(s)

Closes #6927

How to test or reproduce

  1. Test pin icon display:

    • Open any channel/room
    • Long-press on any message to open the action menu
    • Select "Pin" from the action menu
    • Verify the pin icon appears immediately on the message (optimistic update)
    • Navigate away and return to the channel - pin icon should still be visible
  2. Test optimistic updates:

    • Pin a message
    • Verify the pin icon appears immediately (optimistic update)
    • Verify the icon persists after server confirmation
    • Test with poor network conditions to ensure UI remains responsive

Screenshots/Video

Screen.Recording.2026-01-21.at.5.15.35.PM.mov

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modulesto

Summary by CodeRabbit

  • New Features

    • Pins toggle instantly via optimistic updates, giving immediate visual feedback while the app confirms the change.
  • Bug Fixes

    • Ensures pinned state is preserved and reconciled when real updates arrive; reliably rolls back on failure to keep UI consistent.

✏️ Tip: You can customize this high-level summary in your review settings.

- Fix duplicate pin icon issue by removing redundant RightIcons rendering on header messages
- Implement optimistic updates for pin action to show immediate UI feedback
- Fix message color update when status changes from TEMP to SENT by adding isTemp to React.memo comparison
- Add optimisticUpdates helper to prevent server race conditions from overwriting user actions
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Walkthrough

Implements per-message optimistic updates for pin actions: adds an in-memory optimistic-update helper, applies optimistic DB updates and registration in MessageActions with rollback on failure, preserves optimistic pinned state in room subscription reconciliation, and introduces a small clarity change in the Message component conditional.

Changes

Cohort / File(s) Summary
Optimistic Updates Utility
app/lib/methods/helpers/optimisticUpdates.ts
New module providing IOptimisticUpdate, registerOptimisticUpdate(), getOptimisticUpdate(), clearOptimisticUpdate(), and isRecentOptimisticUpdate() using an internal Map and time-based cleanup.
MessageActions (pin flow)
app/containers/MessageActions/index.tsx
Adds imports (getMessageById, protectedFunction, registerOptimisticUpdate) and changes handlePin() to perform an optimistic local DB update (set pinned = willPin), register the optimistic update, call togglePinMessage, and attempt DB revert + clear optimistic entry on API failure; separates optimistic-phase error handling for revertability.
Message component
app/containers/message/Message.tsx
Replaces inline conditional with showRightIcons (const showRightIcons = !props.isHeader) and uses it for rendering RightIcons; no behavior change.
Room subscription message reconciliation
app/lib/methods/subscriptions/room.ts
Imports getOptimisticUpdate, isRecentOptimisticUpdate, clearOptimisticUpdate; when updating existing messages, prefers recent optimistic pinned value over incoming server value, reconstructs message accordingly, and clears recent optimistic entries after reconciliation.

Sequence Diagram

sequenceDiagram
    actor User
    participant MessageActions
    participant LocalDB as Local DB
    participant OptimisticStore as Optimistic Store
    participant API as Server API
    participant RoomSub as Room Subscription
    participant Store as Message Store

    User->>MessageActions: Trigger pin/unpin
    MessageActions->>LocalDB: Optimistically set message.pinned = willPin
    MessageActions->>OptimisticStore: registerOptimisticUpdate(messageId, { pinned: willPin })
    MessageActions->>API: Call togglePinMessage(currentPinned)

    alt API Success
        API-->>MessageActions: Success
        MessageActions->>OptimisticStore: clearOptimisticUpdate(messageId)
    else API Failure
        API-->>MessageActions: Error
        MessageActions->>LocalDB: Revert message.pinned = currentPinned
        MessageActions->>OptimisticStore: clearOptimisticUpdate(messageId)
    end

    RoomSub->>RoomSub: Receive server message update
    RoomSub->>OptimisticStore: isRecentOptimisticUpdate(messageId)?
    alt Recent optimistic update exists
        OptimisticStore-->>RoomSub: getOptimisticUpdate(messageId) -> { pinned }
        RoomSub->>Store: Apply server data but preserve optimistic pinned
        RoomSub->>OptimisticStore: clearOptimisticUpdate(messageId)
    else No recent update
        RoomSub->>Store: Apply server data as-is
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A tiny hop, a hopeful tweak,
I pin before the server speaks,
With timestamped notes and fast replay,
I guard the change if calls betray,
Cheer—icons bloom without delay.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary fix: implementing optimistic UI updates so the pin icon appears immediately after pinning.
Linked Issues check ✅ Passed The code changes implement optimistic updates for pin actions and fix the pin icon rendering issue, directly addressing all requirements from issue #6927.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the pin icon visibility issue and removing redundant rendering; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@app/containers/message/Message.tsx`:
- Around line 216-226: The custom memo comparator for the Message component only
compares eight props and so can prevent necessary re-renders; update the
comparator (the function passed as the second argument to React.memo around
Message) to either remove the custom comparator and rely on React's default
shallow comparison or expand the comparator to include all props used during
render (e.g., type, isInfo, isIgnored, isThreadReply, isThreadSequential,
unread, isReadReceiptEnabled, ts, author, mentions, channels, attachments,
isBeingEdited, highlighted, and any other props referenced in Message.tsx) so
changes to those fields trigger re-renders.

In `@app/containers/MessageActions/index.tsx`:
- Around line 328-343: The optimistic update is registered before the DB write,
so if database write in database.active / getMessageById / messageRecord.update
(inside protectedFunction) fails the optimistic state remains inconsistent;
change the flow to perform the DB write first and only call
registerOptimisticUpdate(message.id, { pinned: willPin }) after the await
db.write(...) completes successfully (or alternatively call
registerOptimisticUpdate then rollback/unregister in the catch), ensuring
registerOptimisticUpdate is invoked only on successful messageRecord.update.
- Around line 324-325: The code casts message.pinned to boolean using "as
boolean" which is misleading when pinned can be undefined/null; change the logic
in the block that sets currentPinned and willPin to explicitly coerce the value
(e.g., use Boolean(message.pinned) or !!message.pinned) so currentPinned is a
real boolean and willPin = !currentPinned remains correct; update the assignment
sites for currentPinned and willPin accordingly (referencing the message.pinned,
currentPinned and willPin variables).

In `@app/lib/methods/helpers/optimisticUpdates.ts`:
- Around line 1-26: optimisticUpdates can grow unbounded because stale entries
are only removed via clearOptimisticUpdate; add an automatic eviction routine
that deletes entries older than a configurable TTL (default e.g. 2000ms) to
prevent memory leaks. Implement a cleanup function (e.g.,
cleanupStaleOptimisticUpdates) that iterates the optimisticUpdates Map and
deletes entries where Date.now() - entry.timestamp > TTL, call this on a
periodic timer (setInterval) started when the module loads, and provide a way to
stop the timer for cleanup (export stopOptimisticUpdateCleanup) if needed; keep
existing APIs registerOptimisticUpdate, getOptimisticUpdate,
clearOptimisticUpdate, and isRecentOptimisticUpdate but ensure
registerOptimisticUpdate and isRecentOptimisticUpdate respect the same TTL.

In `@app/lib/methods/subscriptions/room.ts`:
- Around line 288-305: Remove the redundant early assignment of m.pinned: the
block that checks message.pinned and sets m.pinned before the Object.assign is
immediately overwritten by the later identical block after Object.assign; keep
the latter block (the assignment after Object.assign) and delete the first one
to avoid duplicate logic involving message.pinned, isRecentOptimistic and
optimisticUpdate when updating m (the variables m, message, optimisticUpdate,
isRecentOptimistic and the Object.assign(m, restMessage) call identify the
location).
🧹 Nitpick comments (2)
app/containers/MessageActions/index.tsx (1)

345-365: Missing clearOptimisticUpdate call on successful server response.

When togglePinMessage succeeds (no exception), the optimistic update entry remains in the Map until the room subscription handler clears it. If the subscription update is delayed or never arrives, the stale entry persists. Consider clearing explicitly on success.

♻️ Suggested improvement
 try {
 	await togglePinMessage(message.id, currentPinned);
+	// Clear optimistic update on success - subscription will provide authoritative state
+	clearOptimisticUpdate(message.id);
 } catch (e) {

This requires importing clearOptimisticUpdate:

-import { registerOptimisticUpdate } from '../../lib/methods/helpers/optimisticUpdates';
+import { registerOptimisticUpdate, clearOptimisticUpdate } from '../../lib/methods/helpers/optimisticUpdates';
app/lib/methods/subscriptions/room.ts (1)

307-309: Optimistic update not cleared when server response arrives after the 2000ms window.

If the server update arrives after maxAge (2000ms), isRecentOptimistic is false, so the entry won't be cleared here. Combined with no explicit clear on success in handlePin, this entry will persist indefinitely. Consider clearing unconditionally when a server update for the same message arrives, or implementing automatic cleanup in the optimisticUpdates module.

♻️ Suggested fix: Clear unconditionally when processing server update
 if (message.pinned !== undefined) {
 	if (isRecentOptimistic && optimisticUpdate?.pinned !== undefined) {
 		m.pinned = optimisticUpdate.pinned;
 	} else {
 		m.pinned = message.pinned;
 	}
 }

-if (isRecentOptimistic) {
-	clearOptimisticUpdate(message._id);
-}
+// Always clear optimistic update when server provides authoritative state
+if (optimisticUpdate) {
+	clearOptimisticUpdate(message._id);
+}

- Fix duplicate pin icon issue by removing redundant RightIcons rendering on header messages
- Implement optimistic updates for pin action to show immediate UI feedback
- Fix message color update when status changes from TEMP to SENT by adding isTemp to React.memo comparison
- Add optimisticUpdates helper to prevent server race conditions from overwriting user actions
- Fix type coercion by using Boolean() instead of 'as boolean' assertion
- Register optimistic update only after successful DB write to prevent inconsistency
- Add automatic cleanup for stale optimistic updates to prevent memory leaks
- Remove duplicate pinned assignment logic in updateMessage
- Remove incomplete React.memo comparator to prevent stale UI
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.

Bug: Pin Icon Not Appearing

1 participant