- 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
- Album-art-tinted cards that crossfade on song change, equalizer bars,
live avatar rings, drifting ambient colour, Space Grotesk display font,
brand purple-to-blue gradient from the logo
- Feed cards rise in and glide to new positions (motion); respects
reduced-motion
- New logo as nav mark, favicon and Apple touch icon
- Installable PWA: manifest, maskable icons, small service worker
(cached build assets + offline page), security headers
- Show-QR-code on your own profile; centred profile header with stats
- Settings explains connecting Spotify via Last.fm or ListenBrainz
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
Settings gets a Sharing card (pause 1 hour / until tomorrow / until
turned back on); the feed shows a resume banner while paused. Now-playing
cards say which app a phone reported, e.g. "via Spotify".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
- API and web images built with turbo prune; the API image also runs the
worker and migrations. Non-root, health-checked, Debian slim.
- docker compose up -d --build starts Postgres, Redis (persistent), a
one-shot migrate job, API, worker and web, in dependency order.
- Fix the API production build (tsup flags moved to a config file) and
bundle the migration script; silence Postgres NOTICE noise.
- npm run infra:up now starts only Postgres and Redis for hot-reload dev.
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