Files
Levyraati26_go/docs/later.md
T
Esa Kataja 2474b42175 Add project docs, glossary, and licence
The Nuxt version is being rewritten in Go. This is the design that came out
of it, split by how each part ages:

- CONTEXT.md    glossary, English identifiers and Finnish UI wording
- docs/spec.md  behaviour: rules, submission pipeline, routes, API contract, schema
- docs/decisions.md  why, append-only
- docs/later.md      deliberately not in v1

testdata/ytdlp-noose.json is a real yt-dlp dump used to test metadata
prefill against a video with no track, artist or album tags.
2026-07-31 19:37:22 +03:00

138 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Later
Ideas with their reasoning, kept so the thinking isn't redone from scratch. **None of this is v1
work**, and none of it should shape v1 code beyond what the API contract already allows.
---
## Email: password reset and announcements
Wanted, blocked on not having a reliable SMTP server. Members already have addresses in their profile
(decisions 31), so the data is there when the transport is.
Two things to remember when it happens:
- **v1 cannot verify addresses.** Some of them will be typos, and a password reset to a typo'd
address is a locked-out member. The admin already resets passwords, so that is the fallback — but
the reset flow should not assume the address works.
- Announcements need an opt-out, or they become the reason someone stops reading them.
---
## Lyrics with scaled autoscroll
Fetch lyrics and scroll them in time with the audio.
- **LRCLIB** (`lrclib.net`) is a community database with no API key, and its responses include
`syncedLyrics` — real LRC with `[mm:ss.xx]` per-line timestamps — alongside `plainLyrics`. Query by
track, artist and duration, all of which are already on the song row. So a decent share of songs
need no faked timing at all.
- **Synced hit** → highlight the current line properly. **Plain hit or manual paste** → distribute
lines evenly across `duration_seconds` and scroll the block *continuously without highlighting a
line*. Highlighting makes every second of drift read as a bug, and drift is guaranteed — intros and
outros alone break a uniform mapping.
- The Web Animations API does the whole thing including seeking: build the scroll animation with
`duration_seconds`, `pause()` it, and bind `play`/`pause`/`seeked` on the audio element. No timers,
no drift accumulation.
- **Leave a nudge knob** — a ±10 s offset slider, remembered per song in `localStorage`. Uniform
distribution models a song no real song obeys, and one drag while listening beats any heuristic.
- Storage: one nullable `lyrics text` column. LRC or plain — tell them apart by whether the first
line starts with `[`, so no second column and no flag. Fetched best-effort in the publish worker.
- **Add a paste box to the submitter's edit form.** The genre list contains *Finnish*,
*Experimental* and *Just Plain Weird*; LRCLIB will miss nearly all of it, and for those songs the
textarea is the entire feature.
- Copyright posture is the same as the YouTube note: private app, ten people, written down
deliberately.
---
## AI features (Gemma-class local model)
The constraint that shapes every idea: **E2B-class multimodal models are speech models.** The audio
encoder targets ASR and spoken-audio QA. Music is out of distribution — genre calls are near
coin-flips, "describe this track" produces beige copy, and singing over instrumentation is a worst
case for ASR. Encoders also work in ~30 s windows, so a four-minute song is a chunk loop, and on CPU
beside Postgres and ffmpeg that is minutes per submission.
So the ideas that use the model to be *correct* are the weak ones, and the idea that uses it to be
*entertaining* is the strong one:
- **A bot reviewer** — a member account that listens to each published song and posts a score and a
few sentences in character. The model's weaknesses become the feature; nobody files a bug because
the robot was wrong about a friend's doom metal demo. It is a `users` row and a `reviews` row, no
new domain concepts, and the reveal rule already makes its take something you unlock by reviewing —
which quietly rewards the behaviour the whole app exists for. Needs: a sharp persona, a
~3-sentence cap, exclusion from the reviewer leaderboards and from the ≥3-review threshold.
- Weak: genre suggestion (buys one click, and a confident wrong default is worse than an empty
dropdown), auto-drafted introductions (same objection as YouTube descriptions), "is this actually
music" screening (ffmpeg already proves it is audio, and the admin can delete).
- If **duplicate detection** is ever the real want, that is Chromaprint/AcoustID, not an LLM.
- Architecture, when it happens: an Ollama or llama.cpp sidecar called over HTTP from the worker with
a timeout — treated exactly like ffmpeg and yt-dlp, an external process allowed to fail. Go's side
is `net/http` and `encoding/json`, so the dependency budget survives. Never block publish on
inference.
---
## Native client
Deferred; the shape depends entirely on what it is *for*.
- **Launcher icon and media keys** → a PWA. `manifest.json` plus ~10 lines of
`navigator.mediaSession` gets media-key control, and on Linux the browser exposes it over MPRIS, so
the song appears in the GNOME/KDE media widget with artwork. Zero packaging.
- **Playback surviving navigation** → a webview shell, and this is *not* free even then: a webview is
one browser context, so in-app navigation tears down `<audio>` exactly like a tab does. The fixes
are an outer shell page with the app in a same-origin `<iframe>`, fetch-based navigation, or audio
played by the native layer with the webview as UI only.
- **Offline listening** → the only motivation nothing else covers, and a genuinely different project:
download endpoint, local cache management, sync.
- **Not Electron.** It bundles Chromium and Node — ~150 MB on disk, 200400 MB idle RAM, and Node
back in the build, which is the exact dependency surface the rewrite exists to escape. The
system-webview equivalents are ~510 MB: **Wails** (Go, same language as the server) is the pick,
with Tauri as the Rust alternative.
- Packaging inverts with that choice: a plain Go binary → **AppImage** (one `appimagetool` step, one
file to hand out). A webview shell links WebKitGTK, which AppImage bundles badly and the GNOME
**Flatpak** runtime provides for free.
- Worth settling first: the usage pattern this app implies is opening a song, listening, and
reviewing it right there — one page, playback never interrupted. If that is the real behaviour, the
entire rationale reduces to wanting a launcher icon, and that is a `.desktop` file with
`chromium --app=…`.
---
## Custom audio player
Native `<audio controls>` is plain, but it is also correct — keyboard-operable, screen-reader
labelled, media-key aware. Replacing it is cheap and purely client-side: drop the `controls`
attribute, keep the same element, and drive it through `play()`, `pause()`, `currentTime`,
`duration`, and `timeupdate`. Playback, buffering, seeking and Range requests keep working untouched,
and the server changes not at all.
Two things keep it cheap and both are already in v1: the player lives in one template partial, and
`duration_seconds` is on the song row. Skip precomputed waveform peaks — if a SoundCloud-style
waveform is ever wanted, backfilling a couple hundred `.ogg` files is a ten-line script.
When building it, re-earn what the native controls gave away for free: keyboard operation, visible
focus states, and `aria-label`s on the buttons. That is the part custom players routinely drop, and
it is not optional.
---
## Stats not built
Wanted at some point, deliberately out of v1: Top 10 by genre, average given versus received per
member, a reviewer agreement matrix, genre distribution, activity over time. The agreement matrix in
particular is an O(n²) query and a UI nobody has asked for twice.
Also here: pruning the count-based leaderboards once the queue has drained and they are all ties
(decisions 35).
---
## Filters on the browse list
`/songs` is newest-first with no filters. Once there are a couple of hundred songs, "which ones
haven't I reviewed" and "show me the metal" become real questions — one `WHERE` each, additive to the
API contract as query parameters. Worth doing when the scrolling annoys someone, not before.