mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-19 06:52:38 +00:00
The stats-recorder relays URL-encoded filenames (encodeURIComponent: '/'→'%2F', ' '→'%20') and POST /api/stats stored them verbatim, but the `songs` table — and every stats read that filters on `filename IN (SELECT filename FROM songs)` — keys on the decoded library path. So recorded plays landed under a non-matching key and were dropped by the filter: the profile "Your best scores" panel, the library accuracy badges (/api/stats/best) and "Jump back in" (/api/stats/recent) all read empty despite real history. PR #549/#550 wired the panel correctly; this fixes the data layer underneath it. - Canonicalize the filename to its decoded form on the write path (_decode_song_filename in api_record_stats). This also lets the arrangement-count bound resolve the real song. - One-time idempotent backfill (_migrate_decode_stat_filenames) that decodes existing rows, merging PK collisions with best=max / plays=sum / last-wins semantics. - Regression tests: encoded write surfaces in top/best/recent + per-song read; arrangement bound still applies; migration decodes + merges legacy rows and is idempotent. Verified against a copy of a real profile DB: top_stats went 0→5 rows, best-accuracy map 0→12, zero encoded ghosts left. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>