forked from Keelrleek/SoulStream
SURFACES: panes add black, surfaces INSIDE panes add white. Every pane stands on 0.7075 black over the acrylic (.content 0.35 over .app 0.55), so a translucent black card nets 0.81 - darker than its own pane and inside the ~0.85 band already rejected once on the artist page. Solid mode's ladder is measured UPWARD from #121212 (+6/+18/+24), so glass elevates with WHITE: new --glass-surface/-hover/-press/-hairline/-well/-ring under :root.glass (0.03/0.08/0.11/0.10/0.18), which is independently the vocabulary .btn-ghost:hover and .ax-ghost:hover already shipped. The four solid tokens are NEVER redefined - they are consumed inside color-mix() and gradient stops where an alpha multiplies through and kills the call site (0.74 x 0.03 = an invisible chip; the album row's text-overflow fade works by BEING opaque), and they paint modal chrome that has to stay solid. 129 additive :global(:root.glass) declarations across 18 files, all 129 verified inside a glass block by walking the diff - solid mode byte-identical, confirmed structurally, by computed style with the class off, and visually. Three of them are not colour swaps: the album row's .rowbtns shim was OCCLUDING the last ~19px of the listeners bar and nothing translucent can occlude, so .tcount masks out just before the buttons instead; Playlist's banner scrim and the discography's pinned ambience both landed on opaque --bg-base and now dissolve like the album banner; and the 0.9 black hero tint on Home/CatalogSearch/Discography paints straight onto the pane when there is no artwork, so :first-child (the {#if} renders no element) flips it to white. Menus, dialogs, toasts and the import surfaces stay opaque deliberately, and the Settings copy for the mode now says so instead of claiming cards do. Measured with a known ground substituted behind the transparent window, since CDP never sees the acrylic: library toolbar chroma +2 as shipped, +16 with a white fill AND its backdrop-filter dropped, +30 for the bare pane beside it. RESIZE: Windows drops the legacy accent-policy acrylic for the whole of a live resize and the window stays transparent through it, so the desktop punches through sharp - measured by toggling window_glass alone, desktop icon labels readable through the titlebar. The blur cannot be kept (documented since Win10 2004, and the modern DWM backdrop was already rejected for the overlay-promotion blink), so armGlassResizeGuard grounds the canvas to --bg-sidebar for the length of the gesture. STALLS: "choppy but the FPS says 144" was 200-750ms MAIN-THREAD TASKS, not frame pacing - idle-scheduled work fires at scroll pauses by definition. Twin bake moved to display resolution and off the ready task (was a same-task 4K WebGL readback plus software blur, 100-170ms per rotation AND per artist entry); gl.finish() -> gl.flush() in buildMaster, which had been draining the whole 4K EASU/RCAS/grade chain on the main thread; copyCanvas realloc guarded; one app-wide scroll clock in scrollbar.js (markAppScroll/appScrollIdleFor) so all three periscopes stop cloning mid-gesture on ANY pane rather than only their own; bounded reclone with the querySelectorAll census deleted, the gate widened 250->1000ms, and the build/attach split across an idle callback and a rAF behind a generation guard; the hero rotation tick gated on scroll recency and its payload cache sized to the working set (2->4 entries, 24->32MiB, 32->64MiB); sessionTick no longer force-rewrites every 12s while merely PAUSED. Scroll storm went from 109-616ms long tasks to zero. Hover recalc and GC were measured innocent - do not re-chase them. 98/98 tests throughout.
272 lines
11 KiB
Rust
272 lines
11 KiB
Rust
mod commands;
|
|
mod config;
|
|
mod cover_logo;
|
|
mod diagnostics;
|
|
mod hero_crop;
|
|
mod import;
|
|
mod hero_detector;
|
|
mod loudness;
|
|
mod lyric_align;
|
|
mod lyric_prep;
|
|
mod lyrics;
|
|
mod pipeline;
|
|
mod playback;
|
|
mod playlist_formats;
|
|
mod playlists;
|
|
mod provider_playlist;
|
|
mod preview;
|
|
mod setlist_scan;
|
|
mod shazam;
|
|
mod smtc;
|
|
mod state;
|
|
mod thumbbar;
|
|
mod vocal_sep;
|
|
mod youtube_playlist;
|
|
mod youtube_setlist;
|
|
mod ytdlp;
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.manage(state::AppState::default())
|
|
.setup(|app| {
|
|
app.handle().plugin(
|
|
tauri_plugin_log::Builder::default()
|
|
.level(log::LevelFilter::Info)
|
|
.clear_targets()
|
|
.target(tauri_plugin_log::Target::new(
|
|
tauri_plugin_log::TargetKind::Stdout,
|
|
))
|
|
.build(),
|
|
)?;
|
|
app.handle().plugin(tauri_plugin_dialog::init())?;
|
|
// Must run before anything touches the app-data dir.
|
|
config::migrate_app_data(app.handle());
|
|
// Prepare the small local vision model while the user is still on
|
|
// the app shell, so a first artist visit pays only warm inference.
|
|
hero_detector::warm_up(app.handle().clone());
|
|
// Before playback::init: the event pump it spawns starts pushing
|
|
// transport state at the card immediately, and the card has to be
|
|
// registered by then or the first track's status is dropped.
|
|
smtc::init(app.handle());
|
|
// Only subclasses the window here; the buttons themselves go up
|
|
// when the shell says the taskbar button exists.
|
|
thumbbar::init(app.handle());
|
|
playback::init(app.handle());
|
|
playback::prune_cache(app.handle());
|
|
preview::init(app.handle());
|
|
loudness::init(app.handle());
|
|
diagnostics::init(app.handle());
|
|
if let Err(e) = pipeline::init(app.handle()) {
|
|
log::error!("library init failed: {e}");
|
|
}
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
commands::app_status,
|
|
commands::excluded_search_phrases,
|
|
commands::get_credentials,
|
|
commands::connect_and_login,
|
|
commands::connect_stored,
|
|
commands::disconnect,
|
|
commands::start_search,
|
|
commands::stop_search,
|
|
commands::download_file,
|
|
commands::copy_stream_to_folder,
|
|
commands::save_to_library,
|
|
commands::stream_cache_len,
|
|
commands::cancel_download,
|
|
commands::show_in_folder,
|
|
commands::refresh_artist_art,
|
|
commands::play_stream,
|
|
commands::play_local,
|
|
commands::preload_local,
|
|
commands::player_control,
|
|
commands::player_state,
|
|
commands::waveform,
|
|
commands::library_albums,
|
|
commands::library_album_stats,
|
|
commands::album_tracks,
|
|
commands::album_art,
|
|
commands::library_art_prewarm,
|
|
commands::stream_embedded_art,
|
|
commands::refresh_album_art,
|
|
commands::refresh_all_album_art,
|
|
commands::get_settings,
|
|
commands::set_settings,
|
|
commands::forget_credentials,
|
|
commands::pick_folder,
|
|
commands::open_external,
|
|
commands::record_play,
|
|
commands::add_play_seconds,
|
|
commands::artist_play_stats,
|
|
commands::recent_albums,
|
|
commands::clear_play_history,
|
|
commands::play_row_count,
|
|
commands::followed_artists,
|
|
commands::follow_artist,
|
|
commands::unfollow_artist,
|
|
commands::home_artist_news,
|
|
commands::home_warm,
|
|
commands::mini_open,
|
|
commands::mini_close,
|
|
commands::window_glass,
|
|
playlists::list_playlists,
|
|
playlists::create_playlist,
|
|
playlists::rename_playlist,
|
|
playlists::set_playlist_description,
|
|
playlists::set_playlist_pinned,
|
|
playlists::duplicate_playlist,
|
|
playlists::delete_playlist,
|
|
playlists::playlist_items,
|
|
playlists::playlist_add_items,
|
|
playlists::playlist_remove_items,
|
|
playlists::playlist_set_order,
|
|
playlists::playlist_relink_item,
|
|
playlists::playlist_cover,
|
|
playlists::playlist_cover_pick,
|
|
playlists::playlist_cover_clear,
|
|
playlists::playlist_import_probe,
|
|
playlists::playlist_import_folder,
|
|
playlists::playlist_import_text,
|
|
playlists::playlist_import_youtube,
|
|
playlists::playlist_import_youtube_playlist,
|
|
playlists::playlist_import_link,
|
|
playlists::playlist_import_track_links,
|
|
playlists::playlist_import_commit,
|
|
setlist_scan::playlist_scan_youtube,
|
|
setlist_scan::playlist_pick_set_file,
|
|
setlist_scan::playlist_scan_file,
|
|
setlist_scan::playlist_scan_cancel,
|
|
ytdlp::ytdlp_status,
|
|
ytdlp::ytdlp_download,
|
|
ytdlp::deno_download,
|
|
playlists::playlist_export,
|
|
playlists::library_track_search,
|
|
lyrics::lyrics_get,
|
|
lyrics::lyrics_search,
|
|
lyrics::lyrics_select,
|
|
lyrics::lyrics_set_offset,
|
|
lyrics::lyrics_set_auto_sync,
|
|
lyrics::lyrics_prepare_instrumental,
|
|
lyrics::lyrics_vocal_activity,
|
|
lyrics::lyrics_save_lines,
|
|
lyrics::lyrics_save_text,
|
|
lyrics::lyrics_clear,
|
|
commands::catalog_search,
|
|
commands::catalog_search_tracks,
|
|
commands::catalog_artist_track_candidates,
|
|
commands::catalog_track_by_isrc,
|
|
import::import_scan,
|
|
import::import_portable_root,
|
|
import::import_resolve_root,
|
|
import::import_session_get,
|
|
import::import_session_set,
|
|
import::import_session_clear,
|
|
import::import_get_dest,
|
|
import::import_set_dest,
|
|
import::import_apply,
|
|
import::import_identify,
|
|
import::import_cover_report,
|
|
import::import_local_art,
|
|
import::import_audition,
|
|
import::import_audition_missing,
|
|
import::import_audition_clear,
|
|
import::import_probe_file,
|
|
pipeline::rescan_library,
|
|
commands::catalog_artist,
|
|
commands::catalog_artist_art,
|
|
commands::catalog_artist_logos,
|
|
commands::catalog_artist_identity,
|
|
commands::catalog_artist_socials,
|
|
commands::remember_fused_artist_portrait,
|
|
commands::catalog_artist_bio,
|
|
commands::catalog_cached_cover_logo,
|
|
commands::catalog_cover_logo,
|
|
commands::catalog_cover_logo_debug,
|
|
commands::catalog_ocr_experiment,
|
|
commands::catalog_artist_hero_seed,
|
|
commands::catalog_artist_overview,
|
|
commands::catalog_artist_popular,
|
|
commands::pick_artist_hero,
|
|
commands::clear_artist_hero,
|
|
commands::catalog_related,
|
|
commands::catalog_track_listeners,
|
|
commands::catalog_artist_profile,
|
|
commands::catalog_album,
|
|
commands::catalog_album_info,
|
|
commands::catalog_art,
|
|
commands::catalog_cover,
|
|
commands::invalidate_catalog_cover,
|
|
commands::catalog_hero_preview,
|
|
commands::catalog_hero_gpu_source,
|
|
commands::catalog_cached_hero_art,
|
|
commands::catalog_hero_art,
|
|
commands::preload_stream,
|
|
commands::preload_cached_stream,
|
|
commands::stream_waveform,
|
|
commands::stream_cache_ready,
|
|
preview::preview_resolve,
|
|
preview::preview_prewarm,
|
|
preview::preview_load,
|
|
preview::preview_control,
|
|
preview::preview_dispose,
|
|
preview::player_pause_acked,
|
|
preview::player_fade_pause_acked,
|
|
preview::player_fade_resume,
|
|
diagnostics::debug_stats,
|
|
smtc::smtc_metadata,
|
|
lyric_prep::lyric_prep_request,
|
|
lyric_align::lyric_model_status,
|
|
lyric_align::lyric_model_download,
|
|
vocal_sep::vocal_sep_status,
|
|
vocal_sep::vocal_sep_download,
|
|
])
|
|
.run({
|
|
let mut context = tauri::generate_context!();
|
|
// Browser args ride the WINDOW CONFIG, nothing else: wry always sets
|
|
// AdditionalBrowserArguments programmatically, and at least WebView2
|
|
// 151.x then drops the WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS env var
|
|
// entirely — flags "delivered" that way vanish silently on some
|
|
// runtimes (they held on the home machine, gone on the work one).
|
|
// Verify in the browser process command line (Get-CimInstance
|
|
// Win32_Process), never by assumption. Explicit args replace wry's
|
|
// defaults, so those are restated. Two WebView2 presentation fixes
|
|
// live here (2026-08-26, the dark-glass work):
|
|
//
|
|
// 1. NO delegated compositing: with it, DWM composes the webview's
|
|
// layers at rest and blends semi-transparent darks in the wrong
|
|
// colorspace — the app rested washed-out LIGHT and looked correct
|
|
// only while an animation forced Chromium to compose in-process.
|
|
// 2. Force the sRGB color profile: on an HDR display the MAXIMIZED
|
|
// window's rest path takes a different SDR-white tone mapping
|
|
// than the animated path — covers blew out near-white at rest
|
|
// and snapped correct on hover. One profile, one mapping, both
|
|
// paths agree.
|
|
// 3. Force the wheel scroll animator ON: this WebView2 shipped
|
|
// wheel detents as INSTANT 120px jumps (measured: a dispatched
|
|
// detent moved scrollTop in exactly one frame) while the
|
|
// present pipeline ran a flawless 144Hz — scroll position
|
|
// updating at detent cadence reads as "choppy but the FPS
|
|
// says 144". The system animation setting is on and
|
|
// prefers-reduced-motion is off, so the snap is WebView2's
|
|
// embedded default, not the Chromium system heuristic; the
|
|
// switch pins enable_scroll_animator=true in WebPreferences.
|
|
//
|
|
// Debug builds add CDP on 9222 for instrumentation (see HANDOFF.md).
|
|
let base = "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection,DelegatedCompositing \
|
|
--autoplay-policy=no-user-gesture-required \
|
|
--enable-smooth-scrolling \
|
|
--force-color-profile=srgb";
|
|
#[cfg(debug_assertions)]
|
|
let args = format!("{base} --remote-debugging-port=9222");
|
|
#[cfg(not(debug_assertions))]
|
|
let args = base.to_string();
|
|
for window in &mut context.config_mut().app.windows {
|
|
window.additional_browser_args = Some(args.clone());
|
|
}
|
|
context
|
|
})
|
|
.expect("error while running tauri application");
|
|
}
|