Skip to content

Fix view count race conditions in TweetBlock#165

Merged
filinvadim merged 2 commits intodevelopfrom
claude/fix-views-tracking-h8Kns
May 4, 2026
Merged

Fix view count race conditions in TweetBlock#165
filinvadim merged 2 commits intodevelopfrom
claude/fix-views-tracking-h8Kns

Conversation

@filinvadim
Copy link
Copy Markdown
Member

Summary

Fixed race conditions in the TweetBlock component where view counts could be incorrectly overwritten by stale data or regress to lower values. The component now maintains monotonically non-decreasing view counts across concurrent API calls.

Key Changes

  • loadTweetStats: Added logic to only update view count if the incoming value is higher than the current value, preventing stale reads from clobbering more recent increments
  • recordView: Changed the condition from count > 0 to count > current to ensure the view count never regresses when the backend reports 0 or when a stale loadTweetStats response arrives after recordView completes
  • Test coverage: Added comprehensive test suite (270 lines) covering:
    • Basic view tracking and count updates
    • Tall tweet visibility detection via fillsViewport logic
    • Threshold validation (no view recorded when barely visible)
    • Deduplication (no double-counting on multiple observer fires)
    • Transient failure retry logic
    • Race condition handling between loadTweetStats and recordView

Implementation Details

The fix ensures that view counts are monotonically non-decreasing by comparing incoming values against the current local state before updating. This handles two critical race scenarios:

  1. When recordView completes after loadTweetStats has already fetched stale data
  2. When the backend returns 0 due to transient failures (unreachable author node)

Version bumped to 0.6.306.

https://claude.ai/code/session_01MABzdRsy5KrkRrWYSicHyq

Symptom: scrolling past a tweet flashed the new view count and then
reverted to the previous lower number, so the UI looked like view
tracking was off.

Root cause: TweetBlock fires getTweetStats (loadTweetStats) and
viewTweet (recordView) concurrently. When viewTweet's RecordView
commits first but the parallel getTweetStats request had already read
the pre-increment count, the stats response lands second with a
smaller value and the unconditional viewsCount.set overwrites the
just-incremented total.

Fix: only update viewsCount when the incoming value is strictly
greater than the current local value, in both loadTweetStats and
recordView. View counts are monotonically non-decreasing under
RecordView + CRDT, so taking the running max is safe and eliminates
the race.

Validated by a new TweetBlock spec that drives the IntersectionObserver,
including a regression test for the loadTweetStats/recordView race.
@filinvadim filinvadim requested a review from Copilot May 4, 2026 18:49
@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a race condition in the TweetBlock Vue component where concurrent loadTweetStats and recordView calls could overwrite the UI’s view count with stale/lower values, ensuring view counts are monotonically non-decreasing on the client.

Changes:

  • Update loadTweetStats to only apply views_count when it increases the current local value.
  • Update recordView to only apply the returned count when it is greater than the current local value.
  • Add a comprehensive Vitest suite covering visibility thresholds, deduplication, transient failure retry, and the loadTweetStats vs recordView race.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
version Bumps application version to 0.6.306.
frontend/src/components/TweetBlock.vue Enforces monotonic view-count updates across async races.
frontend/tests/components/TweetBlock.spec.js Adds coverage for view tracking behavior and race scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,270 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/vue';
Comment thread frontend/src/components/TweetBlock.vue Outdated
Comment on lines +342 to +343
// loadTweetStats response lands after us, or when the backend
// reports 0 because the author's node is unreachable.
- TweetBlock.spec.js: drop unused `screen` import.
- TweetBlock.vue: simplify the recordView monotonic-update comment.
  The "0 because the author's node is unreachable" wording conflicted
  with viewTweet's documented contract (null = failure, numeric =
  backend-reported count). The race we actually guard against is the
  loadTweetStats response landing after us with a stale value, so
  keep that as the sole justification.
@filinvadim filinvadim merged commit 51c4b1d into develop May 4, 2026
6 checks passed
@filinvadim filinvadim deleted the claude/fix-views-tracking-h8Kns branch May 4, 2026 19:46
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.

4 participants