Skip to content

Fix payout double-count flash after voting#3330

Merged
feruzm merged 4 commits into
developmentfrom
fix/vote-payout-double-count
Jul 7, 2026
Merged

Fix payout double-count flash after voting#3330
feruzm merged 4 commits into
developmentfrom
fix/vote-payout-double-count

Conversation

@feruzm

@feruzm feruzm commented Jul 6, 2026

Copy link
Copy Markdown
Member

What

Voting on the post page briefly showed the payout increased by twice the vote value (e.g. payout 1 + vote 2 showed 3, then flashed 5, then settled at 3).

Why

Two optimistic layers stack on the same cached entry:

  1. At press, updateVoteInQueryCaches bumps the payout and stores the voter's record with an amount field, which applyRecentVoteOverrideToEntry uses to stay idempotent.
  2. On broadcast success, the SDK's useVote onSuccess rewrites the same ["posts", "entry", ...] cache entry: payout is re-derived from the untouched numeric payout field (correct), but the voter's record is replaced with a bare { rshares, voter } — dropping amount.
  3. The recent-vote override (15s TTL) then sees an unknown old contribution, assumes 0, and re-adds the full vote value on every render → base + 2x vote, until the SDK's deferred (4s) invalidation refetches and reconciles.

How

  • applyRecentVoteOverrideToEntry now skips re-applying when the entry already carries the voter's record without a numeric amount marker: that data already includes the vote (server-indexed or SDK-written) and re-adding is always an overcount. Unvotes (DELETED) are unaffected.
  • Downvotes now pass a signed estimate, clamped to the pre-vote payout: the SDK adds estimated unsigned, so the positive value briefly showed the payout increased by the downvote's value once the override stopped compensating for it. The clamped negative delta makes the SDK's write land on the same reduced value the at-press update shows.

Complementary SDK-side fix (skips the stacked write entirely): ecency/vision-next#1096. This change fixes the visible flash on its own with the currently pinned SDK; the SDK bump can follow the usual release order.

Tests

7 new jest cases in voteCacheUtils.test.ts covering the skip (up/down), lagging-refetch re-apply, amount-marker idempotency, amount: 0 semantics, TTL expiry, and the unvote path. Verified the guard test fails against the pre-fix code with the exact reported values (5.000 vs 3.000).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed downvote payout flicker by ensuring the downvote estimate is clamped to the pre-vote payout, preventing a brief incorrect payout increase.
    • Prevented recent vote overrides from double-counting when a cached vote entry already exists without an amount marker.
    • Improved reconciliation of vote totals after cache refreshes, with added unit test coverage for recent vote/unvote edge cases.

The SDK's post-broadcast cache write replaces the voter's active_votes
record with a bare { rshares, voter }, dropping the optimistic amount
marker. The recent-vote override then re-added the full vote value on
top of a payout that already included it, briefly showing
base + 2x vote value until the deferred refetch reconciled.

Skip re-applying the recent vote when the entry already carries the
voter's record without an amount marker: that data already includes
the vote (server-indexed or SDK-written) and its old contribution is
unknown, so re-adding is always an overcount.

Also pass estimated: 0 on downvotes: the SDK adds estimated unsigned,
so a positive value briefly showed the payout increased by the
downvote's value now that the override no longer re-touches it.
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes the payout flash after voting. The main changes are:

  • Bumps @ecency/sdk to 2.3.39.
  • Sends a signed, clamped downvote estimate to the vote mutation.
  • Skips recent-vote payout reapplication when a bare active-vote record is already present.
  • Adds tests for the recent-vote guard, amount markers, TTL expiry, downvotes, and unvotes.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
src/components/upvotePopover/container/upvotePopover.tsx Downvotes now pass a signed estimate clamped to the current payout.
src/providers/queries/postQueries/voteCacheUtils.ts Recent vote overrides now skip reapplying when the active vote already exists without an app amount marker.
src/providers/queries/postQueries/voteCacheUtils.test.ts Adds coverage for the payout guard, lagging refetches, amount markers, expiry, downvotes, and unvotes.
package.json Updates the SDK dependency used by the vote flow.
yarn.lock Locks the updated SDK package version.

