feat(v3 library): batch→popup handoff + English-base romaji (metadata-curation capstone) (#782)

* feat(v3 library): click a "No match" badge to fix it — batch → popup handoff

Connects the two halves: the "No match" badge (the unmatched pile) now opens the
Fix-metadata popup for that song in one click, instead of right-click → menu.
The resting badge becomes interactive (pointer-events-auto + hover), carrying a
data-meta-fix hook; wireCards opens window.__fbFixMatch(playTarget) on click and
stops propagation so it doesn't also play the card. Batch tile states stay
non-interactive. Loop becomes: Unmatched filter → see the pile → click one →
fix it. tailwind.min.css regenerated for the badge's hover classes.

* feat(v3 library): show the author's romaji, not blank/native script (English base)

Two changes so an English-speaking base never sees a blank name or native script:

- Filename romaji fallback: a blank-artist CDLC pack ("Artist_Title_v1_p") shows
  nothing useful (artist blank; title = the raw filename), and a match fills it
  with kanji/kana. query_page + pack_fields now surface the author's own romaji
  parsed from the filename ("Junko Yagami — BAY CITY") when the pack has no
  artist of its own — display-only, keyset-safe (raw title stashed for the
  cursor), a real pack artist or a user override still wins.
- Smart adopt: "Use these values" now KEEPS the readable romaji name + title the
  card already shows and takes only album/year/genre (+ art via the pin) from the
  match, so identifying a Japanese song gives "Junko Yagami — BAY CITY — FULL MOON"
  with the right cover, never native script.

Tests: romaji fallback fires for a blank-artist CDLC pack (grid + pack_fields
agree) and is left alone when the pack has a real artist.
This commit is contained in:
ChrisBeWithYou
2026-07-05 23:35:58 +02:00
committed by GitHub
parent 1a8540935b
commit 6aaa2dcf47
4 changed files with 77 additions and 11 deletions
+10 -3
View File
@@ -511,10 +511,17 @@
// then land on Details pre-filled for review.
async function useTheseValues(song, cand) {
if (!cand) return;
// Smart adopt for an English base: KEEP the readable name + title the card
// already shows (the author's romaji, e.g. "Junko Yagami / BAY CITY") — the
// match is often native script (kanji/kana). Take only what the pack lacks
// — album / year / genre — from the match; the pin below still brings the
// correct art + identity. The user can still edit any field.
song._pendingDetails = {
title: String(cand.title || ''), artist: String(cand.artist || ''),
album: String(cand.album || ''), year: String(cand.year || ''),
genre: (Array.isArray(cand.genres) && cand.genres[0]) ? String(cand.genres[0]) : String(cand.genre || ''),
artist: String(song.artist || cand.artist || ''),
title: String(song.title || cand.title || ''),
album: String(cand.album || song.album || ''),
year: String(cand.year || song.year || ''),
genre: String((Array.isArray(cand.genres) && cand.genres[0]) || cand.genre || ''),
};
try {
await post('/api/enrichment/review/' + enc(song.filename) + '/pick', { candidate: cand });
+20 -6
View File
@@ -515,15 +515,22 @@
done: ['bg-fb-good/90 text-black', '✓ Updated', ''],
nochange: ['bg-black/60 text-fb-textDim', '— No match', ''],
// Resting indicator: subtle, so a mostly-unmatched library isn't a
// wall of loud badges; points at the manual fix.
nomatch: ['bg-black/60 text-fb-textDim', 'No match', 'No metadata match found — right-click to fix it by hand'],
// wall of loud badges. Clickable — a one-click handoff into the
// Fix-metadata popup for this song (see the [data-meta-fix] wiring).
nomatch: ['bg-black/60 text-fb-textDim', 'No match', 'Click to fix the metadata by hand'],
};
const conf = M[st] || M.queued;
const fixable = st === 'nomatch'; // resting badge → opens Fix-metadata
// top-10 clears the tuning chip (top-2) in both normal and select mode;
// z-20 sits it above the art. Non-interactive so it never eats a click.
return '<span class="v3-meta-tile absolute top-10 left-2 z-20 ' + conf[0] +
' text-[0.5625rem] font-bold px-1.5 py-0.5 rounded-sm leading-tight pointer-events-none"' +
(conf[2] ? ' title="' + conf[2] + '"' : '') + '>' + conf[1] + '</span>';
// z-20 sits it above the art. Batch states are non-interactive; the
// resting "no match" badge is the handoff into the popup.
const cls = 'v3-meta-tile absolute top-10 left-2 z-20 ' + conf[0] +
' text-[0.5625rem] font-bold px-1.5 py-0.5 rounded-sm leading-tight ' +
(fixable ? 'pointer-events-auto cursor-pointer hover:bg-fb-primary hover:text-white transition-colors' : 'pointer-events-none');
return '<span class="' + cls + '"' +
(fixable ? ' data-meta-fix="1"' : '') +
(conf[2] ? ' title="' + conf[2] + '"' : '') +
'>' + conf[1] + '</span>';
}
// After a song is scored, the badge for that card is stale until the next
@@ -1467,6 +1474,13 @@
e.stopPropagation();
openChartsDrawer(e.currentTarget.getAttribute('data-charts'), song);
});
// "No match" badge → straight into the Fix-metadata popup for this
// song (the batch → fix handoff). stopPropagation so it doesn't also
// trigger the card's play. Follows the displayed chart, like the menu.
el.querySelector('[data-meta-fix]')?.addEventListener('click', (e) => {
e.stopPropagation();
if (window.__fbFixMatch) window.__fbFixMatch(playTarget);
});
// Artist line → the artist page (PR-B). In select mode the grid's
// capture-phase toggle intercepts first, so selection still wins.
el.querySelector('[data-v3-artist]')?.addEventListener('click', (e) => {