feat(locallibrary): tag editor remote lookup (MusicBrainz + Discogs)

Complete the tag editor with remote metadata auto-fill.

- qbz-integrations: add frontend-agnostic `discogs` (client + DTOs, Tauri-free)
  and `remote_metadata` (unified DTOs + the pure MusicBrainz/Discogs ->
  RemoteAlbum* converters). Copied from src-tauri so the Slint frontend can do
  remote lookup without depending on the Tauri binary; src-tauri is left fully
  untouched (the production build keeps its own copy — no Tauri-build risk).
- tag_editor.rs: search_remote (provider QbzSelect; MusicBrainzClient::new
  search_releases_extended / DiscogsClient search_releases, generation-guarded),
  select_result, apply_remote (get_release_with_tracks / get_release_metadata ->
  full metadata; overwrites album fields + positional per-track title merge;
  track-count-mismatch + rate-limit toasts), open_in_browser.
- TagEditorModal: a remote-lookup box (provider + Search, a horizontal result
  card strip with select highlight, Apply + open-in-browser). ADR-008: square
  badges, no pills.
- state.slint: RemoteResultItem struct + remote fields/actions on TagEditor*.

LocalLibrary Slint port is now feature-complete (scan/folders, artists images,
tag editor manual+remote, tracks bulk). BUILT BLIND — runtime smoke-test pending.

cc
This commit is contained in:
vicrodh committed 2026-05-29 15:32:16 -06:00
1 parent 5a076187e7
commit 3c2f46b756
8 files changed
+1723

No files matched your search

+30
View File
@@ -628,6 +628,23 @@ export struct TagTrackEdit {
track_number: string, // raw text (LineEdit); controller parses on save ("" = unset)
}
// One remote (MusicBrainz/Discogs) search-result card. Option fields map to
// a 0/"" sentinel + a has_* companion where the zero value is valid.
export struct RemoteResultItem {
provider: string, // "musicbrainz" | "discogs"
provider_id: string, // selection key + open-in-browser
title: string,
artist: string,
year: int,
has_year: bool,
track_count: int,
has_track_count: bool,
country: string,
format: string,
label: string,
catalog_number: string,
}
export global TagEditorState {
in-out property <bool> open: false;
in property <string> album_group_key; // == LocalAlbum.id == directory_path
@@ -644,12 +661,25 @@ export global TagEditorState {
in-out property <bool> saving: false;
in-out property <int> write_progress_current: 0;
in-out property <int> write_progress_total: 0;
// Remote lookup (MusicBrainz / Discogs).
in-out property <int> remote_provider_index: 0; // 0 musicbrainz, 1 discogs
in-out property <bool> remote_searching: false;
in-out property <bool> remote_loading: false; // apply (get full) in flight
in-out property <[RemoteResultItem]> remote_results: [];
in-out property <string> selected_result_id: ""; // "" = none
in-out property <bool> show_remote_panel: false;
in-out property <bool> has_searched: false;
}
export global TagEditorActions {
callback close();
callback save();
callback set-persistence(int); // QbzSelect index -> mode (controller maps)
callback set-provider(int); // provider QbzSelect index
callback search-remote();
callback select-result(string /* provider_id */);
callback apply-remote();
callback open-in-browser();
}
// Global toast state, driven by crate::toast. The Toast overlay (mounted