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

+3 -3
View File
@@ -331,11 +331,11 @@ func (s *Server) handleValidatePair(w http.ResponseWriter, r *http.Request) {
in.apply(&p)
warns, err := s.eng.NormalizePair(&p)
if err != nil {
writeJSON(w, 200, map[string]any{"ok": false, "error": err.Error(), "warnings": []string{}})
writeJSON(w, 200, map[string]any{"ok": false, "error": err.Error(), "warnings": []store.Warning{}})
return
}
if warns == nil {
warns = []string{}
warns = []store.Warning{}
}
writeJSON(w, 200, map[string]any{"ok": true, "warnings": warns, "pair": p})
}
@@ -374,7 +374,7 @@ func (s *Server) handleSavePair(w http.ResponseWriter, r *http.Request) {
}
p, _ = s.st.Pair(r.Context(), p.ID)
if warns == nil {
warns = []string{}
warns = []store.Warning{}
}
writeJSON(w, 200, map[string]any{"pair": s.view(r, p), "warnings": warns})
}