~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
60 lines · 2.4 KB Raw History
# For You Feed Design

Collaborative filtering feed for Grain, reverse-engineered from the Bluesky For You feed playground by spacecowboy17.

## Algorithm

### Personalized mode (user has favorites)

1. **Seed**: Get user's 500 most recent favorites
2. **Co-likers**: Find other users who also favorited those same galleries
3. **Candidates**: Collect galleries those co-likers favorited (excluding user's own favorites and own galleries)
4. **Score** each candidate:

```
For each path (co-liker → candidate gallery):
  path_score = 1 / (co_liker_total_likes ^ divisor_power)
  path_score *= (1 - corater_decay) ^ (items_from_this_corater - 1)

base_score = sum(path_scores)
smoothed = base_score * (num_paths ^ smoothing_factor)
time_factor = 0.5 ^ (age_hours / half_life)    // half_life=0 disables decay
final_score = smoothed * time_factor / (total_favorites ^ popularity_penalty)
```

5. **Rank** by final_score descending, paginate with cursor

### Cold start fallback (no favorites)

Return galleries ordered by favorite count descending, filtered to last 30 days.

## Default Parameters

| Parameter          | Default | Description                        |
| ------------------ | ------- | ---------------------------------- |
| half_life          | 6       | Hours; demotes older posts         |
| smoothing_factor   | 0.5     | Boosts multi-path posts            |
| popularity_penalty | 0.3     | Demotes generally popular posts    |
| divisor_power      | 1.0     | Divides by co-liker's total likes  |
| corater_decay      | 0       | Decay for same co-liker (0=none)   |
| time_shift         | 24      | Hours; co-like contribution window |
| seed_limit         | 500     | Max seed favorites                 |

## Files

- `server/feeds/foryou.ts` — feed generator with scoring logic
- `app/lib/queries.ts` — add forYouFeedQuery
- `app/routes/feeds/for-you/+page.ts` — page load
- `app/routes/feeds/for-you/+page.svelte` — feed UI
- `app/lib/preferences.ts` — add to DEFAULT_PINNED

## Pagination

Score-based cursor: encode `score:rkey` as cursor string. Since scores are computed in JS (not SQL), fetch all candidates, score, sort, then slice by cursor position. For v1, use offset-based cursor to keep it simple.

## Filters

- Exclude galleries user already favorited
- Exclude user's own galleries
- Respect takedown status and hidden labels
- Require gallery to have at least 1 item