Skip to content

v3 library: "Refresh Metadata" button + per-view re-match + filename-artist seed - #764

Merged
byrongamatos merged 1 commit into
mainfrom
feat/v3-refresh-metadata
Jul 4, 2026
Merged

byrongamatos merged 1 commit into
mainfrom
feat/v3-refresh-metadata

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What

A media-server-style Refresh Metadata control on the Songs toolbar (beside ⟳ Refresh) — the metadata counterpart to a file scan. Where Refresh scans the folder for new files, this re-matches the songs you're viewing against MusicBrainz.

Behaviour

  • Re-matches the visible grid window — a per-view refresh, so it's visibly useful even on a library that's already been matched (the incremental background pass is a no-op there). The button doubles as Stop while a pass runs, with a batch progress bar and per-tile queued → working → done badges. User-pinned (manual) matches are never touched.
  • Filename-artist seed: a lot of community charts ship with a blank artist field, which text search can't match on (and the per-field floor rejects). When the pack's artist is blank, the matcher now derives artist/title from the CDLC Artist_Song-Title filename convention as a search seed only — the displayed values still come solely from the confirmed MusicBrainz match, so nothing estimated is presented as author-set. This rescues blank-artist packs that previously always failed.

API

  • POST /api/enrichment/rematch {filenames} — reset the given (visible) songs to unscanned (skips manual via the existing never-overwrite guard) + kick one pass; returns the queued filenames.
  • POST /api/enrichment/cancel — cooperative Stop; a cancel Event is checked between songs in the match + art phases.
  • POST /api/enrichment/states {filenames} — per-window {filename: match_state} (+ current + running) for the tile badges. Read-only.
  • GET /api/enrichment/status gains total / matched / current / cancelling.
  • Demo mode: cancel + rematch blocked (mutate/spend rate budget); states stays open (read-only).

Tests

enrichment_states_for, the three new routes, cancel-halts-a-pass-between-songs, kick-clears-a-stale-cancel, the filename parser, blank-artist seeding, and present-artist-not-overridden. Full enrichment + demo + scraper + context-menu + art suites green (162 passed).

Verification

Exercised end-to-end on a live preview against a 1002-song DB + real MusicBrainz: the pass progresses (running/total/matched/current at the ≤1/s throttle), Stop halts mid-pass (1 of 8 before breaking), cancel-when-idle is a clean no-op, and the filename seed rescued a real blank-artist pack (failed → matched). Frontend parses + is served correctly; the in-app visual pass (button/bar/tiles/toast pixels) is still worth an eyeball.

Honest scope note: text enrichment has a real data-coverage ceiling — e.g. a library heavy in obscure romanized-Japanese city-pop returns 0 MusicBrainz candidates regardless of a correct artist/title seed. Those need audio-fingerprint (#759) or manual Fix-match (the metadata popup, next).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a batch metadata refresh flow with visible progress, per-item status badges, and the ability to stop an in-progress refresh.
    • Added support for refreshing metadata on selected items while keeping manual selections unchanged.
  • Bug Fixes
    • Demo mode now blocks additional metadata write actions.
    • Improved matching for items with incomplete artist information by deriving details from the filename when needed.
    • Cancellation now stops refreshes cleanly and avoids starting with stale cancel state.

…ename-artist seed

Adds a media-server-style "Refresh Metadata" control to the Songs toolbar
(beside "⟳ Refresh") — the metadata counterpart to a file scan.

- Re-matches the songs currently SHOWN (the visible grid window) against
  MusicBrainz: a per-view refresh that's visible even on an already-matched
  library. The button doubles as Stop while a pass runs; a batch progress bar
  + per-tile queued→working→done badges show what's happening. User-pinned
  `manual` matches are never re-matched.
- Backend: POST /api/enrichment/{cancel,states,rematch}; /status gains
  total/matched/current/cancelling; a cooperative cancel Event is checked
  between songs in the match + art phases so Stop halts without waiting for
  the whole queue. `states` is read-only (open); `cancel`/`rematch` are
  demo-blocked.
- Matcher: when a pack's `artist` field is blank (common in community
  charts), derive artist/title from the CDLC `Artist_Song-Title` filename
  convention as a SEARCH SEED so text matching can identify it — the displayed
  values still come only from the confirmed MusicBrainz match, nothing
  estimated is shown as author-set. Rescues blank-artist packs that otherwise
  always failed.

Tests: enrichment_states_for, the three new routes, cancel-halts-a-pass,
kick-clears-stale-cancel, filename parse, blank-artist seeding, and
present-artist-not-overridden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 51fb3d02-a602-48ea-af27-b16ceaa77c2c

📥 Commits

Reviewing files that changed from the base of the PR and between b6169af and 14ca943.

📒 Files selected for processing (3)
  • server.py
  • static/v3/songs.js
  • tests/test_enrichment_plumbing.py

