feed: make Explore "new-to-you + diverse, less recency"

Explore shared the full active set with Recent/For You and leaned on freshness,
so it read like a reshuffled Recently-added. Give it a discovery identity:

- Weights: drop freshness (0.25 -> 0.12) and bump novelty (0.30 -> 0.35) so
  recency stops dominating and topic-novelty leads.
- Add a soft followed-author penalty (weight 0.3): the scorer now reads
  subscriptionAuthors from the Explore runtime and deprioritizes creators you
  already follow, so non-followed creators surface ("new to you"). A penalty,
  not a filter, so the feed stays full if you follow lots of people.
- More variety: lower the diversity sorter's MMR lambda (0.7 -> 0.5).

Also compacted For You's subscriptionAuthors reader to a one-liner to stay
under feedCoordinator's size cap while adding the same reader to Explore.

Tests: explore follows-penalty scoring; existing explore-scorer/diversity +
feedCoordinator suites green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-24 08:50:50 -04:00
1 parent 4873a43135
commit 087cf410cb
3 files changed
+55 -18

No files matched your search

+31
View File
@@ -158,3 +158,34 @@ test("explore scorer rewards novelty and new tag fraction", async () => {
assert.equal(novel.novelty, 1);
assert.equal(novel.newTagFraction, 1);
});
// Explore identity: "new-to-you". A creator you already follow gets a soft
// penalty so non-followed creators surface, without filtering the feed empty.
test("explore soft-penalizes authors you already follow", async () => {
const NOW = Math.floor(Date.now() / 1000);
const FOLLOWED = "f".repeat(64);
const STRANGER = "1".repeat(64);
const tags = [["t", "music"]];
const items = [
{ video: { id: "stranger", pubkey: STRANGER, created_at: NOW, tags }, metadata: {} },
{ video: { id: "followed", pubkey: FOLLOWED, created_at: NOW, tags }, metadata: {} },
];
await createExploreScorerStage()(items, {
now: NOW,
runtime: {
subscriptionAuthors: [FOLLOWED],
tagPreferences: { interests: [], disinterests: [] },
},
addWhy() {},
});
const strangerScore = items[0].metadata.exploreScore;
const followedScore = items[1].metadata.exploreScore;
assert.ok(
followedScore < strangerScore,
`followed (${followedScore}) must score below an identical stranger (${strangerScore})`,
);
assert.equal(items[1].metadata.exploreComponents.followedMatch, 1);
assert.equal(items[0].metadata.exploreComponents.followedMatch, 0);
});