Skip to content

feat: personalized 'For you' feed + open-source repo hygiene#1336

Merged
NiallJoeMaher merged 3 commits into
developfrom
feat/phase3-personalized-feed
Jun 14, 2026
Merged

feat: personalized 'For you' feed + open-source repo hygiene#1336
NiallJoeMaher merged 3 commits into
developfrom
feat/phase3-personalized-feed

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

What

Opt-in feed personalization ("For you"), plus repo hygiene.

Personalization (Phase 3)

Built on the topic vocabulary, additive and cold-start safe — the existing feed is untouched.

  • Schema (migration 0039, additive): user_topic_pref (follow/mute) and user_topic_affinity (implicit, time-decayed interest).
  • feedRanking — a pure, unit-tested scoring module: a transparent weighted blend of recency, quality, and topic affinity, with muted topics filtered out. "Why did this rank here?" is always answerable.
  • topicAffinity — derives per-user affinity from votes/bookmarks/comments through post_topic edges with time decay; recomputed for recently-active users as a new pass in the nightly cron.
  • profile.getTopicPrefs / setTopicPref — manage follows/mutes against the controlled topic vocabulary.
  • content.getForYouFeed — re-ranks a recent candidate window for the signed-in user. No profile signal → falls back to recency, so a new user sees the normal feed. Ready for the client to adopt as a "For you" tab.

