SQLite replaces Postgres, and two fixes from using the thing.
- The database is a file under ./storage instead of a second container. Ten
members never needed a database server, and the driver is pure Go, so the
build stays CGO_ENABLED=0 and the dependency count is unchanged. One bind
mount is now the whole backup: no pgdata, no healthcheck-gated depends_on,
no startup retry loop. Timestamps are UTC text, idle_ttl is seconds, the
divisive/unified boards carry their own stddev, and foreign keys are on by
pragma. Tests get a database file each and run without any setup
- Invites are copied, not clicked. An invite is something to send, and the
anchor opened the join form in the admin's own browser
- Feedback asks for more than faults: the footer reads "Ongelmia? Ideoita?
Palautetta?" and the page behind it invites ideas rather than only bugs
- Kuuntele YouTubessa opens in a new tab, so a half-typed review survives it
Lyrics, versioning, and the song page's fact line.
- Lyrics are suggested at submission and never imposed: the worker makes one
LRCLIB lookup, and Hae sanoitukset re-queries with whatever title and artist
are typed. Neither overwrites what the submitter wrote. They live on the
submission, travel to the song at publish, and stay editable after the song
locks — the lock freezes what a song claims to be, and nobody reviewed the
lyrics
- The review strip reads and writes side by side: lyrics left, review right.
Synced LRC highlights the playing line and seeks on click; plain text scrolls
continuously with a nudge knob. Following can be turned off
- CalVer YYYY.MM.DD-N, injected from the git tag with -ldflags, shown in the
footer, the startup log and /healthz
- The song page's metadata became four labelled cells instead of one flat run
of five different kinds of fact
Fixes: publishing wiped lyrics the worker had just fetched (a request that
omitted a field cleared it), lyric auto-scroll landed in the wrong place, and
the fader shifted the deck sideways at score 100.
Supersedes decision 17, which expected endpoints to appear one at a time.
Nothing calls /api at all, so even that would be handlers with no callers and
golden tests guarding shapes nothing reads.
The contract stays in the spec as a design — it is what stops the shape
changing under a future client — marked as not built so the spec doesn't
claim behaviour the code lacks. later.md records what to build first when a
consumer appears.
The theme handoff encoded as CSS custom properties rather than a Tailwind
config, since there is no Tailwind here. Palette, spacing, radii, shadows and
motion follow it as written; docs/theme.md lists what differs and why.
- Oswald vendored as a 21KB variable woff2, latin subset, no CDN. Its phantom
weight 900 resolved to 700 — loading a weight you don't have is what made
the brand render differently per platform
- color-scheme: dark makes the native audio element fit the palette, which
was the handoff's complaint about it
- Songs are text cards rather than artwork tiles, because there is no
artwork. The unreviewed state keeps its red-brown border and gains a badge,
so it is never carried by colour alone
- Nav is the three-column grid; the mobile menu is <details>, no JS
- Flash messages became bottom-right toasts
Favicon carried over from the Nuxt project.
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.