Skip to content

feat(shared): display bookmark count on post cards#5659

Open
nimrodkra wants to merge 10 commits intomainfrom
eng-974-show-number-of-bookmarks-on-post-cards
Open

feat(shared): display bookmark count on post cards#5659
nimrodkra wants to merge 10 commits intomainfrom
eng-974-show-number-of-bookmarks-on-post-cards

Conversation

@nimrodkra
Copy link
Member

@nimrodkra nimrodkra commented Mar 5, 2026

Summary

  • Add show_bookmark_count GrowthBook feature flag (default false)
  • Add numBookmarks to Post interface and both FEED_POST_INFO_FRAGMENT / SHARED_POST_INFO_FRAGMENT GraphQL fragments
  • Display InteractionCounter inside BookmarkButton in ActionButtons, gated by the feature flag
  • Add optional bookmarks prop to PostEngagementCounts for the Similar Posts widget, also gated by the flag

Key decisions

  • Feature flag evaluated independently in ActionButtons and SimilarPosts since they render in separate contexts
  • PostEngagementCounts receives bookmarks as an optional prop threaded from the parent to avoid hooks in a static function component
  • Separator rendering uses ternary instead of && to prevent React's falsy-number-rendering pitfall

Closes ENG-974


Created by Huginn 🐦‍⬛

Preview domain

https://eng-974-show-number-of-bookmarks.preview.app.daily.dev

rebelchris and others added 2 commits March 5, 2026 09:00
Add feature flag `show_bookmark_count` to gate bookmark count display.
Update Post interface and GraphQL fragments with numBookmarks field.
Show InteractionCounter in ActionButtons BookmarkButton and
PostEngagementCounts, both gated by the feature flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove redundant invisible class from ActionButtons bookmark counter
(already gated by > 0 check). Fix PostEngagementCounts to avoid
rendering falsy number 0 in separator conditional.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Mar 6, 2026 3:16am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Mar 6, 2026 3:16am

Request Review

Switch bookmark-count consumers from post.numBookmarks to post.analytics.bookmarks and update shared GraphQL fragments/types accordingly.

Made-with: Cursor
@nimrodkra
Copy link
Member Author

Updated frontend to consume the analytics-based bookmark count from API.

What changed:

  • switched bookmark count reads from post.numBookmarks to post.analytics.bookmarks
  • updated shared GraphQL fragments/types accordingly
  • kept feature flag gating behavior unchanged

Latest commit: 7774b0e.

Apply the bun tertiary button class to bookmark actions in card ActionButtons so counter color and hover state match upvote/comment interaction patterns.

Made-with: Cursor
Update bookmark mutation cache handlers to optimistically adjust analytics.bookmarks on toggle and restore previous values on rollback for feed and ad posts.

Made-with: Cursor
Drop stale Bookmark type import after optimistic cache refactor to satisfy shared lint checks.

Made-with: Cursor
@nimrodkra
Copy link
Member Author

Found and fixed a likely CI lint failure cause: removed an unused Bookmark type import introduced during the optimistic bookmark count cache refactor (useBookmarkPost.ts).

Fix commit: 749b20b29.

@nimrodkra
Copy link
Member Author

@nimrodkra
Copy link
Member Author

@claude please review this pr

@claude
Copy link
Contributor

claude bot commented Mar 5, 2026

Claude finished @nimrodkra's task in 2m 2s —— View job


PR Review: feat(shared): display bookmark count on post cards

  • Read all changed files
  • Analyze code for bugs, correctness, and best practices
  • Post review feedback

Overview

The implementation is generally clean and follows existing codebase patterns well. Feature flag gating is correctly applied, GraphQL fragments are updated appropriately, and the UI integration is consistent. A few issues worth calling out:


Issues

🔴 Rollback logic in defaultOnMutate is fragile

packages/shared/src/hooks/useBookmarkPost.ts lines 101–129:

const defaultOnMutate = ({ id }) => {
  updatePostCache(client, id, (post) => ({
    bookmarked: !post.bookmarked,
    analytics: post.analytics ? { ...post.analytics, bookmarks: getOptimisticBookmarkCount(...) } : post.analytics,
  }));

  return () => {
    // Rollback: re-reads current cache state and applies inverse
    updatePostCache(client, id, (post) => ({
      bookmarked: !post.bookmarked,
      analytics: post.analytics ? { ...post.analytics, bookmarks: getOptimisticBookmarkCount(...) } : post.analytics,
    }));
  };
};

