Skip to content

fix(notifications): personalize embedded users in related response#14366

Merged
raymondjacobson merged 3 commits into
mainfrom
claude/fix-notifications-personalization
May 20, 2026
Merged

fix(notifications): personalize embedded users in related response#14366
raymondjacobson merged 3 commits into
mainfrom
claude/fix-notifications-personalization

Conversation

@raymondjacobson
Copy link
Copy Markdown
Member

@raymondjacobson raymondjacobson commented May 20, 2026

Summary

  • Regenerate @audius/sdk to pick up the new user_id query parameter and renamed path placeholder on GET /notifications/{id} and GET /notifications/{id}/playlist_updates (api PRs AudiusProject/api#837 + AudiusProject/api#838, both merged).
  • Pass the current user id through every call site so the backend personalizes embedded related.users in the response (does_current_user_follow, etc.).

Why

The notifications endpoint embeds user objects in related.users. The backend personalizes those via MyID: app.getMyId(c) in api/v1_notifications.go:336, and getMyId reads from the query string ?user_id=, not the URL path. The SDK only put the id in the path, so on the backend MyID = 0 and the SQL short-circuited does_current_user_follow to false for every embedded user.

On a cold app boot, useNotifications fires early and primeRelatedData writes those un-personalized users into the tan-query cache. primeUserData only writes when the slot is empty (no forceReplace), so the bad entries stick until the user navigates to that profile and a personalized getUser fetch overwrites the cache. That's why visiting @audius directly and refreshing makes the Following state appear correct.

A backend-only fix would collapse two semantically distinct ids (notifications owner vs. requester) — incorrect when a manager reads a managed user's notifications. The fix is client-side: send the requester id as ?user_id=, keep the path id for whose notifications.

Changes

File Change
packages/sdk/src/sdk/api/generated/default/apis/NotificationsApi.ts Regenerated. Path field renames userIdid; new userId?: string query field carries the requester id.
packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts Incidental drift: getUserForYouFeed was removed upstream and the generated copy was stale.
packages/common/src/api/tan-query/notifications/useNotifications.ts Pass { id, userId } to the SDK.
packages/common/src/api/tan-query/notifications/useNotificationUnreadCount.ts Update local cast type and call site to use id as the path field. (No requester id passed here — the unread count doesn't hydrate related.users.)
packages/common/src/api/tan-query/playlist-updates/usePlaylistUpdates.ts Pass { id, userId } to the SDK; widen the local cast type.

Breaking SDK change (external consumers)

The path field on GetNotificationsRequest and GetPlaylistUpdatesRequest renames from userId to id. External @audius/sdk consumers of these two methods will need to rename the field. Wire format and server behaviour are unchanged.

Test plan

  • CI green (typecheck/lint/tests).
  • On a fresh app load with a signed-in account that follows @audius: confirm Following state renders correctly on lists hydrated from notification responses (e.g. notification panel actor avatars, recommended profiles surfaced from related users) without first having to navigate to @audius's profile.
  • Network inspector on the notifications request shows ?user_id=<hashed-current-user-id> and does_current_user_follow: true for users actually followed in related.users.
  • Manager flow: read notifications while acting as a managed user. ?user_id= carries whatever the app treats as "current user" (the managed user in normal manager-switched state); personalization in the response reflects that perspective.

🤖 Generated with Claude Code

On initial app load the current user's follow relationship to other
accounts (e.g. @audius) renders as not-following in lists hydrated from
notification responses. Visiting that profile directly fixes the state.
The bug: the /notifications/{user_id} endpoint embeds users in the
related field, and the backend personalizes them via getMyId(c) which
reads ?user_id= from the query string, not the URL path. The SDK was
only sending the id in the path, so MyID resolved to 0 and the SQL
short-circuited does_current_user_follow to false for every embedded
user. The tan-query cache primed those entries on a cold notifications
fetch and sticky-cached the wrong follow state until a personalized
profile fetch overwrote them.

The accompanying api PR adds an optional user_id query param to both
/notifications/{user_id} and /notifications/{user_id}/playlist_updates.
This change:

- Regenerates @audius/sdk from the updated spec. The openapi generator
  renamed the new query param to `userId2` to avoid colliding with the
  path's `userId`. Wire format is correct (`queryParameters['user_id']`),
  the field name is just awkward — see PR description for follow-up
  options.
- Threads the current user id through both call sites
  (useNotifications.ts, usePlaylistUpdates.ts) as `userId2` so the
  backend can compute personalization for related.users. Path and query
  carry the same value in the normal flow; they may diverge when a
  manager reads a managed user's notifications.

The UsersApi.ts diff is incidental drift: getUserForYouFeed was removed
upstream and our generated copy was stale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 20, 2026

🦋 Changeset detected

Latest commit: 77d303f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@audius/sdk Major
@audius/sdk-legacy Patch
@audius/protocol-dashboard Patch
@audius/sp-actions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

raymondjacobson added a commit to AudiusProject/api that referenced this pull request May 20, 2026
)

## Summary

Rename the OpenAPI path placeholder on the two notifications routes from
`{user_id}` to `{id}`, matching the convention used by every
`/users/{id}/...` route. Concrete URL is unchanged.

- `/notifications/{user_id}` → `/notifications/{id}`
- `/notifications/{user_id}/playlist_updates` →
`/notifications/{id}/playlist_updates`

## Why

Follow-up to #837, which added a `user_id` query parameter to both
routes for personalization of embedded `related.users`. With the path
placeholder *also* named `user_id`, `openapi-generator`
(typescript-fetch) emitted a colliding TS field (`userId2`) in the
generated `GetNotificationsRequest` / `GetPlaylistUpdatesRequest`. The
wire format was correct but the public SDK type surface was awkward.

Renaming the path placeholder to `id` lets the generator produce a clean
shape:

```ts
GetNotificationsRequest { id: string; userId?: string; ... }
```

The Fiber router matches `/notifications/:userId` by position, not by
name, so no Go change is required and the concrete URL clients send is
unchanged.

## Breaking change

This **is** a breaking change for external `@audius/sdk` consumers of
`getNotifications` / `getPlaylistUpdates`: the path field renames from
`userId` to `id`. Server behaviour, wire format, and URL paths are
unchanged.

In-monorepo call sites will be updated in the corresponding apps PR
([AudiusProject/apps#14366](AudiusProject/apps#14366))
after this merges.

## Test plan

- [x] Spec validates in CI.
- [x] Hitting `/notifications/<hashed-id>?user_id=<hashed-id>`
post-deploy returns the same response as before.
- [x] Regenerating the apps SDK against the merged spec produces a clean
`GetNotificationsRequest` with `id` (path) and `userId` (query), no
`userId2`.

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

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

🌐 Web preview ready

Preview URL: https://audius-web-preview-pr-14366.audius.workers.dev

Unique preview for this PR (deployed from this branch).
Workflow run

Picks up AudiusProject/api#838, which renamed the OpenAPI path placeholder
on /notifications/{user_id} and /notifications/{user_id}/playlist_updates
to /notifications/{id} and /notifications/{id}/playlist_updates. With the
collision against the query param gone, the generator now emits clean
shapes:

  GetNotificationsRequest    { id: string; userId?: string; ... }
  GetPlaylistUpdatesRequest  { id: string; userId?: string }

Update the three call sites to use { id, userId } instead of the previous
awkward { userId, userId2 }. Concrete URL is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels May 20, 2026
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@raymondjacobson raymondjacobson merged commit 6bd5c27 into main May 20, 2026
14 checks passed
@raymondjacobson raymondjacobson deleted the claude/fix-notifications-personalization branch May 20, 2026 22:01
raymondjacobson added a commit that referenced this pull request May 20, 2026
…14367)

## Summary

Three more hooks were priming un-personalized users into the shared user
cache — same bug class as the notifications fix in
[#14366](#14366). API spec
change merged in
[AudiusProject/api#839](AudiusProject/api#839);
this PR consumes it.

| Hook | SDK call | Prime |
| --- | --- | --- |
| `useAllRemixContests` | `events.getRemixContests` | `primeRelatedData`
|
| `useUserRemixContests` | `users.getContestsByUser` |
`primeRelatedData` |
| `useNewAlbumReleases` | `playlists.getPlaylistsNewReleases` |
`primeCollectionData` → `primeUserData` |

In every case the backend handler already calls `app.getMyId(c)` to feed
`MyID` into the `Parallel` query that hydrates embedded users — the
query param just wasn't being sent because the SDK request type didn't
expose it. With no value, `MyID = 0` and the SQL
[short-circuited](https://github.com/AudiusProject/api/blob/main/api/dbv1/get_users.sql.go#L129-L136)
`does_current_user_follow` to false for every embedded user. Clients
then primed those into a shared cache that other surfaces read.

## Changes

| File | Change |
| --- | --- |
| `packages/sdk/src/sdk/api/generated/default/apis/EventsApi.ts` | Add
`userId?: string` to `GetRemixContestsRequest`; map to
`queryParameters['user_id']`. |
| `packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts` | Add
`userId?: string` to `GetContestsByUserRequest`; map to
`queryParameters['user_id']`. Path field is still `id` (the contest
host); query `userId` is the requester. |
| `packages/sdk/src/sdk/api/generated/default/apis/PlaylistsApi.ts` |
Add `userId?: string` to `GetPlaylistsNewReleasesRequest`; map to
`queryParameters['user_id']`. |
| `packages/common/src/api/tan-query/events/useAllRemixContests.ts` |
Pull `currentUserId` via `useCurrentUserId`; pass as `userId`. |
| `packages/common/src/api/tan-query/events/useUserRemixContests.ts` |
Same. Note this hook already took a `userId` prop for the contest host —
that maps to the path `id`. `currentUserId` is separately threaded as
the query. |
| `packages/common/src/api/tan-query/collection/useNewAlbumReleases.ts`
| Pull `currentUserId`; pass as `userId`. |
| `.changeset/personalization-three-endpoints.md` | Minor `@audius/sdk`
bump for the new optional fields. |

## Note on the SDK files in this diff

I edited the generated `*.ts` files by hand here because `npm install`
in my environment was blocked by a date-pinned registry constraint,
which broke `node_modules` and stopped both `gen.js` and the rollup
build from running. The edits are deterministic — they replicate the
exact pattern openapi-generator already produces for analogous endpoints
(e.g. `GetTrendingPlaylistsRequest` in the same `PlaylistsApi.ts`). A
fresh `npm run gen` on a clean checkout should produce the same diff.
Worth a quick visual confirmation during review.

## Test plan

- [ ] CI green (typecheck/lint/tests).
- [ ] On a fresh app load with a signed-in account: explore → contests
carousel, an artist's contests tab, and browse → new albums all show
correct Following state for surfaced artists without first having to
navigate to each profile.
- [ ] Network inspector on each of the three endpoints carries
`?user_id=<hashed-current-user-id>` and `does_current_user_follow: true`
for users actually followed in `related.users` / collection owners.

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

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant