Files
bitvid/docs/feed-algo-audit.md
thePR0M3TH3ANandClaude Opus 4.8 c0a8636678 feat(feed): make For You inclusive + ranked by your lists (G1-G5)
The For You feed was chronological, hashtag-interest-EXCLUSIVE, and ignored
follows entirely (docs/feed-algo-audit.md). Now it is inclusive + ranked:

- createForYouScorerStage scores each video: follows-boost (author you follow)
  + interest/watch-topic affinity (cosine vs your interests+watch-history vector)
  + freshness - disinterest penalty. Reuses Explore's vector/similarity helpers.
- createForYouScoreSorter ranks by that score (muted last, recency tiebreak).
- For You pipeline: interests now BOOST not exclude (enforceInterests:false);
  scorer added; chronological sorter replaced. Never empty (falls back to
  recency when you have no follows/interests/history).
- buildForYouFeedRuntime now feeds subscriptionAuthors + watchHistoryTagCounts.

Watched/blocked/muted are still removed (suppression/blacklist/moderation
stages unchanged). Cheat-resistant tests (older boosted items only win via the
boost). Grid renders end-to-end (~1.3s). Weight tuning is a follow-up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 16:57:05 -04:00

101 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feed Algorithm ↔ Lists/Notes — Wiring Audit
> Read-only audit (no code changed). Goal: confirm whether the **For You** feed is
> actually driven by the user's lists/notes (follows, hashtag prefs, watch
> history, blocks/moderation), now that those lists load reliably after the
> data-flow refactor. Source of truth: `js/app/feedCoordinator.js`
> (`registerForYouFeed`, `buildForYouFeedRuntime`) + `js/feedEngine/*`.
## For You pipeline, as built today
```
source: createActiveNostrSource // ALL active videos (block-filtered only)
stages:
1. createTagPreferenceFilterStage() // hashtag interests/disinterests
2. createBlacklistFilterStage() // blocks + admin blacklist
3. createWatchHistorySuppressionStage()// drop already-watched
4. createDedupeByRootStage()
5. createModerationStage() // NIP-56 reports + trusted mutes
6. createResolvePostedAtStage()
sorter: createChronologicalSorter() // newest-first
```
Runtime inputs (`buildForYouFeedRuntime`): `blacklistedEventIds`,
`isAuthorBlocked`, `tagPreferences {interests, disinterests, available}`,
`moderationThresholds`, `watchHistory.shouldSuppress`.
## What IS correctly wired (drives the feed) ✅
- **Hashtag interests/disinterests** — from the real `hashtagPreferencesSnapshot`.
Interests are a **hard filter** (see gap 3); disinterested tags are dropped.
- **Blocks / admin blacklist** — `isAuthorBlocked` + `blacklistedEventIds`.
- **Watch history** — watched items are **suppressed** (removed) via
`runtime.watchHistory.shouldSuppress`.
- **Moderation** — NIP-56 trusted reports + trusted mutes, threshold-gated.
So the refactor's payoff is real: these lists now load and *do* shape For You.
## Resolution (2026-06-14)
Product decision: **For You = inclusive + ranked**. Implemented:
- New `createForYouScorerStage` (in `exploreScoring.js`) scores each video by
**follows-boost + interest/watch-topic affinity + freshness − disinterest**
(reusing Explore's vector/similarity/freshness helpers; affinity uses the
combined interests+watch-history user vector).
- New `createForYouScoreSorter` ranks by that score (muted last, recency tiebreak).
- `registerForYouFeed`: interests now **boost** instead of exclude
(`enforceInterests:false`); scorer added; chronological sorter replaced.
- `buildForYouFeedRuntime` now supplies `subscriptionAuthors` (follows) and
`watchHistoryTagCounts`.
- Result: **G1 follows, G2 ranking, G3 inclusivity (never empty), G4 watch-topic
affinity, G5 blending — all addressed.** Cheat-resistant tests in
`tests/for-you-scorer.test.mjs`.
Tuning of the weights is a follow-up (currently follows .4 / affinity .35 /
freshness .25 / disinterest .3).
## Gaps (original audit)
### G1 — Follows/subscriptions have ZERO effect on For You *(highest impact)*
For You's source is `createActiveNostrSource` (all videos); the runtime passes no
`subscriptionAuthors`. Follows are used only by the **separate** Subscriptions
feed. So "who you follow" does not include, boost, or rank anything in For You.
For a feed meant to be "driven by your lists," this is the biggest miss.
### G2 — For You is chronological, not relevance-ranked
The tag-preference stage records `metadata.matchedInterests`, but the sorter is
`createChronologicalSorter()`, so match strength/count never affects order. There
is no scoring that blends interest strength + freshness + follows. (Explore *does*
have `createExploreScorerStage`; For You does not.)
### G3 — Interest filter is binary/exclusive
If the user has any interests set, For You keeps **only** videos with a matching
tag and drops the rest. Niche interests or sparsely-tagged videos → a thin or
empty For You. No "boost-but-don't-exclude" mode and no fallback when matches are
too few.
### G4 — Watch-history *topic affinity* is unused in For You
For You only uses watch history to *suppress* watched items. The topics you watch
(`watchHistoryTagCounts`) are **not** an interest signal here — though Explore's
runtime *does* compute and use them. So For You ignores a strong implicit signal.
### G5 — No weighting/blending overall
For You = a chain of hard filters + chronological. "Driven by lists" today means
"filtered by hashtag prefs, newest-first" — missing follows weighting, interest
ranking, and watch-topic affinity working together.
## Suggested fix order (incremental, each shippable)
1. **G1**: feed follows into For You — add `subscriptionAuthors` to the runtime
and either (a) blend a follows-boost into a scorer, or (b) merge a
follows-source with the active source. (Biggest UX win.)
2. **G2 + G3**: replace the chronological sorter with a **For You scorer** that
*ranks* by interest matches + freshness + follows instead of hard-excluding,
with a graceful fallback so the feed is never empty.
3. **G4**: pass `watchHistoryTagCounts` into the For You runtime and factor watch-
topic affinity into the score (reuse Explore's machinery).
Open question for product: should For You be **inclusive+ranked** (show lots,
order by relevance — recommended) or **strict** (only interest/follow matches)?
This decides G2/G3.