Repo hygiene

  • Add CLAUDE.md orienting contributors; flags the repo as public and sets a high bar for code that must pass open-source review.
  • Remove internal docs/plans/* design/planning notes (not appropriate for a public repo) and drop their references from code comments.
  • gitignore local AI-assistant config.
  • Prettier-format the OG card files (pre-existing CI failures) and trim verbose comments across the content-pipeline modules.

Verification (local)

  • tsc: 0 errors · eslint: clean · prettier --check: clean · next build: succeeds.
  • vitest: 110 tests pass (incl. 16 new ranking/affinity tests). The one failing file is the pre-existing backfill-url-id DB-integration test that needs DATABASE_URL — unrelated to this change.

Notes

  • Migration 0039 is additive and applies on the production deploy via db:migrate.
  • The personalized re-rank operates over a recent candidate window (200 posts); a deeper candidate-generation strategy can follow if needed.

- Add CLAUDE.md orienting contributors; flags the repo as public and sets a high
  bar for code that must pass open-source review.
- Remove docs/plans/* internal design/planning notes (not appropriate for a
  public repo) and drop their references from code comments.
- gitignore .claude/ and other local AI assistant config.
Adds opt-in feed personalization built on the topic vocabulary:

- Schema (migration 0039, additive): user_topic_pref (follow/mute) and
  user_topic_affinity (implicit interest, time-decayed).
- feedRanking: pure, unit-tested scoring — a transparent weighted blend of
  recency, quality, and topic affinity, with muted topics filtered out.
- topicAffinity: derive per-user affinity from votes/bookmarks/comments through
  post_topic edges with decay; recomputed for active users by the nightly cron.
- profile.getTopicPrefs / setTopicPref: manage follows and mutes.
- content.getForYouFeed: re-rank a recent candidate window for the user; cold
  start (no signal) falls back to recency, so the existing feed is untouched.

Also trims verbose comments across the content-pipeline modules.
@NiallJoeMaher NiallJoeMaher requested a review from a team as a code owner June 14, 2026 20:50
@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
codu Ready Ready Preview, Comment Jun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds a full feed personalization pipeline: new user_topic_affinity and user_topic_pref DB tables, topic affinity scoring with time decay, a deterministic feed ranking module (recency/quality/affinity), data loaders, a getForYouFeed tRPC endpoint, topic preference tRPC mutations, and a nightly cron affinity recomputation pass. Also rewrites OG card templates with Satori-safe style objects, adds CLAUDE.md, and removes stale planning documents.

Changes

Feed Personalization Pipeline

Layer / File(s) Summary
DB schema and migration for personalization tables
server/db/schema.ts, drizzle/0039_feed_personalization.sql
Adds topicPref enum, user_topic_pref and user_topic_affinity tables with composite PKs, cascade FK constraints, btree indexes, and Drizzle ORM relations. Updates adjacent AI metadata field comments.
Topic affinity computation with decay
server/lib/topicAffinity.ts, server/lib/topicAffinity.test.ts
Implements decayedWeight (exponential half-life decay), recomputeUserAffinity (fetches votes/bookmarks/comments, maps to topics via post_topic, accumulates decayed scores, persists results), and findRecentlyActiveUsers. Covers decayedWeight with Vitest tests.
Feed ranking scoring and rankCandidates
server/lib/feedRanking.ts, server/lib/feedRanking.test.ts
Introduces RankingWeights, FeedCandidate, UserProfile types plus recencyComponent, qualityComponent, affinityComponent, isMuted, scoreCandidate, rankCandidates, and hasProfileSignal. Full Vitest test coverage for all components including mute filtering and tie-breaking.
loadUserProfile and loadTopicsByPost data loaders
server/lib/feedPersonalization.ts
Adds loadUserProfile (parallel prefs + affinity queries → follows/mutes sets + affinity map) and loadTopicsByPost (post_topic edge query → postId→topicIds map).
tRPC getForYouFeed and topic preference endpoints
server/api/router/content.ts, server/api/router/profile.ts
Adds getForYouFeed protected procedure with conditional re-ranking via rankCandidates when user has profile signal, plus getTopicPrefs query and setTopicPref upsert/delete mutation.
Nightly cron affinity recomputation pass
app/api/cron/daily-review/route.ts
Adds reviewAffinity() step to the daily-review cron: fetches recently active users, recomputes affinity per user with per-user Sentry isolation, and includes affinityUsersUpdated in the run summary response.

OG Card Templates Rewrite

Layer / File(s) Summary
OG design tokens and font loader formatting
lib/og/tokens.ts, lib/og/fonts.ts, lib/og/url.ts, app/og/route.tsx
Adds explicit hex literals to design tokens, reformats loadGoogleFont/coduFonts to multi-line, and reformats OG URL builder helpers. No logic changes.
OG card template rewrite
lib/og/templates.tsx
Rewrites with Satori-safe style atoms (mono, root, glow, topbar), new KindBadge, Chips, MintBadge, MetaLine helper components, and refactored MainCard, PostCard, IdentityShell, ProfileCard, PublicationCard, JobCard with explicit flex/style objects. Signature-compatible with prior exports.

Repo Housekeeping

Layer / File(s) Summary
CLAUDE.md, .gitignore, removed plan docs, and minor fixes
CLAUDE.md, .gitignore, docs/plans/*, server/lib/contentAnalysis.ts, components/Admin/AdminShell.tsx, app/(app)/s/[sourceSlug]/_sourceProfileClient.tsx
Adds contributor guide and AI tool gitignore entries; removes four stale planning docs; clarifies inline comments in content analysis and admin shell; reorders one Tailwind className token.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant contentRouter
  participant feedPersonalization as feedPersonalization.ts
  participant feedRanking as feedRanking.ts
  participant DB

  Client->>contentRouter: getForYouFeed(limit, offset)
  contentRouter->>feedPersonalization: loadUserProfile(db, userId)
  feedPersonalization->>DB: SELECT user_topic_pref + user_topic_affinity
  DB-->>feedPersonalization: prefs + affinity rows
  feedPersonalization-->>contentRouter: UserProfile {follows, mutes, affinity}

  contentRouter->>DB: SELECT posts + qualityScore (recent window)
  DB-->>contentRouter: post candidates[]

  alt hasProfileSignal(profile)
    contentRouter->>feedPersonalization: loadTopicsByPost(db, postIds)
    feedPersonalization->>DB: SELECT post_topic edges
    DB-->>feedPersonalization: postId→topicIds
    feedPersonalization-->>contentRouter: topicsByPost Map
    contentRouter->>feedRanking: rankCandidates(candidates, profile, nowMs)
    feedRanking-->>contentRouter: RankedCandidate[] sorted by score
  end

  contentRouter-->>Client: {items, nextOffset, personalized}
Loading
sequenceDiagram
  participant CronRoute as daily-review cron
  participant topicAffinity as topicAffinity.ts
  participant DB

  CronRoute->>topicAffinity: findRecentlyActiveUsers(db, since24h, cap)
  topicAffinity->>DB: SELECT distinct userIds from votes/bookmarks/comments
  DB-->>topicAffinity: userIds[]
  topicAffinity-->>CronRoute: activeUsers[]

  loop per user
    CronRoute->>topicAffinity: recomputeUserAffinity(db, userId, nowMs)
    topicAffinity->>DB: fetch votes/bookmarks/comments + post_topic edges
    DB-->>topicAffinity: interactions with timestamps
    topicAffinity->>topicAffinity: decayedWeight per interaction per topic
    topicAffinity->>DB: DELETE existing user_topic_affinity rows
    topicAffinity->>DB: INSERT positive-score rows
    topicAffinity-->>CronRoute: updatedTopicCount
  end

  CronRoute-->>CronRoute: summary.affinityUsersUpdated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • codu-code/codu#1335: Directly modifies the same app/api/cron/daily-review/route.ts nightly pipeline that this PR extends with the affinity recomputation pass.
  • codu-code/codu#1334: Overlaps the OG card stack (app/og/route.tsx, lib/og/fonts.ts) that this PR reformats with Satori-safe style changes.
  • codu-code/codu#1332: Touches the same _sourceProfileClient.tsx element whose Tailwind className is adjusted in this PR.

Poem

🐇 Hoppity-hop through the topic graph I go,
Decaying old weights with each tick of the clock,
Follows and mutes tell me what feeds to show,
Quality + recency + affinity in a flock.
The cron runs at night while the warren's asleep,
Ranking your feed so the good posts go deep! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check ✅ Passed The description comprehensively covers PR objectives, implementation details, verification steps, and notes. It follows a clear structure with sections explaining features and hygiene improvements, though it diverges from the template's bullet-point format.
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.

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

✨ 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 feat/phase3-personalized-feed

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.

@NiallJoeMaher NiallJoeMaher merged commit d1ad53f into develop Jun 14, 2026
6 of 8 checks passed
@NiallJoeMaher NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
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