Skip to content

Add /weekly-rotation/:handle collage card - #6

Merged
dylanjeffers merged 1 commit into
mainfrom
feat/weekly-rotation-collage
Sep 8, 2026
Merged

Add /weekly-rotation/:handle collage card#6
dylanjeffers merged 1 commit into
mainfrom
feat/weekly-rotation-collage

Conversation

@dylanjeffers

@dylanjeffers dylanjeffers commented Sep 8, 2026

Copy link
Copy Markdown

What

New route GET /weekly-rotation/:handle: the OG card for a user's Weekly Rotation. A 2x2 collage of the first four tracks' artwork in the same 598px frame the entity cards use, background tinted from the top track, listener name + badges, MIX tag, play button.

Rendered locally against prod for dylan via wrangler dev: four live artworks in the grid, background pulled from the first. (Screenshot shared in the Claude session; drop it here if you want it on the PR.)

Details

  • Keyed by handle because that's what the shareable URL carries. Resolves via /v1/full/users/handle/:handle, then /v1/users/:id/weekly-rotation?limit=30. Limit 30 on purpose: it's what the apps request, so this hits the API's existing cache entry instead of re-running the ranking query.
  • Fewer than four tracks pad with blank-artwork.png; zero tracks still renders.
  • Cache-Control expires at the next Wednesday 00:00 UTC rather than the one-year immutable the entity cards use, since the same URL means a new image after rollover. The web app additionally appends ?week=YYYY-WW for scrapers that cache by URL.
  • ArtworkCollage sizes cells from the frame's inner width. Satori lays out border-box, so sizing from the outer width left each row one pixel short of two cells and the grid collapsed into a single column (first render did exactly that).
  • README's "coming soon" section replaced; track/user/collection shipped a while ago.

bunx tsc --noEmit is clean for the new files (the three pre-existing errors in default.tsx / imageResponse.ts are untouched). Prettier clean.

Deploy

