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.
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
# 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, 200–400 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 ~5–10 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.
|
||||
Reference in New Issue
Block a user