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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 ownerJune 14, 2026 20:50
@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
coduReadyReadyPreview, CommentJun 14, 2026 8:52pm

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title directly and specifically describes the two main change areas: opt-in feed personalization feature and repository hygiene improvements.
Description check✅ PassedThe 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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 developJun 14, 2026
6 of 8 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/phase3-personalized-feed branch June 14, 2026 20:53
Sign up for freeto 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

@NiallJoeMaher