# 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. Designed and decided (decisions 45), not built. **Coverage, measured 2026-07-31** rather than assumed. An earlier version of this page guessed LRCLIB would miss nearly all Finnish music. It does not: | Search | Results | With `syncedLyrics` | |---|---|---| | Nightwish | 20 | 20 | | Eppu Normaali | 20 | 13 | | CMX | 15 | 12 | | Popeda | 20 | 8 | **LRCLIB** (`lrclib.net`) needs no API key. `/api/get` matches on artist, track and duration within ±2 s and returns `syncedLyrics` — real LRC with `[mm:ss.xx]` per line — alongside `plainLyrics`; `/api/search?q=` is the looser fallback. Go's side is `net/http` and `encoding/json`, so the dependency budget survives, and it is treated exactly like ffmpeg and yt-dlp: a timeout, allowed to fail, never blocking anything. **Where it happens: at submission, as a suggestion.** - The worker attempts one automatic lookup after conversion, using whatever metadata exists. - The waiting page has a **Hae sanoitukset** button that re-queries with whatever is currently typed in the title and artist fields. That is the answer for messy tags — `Sentenced Noose` from a YouTube upload will not match until the submitter fixes it, and the automatic attempt would otherwise just look broken. - Neither ever overwrites text the submitter has typed. They can accept the suggestion, edit it, or leave the field empty. **Storage:** one nullable `lyrics text` column on both `submissions` and `songs`, copied across at publish. LRC or plain is told apart by whether the first line starts with `[`, so no second column and no flag. **Nothing has launched, so this goes into `001_init.sql` rather than a migration 002.** **Lyrics stay editable after the song locks** — the lock exists so the thing people reviewed stops changing, and nobody reviewed the lyrics. It also means someone can paste them for an old song a year later, which is when this feature is most useful. **Playback:** - **Synced hit** → highlight the current line properly, driven by the transport's `timeupdate`. - **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. **Still open:** where the panel lives on the song page. That page's job is now *listen and write*, and a scrolling lyrics panel competes with the review textarea for both space and attention — a collapsed panel under the player is the starting guess, not a decision. 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 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 `