- Upload a profile picture on web and mobile. The API square-crops it to
512px WebP, strips metadata (phone photos carry GPS), stores it in
Postgres and serves it with immutable caching. Avatar URLs are stored as
paths, so they survive moving the API. Arbitrary avatar URLs are no
longer accepted (they'd let a profile track its viewers).
- New accounts get the username as typed as their display name; existing
accounts are backfilled, and clearing it falls back to the username.
- Service names in Settings get a brand-coloured badge and larger type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
- Followers/following lists (paginated), 'follows you', remove a follower
- Blocking severs follows both ways and hides each user from the other
(profile 404s, search, follows); the blocker sees activity hidden
- Reports for moderation (App Store guideline 1.2)
- First account on a server becomes admin; admins see the report queue,
resolve reports, and suspend accounts (signs out, blocks login, hides
the profile, stops sharing). set-role script to promote/demote
- users.onboardedAt drives first-run onboarding; existing accounts are
backfilled as already onboarded. Empty PATCH /me no longer 500s
- Remove Spotify account sign-in and polling: Spotify caps new apps at 5
accounts, so Spotify arrives via its Last.fm/ListenBrainz link or the
Android reader. App-level catalog search stays for exact links
- Shared: QR code generation and profile-link parsing (verified with jsQR)
- Integration test timeout raised; password hashing is slow by design
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
- Every feature is free for everyone (the plan system stays for later);
remaining limits are abuse guards
- Optional website donation link (NEXT_PUBLIC_DONATION_URL). Not shown in
the mobile apps, where tips to the developer generally need in-app
purchase
- Last.fm/ListenBrainz-sourced plays link to the user's profile, as
Last.fm's API terms require
- Connections track when they last saw activity; a scrobbler silent for
over a week prompts re-linking Spotify (its link expires every 6 months)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
Phones that read system media sessions report playback themselves:
- POST /me/devices issues a per-phone token that can only call
POST /devices/now-playing; revocable, hashed at rest, 10 per user
- Reports name the app ("via Spotify"); unknown apps use the OS label
- Push-reported playback expires after 9 minutes of silence, since
phones send a heartbeat every 4 minutes while playing
Users can pause sharing (PUT /me/sharing: 1h, 8h, indefinitely). While
paused, connections aren't polled, phone reports are dropped, nothing is
added to history, and the current track switches to "played … ago".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
A play is recorded when a new track starts (pausing and resuming doesn't
count; returning after 10+ minutes does). Profiles show the last 10,
excluding the track playing now; 50 are kept per user. Recent plays are
free on every plan; the paid flag is renamed to fullHistory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
buildApp() returns the configured Fastify instance without listening, so
tests can drive it with inject(). Shutdown of database, Redis and queue
connections now lives in one place shared by the server and worker.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
- Fastify API with cookie (web) and bearer (mobile) sessions, follows,
pinned friends, featured songs, account deletion, Redis-backed rate limits
- Providers: Spotify (OAuth), Last.fm, ListenBrainz, Plex (PIN flow and
server discovery), Jellyfin (token auth, revoked on disconnect)
- Worker polls on an adaptive schedule in a Redis sorted set, so polling
scales horizontally and API usage tracks whether anyone is watching
- Track resolver links songs across services via ISRC (Deezer, Spotify),
iTunes search and song.link
- Realtime updates over WebSocket fan out through Redis pub/sub
- User-supplied server URLs are restricted to public addresses, checked
both up front and at connect time to defeat DNS rebinding
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8