📝 Walkthrough

Walkthrough

Server-side enrichment now tracks per-pass progress and supports cooperative cancellation, with new endpoints for cancel, rematch, and per-filename state queries. Matching falls back to filename-derived artist/title when manifest artist is blank. The Songs UI adds a metadata refresh button, progress bar, and per-tile badges polling these endpoints.

Changes

Enrichment cancellation, progress, and rematch workflow

Layer / File(s) Summary
Enrichment pass state and cancellation core
server.py
Adds _enrich_cancel event and expanded _enrich_status fields (total, matched, current); matching and art-evaluation loops check cancellation between items, clearing/resetting flags on start, stop, and completion.
Filename-derived matching fallback
server.py
Adds a regex-based filename parser deriving artist/title from CDLC naming when manifest artist is blank, and uses it in _enrich_one to seed text-search matching.
Enrichment API surface
server.py
Adds MetadataDB.enrichment_states_for(), extends /api/enrichment/status, adds /api/enrichment/cancel, /api/enrichment/rematch, /api/enrichment/states endpoints, and blocks the new write endpoints in demo mode.
Server-side test coverage
tests/test_enrichment_plumbing.py
Adds tests for states lookup, new endpoints, cancellation mid-loop, rematch requeue logic, filename parsing, and cancel-flag reset on kick.
Songs UI metadata refresh workflow
static/v3/songs.js
Adds a Metadata refresh button, progress bar, per-tile enrichment badges, and client polling/control logic wired to the new server endpoints.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SongsUI
  participant Server
  participant EnrichRunner

  User->>SongsUI: click Metadata button
  SongsUI->>Server: POST /api/enrichment/rematch
  Server->>EnrichRunner: reset filenames to unscanned, kick pass
  loop polling
    SongsUI->>Server: GET /api/enrichment/status
    SongsUI->>Server: POST /api/enrichment/states
    Server-->>SongsUI: progress + per-filename states
    SongsUI->>SongsUI: patch tile badges, update progress bar
  end
  User->>SongsUI: click Stop
  SongsUI->>Server: POST /api/enrichment/cancel
  Server->>EnrichRunner: set _enrich_cancel
  EnrichRunner->>EnrichRunner: stop after current item
  Server-->>SongsUI: running=false
  SongsUI->>SongsUI: clear badges, emit library:changed
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: a Refresh Metadata button, per-view rematch flow, and filename-based artist seeding.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v3-refresh-metadata

Comment @coderabbitai help to get the list of available commands.

@byrongamatos

Copy link
Copy Markdown
Contributor

Review pass (Claude Code). Solid. Backend: rematch/states cap the batch at 500 + string-filter inputs and look songs up by DB key (enrichment_song_row), so a crafted filename can't traverse the FS — an unknown name is simply skipped; rematch resets to unscanned with allow_manual_overwrite=False so user pins are preserved. The Stop button's cooperative cancel is a thread-safe Event checked between songs in both the match and art phases, and _kick_enrich clears it before launching so a stale cancel can't abort a fresh pass. The filename-artist seed (_artist_title_from_filename) is search-seed-only — displayed values still come from the confirmed match, and it returns None for non-CDLC names. Frontend badge poll loops are bounded (tick caps, stop on pass-end), matching the existing _watchScan pattern. 18 plumbing tests pass; node --check clean. CI red is the unrelated main-level starter-content bug. No change needed. (Codex second-opinion unavailable this session — rate limit — manual + test-backed.)

@byrongamatos
byrongamatos merged commit fa2d122 into main Jul 4, 2026
2 of 4 checks passed
byrongamatos added a commit that referenced this pull request Jul 5, 2026
#781)

* feat(v3 library): persistent "no match" badge + Unmatched quick filter

The Refresh-Metadata batch (#764) shows a transient per-tile "no match" only
while a pass runs, so the unmatched pile goes quiet at rest. Two additions make
it visible + reachable:

- Persistent per-card "No match" badge: query_page now marks each row
  `unmatched` (a cheap failed-set membership like favs/estd), and enrichBadge
  paints a subtle resting marker for those cards — tracked in a `_unmatched` set
  so a batch tile clearing falls back to it instead of wiping it. A live batch
  tile still wins while a pass runs.
- "Unmatched" toolbar toggle (local-only): one click applies the same filter as
  the drawer's Match → Unmatched (match_state='failed'), so the no-match pile is
  a click away right after a batch. Re-queries + reflects active state.

Test: query_page flags a failed row + the match=unmatched filter returns it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(v3 library): repaint persistent no-match badge after metadata tile-clear

_clearMetaTiles removed every .v3-meta-tile node — including the new
persistent 'No match' resting badge, which derives from _unmatched rather
than _metaTile. A metadata rescan's tile-clear therefore dropped the badge
until the next scroll re-rendered the card. Repaint it from _unmatched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants