Editing artist/album_artist/album now relocates the file to its new
<music_folder>/Music/Artist/Album/ home (folders created as needed,
"Unknown Artist" fallback via the same organized_destination the
importer uses), deliberately relaxing the old never-move rule:
- LibraryManager.organize_root() is the shared definition of the
managed tree; MainWindow._music_import_dir delegates to it.
- Only files already inside the organize root are managed; external
files get their tags edited but are never pulled into the tree.
- The move rides the existing undo Command maps as a "location" field:
Ctrl+Z moves the file back, redo re-moves, both best-effort (a failed
physical move drops "location" so memory tracks the real file).
- A failed move applies the edit, keeps the old path, and reports via
the new file_move_failed signal (status bar), mirroring
tag_write_failed. Collisions get the importer's " 1" suffix.
- Directories left empty are pruned up to (never including) the root.
- conflict_resolver._merge_track_fields now carries "location" with
the newest-date_modified rule, so a rename-move on one machine
propagates through both the sync-conflict merge and the live
reload_from_disk reconcile on the other.
- The on-disk basename is kept verbatim (no re-sanitizing), so files
named on macOS aren't gratuitously renamed by a tag edit.
Same-filesystem rename keeps the player's open fd valid, so moving the
currently-playing track is safe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The table already computed and emitted the drop row for external URL
drops, but PlaylistView._on_files_dropped threw it away, so imported
files always appended. Thread it through: playlist_view.files_dropped
gains a row slot (None = append), MainWindow.import_files and
file_importer.import_paths take an optional position and hand it to
add_tracks_to_playlist, which already supported positional insert.
Files already in the library dedup to the existing track and insert at
the drop position without duplicating the library entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New prefs: visualizer_mode (on/dim/off, saved on every click) and
color_visualizer_gray (None = keep the derived alternateBase look).
- VisualizerWidget takes prefs (optional, so existing call sites/tests
keep working); restores its mode at startup and validates junk values.
- New "Visualizer (gray mode):" row in the Preferences color sliders;
theme.default_gray mirrors the widget's 0.88-lightness derivation so
an untouched slider shows the current appearance.
- TransportBar.refresh_theme() repaints the visualizer so slider drags
preview live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>