Taste-profile fidelity (M160) + Songs-like row + home polish #109
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ships milestone #160 — Taste-profile fidelity via metadata enrichment (no sidecar) — plus adjacent home/discover polish. All 22 commits are CI-green on
dev(tip5749f48b).Milestone #160 — taste profile went from 2 facets → multi-dimensional
The profile started as artists + raw ID3 genre. It now scores across artists, enriched genre tags, era, and mood, plus a collaborative co-play candidate arm and time / weekday / device context conditioning — all in the single Go+Postgres binary.
track_tagscache → folded into the tag facet; admin tag-sources card (web + Android);EnrichedTagScaletuning knob; artist-MBID fallback.albums.release_date, additive scorer term,era_scaleknob.internal/mood) →taste_profile_moods, additive scorer term,mood_scaleknob.ContextAffinityScoreterm,context_time_weightknob.device_classon play-start; radio narrows the affinity cell to (daypart × weekday × device) via the latest-play device. Reusescontext_time_weight.play_events→user_cooccurrencesimilarity edges (Jaccard, worker-refreshed) → new candidate arm. Empty on single-user servers.Adjacent
Every operator-tunable value lives in the DB-backed web tuning lab. Enrichment-derived facets (mood, tags) and context axes degrade gracefully (neutral) where coverage is thin.
🤖 Generated with Claude Code
Promote the best-performing surface ("Songs like {artist}", ~8% skip / ~86% completion) out of the shared Playlists carousel into its own Home row on both Android and web, and widen the daily build from 3 to 6 mixes so the dedicated row shows a wider spread. Server (internal/playlists): - PickSeedArtists candidate pool 5 → 12; pickSeedArtistsForDay now takes songsLikeSeedCount (6) instead of a hardcoded 3. Graceful degradation and daily rotation preserved. Android (HomeScreen.kt): - New songsLikeSection + buildSongsLikeRow; PlaylistsRow takes a title so it renders both the "Playlists" and "Songs like…" rows. buildOnlineRow / orderedRealPlaylists no longer reserve the 3 songs-like slots. Offline shows cached mixes (available-first), hides the row when none. Web (+page.svelte): - Dedicated "Songs like…" row from songsLikeRow; dropped the 3-slot cap and removed songs-like from the Playlists carousel. Tests: seed_selection_test.go, BuildPlaylistsRowTest.kt, page.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Expose the tag-enrichment provider settings over the admin API, mirroring the cover-sources surface so a Last.fm key can be pasted and sources toggled from the web admin UI (rules #25/#27). - GET /api/admin/tag-sources — list providers + version - PATCH/api/admin/tag-sources/{id} — enable / set api_key - POST /api/admin/tag-sources/{id}/test — test connection - POST /api/admin/tag-sources/research — bump version, re-open settled rows for re-enrich - Thread tags.SettingsService through server.New (struct field, like RecSettings) → Router → api.Mount → handlers.tagSettings; main.go sets srv.TagSettings. Handler tests mirror admin_cover_sources_test (list / flip-bumps-version / key-only-no-bump / unknown-404 / non-admin-403 / not-testable-ok-false), integration-tier (skip without MINSTREL_TEST_DATABASE_URL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Widen keyless coverage: when a track's recording is untagged or has no recording MBID, fall back to the artist's tags (/ws/2/artist/{mbid} ?inc=tags), down-weighted 0.6 as a coarser signal. Recording tags still win outright when present. - ListTracksMissingTags returns a.mbid AS artist_mbid; TrackRef gains ArtistMBID; the enricher threads it through. - MB provider: recording-first, artist-fallback via a shared fetchEntityTags helper. A transient error at the recording step is returned (retry) rather than masked by the fallback. Enricher, registry, and settings are untouched — the pluggable design absorbs the wider lookup. Tests cover fallback-when-untagged, artist-only-when-no-recording-MBID, and recording-preferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Bring the tag-sources settings surface to the Android admin, over /api/admin/tag-sources — the operator can enable/disable each provider, paste an API key (e.g. Last.fm), and test the connection from the phone, mirroring the web integrations card. New vertical stack (mirrors the AdminUsers/AdminRequests pattern): - AdminTagSourcesApi (Retrofit, api/admin/tag-sources GET/PATCH/{id}/test) + UpdateTagSourceBody - AdminTagSourceWire / list envelope / TestTagSourceWire + domain AdminTagSourceRef / TagSourceTestResult - AdminTagSourcesRepository (shared Retrofit, .toDomain() at bottom) - AdminTagSourcesViewModel (@HiltViewModel, sealed UiState, optimistic toggle + key-save + per-row test result, network auto-recovery) - AdminTagSourcesScreen (MinstrelTopAppBar + PullToRefreshScaffold; per provider: Switch, password key field + Save, Test connection + result) - nav route + graph registration; AdminLanding gains a "Tag sources" section card (count = enabled providers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>