Manual as always: bun run deploy. The apps PR that points og:image here (AudiusProject/apps#14587, alongside AudiusProject/api#1034) can merge before or after; until this is deployed the shared page falls back to a 500 from this worker, which unfurlers treat as no image.

🤖 Generated with Claude Code

OG card for a user's Weekly Rotation: a 2x2 collage of the first four
tracks' artwork in the same frame the entity cards use, tinted from the
top track, with the listener's name and badges.

Keyed by handle because that is what the shareable URL carries
(audius.co/explore/weekly-rotation/:handle). Fetches the mix at limit 30,
the same limit the apps use, so it hits the API's cache entry instead of
re-running the ranking query.

Not served immutable like the entity cards: the same URL means a new image
once the week rolls over, so Cache-Control expires at the next Wednesday
00:00 UTC. The web app also stamps the period into the URL as ?week= for
scrapers that cache by URL.

The collage sizes its cells from the frame's inner width. Satori lays out
border-box, so sizing from the outer width leaves each row one pixel short
of two cells and the grid silently collapses into a single column.

Also replaces the README's stale "coming soon" section: track, user and
collection cards have shipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dylanjeffers added a commit to AudiusProject/api that referenced this pull request Sep 8, 2026
… shared (#1034)

## Why

Weekly Rotation is becoming shareable
(`audius.co/explore/weekly-rotation/:handle`: AudiusProject/apps#14587
and AudiusProject/og#6). Two things on the API side had to change for a
shared link to mean anything.

## What

**Rollover moves to Wednesday 00:00 UTC** (was the ISO week boundary,
Monday). Product call from Michael's Weekly Rotation thread: Monday
already belongs to the other weekly surfaces and Friday is Spotify's.
The period is still identified by an ISO `(year, week)` pair, it's the
ISO week shifted forward two days, so cache keys, the jitter seed and
share links keep their shape. `weeklyRotationPeriod` /
`weeklyRotationPeriodStart` are inverses and tested as such.

**Listener history is frozen at the rollover instant.** Plays, saves,
reposts and follows are now read as of the period's start
(`@periodStart`) rather than the request. Before this, playing a track
*from* the mix met the played-exclusion and removed it on the next cache
miss, so a mix shared on Wednesday was a different, shorter list by
Thursday. The mix has to survive being listened to.

Still drifts mid-week: the trending candidate pool and engagement
counts. Freezing those needs a stored snapshot table; not in scope here.

## Tests

- `TestWeeklyRotationPeriod`: rollover instant, one second before it,
Monday/Tuesday belonging to the previous period, non-UTC input, the
2026/2027 year boundary, and a round trip over every hour of 2026.
- `TestV1UsersWeeklyRotationKeepsTracksPlayedThisPeriod`: a play dated
now leaves the track in the mix; a play from 8 days ago still excludes.
- Existing fixtures dated their history 8 days back so they predate any
possible rollover.

All eight weekly-rotation tests pass against the compose DB.

## Not in this PR

Michael's thread also describes the mix as new music from artists you
follow / have liked. The shipped ranking does the opposite on purpose
(followed artists demoted, For You is the in-network surface). Needs a
product decision before anyone touches the scoring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@dylanjeffers
dylanjeffers merged commit b3aba85 into main Sep 8, 2026
@dylanjeffers
dylanjeffers deleted the feat/weekly-rotation-collage branch September 8, 2026 23:14
dylanjeffers added a commit to AudiusProject/apps that referenced this pull request Sep 8, 2026
## What

A Weekly Rotation can now be shared. Share produces
`audius.co/explore/weekly-rotation/:handle`, which shows that listener's
mix to anyone (the endpoint is public, no auth), and unfurls with a 2x2
collage of the mix's first four tracks rendered by
`og.audius.co/weekly-rotation/:handle?week=YYYY-WW`.

The bare `/explore/weekly-rotation` stays the signed-in user's own mix.
Under `/explore` rather than `/:handle/weekly-rotation` because
`/:handle/:slug` is the track permalink pattern.

## By package

**common**
- `ShareType` / `ShareModalRequest` / `ShareContent` gain
`weeklyRotation`. The content is just the listener (`User`): there's no
entity behind the mix, the link is a function of the handle.
- `useWeeklyRotation({ userId })` takes an optional target user;
defaults to the viewer.
- `Share` / `ShareToTwitter` analytics get a `weeklyRotation` kind.
- `WEEKLY_ROTATION_USER_PAGE` + `weeklyRotationPage(handle)`. Copy says
Wednesday: the rollover moved (api PR).

**web**
- Share button next to Play. The share modal builds the link from the
handle and copies it directly with the same `Share` event the sagas
emit, there being no social saga for a non-entity.
- Handle route registered in both router trees. The page resolves the
handle via `useUserByHandle`, titles as "{name}'s Weekly Rotation" for
someone else's mix, and only the handle route gets `og:image` +
canonical (the bare route is per-viewer and shouldn't be indexed as
anyone's).
- New Vike SSR entry `ssr/weekly-rotation`
(`/explore/weekly-rotation/@handle`): fetches the user for the name,
emits the collage URL as `og:image`, `summary_large_image`. The bare
route still falls to the explore handler, which now has a
`weekly-rotation` entry in `exploreMap`.
- `utils/weeklyRotationPeriod.ts`: dependency-free mirror of the API's
Wednesday-shifted ISO week, used by SSR (which avoids
`@audius/common/utils`) and the client. Same test cases as the Go side
so they can't drift apart. 7 vitest cases.

**mobile**
- Share icon in the top bar. Drawer offers DM, X, Copy Link, and the
system sheet. No Instagram/TikTok/Snapchat story rows: those need a
single streamable mp3 for the FFmpeg background video, which a mix
doesn't have.
- `WeeklyRotationScreen` takes an optional `handle` param;
`explore/weekly-rotation/:handle?` added to the explore deep-link map.

## Verified

- `tsc` clean in common, web, mobile. ESLint clean on every touched
file.
- `weeklyRotationPeriod.test.ts` passes.
- The collage endpoint was rendered locally against prod (see og PR).

## Not verified

- Mobile share drawer and deep link compile but were not exercised on a
simulator.

## Depends on

- AudiusProject/og#6 for the collage route (until deployed, `og:image`
500s and unfurlers show no image).
- AudiusProject/api#1034 for the Wednesday rollover and week-stable
history. Without it the shared mix can shrink mid-week as the owner
listens.

## Product note

Michael's Weekly Rotation thread describes the mix as new music from
artists you follow / have liked. The shipped ranking does the opposite
on purpose. Not changed here; flagged on the api PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant