Show scan warnings as structured cards instead of long sentences

Warnings are now stored and served as structured data (title, detail,
slave/master items, total) and rendered as cards with a Slave -> Master
table: the first five rows, with "Show all" for the rest.

- Renamed folders highlight the characters that differ; non-ASCII
  characters get their code point in a tooltip, so look-alikes such as
  "-" (U+002D) and "‐" (U+2010) can be told apart.
- Songs in a different folder are grouped by folder pair with a song
  count, most affected first, instead of two long examples.
- Folders that only differ by name are no longer also reported as
  songs in a different folder.
- Warnings saved as plain text by earlier scans still display.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-23 21:49:22 -07:00
1 parent 1d31999793
commit 9e848cd765
13 files changed
+305 -92

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
package store
import "testing"
func TestDecodeWarnings(t *testing.T) {
old := decodeWarnings(`["Only 3 of 9 folders match."]`)
if len(old) != 1 || old[0].Title != "Only 3 of 9 folders match." {
t.Fatalf("plain-string warnings from older scans should decode as titles, got %+v", old)
}
cur := decodeWarnings(`[{"title":"t","items":[{"slave":"a","master":"b","count":2}],"total":5}]`)
if len(cur) != 1 || cur[0].Total != 5 || cur[0].Items[0].Count != 2 {
t.Fatalf("structured warnings should round-trip, got %+v", cur)
}
if ws := decodeWarnings(`[]`); ws == nil || len(ws) != 0 {
t.Fatalf("empty list should stay an empty (non-nil) list, got %#v", ws)
}
}