Reviews (4): Last reviewed commit: "Merge branch 'development' into fix/vote..." | Re-trigger Greptile

Comment thread src/components/upvotePopover/container/upvotePopover.tsx Outdated
Comment thread src/providers/queries/postQueries/voteCacheUtils.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58e313d1c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +88 to +91
if (recentVote.status !== 'DELETED' && Array.isArray(entry.active_votes)) {
const existingVote = entry.active_votes.find((v: any) => v?.voter === recentVote.voter);
if (existingVote && typeof existingVote.amount !== 'number') {
return entry;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the downvote payout delta after SDK success

When the recent vote is a downvote, this guard also skips the override as soon as the SDK success write replaces the vote with a bare { voter, rshares } record. In that same path the mutation now passes estimated: 0, and the SDK computes the entry payout from the unchanged base payout plus estimated, so the post jumps back to the pre-downvote payout until the deferred refetch arrives instead of continuing to show the optimistic reduction. Please avoid applying this bare-record skip to downvotes, or otherwise reapply the negative payout delta for that SDK-written shape.

Useful? React with 👍 / 👎.

Sending 0 kept the SDK's post-broadcast write at the base payout,
briefly reverting the at-press reduction until the deferred refetch.
The signed delta, clamped to the pre-vote payout, makes that write
land on the same reduced value the optimistic layer already shows.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07209f9e-da6e-4a10-87ea-3916f6446e7c

📥 Commits

Reviewing files that changed from the base of the PR and between 58e313d and 49e83b4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • package.json
  • src/components/upvotePopover/container/upvotePopover.tsx

📝 Walkthrough

Walkthrough

Updates downvote mutation payout estimation, adds a guard in recent vote cache override handling to avoid double-counting, and expands unit tests for the updated cache behavior.

Changes

Downvote payout double-count fix

Layer / File(s) Summary
Downvote mutation payload
src/components/upvotePopover/container/upvotePopover.tsx, package.json
Changes the downvote estimated value to a negative, payout-clamped amount and bumps the @ecency/sdk dependency.
Cache override guard and tests
src/providers/queries/postQueries/voteCacheUtils.ts, src/providers/queries/postQueries/voteCacheUtils.test.ts
Adds an early-return guard in applyRecentVoteOverrideToEntry when the voter is already present without a numeric amount, updates the query-client mock, and adds tests for double-count prevention, idempotency, TTL expiry, and recent unvote handling.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UpvotePopover
  participant voteMutation
  participant voteCacheUtils
  participant QueryCache

  UpvotePopover->>voteMutation: mutateAsync(negative estimated)
  voteMutation->>QueryCache: optimistic payout update
  voteMutation-->>voteCacheUtils: recentVote cache write
  voteCacheUtils->>voteCacheUtils: inspect active_votes for voter without amount
  alt voter already present without amount
    voteCacheUtils-->>QueryCache: return entry unchanged
  else
    voteCacheUtils->>voteCacheUtils: applyVoteToPost(entry, recentVote)
    voteCacheUtils-->>QueryCache: updated entry
  end
Loading

Poem

A downvote hops with care and grace,
The cache won’t count a vote twice in place,
A tiny guard, a test suite bright,
Keeps payouts snug and landing right,
This bunny nods and thumps hooray 🐇

🚥 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 title clearly summarizes the main fix: preventing a brief payout double-count flash after voting.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/vote-payout-double-count
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/vote-payout-double-count

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.

@feruzm feruzm merged commit 92438ab into development Jul 7, 2026
7 of 10 checks passed
@feruzm feruzm deleted the fix/vote-payout-double-count branch July 7, 2026 05:33
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.

1 participant