docs: plan-era "v1", "Task N" and "Phase N" wording restated as facts (#6277)
release / govulncheck (push) Successful in 25s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m47s
release / integration (push) Successful in 4m35s
release / android (push) Successful in 7m5s
release / Build signed APK (releases and dev) (push) Successful in 7m16s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m1s
release / Verify release artifacts (tag releases only) (push) Skipped

The marker list behind the earlier server and web passes did not catch
"for v1", "Task 5", "Phase 2" or "today: Flutter". Those comments now say
what the code does.

Corrected: coverart said "both v1 providers" ship on. There are four
providers (MBCAA, TheAudioDB, Deezer, Last.fm), and Last.fm, which needs a
key, ships off.

Non-comment tokens unchanged against HEAD: 10 Go files, 7 TS/Svelte files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 13:05:03 -04:00
co-authored by Claude Opus 5.5
parent c96231106f
commit 466912e202
17 changed files with 31 additions and 32 deletions
+4 -5
View File
@@ -41,9 +41,8 @@ func TestLibrarySync_AfterArtistUpsert_ReturnsHydratedPayload(t *testing.T) {
truncateLibrary(t, pool)
u := seedUser(t, pool, "alice", "x", false)
// Seed an artist + write a change row in the same tx — that's the
// invariant the scanner wiring (Task 5) will enforce, and the
// handler relies on.
// Seed an artist + write a change row in the same tx — the invariant
// library writes keep, and the handler relies on.
ctx := context.Background()
tx, err := pool.Begin(ctx)
if err != nil {
@@ -122,8 +121,8 @@ func TestLibrarySync_DeleteIsReflected(t *testing.T) {
u := seedUser(t, pool, "alice", "x", false)
// Just write a delete change row directly (no underlying mutation).
// Scanner wiring (Task 5) is what couples real deletes to LogChange;
// here we test the handler reflects what's in the log.
// Library writes couple real deletes to LogChange; here we test the
// handler reflects what's in the log.
ctx := context.Background()
tx, _ := pool.Begin(ctx)
_ = sync.LogChange(ctx, tx, sync.EntityArtist, "deadbeef-dead-beef-dead-beefdeadbeef", sync.OpDelete)
+1 -1
View File
@@ -23,7 +23,7 @@ type updateProfileReq struct {
// nil = leave unchanged; non-nil empty string = clear; non-nil
// non-empty = update to that value. Note: distinguishing
// omit-vs-clear at JSON-decode time requires custom unmarshaling
// or a sentinel; for v1 we use the simpler "empty string clears"
// or a sentinel; we use the simpler "empty string clears"
// semantics: any field present in the JSON body is applied.
DisplayName *string `json:"display_name"`
Email *string `json:"email"`
+3 -3
View File
@@ -32,7 +32,7 @@ type Provider interface {
RequiresAPIKey() bool
// DefaultEnabled is whether a freshly-installed Minstrel ships
// with this provider on. Both v1 providers return true.
// with this provider on. Providers that need an API key ship off.
DefaultEnabled() bool
// Configure receives the admin's settings (api_key, enabled
@@ -102,8 +102,8 @@ type ArtistArtProvider interface {
// TestableProvider is an opt-in capability: providers that can answer
// "is my config working?" without performing a full enrichment cycle.
// Used by the admin Test-Connection button. v1: both MBCAA and
// TheAudioDB implement this.
// Used by the admin Test-Connection button. MBCAA and TheAudioDB
// implement this.
type TestableProvider interface {
Provider
TestConnection(ctx context.Context) error
+2 -2
View File
@@ -23,8 +23,8 @@ const (
// theAudioDBMinPeriod enforces TheAudioDB's documented free/test
// rate limit (2 req/sec). Patreon supporter keys allow higher
// rates; we don't differentiate in v1 — operators with supporter
// keys experience the conservative 2 req/sec ceiling.
// rates; we don't differentiate — supporter keys get the same
// conservative 2 req/sec ceiling.
theAudioDBMinPeriod = 500 * time.Millisecond
// theAudioDBMaxRetries is the number of retry attempts on 429 /
+5 -4
View File
@@ -1,5 +1,6 @@
// Package eventbus is an in-process publish/subscribe bus for real-time
// events the server emits to subscribed clients (today: Flutter via SSE).
// events the server emits to subscribed clients (the web and Android apps,
// over SSE).
//
// Writers (likes handler, requests reconciler, scanner, etc.) call Publish
// to announce a state change. Each connected SSE client holds a
@@ -13,9 +14,9 @@
// - No persistence. A client that connects after an event was emitted
// will not see that event. SSE clients are expected to fetch a fresh
// snapshot via the normal /api/* endpoints on (re)connect.
// - No cross-process broadcast. v1 runs as a single Go binary; a future
// multi-instance deployment will need a real broker (NATS / Redis
// pub-sub / etc.) but that's out of scope here.
// - No cross-process broadcast. Minstrel runs as a single Go binary; a
// multi-instance deployment would need a real broker (NATS / Redis
// pub-sub / etc.).
package eventbus
import (
+2 -2
View File
@@ -74,8 +74,8 @@ func (s *SMTPSender) Send(ctx context.Context, to, subject, textBody, htmlBody s
// We respect the use_tls flag: if true, we require STARTTLS via
// smtp.SendMail's default behavior (which negotiates STARTTLS when
// the server advertises it). For implicit-TLS (port 465 style),
// we'd need a custom dial — skip that complexity for v1 and rely on
// STARTTLS via port 587 (the default).
// we'd need a custom dial; instead this relies on STARTTLS via port
// 587 (the default).
dialCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
_ = dialCtx // smtp.SendMail doesn't accept ctx; the timeout is enforced via Dialer below
+1 -1
View File
@@ -92,7 +92,7 @@ type CandidateSourceLimits struct {
UserCoplay int
}
// DefaultCandidateSourceLimits returns the v1 hardcoded constants per spec.
// DefaultCandidateSourceLimits returns the hardcoded per-source limits.
func DefaultCandidateSourceLimits() CandidateSourceLimits {
return CandidateSourceLimits{
LBSimilar: 30,
+1 -2
View File
@@ -1,8 +1,7 @@
package recommendation
// SimilarityWeights balances the per-axis contribution to the weighted Jaccard
// score. v1 hardcodes the defaults — operators cannot tune via YAML. If
// telemetry justifies it, expose under recommendation.similarity.* later.
// score. The defaults are hardcoded; there is no config key for them.
type SimilarityWeights struct {
TagsWeight float64
ArtistsWeight float64
+2 -2
View File
@@ -48,8 +48,8 @@ func parseUUID(s string) (pgtype.UUID, bool) {
return u, u.Valid
}
// MusicFolder matches Subsonic's /rest/getMusicFolders. v1 always reports a
// single folder because Minstrel has no multi-library concept yet.
// MusicFolder matches Subsonic's /rest/getMusicFolders. Minstrel always
// reports a single folder: it has no multi-library concept.
type MusicFolder struct {
XMLName xml.Name `json:"-" xml:"musicFolder"`
ID int `json:"id" xml:"id,attr"`
+1 -1
View File
@@ -50,7 +50,7 @@ func TestRegisterDuplicatePanics(t *testing.T) {
Register(&fakeProvider{id: "dup"})
}
// TestBuiltinProvidersRegister confirms the two v1 sources register with
// TestBuiltinProvidersRegister confirms the two built-in sources register with
// the expected identities/defaults. Runs against a fresh registry so it is
// independent of test ordering: re-register constructed instances.
func TestBuiltinProvidersRegister(t *testing.T) {