This rollback works correctly by symmetry (applying the inverse transform produces the original values), but it doesn't capture the previous state. If any background refetch or concurrent update modifies post.analytics.bookmarks between the optimistic mutation and the rollback, the rollback will produce an incorrect result rather than restoring the original snapshot.

The mutateBookmarkFeedPost function does this correctly by capturing previousAnalytics explicitly before mutating. defaultOnMutate should do the same:

const defaultOnMutate = ({ id }) => {
  let previousAnalytics: Post['analytics'];
  let previousBookmarked: boolean | undefined;

  updatePostCache(client, id, (post) => {
    previousAnalytics = post.analytics;
    previousBookmarked = post.bookmarked;
    return {
      bookmarked: !post.bookmarked,
      analytics: post.analytics ? { ...post.analytics, bookmarks: getOptimisticBookmarkCount(...) } : post.analytics,
    };
  });

  return () => {
    updatePostCache(client, id, () => ({
      bookmarked: previousBookmarked,
      analytics: previousAnalytics,
    }));
  };
};

Fix this →


🟡 defaultOnMutate has an untyped parameter

packages/shared/src/hooks/useBookmarkPost.ts line 101:

const defaultOnMutate = ({ id }) => {

The { id } parameter is untyped. It should be typed as UseBookmarkPostMutationProps (which already exists):

const defaultOnMutate = ({ id }: UseBookmarkPostMutationProps) => {

Fix this →


🟡 analytics.impressions added to FEED_POST_INFO_FRAGMENT — was this intentional?

packages/shared/src/graphql/fragments.ts:

Both fragments now include analytics { impressions bookmarks }. If impressions wasn't previously fetched by FEED_POST_INFO_FRAGMENT (the main feed fragment), adding it increases payload size for every feed load. Worth confirming this is intentional and the impressions field is actually used elsewhere with FeedPostInfo, or trimming it to just bookmarks here.


Positives

  • Separator logic in PostEngagementCounts correctly uses ternary (? ... : null) instead of && to prevent falsy-number rendering — good catch, mentioned in PR description.
  • Feature flag pattern is consistent with other flags in the file; default false is appropriate for a new unreleased feature.
  • Optimistic update in mutateBookmarkFeedPost correctly captures previousAnalytics and restores it on rollback.
  • getOptimisticBookmarkCount helper correctly clamps at 0 with Math.max(0, ...) to prevent negative counts.
  • PostEngagementCounts bookmark prop is optional and threaded correctly from SimilarPosts — keeps the component a pure function without hook dependencies.
  • analytics type on Post is properly scoped as Partial<Pick<PostAnalytics, 'impressions' | 'bookmarks'>> rather than the full PostAnalytics type — matches what's actually fetched.

Summary

Two things to address before merging: the defaultOnMutate rollback fragility (minor in practice since concurrent mutations + rollbacks are rare, but correctness matters), and the untyped parameter. The impressions field question is low-priority but worth a quick sanity check.
| Branch

Capture and restore the exact pre-mutation bookmark snapshot in defaultOnMutate, add an explicit onMutate type, and avoid requesting feed impressions where they are not needed.

Made-with: Cursor
@nimrodkra
Copy link
Member Author

Addressed Claude review in commit f6f231a.

Changes made:

  • useBookmarkPost: defaultOnMutate now captures and restores an exact pre-mutation snapshot (bookmarked/bookmark/analytics) instead of relying on inverse symmetry during rollback.
  • useBookmarkPost: added explicit typing to defaultOnMutate via NonNullable<UseBookmarkPostProps['onMutate']>.
  • FEED_POST_INFO_FRAGMENT: removed analytics.impressions from this feed fragment to avoid unnecessary payload growth; it now requests only analytics.bookmarks for this feature.

This should resolve the two actionable findings and the fragment payload concern.

@nimrodkra nimrodkra requested a review from idoshamun March 5, 2026 19:40
Format the snapshot query call in useBookmarkPost to satisfy prettier in shared lint CI.

Made-with: Cursor
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.

2 participants