Skip to content

feat(user): add follow/unfollow on profile pages#81

Merged
jcserv merged 3 commits into
mainfrom
feat-follow-users
Jun 30, 2026
Merged

feat(user): add follow/unfollow on profile pages#81
jcserv merged 3 commits into
mainfrom
feat-follow-users

Conversation

@jcserv

@jcserv jcserv commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Adds the Follow model and the follow/unfollow actions as a foundation for the updates feed (issue #28).

Key changes:

  • Add Follow join table (followerId, followingId, createdAt) with cascade deletes and a desc index on followingId for feed queries
  • Add userFollowersTag / userFollowingTag cache-tag helpers
  • Add followUser / unfollowUser server actions with self-follow guard, target-existence check, and targeted cache invalidation
  • Add getFollowStats cached query (follower count, following count, viewer isFollowing) with per-user cache tags
  • Add FollowButton client component with optimistic count toggle
  • Surface follower/following counts and Follow button on the profile page header (button hidden for owner or unauthenticated viewers)
  • Ignore docs/backlog/** in ESLint (backlog scratch files were causing lint errors on untracked JS)
  • 9 new unit tests covering all action branches

Description

Fixes: #26

Checklist

  • Tests pass (pnpm test) and lint is clean
  • New behavior is covered by tests
  • No revalidate / dynamic / unstable_cache — used 'use cache' + cacheLife/cacheTag
  • <Link> imported from app/_components/link.tsx, not next/link
  • Suspense fallbacks reserve layout space (explicit heights)

Screenshots / notes

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added follow/unfollow functionality with an on-profile follow button.
    • Profile headers now display follower/following counts and follow state for signed-in visitors.
    • Follow stats are loaded alongside public decks for faster, coordinated rendering.
  • Bug Fixes

    • Prevents self-follow/unfollow and ensures follow/unfollow actions are safe to repeat.
    • Keeps displayed follow counts in sync via targeted cache invalidation.
  • Tests

    • Added tests covering follow/unfollow behavior, idempotency, error cases, and cache tag invalidation.

Adds the Follow model and the follow/unfollow actions as a foundation
for the updates feed (issue #28).

Key changes:
- Add Follow join table (followerId, followingId, createdAt) with
  cascade deletes and a desc index on followingId for feed queries
- Add userFollowersTag / userFollowingTag cache-tag helpers
- Add followUser / unfollowUser server actions with self-follow guard,
  target-existence check, and targeted cache invalidation
- Add getFollowStats cached query (follower count, following count,
  viewer isFollowing) with per-user cache tags
- Add FollowButton client component with optimistic count toggle
- Surface follower/following counts and Follow button on the profile
  page header (button hidden for owner or unauthenticated viewers)
- Ignore docs/backlog/** in ESLint (backlog scratch files were causing
  lint errors on untracked JS)
- 9 new unit tests covering all action branches
@jcserv jcserv self-assigned this Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@jcserv, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0daab2a-e5ed-4ecb-bd56-1dabf191819e

📥 Commits

Reviewing files that changed from the base of the PR and between e1e8148 and 8071894.

📒 Files selected for processing (7)
  • app/_actions/deck/bulk-printings.ts
  • lib/card/__tests__/printing-heuristics.test.ts
  • lib/deck/__tests__/external.test.ts
  • lib/edhrec/__tests__/recommendations.test.ts
  • lib/search/card-search.ts
  • lib/user/__tests__/queries.test.ts
  • workflows/scryfall/__tests__/steps.test.ts
📝 Walkthrough

Walkthrough

This PR adds a user follow/unfollow feature: a new Prisma Follow model and migration, cache-tag helpers, a getFollowStats query, followUser/unfollowUser server actions with tests, a FollowButton client component using optimistic updates, and profile page integration showing follower/following counts. An unrelated ESLint ignore entry is also added.

Changes

User Follow Feature

Layer / File(s) Summary
Follow data model and migration
prisma/schema.prisma, prisma/migrations/20260630181407_add_user_follows/migration.sql, prisma/migrations/20260630190000_follow_no_self_follow_check/migration.sql
Adds Follow model with relations to User and migrations creating the follow table, composite primary key, index, foreign keys, and self-follow check.
Cache tags and follow stats query
lib/deck/cache-tags.ts, lib/user/queries.ts
Adds userFollowersTag/userFollowingTag helpers and getFollowStats returning follower/following counts and isFollowing.
Follow/unfollow server actions
app/_actions/user-follows.ts, app/_actions/__tests__/user-follows.test.ts
Implements followUser/unfollowUser with session checks, upsert/deleteMany, cache tag invalidation, and a Vitest test suite.
FollowButton component and profile page wiring
app/_components/user/follow-button.tsx, app/(ui)/u/[username]/page.tsx
Adds the optimistic FollowButton component and integrates follow stats and follow controls into the profile header via concurrent data fetching.

Lint Configuration

Layer / File(s) Summary
ESLint ignore update
eslint.config.mjs
Adds docs/backlog/** to ignored paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

A hop, a skip, a follow grows 🐇
New counts appear where profile glows,
Tags ping-pong fast, the cache wakes bright,
One button flips from left to right,
And bunny tails wiggle in follow flow ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The ESLint config update to ignore docs/backlog/** is unrelated to follow/unfollow support. Remove the docs/backlog/** ignore unless it is needed for a separate, approved change.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 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 change: adding follow/unfollow support on profile pages.
Linked Issues check ✅ Passed The PR implements the requested follow/unfollow button on user profile pages and related supporting logic for issue #26.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-follow-users

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/_components/user/follow-button.tsx`:
- Around line 24-25: Block follow/unfollow re-entry while a mutation is in
flight in follow-button.tsx, because rapid clicks can trigger overlapping
followUser and unfollowUser actions. Add an in-flight guard around the
FollowButton interaction and use it in the click handler that calls
startTransition so subsequent clicks are ignored until the current transition
settles. Update the optimistic state handling in useTransition/useOptimistic
flow to disable or short-circuit the button during the pending mutation,
ensuring the latest user intent wins in the FollowButton component.

In `@app/`(ui)/u/[username]/page.tsx:
- Around line 136-154: The profile header is still rendering the server-side
follower count instead of the optimistic count maintained by FollowButton, so
the visible number doesn’t update immediately. Update the follower count display
in app/(ui)/u/[username]/page.tsx to consume the optimistic count state from
FollowButton (or lift that state up and pass it into the header) so the rendered
count stays in sync with the button’s immediate follow/unfollow changes. Use the
FollowButton component and the followStats.followerCount display block as the
key places to wire the optimistic value through.

In `@prisma/migrations/20260630181407_add_user_follows/migration.sql`:
- Around line 2-17: The follow table currently allows rows where follower_id
equals following_id, so add a database-level no-self-follow constraint in the
migration to match the app validation. Update the follow table definition in the
migration to enforce follower_id <> following_id (or an equivalent CHECK
constraint) so invalid self-follow rows cannot be inserted by seeds, scripts, or
future write paths. Keep the change localized to the follow table creation in
the migration and preserve the existing primary key, index, and foreign key
setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f21ec12-12e9-4a4e-83a1-8ad89423a5ae

📥 Commits

Reviewing files that changed from the base of the PR and between 483cfb8 and 4877988.

📒 Files selected for processing (9)
  • app/(ui)/u/[username]/page.tsx
  • app/_actions/__tests__/user-follows.test.ts
  • app/_actions/user-follows.ts
  • app/_components/user/follow-button.tsx
  • eslint.config.mjs
  • lib/deck/cache-tags.ts
  • lib/user/queries.ts
  • prisma/migrations/20260630181407_add_user_follows/migration.sql
  • prisma/schema.prisma

Comment thread app/_components/user/follow-button.tsx Outdated
Comment thread app/(ui)/u/[username]/page.tsx
Comment thread prisma/migrations/20260630181407_add_user_follows/migration.sql
jcserv added 2 commits June 30, 2026 16:54
Guard rapid follow/unfollow clicks, surface optimistic follower
count, and block self-follows at the DB level.

Key changes:
- FollowButton wires isPending to disable the button mid-transition
- FollowButton renders the optimistic follower count itself, so the
  number updates immediately on click; page.tsx falls back to the
  static count only when no FollowButton is rendered (owner view or
  logged-out viewer)
- Add CHECK (follower_id <> following_id) constraint via a new
  migration, since the original migration was already applied locally
getFollowStats had zero tests; external adapters, edhrec meta
fallback, and printing-heuristics edge cases were missing branch
coverage. Two truly unreachable defensive fallbacks (string ops
that can't return undefined) are marked v8 ignore per existing
convention.

- lib/user/queries.ts: add getFollowStats tests (viewer/no-viewer,
  following/not-following)
- lib/deck/external/{archidekt,moxfield}.ts: cover missing-field
  fallback branches (cards/categories/quantity/format)
- lib/edhrec/recommendations.ts: cover meta-miss synergy/inclusion
  fallback
- lib/card/printing-heuristics.ts: cover canonicalFirstPrinting
  empty-list and out-of-order reduce branches
- workflows/scryfall/steps.ts: cover missing `data` field on
  search response
- app/_actions/deck/bulk-printings.ts, lib/search/card-search.ts:
  mark unreachable ?? fallbacks with v8 ignore
@jcserv jcserv merged commit f54dce1 into main Jun 30, 2026
5 checks passed
@jcserv jcserv deleted the feat-follow-users branch June 30, 2026 21:13
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.

[Feature]: Follow users

1 participant