Commit Graph
11 Commits
Author SHA1 Message Date
nvrl b42780d24b Timed comments on the waveform
Each comment sits as a dot under the moment it was left at. When the music
gets there the dot pings and a bubble grows out of it on a spark, then drifts
up and fades; a mouse over a dot peeks at it. The server fetches the newest
60 comments from pl8list on demand and keeps them for 15 minutes. A setting
turns them off.
2026-11-02 23:18:44 +01:00
nvrl fbcff2266b Harden the backend: read pool, tighter limits, crawler guards
Feed, likes, artist, track and the session lookup run on four
read-only SQLite connections, so a slow feed no longer blocks writes.
Reads are capped at 120 a minute per user and the audio and image
proxy at 3000 a minute per IP. Session rotation happens once when
requests race, and the page, assets and legal pages skip the session
lookup. Events and follows for unknown tracks or artists return 404,
and radio only explores artists already in the catalogue.

The crawler queue is capped at 20k jobs, an empty home page counts as
a failure, and 25 failures in a row log an error that pl8list may
have changed its API. A guest who signs in with "stay connected" gets
up to 200 likes and follows saved to pl8list. The old Python server is
removed; it lives on in 54a436a.
2026-11-02 22:03:14 +01:00
nvrl 300674b8ac Listen without a pl8list account
"start listening" on the sign-in page makes a guest: a user with a negative
id and a session of its own, so the feed, likes, follows and radio work as
for anyone. Signing in with pl8list later moves the guest's plays, likes and
follows to the account. Signing out as a guest deletes it; guests whose
session ran out are purged hourly. New guests are capped at 10 per IP and
hour, and that limiter forgets IPs hourly. The privacy policy covers guests.
2026-11-02 13:20:22 +01:00
nvrl 53ac54b178 Let a dislike be taken back and look tracks up by their pl8list path
An undislike event cancels a dislike in the taste profile, for the undo on the
dislike toast. GET /local/track?handle=&slug= returns one known track, so a link
into the app (/?t=handle/slug) can open on it.
2026-11-01 19:11:57 +01:00
nvrl bcdca34244 Rename the site to pl8list rewind with a dubplate mark
The infinity sign becomes a dubplate: a wobbling riddim groove around a red label
with a rewind arrow, after the soundsystem rewind where a tune the dance loves is
pulled back and run again. Icons, manifest, titles, legal pages and the README use
the new name; cookie names, storage keys and the link key info keep pl8inf so no
one is signed out or loses settings.
2026-11-01 18:33:12 +01:00
nvrl 065c9980dc Draw a clean play/pause on the drop and make the app installable
The raised icon was cut into a mesh too coarse for its edges and did not line up
with the icon painted over it, so it came out doubled and jagged, and melting one
shape into the other went through blobby in-betweens. The icon is now only painted:
crisp and white on a dark disc like the page's round buttons, still turning with the
drop, and pause and play fade into each other as the new one grows in.

Some browsers only offer to install an app with a service worker. A minimal one at
/sw.js (served from the root so it covers the whole app) caches nothing and only
shows a short offline note instead of the browser's error page. The manifest gets
an id.
2026-11-01 16:08:59 +01:00
nvrl 76cfbb12ac Turn artist radio into a station sheet with reasons on each track
The radio button (r) opens a sheet with the artist, their tags and their closest
neighbours instead of switching the feed at once; tapping a neighbour walks the
artist graph. Starting jumps to the radio's first track, the top-bar chip shows the
station, each radio card says why it is there, and an end card offers the way back
when the radio runs dry.

GET /local/artist feeds the sheet and starts crawling a new artist's neighbourhood
(at most every 10 minutes per artist). Artist-radio tracks carry why: seed, related,
or via the neighbour that leads to them.
2026-11-01 14:32:51 +01:00
nvrl f6c505907a Add discovery modes, track tags and an artist graph
Tracks get tags from their title, description, artist name and BPM
(styles such as riddim, versions such as VIP/dub/flip, and tempo), stored
in track_tags and shown on cards. Related pages the crawler reads are kept
in track_related, which links artists to each other.

The feed takes ?mode=: riddim (140 BPM, riddim artists and their graph
neighbours first), fresh, deep cuts, and an artist radio that plays an
artist and the artists around them. Opening a radio crawls that artist's
related pages first. The crawler searches riddim-adjacent terms first.

The filters panel picks the feed, each card has a radio button (r), and a
chip in the top bar returns to the normal feed.
2026-11-01 13:46:52 +01:00
nvrl b4470850bf Rebuild front end in TypeScript with a pager, crossfades and reactive visuals
The front end moves to web/ (Vite, TypeScript, no framework) and is built
into web/dist, which the server embeds. hls.js is bundled from npm and
loaded on demand instead of vendored.

The feed is a custom pager instead of CSS scroll snapping: drag and flick
with a spring that keeps the finger's speed, a rubber band at the ends,
one page per wheel or trackpad gesture, and only three cards in the DOM.

Playback uses two decks. The next track is buffered shortly after the
current one starts and tracks crossfade through a Web Audio graph; on iOS,
where that would break lock-screen playback, they switch with a cut. An
analyser drives a bass pulse of the artwork and its glow, and each card
takes its accent colour from the artist image. Styles start from design
tokens and respect reduced motion.

Docker gets a Node build stage; legal pages use their own small stylesheet.
2026-11-01 12:57:17 +01:00
nvrl 27225332fb Add opt-in pl8list connection with library sync, follows and resync
At sign-in users can choose to stay connected to pl8list on that device.
The pl8list session is then stored in the device's session row, encrypted
with XChaCha20-Poly1305 under a key derived from the device's local token
(only the browser has it) and a server key kept outside the database. It
is only decrypted during that device's own requests, re-encrypted when the
token rotates or pl8list sends a new cookie, and deleted when pl8list
rejects it.

Every sign-in now syncs the personal feed, saved tracks (imported as likes)
and saved artists (imported as follows). Connected devices resync on use
every six hours and push new likes and follows to the pl8list library with
addOnly, so nothing is ever removed there. Without the connection, the
resync button runs the email-code sign-in again.

Artists can be followed from each card (f); follows feed the ranker. The
account menu shows the sync state, resync and disconnect. Privacy policy,
export and account deletion cover the new data.
2026-11-01 12:36:00 +01:00
nvrl 54a436ae48 Rewrite backend in Rust, add Impressum, privacy policy and account tools
New axum server in backend/ with the same HTTP API and database as the
Python one. The pl8list session is only held in memory during the feed
crawl and logged out afterwards. Local sessions use a __Host- cookie,
30-day idle expiry and weekly token rotation; the old cookie is migrated.

The audio and image proxy only accepts known parameters, fixes the content
type, follows redirects only to pl8list's file host and caches segments and
images on disk. Strict security headers, origin checks on POST, body and
concurrency limits.

/impressum and /datenschutz are rendered from env variables. The account
menu offers data export, sign out on all devices and account deletion.
hls.js is self-hosted and artist images are proxied, so the browser only
talks to this server. The Docker image builds a static binary on scratch.
2026-11-01 12:02:37 +01:00