- Anyone who can see a play can read its comments; the player and the people they follow can write.
The author, the player and admins can delete; comments can be reported (admins see the text).
- Notifications: "commented on" for the player, "replied on" for earlier commenters, each with its
own setting. Reaction and comment notifications now open the play.
- A 💬 chip with the count next to reactions on the feed and recent plays, opening the thread on
the app (/play/[id]) and the website. Threads refresh every few seconds while open.
- Blocking removes comments between the two people. Plays with comments survive history trimming.
- Privacy page updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
Reactions to your songs, new followers, and songs sent to you are now
notifications: a bell on the feed (app and website) with an unread
count that updates live, and a list that marks them read. "Send ✉" on
a song sends it to people who follow you, with an optional note.
Settings picks which kinds to be notified about.
Push delivery uses no third-party accounts:
- Android: UnifiedPush through a distributor app (ntfy), pointed at
our own ntfy server (docker-compose.ntfy.yml). Works the same in
the F-Droid build; no Firebase.
- Browsers: standard Web Push from the existing service worker.
Both use Web Push encryption with a VAPID key pair the server makes
and keeps in the database. Push addresses are checked like any other
user-supplied URL. Setup is in docs/PUSH.md; the privacy page covers
the new data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Songs offered links to all five services. Settings now has "Your music
services": pick the ones you use and songs only show those, with the
main one as the big button. Until someone picks, they still see all of
them. Onboarding sets it to the app you said you listen with.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sign-up emails a confirmation link (24h); unconfirmed accounts see a
banner on the web with "send it again", and status in Settings.
- Change email (needs the password): the new address gets a confirmation
link and only replaces the old one once clicked; the old address gets a
heads-up with a reset-password link. Pending changes can be cancelled.
- Change password (needs the current one): signs out other sessions.
- Password fields have Show/Hide on web and mobile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
Web and mobile:
- Follower/following lists (tap the counts), with Follow buttons and
"Remove" on your own followers.
- Profile ⋯ menu: block (with confirmation) / unblock, and report with a
reason and optional note. "Follows you" label, "Follow back", and a
banner on profiles you've blocked.
- Blocked accounts list in Settings, with unblock.
Web only: /admin for the admin account: stats, open/resolved reports,
resolve, suspend/unsuspend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
"Forgot your password?" on web and mobile sends a one-time link (1 hour)
to the account's email; the website's reset page sets the new password,
signs out every other session and signs in. The request responds the same
whether or not the email has an account, and the email goes out in the
background so timing doesn't reveal it either. Only a hash of the link
token is stored. Sends through SMTP_* settings; without them, emails are
logged (and captured in tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
- 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
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
Holds everything web, mobile and API must agree on: DTOs, zod input
schemas, plan entitlements (the single place feature gates live),
streaming-service link helpers, feed ordering and a typed API client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8