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.
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.
The rim hue drifted with play time until it left the card's colour altogether, so a
pink card could carry a green drop. It now only sways around the accent. The edge
fades into the page, a halo in the accent pulses behind it like the plain artwork's
glow, the surface reflects the accent from above, and the title and the rest of the
card sit on top where the drop spills over them.
Dragging the drop turns it (sideways only on touch, where up and down turn the page).
A flick keeps it spinning; it slows down and a weak spring wobbles it back to face the
front. Spun fast it flattens a little and sloshes, and the artwork blurs into colour
where it is seen from the side. A spin is not a tap, so it does not pause.
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.
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.
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.
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.
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.
- Tap/click a track to play or pause, double-tap/double-click to like
with a heart burst; any key or tap starts playback
- Media Session API: lock-screen, notification and headphone controls,
with next/previous working while the screen is locked
- Drag the waveform to seek, hover shows the time
- "Not for me" button feeds a dislike signal into the ranker
- Share button (native share sheet or copy link)
- Likes newest first, each playable right away
- Filters remembered per browser, clear button, active indicator
- Panels close on outside click or Esc; help overlay on "?"
- Buffering spinner, paused badge, toast messages, first-run hint
- Installable as an app (manifest and icons)
- Refreshed README screenshots and controls
Endless TikTok-style feed over pl8list's Remix loaders. A guest crawler
builds a shared catalogue; a per-user ranker learns from likes, full
listens and skips. Users sign in with pl8list's email code; only a hash
of the local session token is stored. Ships as a hardened Docker image
with a Traefik override for pl8.narl.io.