diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index c4e8716..32a2c07 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -5,7 +5,6 @@ tone_instructions: >-
utilisateur, les problèmes de sécurité, les erreurs de logique et les
oublis de tests/validation. Évite les remarques purement cosmétiques.
early_access: true
-
reviews:
profile: assertive
request_changes_workflow: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf5ec31..43c843b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,86 +2,83 @@
## [1.1.0](https://github.com/InstaZDLL/WaveFlow/compare/v1.0.0...v1.1.0) (2026-05-17)
-
### Features
-* **apt:** publish .deb to Buildkite Packages registry on release ([328367f](https://github.com/InstaZDLL/WaveFlow/commit/328367f8fd2ae96dba34742c6efdcafd61cd47b9))
-* **apt:** publish .deb to Buildkite Packages registry on release ([cb9a4bc](https://github.com/InstaZDLL/WaveFlow/commit/cb9a4bccccb9373e00ab2927053a259ea882fff0))
-* **artist:** add UI to pick or remove the artist image ([d277519](https://github.com/InstaZDLL/WaveFlow/commit/d277519aef7ed0b999b1888f748c44b191384de4))
-* **backup:** optionally bundle shared Deezer artwork cache ([03365ca](https://github.com/InstaZDLL/WaveFlow/commit/03365ca3fc01e3b8aae9373cd33eb98efc93c88b))
-* **backup:** optionally bundle shared Deezer artwork cache ([932106e](https://github.com/InstaZDLL/WaveFlow/commit/932106e2cac4128170a7377a7e05c09bc9ff36fc))
-* **config:** add .coderabbit.yaml for project configuration and review instructions ([cc72976](https://github.com/InstaZDLL/WaveFlow/commit/cc729762a4e4ebe3d31daac1f274fc42d77c4762))
-* **distribution:** publish to fedora copr on every release ([8af4104](https://github.com/InstaZDLL/WaveFlow/commit/8af41043fde2ee502503dd8acf84ea3753495bee))
-* **distribution:** publish to winget-pkgs on every release ([e069679](https://github.com/InstaZDLL/WaveFlow/commit/e0696791076e6ad20d479cc0e4d067dc591807be))
-* **library:** enhance create playlist functionality with source tracking ([3b806bf](https://github.com/InstaZDLL/WaveFlow/commit/3b806bfefd782622d0ce515c640052296bcf5245))
-* **library:** local artist images + picker UI ([#33](https://github.com/InstaZDLL/WaveFlow/issues/33)) ([af719b6](https://github.com/InstaZDLL/WaveFlow/commit/af719b6841423b5080b88494dd9c2cc5f9ab0b03))
-* **library:** open genre detail page from tags grid ([#23](https://github.com/InstaZDLL/WaveFlow/issues/23)) ([0a5fbb9](https://github.com/InstaZDLL/WaveFlow/commit/0a5fbb9108be3ae6035f8751c846e671f8500628))
-* **library:** use local artist.jpg sidecar as artist photo ([e23ece4](https://github.com/InstaZDLL/WaveFlow/commit/e23ece4b5465cd962afb0b0537f0311c67735082))
-* **lyrics:** word-level karaoke (enhanced LRC + TTML) ([7aeb19e](https://github.com/InstaZDLL/WaveFlow/commit/7aeb19ee9d953e99591ceb4faca5d27d67d9539d))
-* **lyrics:** word-level karaoke (enhanced LRC + TTML) ([#25](https://github.com/InstaZDLL/WaveFlow/issues/25)) ([62701c3](https://github.com/InstaZDLL/WaveFlow/commit/62701c3c8fa6c85d968ae609efda253074c50bad))
-* **player-bar:** host A-B loop + sleep timer in overflow menu by default ([1a7aabb](https://github.com/InstaZDLL/WaveFlow/commit/1a7aabbf5dc552f1d5fec4ac34bd862e5b767f7e))
-* **player-bar:** move playback speed into the overflow menu ([c322e54](https://github.com/InstaZDLL/WaveFlow/commit/c322e54269f9682c355b3f177c2ee000a198556c))
-* **player-bar:** redesign right cluster (Spotify-style) + overflow defaults ([ade9f13](https://github.com/InstaZDLL/WaveFlow/commit/ade9f1379faefe3f1be61d6ccd60b70902131258))
-* **player-bar:** spotify-style mini-player + fullscreen icons after volume ([7d58651](https://github.com/InstaZDLL/WaveFlow/commit/7d58651ddcec4259014deb6465140dc5431eef34))
-* **playlist:** add filename sort mode ([83b5fda](https://github.com/InstaZDLL/WaveFlow/commit/83b5fda3abd054cbd337415c3276c144331866c7))
-* **playlist:** add filename sort mode ([525ff85](https://github.com/InstaZDLL/WaveFlow/commit/525ff85a21413eb14a8110fd6f7cc56ed32be384))
-* **playlist:** add Spotify-style sort modes with per-playlist memory ([685bfbf](https://github.com/InstaZDLL/WaveFlow/commit/685bfbff6082b55daa30bcb8587e78217a6d75b7))
-* **scan:** auto-merge implicit compilations into Various Artists ([1b24e7e](https://github.com/InstaZDLL/WaveFlow/commit/1b24e7e9b1cb4e6a7d5c908d3b270672c55c5227))
-* **ui:** add splash screen to mask cold-start delay ([#26](https://github.com/InstaZDLL/WaveFlow/issues/26)) ([813a38d](https://github.com/InstaZDLL/WaveFlow/commit/813a38d64ca6240e6d74bcd1d3acacf3292473a9))
-* **ui:** drop the beta badge from the sidebar brand ([e731834](https://github.com/InstaZDLL/WaveFlow/commit/e73183477f0ba778fbddb069dcf0c8ad5e2968d4))
-* **ui:** make album cell clickable in playlist + library track rows ([209b8be](https://github.com/InstaZDLL/WaveFlow/commit/209b8bebda8ad56f5346bed93e77422eb6e0e6fa))
-
+- **apt:** publish .deb to Buildkite Packages registry on release ([328367f](https://github.com/InstaZDLL/WaveFlow/commit/328367f8fd2ae96dba34742c6efdcafd61cd47b9))
+- **apt:** publish .deb to Buildkite Packages registry on release ([cb9a4bc](https://github.com/InstaZDLL/WaveFlow/commit/cb9a4bccccb9373e00ab2927053a259ea882fff0))
+- **artist:** add UI to pick or remove the artist image ([d277519](https://github.com/InstaZDLL/WaveFlow/commit/d277519aef7ed0b999b1888f748c44b191384de4))
+- **backup:** optionally bundle shared Deezer artwork cache ([03365ca](https://github.com/InstaZDLL/WaveFlow/commit/03365ca3fc01e3b8aae9373cd33eb98efc93c88b))
+- **backup:** optionally bundle shared Deezer artwork cache ([932106e](https://github.com/InstaZDLL/WaveFlow/commit/932106e2cac4128170a7377a7e05c09bc9ff36fc))
+- **config:** add .coderabbit.yaml for project configuration and review instructions ([cc72976](https://github.com/InstaZDLL/WaveFlow/commit/cc729762a4e4ebe3d31daac1f274fc42d77c4762))
+- **distribution:** publish to fedora copr on every release ([8af4104](https://github.com/InstaZDLL/WaveFlow/commit/8af41043fde2ee502503dd8acf84ea3753495bee))
+- **distribution:** publish to winget-pkgs on every release ([e069679](https://github.com/InstaZDLL/WaveFlow/commit/e0696791076e6ad20d479cc0e4d067dc591807be))
+- **library:** enhance create playlist functionality with source tracking ([3b806bf](https://github.com/InstaZDLL/WaveFlow/commit/3b806bfefd782622d0ce515c640052296bcf5245))
+- **library:** local artist images + picker UI ([#33](https://github.com/InstaZDLL/WaveFlow/issues/33)) ([af719b6](https://github.com/InstaZDLL/WaveFlow/commit/af719b6841423b5080b88494dd9c2cc5f9ab0b03))
+- **library:** open genre detail page from tags grid ([#23](https://github.com/InstaZDLL/WaveFlow/issues/23)) ([0a5fbb9](https://github.com/InstaZDLL/WaveFlow/commit/0a5fbb9108be3ae6035f8751c846e671f8500628))
+- **library:** use local artist.jpg sidecar as artist photo ([e23ece4](https://github.com/InstaZDLL/WaveFlow/commit/e23ece4b5465cd962afb0b0537f0311c67735082))
+- **lyrics:** word-level karaoke (enhanced LRC + TTML) ([7aeb19e](https://github.com/InstaZDLL/WaveFlow/commit/7aeb19ee9d953e99591ceb4faca5d27d67d9539d))
+- **lyrics:** word-level karaoke (enhanced LRC + TTML) ([#25](https://github.com/InstaZDLL/WaveFlow/issues/25)) ([62701c3](https://github.com/InstaZDLL/WaveFlow/commit/62701c3c8fa6c85d968ae609efda253074c50bad))
+- **player-bar:** host A-B loop + sleep timer in overflow menu by default ([1a7aabb](https://github.com/InstaZDLL/WaveFlow/commit/1a7aabbf5dc552f1d5fec4ac34bd862e5b767f7e))
+- **player-bar:** move playback speed into the overflow menu ([c322e54](https://github.com/InstaZDLL/WaveFlow/commit/c322e54269f9682c355b3f177c2ee000a198556c))
+- **player-bar:** redesign right cluster (Spotify-style) + overflow defaults ([ade9f13](https://github.com/InstaZDLL/WaveFlow/commit/ade9f1379faefe3f1be61d6ccd60b70902131258))
+- **player-bar:** spotify-style mini-player + fullscreen icons after volume ([7d58651](https://github.com/InstaZDLL/WaveFlow/commit/7d58651ddcec4259014deb6465140dc5431eef34))
+- **playlist:** add filename sort mode ([83b5fda](https://github.com/InstaZDLL/WaveFlow/commit/83b5fda3abd054cbd337415c3276c144331866c7))
+- **playlist:** add filename sort mode ([525ff85](https://github.com/InstaZDLL/WaveFlow/commit/525ff85a21413eb14a8110fd6f7cc56ed32be384))
+- **playlist:** add Spotify-style sort modes with per-playlist memory ([685bfbf](https://github.com/InstaZDLL/WaveFlow/commit/685bfbff6082b55daa30bcb8587e78217a6d75b7))
+- **scan:** auto-merge implicit compilations into Various Artists ([1b24e7e](https://github.com/InstaZDLL/WaveFlow/commit/1b24e7e9b1cb4e6a7d5c908d3b270672c55c5227))
+- **ui:** add splash screen to mask cold-start delay ([#26](https://github.com/InstaZDLL/WaveFlow/issues/26)) ([813a38d](https://github.com/InstaZDLL/WaveFlow/commit/813a38d64ca6240e6d74bcd1d3acacf3292473a9))
+- **ui:** drop the beta badge from the sidebar brand ([e731834](https://github.com/InstaZDLL/WaveFlow/commit/e73183477f0ba778fbddb069dcf0c8ad5e2968d4))
+- **ui:** make album cell clickable in playlist + library track rows ([209b8be](https://github.com/InstaZDLL/WaveFlow/commit/209b8bebda8ad56f5346bed93e77422eb6e0e6fa))
### Bug Fixes
-* **about:** read version from Tauri at runtime + bump to 1.0.1 ([829ec89](https://github.com/InstaZDLL/WaveFlow/commit/829ec89f8f7196d9d69b28588eba42163b96e98c))
-* **about:** read version from Tauri at runtime instead of hardcoded 0.1.0 ([6bdc69d](https://github.com/InstaZDLL/WaveFlow/commit/6bdc69debbcd2b5fd25d88304de98536dc28fa3c))
-* **apt:** address coderabbit review ([285a5f1](https://github.com/InstaZDLL/WaveFlow/commit/285a5f104b5038b368bd70633dcf7a1fd5a86db5))
-* **audio:** handle panics in decoder thread and improve WASAPI exclusive mode management ([27cfdf7](https://github.com/InstaZDLL/WaveFlow/commit/27cfdf7a262ea7627abf828e0a14219ff8e83c62))
-* **audio:** keep splash close from stopping playback ([a4fdaa9](https://github.com/InstaZDLL/WaveFlow/commit/a4fdaa90cf6645d7e668a8ddc560d930f3e504a2))
-* **backup:** preserve metadata-artwork flag on archive failure + fr typo ([9b8030d](https://github.com/InstaZDLL/WaveFlow/commit/9b8030d931d19e0569fb41a2ffa6488392cd0081))
-* **ci:** copr project name is lowercase 'instazdll/waveflow' ([6b4235d](https://github.com/InstaZDLL/WaveFlow/commit/6b4235d0288de42e169c8a008ffdcce07e6d99c0))
-* **ci:** copr workflow checks out main instead of the release tag ([37e7dd1](https://github.com/InstaZDLL/WaveFlow/commit/37e7dd1397e9e5becc8d07586c9dcf2ae24114a6))
-* **ci:** plug script injection in lockfile-build (head.ref attacker-controlled) ([ac13cae](https://github.com/InstaZDLL/WaveFlow/commit/ac13cae1f33f3b532955a5076cc75aa6c1c8f544))
-* **ci:** split release-please lockfile pipeline (codeql untrusted-checkout/critical) ([9db6031](https://github.com/InstaZDLL/WaveFlow/commit/9db60314e81700bfd7c302e3cc2efd9a63b924e9))
-* **ci:** split release-please lockfile pipeline (codeql untrusted-checkout/critical) ([49645b0](https://github.com/InstaZDLL/WaveFlow/commit/49645b056069786178da6e21778ef8413dc5ae4d))
-* **copr:** match the upstream rpm's mixed-case file layout ([093a516](https://github.com/InstaZDLL/WaveFlow/commit/093a51651009f9f2671c3d6ecd8c854e13b650d9))
-* **decoder:** simplify parameter passing in decoder_loop function ([ebd4d85](https://github.com/InstaZDLL/WaveFlow/commit/ebd4d851d501e3f9f21a8b5efde30749e9e9a626))
-* **deezer:** surface result of missing-cover batch fetch ([4bb905b](https://github.com/InstaZDLL/WaveFlow/commit/4bb905ba7299fe4c8b476f3751c83746df5250ec))
-* **feedback:** correct contact email domain + wire mailto handler ([6cf797f](https://github.com/InstaZDLL/WaveFlow/commit/6cf797ff397d8369811ecc46f4877791eb18422c))
-* **home:** avoid nested button in recently played tile ([fb0eff4](https://github.com/InstaZDLL/WaveFlow/commit/fb0eff41b7dc6aac64858dbf615511518c73b8fe))
-* **locale:** add missing spotify.emptyPlaylist key to 15 locales ([fe3aa19](https://github.com/InstaZDLL/WaveFlow/commit/fe3aa197e8422724a7fca5b54fb7b382c68668a5))
-* **locale:** declare missing keys in playlist modal, spotify integration, and progress bar ([dd1d7a7](https://github.com/InstaZDLL/WaveFlow/commit/dd1d7a7bffab98600505bdfda218c53a8142f6ea))
-* **locale:** translate home daily mix section and tray menu ([3475280](https://github.com/InstaZDLL/WaveFlow/commit/34752801783815700422649b6a06930f55288716))
-* **locale:** translate home daily mix section and tray menu ([#32](https://github.com/InstaZDLL/WaveFlow/issues/32)) ([9bbdd64](https://github.com/InstaZDLL/WaveFlow/commit/9bbdd64b3facb6cbc310442a817fda2790c7b0b5))
-* **lyrics:** address coderabbit review ([ddd89ff](https://github.com/InstaZDLL/WaveFlow/commit/ddd89ff4ed3dbbd6438a17cee9f6dda83950563b))
-* **lyrics:** bind prefix word to each duplicated line stamp ([ecd87c1](https://github.com/InstaZDLL/WaveFlow/commit/ecd87c127f753d765db822383e4269739d438cf8))
-* **lyrics:** build enhanced LRC word stamps directly (codeql) ([3b80c8e](https://github.com/InstaZDLL/WaveFlow/commit/3b80c8e5e4fc3ba3f55b0b308f709d9e04d3e22c))
-* **lyrics:** keep unstamped text rows on word-mode save ([4240f9d](https://github.com/InstaZDLL/WaveFlow/commit/4240f9db8a2c4a9782306922f4a59f3f3eff77a4))
-* **lyrics:** keep word/ttml badge intact in the panel footer ([fb9c7bf](https://github.com/InstaZDLL/WaveFlow/commit/fb9c7bfa1b8419bf866649379021f79339292582))
-* **lyrics:** mirror fullscreen word animation in the side panel ([da44f8b](https://github.com/InstaZDLL/WaveFlow/commit/da44f8b3b05074d7201a12e460b1a2371a62dd71))
-* **migration:** force file_modified=0 so album_artist backfill triggers ([277871d](https://github.com/InstaZDLL/WaveFlow/commit/277871d35fbcac40beebcca0a27ee80485bc1297))
-* **nav:** preserve detail payloads in history + don't toggle off shuffle ([#24](https://github.com/InstaZDLL/WaveFlow/issues/24)) ([981144c](https://github.com/InstaZDLL/WaveFlow/commit/981144c50a972b42f70afa3875fd3d5fcf7f1bee))
-* **packaging:** drop duplicate ReleaseNotesUrl from winget installer manifest ([f62b242](https://github.com/InstaZDLL/WaveFlow/commit/f62b2429ffd6d008376ad0f533233c2aea8fc33e))
-* **player-bar:** address CodeRabbit review on MoreActionsMenu ([6458185](https://github.com/InstaZDLL/WaveFlow/commit/6458185a293f7148b473a27057559663a8a74512))
-* **playlist-cover:** dedupe artwork hashes before composing auto-cover ([b75e80c](https://github.com/InstaZDLL/WaveFlow/commit/b75e80c03d3f9054131b5feffb25569547f8c0e3))
-* **playlist:** drop dead i18n fallback for sort.filename ([a2e0467](https://github.com/InstaZDLL/WaveFlow/commit/a2e0467dad28bfa911d46d63a8a7cd505acd72d0))
-* **profile:** normalise sqlx migration checksums on import + pin sources to LF ([#27](https://github.com/InstaZDLL/WaveFlow/issues/27)) ([521e484](https://github.com/InstaZDLL/WaveFlow/commit/521e4842d4de7cd9537e50d15aa12cc3d85149b7))
-* **profile:** roll back partial import on checksum/migrate failure ([#28](https://github.com/InstaZDLL/WaveFlow/issues/28)) ([921678b](https://github.com/InstaZDLL/WaveFlow/commit/921678be8493c56d78248ab8a2f8487a67f7afe4))
-* **review:** address PR [#33](https://github.com/InstaZDLL/WaveFlow/issues/33) review feedback ([f67f496](https://github.com/InstaZDLL/WaveFlow/commit/f67f496bb8018c5af4b935937e747be2a8a0d6f9))
-* **review:** batch rescan tx + modal i18n and request-id cleanup ([a3429bc](https://github.com/InstaZDLL/WaveFlow/commit/a3429bce43e2edb1a8b406044570eb696068b82d))
-* **scan:** group albums by Album Artist tag + compilation flag ([d341e5f](https://github.com/InstaZDLL/WaveFlow/commit/d341e5f1ed9be6125336e6163f5b96daf0534ebe))
-* **settings:** persist autostart, close-to-tray and scan-on-start toggles ([35e3719](https://github.com/InstaZDLL/WaveFlow/commit/35e3719cf767d00943ae47f86f914896f3fe7491))
-* **theme:** persist preference before view transition ([415dbbe](https://github.com/InstaZDLL/WaveFlow/commit/415dbbe23ff88aa56eac9e083218636fa6971205))
-* **theme:** persist preference before view transition ([aaeeec7](https://github.com/InstaZDLL/WaveFlow/commit/aaeeec75ae297873244f9e3e623b5485a53a231c)), closes [#34](https://github.com/InstaZDLL/WaveFlow/issues/34)
-* **ui:** cap grid item width on wide screens (auto-fill instead of fixed cols) ([36c44a1](https://github.com/InstaZDLL/WaveFlow/commit/36c44a19ab5e26258950e9ba6ae76feeb7c33b20))
-* **ui:** cap grid item width on wide screens (auto-fill instead of fixed cols) ([#22](https://github.com/InstaZDLL/WaveFlow/issues/22)) ([194d7ee](https://github.com/InstaZDLL/WaveFlow/commit/194d7ee8311cd53fcfc3c91dc59569e376b041ce))
-* **workflow:** enhance security by restricting PR execution to github-actions[bot] ([6b993df](https://github.com/InstaZDLL/WaveFlow/commit/6b993dfe5fd81b7d836703e04de275947f932e12))
-
+- **about:** read version from Tauri at runtime + bump to 1.0.1 ([829ec89](https://github.com/InstaZDLL/WaveFlow/commit/829ec89f8f7196d9d69b28588eba42163b96e98c))
+- **about:** read version from Tauri at runtime instead of hardcoded 0.1.0 ([6bdc69d](https://github.com/InstaZDLL/WaveFlow/commit/6bdc69debbcd2b5fd25d88304de98536dc28fa3c))
+- **apt:** address coderabbit review ([285a5f1](https://github.com/InstaZDLL/WaveFlow/commit/285a5f104b5038b368bd70633dcf7a1fd5a86db5))
+- **audio:** handle panics in decoder thread and improve WASAPI exclusive mode management ([27cfdf7](https://github.com/InstaZDLL/WaveFlow/commit/27cfdf7a262ea7627abf828e0a14219ff8e83c62))
+- **audio:** keep splash close from stopping playback ([a4fdaa9](https://github.com/InstaZDLL/WaveFlow/commit/a4fdaa90cf6645d7e668a8ddc560d930f3e504a2))
+- **backup:** preserve metadata-artwork flag on archive failure + fr typo ([9b8030d](https://github.com/InstaZDLL/WaveFlow/commit/9b8030d931d19e0569fb41a2ffa6488392cd0081))
+- **ci:** copr project name is lowercase 'instazdll/waveflow' ([6b4235d](https://github.com/InstaZDLL/WaveFlow/commit/6b4235d0288de42e169c8a008ffdcce07e6d99c0))
+- **ci:** copr workflow checks out main instead of the release tag ([37e7dd1](https://github.com/InstaZDLL/WaveFlow/commit/37e7dd1397e9e5becc8d07586c9dcf2ae24114a6))
+- **ci:** plug script injection in lockfile-build (head.ref attacker-controlled) ([ac13cae](https://github.com/InstaZDLL/WaveFlow/commit/ac13cae1f33f3b532955a5076cc75aa6c1c8f544))
+- **ci:** split release-please lockfile pipeline (codeql untrusted-checkout/critical) ([9db6031](https://github.com/InstaZDLL/WaveFlow/commit/9db60314e81700bfd7c302e3cc2efd9a63b924e9))
+- **ci:** split release-please lockfile pipeline (codeql untrusted-checkout/critical) ([49645b0](https://github.com/InstaZDLL/WaveFlow/commit/49645b056069786178da6e21778ef8413dc5ae4d))
+- **copr:** match the upstream rpm's mixed-case file layout ([093a516](https://github.com/InstaZDLL/WaveFlow/commit/093a51651009f9f2671c3d6ecd8c854e13b650d9))
+- **decoder:** simplify parameter passing in decoder_loop function ([ebd4d85](https://github.com/InstaZDLL/WaveFlow/commit/ebd4d851d501e3f9f21a8b5efde30749e9e9a626))
+- **deezer:** surface result of missing-cover batch fetch ([4bb905b](https://github.com/InstaZDLL/WaveFlow/commit/4bb905ba7299fe4c8b476f3751c83746df5250ec))
+- **feedback:** correct contact email domain + wire mailto handler ([6cf797f](https://github.com/InstaZDLL/WaveFlow/commit/6cf797ff397d8369811ecc46f4877791eb18422c))
+- **home:** avoid nested button in recently played tile ([fb0eff4](https://github.com/InstaZDLL/WaveFlow/commit/fb0eff41b7dc6aac64858dbf615511518c73b8fe))
+- **locale:** add missing spotify.emptyPlaylist key to 15 locales ([fe3aa19](https://github.com/InstaZDLL/WaveFlow/commit/fe3aa197e8422724a7fca5b54fb7b382c68668a5))
+- **locale:** declare missing keys in playlist modal, spotify integration, and progress bar ([dd1d7a7](https://github.com/InstaZDLL/WaveFlow/commit/dd1d7a7bffab98600505bdfda218c53a8142f6ea))
+- **locale:** translate home daily mix section and tray menu ([3475280](https://github.com/InstaZDLL/WaveFlow/commit/34752801783815700422649b6a06930f55288716))
+- **locale:** translate home daily mix section and tray menu ([#32](https://github.com/InstaZDLL/WaveFlow/issues/32)) ([9bbdd64](https://github.com/InstaZDLL/WaveFlow/commit/9bbdd64b3facb6cbc310442a817fda2790c7b0b5))
+- **lyrics:** address coderabbit review ([ddd89ff](https://github.com/InstaZDLL/WaveFlow/commit/ddd89ff4ed3dbbd6438a17cee9f6dda83950563b))
+- **lyrics:** bind prefix word to each duplicated line stamp ([ecd87c1](https://github.com/InstaZDLL/WaveFlow/commit/ecd87c127f753d765db822383e4269739d438cf8))
+- **lyrics:** build enhanced LRC word stamps directly (codeql) ([3b80c8e](https://github.com/InstaZDLL/WaveFlow/commit/3b80c8e5e4fc3ba3f55b0b308f709d9e04d3e22c))
+- **lyrics:** keep unstamped text rows on word-mode save ([4240f9d](https://github.com/InstaZDLL/WaveFlow/commit/4240f9db8a2c4a9782306922f4a59f3f3eff77a4))
+- **lyrics:** keep word/ttml badge intact in the panel footer ([fb9c7bf](https://github.com/InstaZDLL/WaveFlow/commit/fb9c7bfa1b8419bf866649379021f79339292582))
+- **lyrics:** mirror fullscreen word animation in the side panel ([da44f8b](https://github.com/InstaZDLL/WaveFlow/commit/da44f8b3b05074d7201a12e460b1a2371a62dd71))
+- **migration:** force file_modified=0 so album_artist backfill triggers ([277871d](https://github.com/InstaZDLL/WaveFlow/commit/277871d35fbcac40beebcca0a27ee80485bc1297))
+- **nav:** preserve detail payloads in history + don't toggle off shuffle ([#24](https://github.com/InstaZDLL/WaveFlow/issues/24)) ([981144c](https://github.com/InstaZDLL/WaveFlow/commit/981144c50a972b42f70afa3875fd3d5fcf7f1bee))
+- **packaging:** drop duplicate ReleaseNotesUrl from winget installer manifest ([f62b242](https://github.com/InstaZDLL/WaveFlow/commit/f62b2429ffd6d008376ad0f533233c2aea8fc33e))
+- **player-bar:** address CodeRabbit review on MoreActionsMenu ([6458185](https://github.com/InstaZDLL/WaveFlow/commit/6458185a293f7148b473a27057559663a8a74512))
+- **playlist-cover:** dedupe artwork hashes before composing auto-cover ([b75e80c](https://github.com/InstaZDLL/WaveFlow/commit/b75e80c03d3f9054131b5feffb25569547f8c0e3))
+- **playlist:** drop dead i18n fallback for sort.filename ([a2e0467](https://github.com/InstaZDLL/WaveFlow/commit/a2e0467dad28bfa911d46d63a8a7cd505acd72d0))
+- **profile:** normalise sqlx migration checksums on import + pin sources to LF ([#27](https://github.com/InstaZDLL/WaveFlow/issues/27)) ([521e484](https://github.com/InstaZDLL/WaveFlow/commit/521e4842d4de7cd9537e50d15aa12cc3d85149b7))
+- **profile:** roll back partial import on checksum/migrate failure ([#28](https://github.com/InstaZDLL/WaveFlow/issues/28)) ([921678b](https://github.com/InstaZDLL/WaveFlow/commit/921678be8493c56d78248ab8a2f8487a67f7afe4))
+- **review:** address PR [#33](https://github.com/InstaZDLL/WaveFlow/issues/33) review feedback ([f67f496](https://github.com/InstaZDLL/WaveFlow/commit/f67f496bb8018c5af4b935937e747be2a8a0d6f9))
+- **review:** batch rescan tx + modal i18n and request-id cleanup ([a3429bc](https://github.com/InstaZDLL/WaveFlow/commit/a3429bce43e2edb1a8b406044570eb696068b82d))
+- **scan:** group albums by Album Artist tag + compilation flag ([d341e5f](https://github.com/InstaZDLL/WaveFlow/commit/d341e5f1ed9be6125336e6163f5b96daf0534ebe))
+- **settings:** persist autostart, close-to-tray and scan-on-start toggles ([35e3719](https://github.com/InstaZDLL/WaveFlow/commit/35e3719cf767d00943ae47f86f914896f3fe7491))
+- **theme:** persist preference before view transition ([415dbbe](https://github.com/InstaZDLL/WaveFlow/commit/415dbbe23ff88aa56eac9e083218636fa6971205))
+- **theme:** persist preference before view transition ([aaeeec7](https://github.com/InstaZDLL/WaveFlow/commit/aaeeec75ae297873244f9e3e623b5485a53a231c)), closes [#34](https://github.com/InstaZDLL/WaveFlow/issues/34)
+- **ui:** cap grid item width on wide screens (auto-fill instead of fixed cols) ([36c44a1](https://github.com/InstaZDLL/WaveFlow/commit/36c44a19ab5e26258950e9ba6ae76feeb7c33b20))
+- **ui:** cap grid item width on wide screens (auto-fill instead of fixed cols) ([#22](https://github.com/InstaZDLL/WaveFlow/issues/22)) ([194d7ee](https://github.com/InstaZDLL/WaveFlow/commit/194d7ee8311cd53fcfc3c91dc59569e376b041ce))
+- **workflow:** enhance security by restricting PR execution to github-actions[bot] ([6b993df](https://github.com/InstaZDLL/WaveFlow/commit/6b993dfe5fd81b7d836703e04de275947f932e12))
### Performance Improvements
-* **scan:** skip album backfill UPDATE when scan has no new tag info ([1b44737](https://github.com/InstaZDLL/WaveFlow/commit/1b447375d32955868ef1608812cca23be1661e38))
+- **scan:** skip album backfill UPDATE when scan has no new tag info ([1b44737](https://github.com/InstaZDLL/WaveFlow/commit/1b447375d32955868ef1608812cca23be1661e38))
## 1.0.0 (2026-05-12)
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index ce2808f..9b83b30 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -80,7 +80,7 @@ Actions):
| `WINGET_PAT` | GitHub Personal Access Token (classic, `public_repo` scope) the `.github/workflows/winget.yml` action uses to fork microsoft/winget-pkgs and open the PR with the new manifest |
| `COPR_LOGIN` | `login` field from — `.github/workflows/copr.yml` uses it to authenticate to Fedora COPR via `copr-cli` |
| `COPR_TOKEN` | `token` field from the same COPR API page (paired with `COPR_LOGIN`). Token lifetime is 6 months — rotate when builds start returning `401 Unauthorized` |
-| `BUILDKITE_PACKAGES_TOKEN` | Buildkite API token (`read_packages` + `write_packages`) for `.github/workflows/apt-publish.yml` to push the `.deb` to the `instazdll/waveflow` registry |
+| `BUILDKITE_PACKAGES_TOKEN` | Buildkite API token (`read_packages` + `write_packages`) for `.github/workflows/apt-publish.yml` to push the `.deb` to the `instazdll/waveflow` registry |
The AUR package itself (`waveflow-bin`) needs a one-off manual setup
on the maintainer's box — see [`packaging/aur/README.md`](../packaging/aur/README.md).
diff --git a/public/splash.html b/public/splash.html
index bbf8ef6..755189d 100644
--- a/public/splash.html
+++ b/public/splash.html
@@ -14,7 +14,7 @@
padding: 0;
height: 100%;
overflow: hidden;
- background: transparent;
+ background: #121212;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif;
diff --git a/src-tauri/src/backup.rs b/src-tauri/src/backup.rs
index 8154dd5..49f8f1d 100644
--- a/src-tauri/src/backup.rs
+++ b/src-tauri/src/backup.rs
@@ -169,7 +169,12 @@ pub async fn write_config(
("backup.retention", retention.to_string(), "int"),
(
"backup.include_metadata_artwork",
- if include_metadata_artwork { "true" } else { "false" }.to_string(),
+ if include_metadata_artwork {
+ "true"
+ } else {
+ "false"
+ }
+ .to_string(),
"bool",
),
] {
diff --git a/src-tauri/src/commands/profile_io.rs b/src-tauri/src/commands/profile_io.rs
index b3c6d00..0abc2b6 100644
--- a/src-tauri/src/commands/profile_io.rs
+++ b/src-tauri/src/commands/profile_io.rs
@@ -140,17 +140,13 @@ pub async fn export_profile(
/// Read the `backup.include_metadata_artwork` app setting. Defaults to
/// `true` so a fresh install + first manual export produces a complete
/// archive without the user having to opt in.
-pub(crate) async fn read_include_metadata_artwork(
- app_db: &SqlitePool,
-) -> AppResult {
+pub(crate) async fn read_include_metadata_artwork(app_db: &SqlitePool) -> AppResult {
let row: Option = sqlx::query_scalar(
"SELECT value FROM app_setting WHERE key = 'backup.include_metadata_artwork'",
)
.fetch_optional(app_db)
.await?;
- Ok(row
- .map(|v| v == "true" || v == "1")
- .unwrap_or(true))
+ Ok(row.map(|v| v == "true" || v == "1").unwrap_or(true))
}
/// Import a `.waveflow` archive as a brand-new profile. The new
@@ -353,7 +349,10 @@ pub(crate) fn write_archive(
let rel = entry_path
.strip_prefix(meta_dir)
.map_err(|e| AppError::Other(format!("metadata_artwork rel: {e}")))?;
- let zip_name = format!("metadata_artwork/{}", rel.to_string_lossy().replace('\\', "/"));
+ let zip_name = format!(
+ "metadata_artwork/{}",
+ rel.to_string_lossy().replace('\\', "/")
+ );
zip.start_file(&zip_name, opts)?;
let mut src = File::open(entry_path)?;
std::io::copy(&mut src, &mut zip)?;
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index a596c2b..1fbc7bc 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -30,11 +30,12 @@ mod watcher;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
+use std::time::Duration;
use tauri::{
menu::{Menu, MenuItem, PredefinedMenuItem},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
- AppHandle, Manager, WindowEvent,
+ AppHandle, Listener, Manager, WindowEvent,
};
use audio::{AudioCmd, AudioEngine};
@@ -364,6 +365,68 @@ pub fn run() {
})
.build(app)?;
+ // Splash → main handoff.
+ //
+ // The frontend emits `app://ready` once the React root has
+ // committed its first useful paint (see src/main.tsx). When
+ // we get that event we close the splash and reveal the
+ // main window from native code — more reliable than the
+ // previous IPC dance, which on Linux WebKitGTK 2.52 raced
+ // against the heavy first-launch init (migrations + DB
+ // pool + library scan) and left the splash hanging
+ // forever (issue #42).
+ //
+ // A 15 s safety-net timer force-reveals the main window if
+ // `app://ready` never fires — guards against a frontend
+ // crash leaving the user stuck on an eternal splash.
+ let handoff_done = Arc::new(AtomicBool::new(false));
+ let handoff_handle = app.handle().clone();
+ let handoff_done_for_event = handoff_done.clone();
+ app.listen("app://ready", move |_event| {
+ if handoff_done_for_event.swap(true, Ordering::SeqCst) {
+ return;
+ }
+ // Reset the flag if the reveal fails so the fallback
+ // timer (or a subsequent `app://ready` re-emission) can
+ // retry — otherwise a transient main.show() / splash.close()
+ // failure would leave the user stuck on an eternal splash
+ // with no recovery path.
+ if !reveal_main_close_splash(&handoff_handle) {
+ handoff_done_for_event.store(false, Ordering::SeqCst);
+ }
+ });
+
+ let fallback_handle = app.handle().clone();
+ let fallback_done = handoff_done.clone();
+ tauri::async_runtime::spawn(async move {
+ // First attempt after 15 s; subsequent retries every
+ // 250 ms up to 10 total attempts. Bounded so a
+ // permanently missing main window doesn't spin forever
+ // (the warn! log surfaces it instead). The retry exists
+ // because `ReadySignal` only emits `app://ready` once
+ // at mount — if we lost the race with that single
+ // event AND the first reveal failed, without a retry
+ // the user would be stuck on the splash.
+ tokio::time::sleep(Duration::from_secs(15)).await;
+ for attempt in 0..10 {
+ if fallback_done.swap(true, Ordering::SeqCst) {
+ return;
+ }
+ tracing::warn!(
+ attempt,
+ "splash handoff fallback: `app://ready` never fired in time, force-revealing main window"
+ );
+ if reveal_main_close_splash(&fallback_handle) {
+ return;
+ }
+ fallback_done.store(false, Ordering::SeqCst);
+ tokio::time::sleep(Duration::from_millis(250)).await;
+ }
+ tracing::error!(
+ "splash handoff fallback: exhausted 10 reveal attempts, user is likely stuck on splash"
+ );
+ });
+
Ok(())
})
.invoke_handler(tauri::generate_handler![
@@ -647,6 +710,41 @@ fn show_main_window(app: &AppHandle) {
}
}
+/// Reveal the main window and close the splash, in that order.
+///
+/// Same ordering rule as the old frontend version: show main first,
+/// then close splash, so there is never a moment where the desktop is
+/// visible between the two on a multi-monitor / compositing setup.
+///
+/// Returns `true` only when *both* operations succeed (main shown +
+/// splash closed, or splash already absent). On any failure, returns
+/// `false` so the caller can clear its "done" flag and let the other
+/// path (event listener or fallback timer) retry — without this,
+/// a transient failure would leave the user stuck on an eternal
+/// splash.
+fn reveal_main_close_splash(app: &AppHandle) -> bool {
+ // Bail out *before* touching the splash if the main window isn't
+ // available or refuses to show — otherwise we'd close the only
+ // visible window the user has and leave them staring at the
+ // desktop with no way back into the app until they re-launch.
+ let Some(main) = app.get_webview_window("main") else {
+ tracing::warn!("splash handoff: main window missing at reveal time");
+ return false;
+ };
+ if let Err(err) = main.show() {
+ tracing::warn!(?err, "splash handoff: main.show failed");
+ return false;
+ }
+ let _ = main.set_focus();
+ if let Some(splash) = app.get_webview_window("splashscreen") {
+ if let Err(err) = splash.close() {
+ tracing::warn!(?err, "splash handoff: splash.close failed");
+ return false;
+ }
+ }
+ true
+}
+
/// Toggle Pause / Resume from the tray. Looks at the engine's current
/// state (atomic, no async needed) so the menu item works as a single
/// "Lecture / Pause" entry instead of two stateful labels we'd have to
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 6f4a459..d65e546 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -30,11 +30,11 @@
"center": true,
"resizable": false,
"decorations": false,
- "transparent": true,
"alwaysOnTop": true,
"skipTaskbar": true,
"focus": false,
- "shadow": false
+ "shadow": false,
+ "backgroundColor": "#121212"
}
],
"security": {
diff --git a/src/components/common/ReadySignal.tsx b/src/components/common/ReadySignal.tsx
new file mode 100644
index 0000000..90fe16b
--- /dev/null
+++ b/src/components/common/ReadySignal.tsx
@@ -0,0 +1,31 @@
+import { useEffect, type ReactNode } from "react";
+import { emit } from "@tauri-apps/api/event";
+
+/**
+ * Emits `app://ready` once after the first React commit so the Rust
+ * backend can reveal the main window and close the splash from native
+ * code (see `reveal_main_close_splash` in src-tauri/src/lib.rs).
+ *
+ * We rely on a `useEffect` rather than a `requestAnimationFrame` dance
+ * because WebKitGTK 2.52 suspends rAF callbacks while a window is
+ * `visible: false` — rAF would never fire until the backend reveals
+ * the window, deadlocking the handoff until the 15 s safety-net timer
+ * trips. `useEffect` runs after React commits, which is the actual
+ * guarantee we care about (DOM is populated before reveal); the
+ * compositor will paint the first frame as part of the reveal itself,
+ * so we don't need to observe a paint to avoid a flash.
+ *
+ * Lives in its own file rather than in `main.tsx` so it satisfies
+ * the React Fast Refresh constraint ("a file must only export
+ * components"). Bundle entry points like `main.tsx` have non-component
+ * side effects (root.render, i18n init) that prevent HMR from
+ * extracting the component cleanly.
+ */
+export function ReadySignal({ children }: { children: ReactNode }) {
+ useEffect(() => {
+ void emit("app://ready").catch((err) => {
+ console.error("[ReadySignal] emit(app://ready) failed", err);
+ });
+ }, []);
+ return <>{children}>;
+}
diff --git a/src/components/views/SettingsView.tsx b/src/components/views/SettingsView.tsx
index 8bff9f7..cf1796e 100644
--- a/src/components/views/SettingsView.tsx
+++ b/src/components/views/SettingsView.tsx
@@ -615,9 +615,7 @@ export function SettingsView({ onNavigate }: SettingsViewProps) {
setCoverResultMsg(null);
try {
const fetched = await batchFetchMissingAlbumCovers();
- setCoverResultMsg(
- t("library.fetchCoversResult", { count: fetched }),
- );
+ setCoverResultMsg(t("library.fetchCoversResult", { count: fetched }));
} catch (err) {
console.error("[SettingsView] fetch missing covers failed", err);
setCoverResultMsg(t("library.fetchCoversFailed"));
diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx
index 95a248d..517bd99 100644
--- a/src/contexts/ThemeContext.tsx
+++ b/src/contexts/ThemeContext.tsx
@@ -34,63 +34,66 @@ const writeStoredTheme = (isDark: boolean) => {
export function ThemeProvider({ children }: { children: ReactNode }) {
const [isDark, setIsDark] = useState(readStoredTheme);
- const toggleTheme = useCallback((event?: ReactMouseEvent) => {
- let nextValue = false;
- const flipTheme = () =>
- setIsDark((prev) => {
- nextValue = !prev;
- return nextValue;
- });
+ const toggleTheme = useCallback(
+ (event?: ReactMouseEvent) => {
+ let nextValue = false;
+ const flipTheme = () =>
+ setIsDark((prev) => {
+ nextValue = !prev;
+ return nextValue;
+ });
- // Persist BEFORE triggering any animation. Some Linux WebKitGTK builds
- // crash the webview during startViewTransition on certain GPU/Wayland
- // stacks (issue #34) — writing first guarantees the next launch picks
- // up the new theme even if this transition kills the process.
- const persistNext = (value: boolean) => writeStoredTheme(value);
+ // Persist BEFORE triggering any animation. Some Linux WebKitGTK builds
+ // crash the webview during startViewTransition on certain GPU/Wayland
+ // stacks (issue #34) — writing first guarantees the next launch picks
+ // up the new theme even if this transition kills the process.
+ const persistNext = (value: boolean) => writeStoredTheme(value);
- // Fallback: no View Transitions API support → instant swap
- if (typeof document === "undefined" || !document.startViewTransition) {
- flipTheme();
- persistNext(nextValue);
- return;
- }
+ // Fallback: no View Transitions API support → instant swap
+ if (typeof document === "undefined" || !document.startViewTransition) {
+ flipTheme();
+ persistNext(nextValue);
+ return;
+ }
- // Compute & persist the future value before the animation starts so the
- // write lands even if the compositor crashes mid-transition.
- persistNext(!isDark);
+ // Compute & persist the future value before the animation starts so the
+ // write lands even if the compositor crashes mid-transition.
+ persistNext(!isDark);
- const transition = document.startViewTransition(flipTheme);
+ const transition = document.startViewTransition(flipTheme);
- // Radial reveal from the click point
- if (event) {
- const x = event.clientX;
- const y = event.clientY;
- const endRadius = Math.hypot(
- Math.max(x, window.innerWidth - x),
- Math.max(y, window.innerHeight - y),
- );
+ // Radial reveal from the click point
+ if (event) {
+ const x = event.clientX;
+ const y = event.clientY;
+ const endRadius = Math.hypot(
+ Math.max(x, window.innerWidth - x),
+ Math.max(y, window.innerHeight - y),
+ );
- transition.ready
- .then(() => {
- document.documentElement.animate(
- {
- clipPath: [
- `circle(0px at ${x}px ${y}px)`,
- `circle(${endRadius}px at ${x}px ${y}px)`,
- ],
- },
- {
- duration: 600,
- easing: "ease-in-out",
- pseudoElement: "::view-transition-new(root)",
- },
- );
- })
- .catch(() => {
- // Animation failed — the theme has still toggled via flipTheme()
- });
- }
- }, [isDark]);
+ transition.ready
+ .then(() => {
+ document.documentElement.animate(
+ {
+ clipPath: [
+ `circle(0px at ${x}px ${y}px)`,
+ `circle(${endRadius}px at ${x}px ${y}px)`,
+ ],
+ },
+ {
+ duration: 600,
+ easing: "ease-in-out",
+ pseudoElement: "::view-transition-new(root)",
+ },
+ );
+ })
+ .catch(() => {
+ // Animation failed — the theme has still toggled via flipTheme()
+ });
+ }
+ },
+ [isDark],
+ );
return (
diff --git a/src/lib/nowPlayingCard.ts b/src/lib/nowPlayingCard.ts
index f519925..09636a1 100644
--- a/src/lib/nowPlayingCard.ts
+++ b/src/lib/nowPlayingCard.ts
@@ -39,10 +39,21 @@ export async function renderNowPlayingCard(
canvas.height = SIZE;
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Canvas 2D context unavailable");
+ // Smoother image scaling for the inevitable cover upscale below.
+ // Browsers default to "low" which produces visibly soft cover art on
+ // a 1080×1080 card.
+ ctx.imageSmoothingEnabled = true;
+ ctx.imageSmoothingQuality = "high";
// ----- Load cover (if any) ------------------------------------------
+ // Prefer the full-resolution artwork over the thumbnails — `_2x` is
+ // 128×128 and `_1x` is 64×64 (see src-tauri/src/thumbnails.rs), both
+ // far too small for the 580 px cover slot and 1320 px blurred
+ // backdrop on this 1080² card. The original `artwork_path` is the
+ // image the scanner extracted from the audio file (typically
+ // 500–1500 px square), which gives a crisp share card.
const coverSrc = resolveRemoteImage(
- track.artwork_path_2x ?? track.artwork_path,
+ track.artwork_path ?? track.artwork_path_2x ?? track.artwork_path_1x,
null,
);
let coverImg: HTMLImageElement | null = null;
@@ -67,15 +78,22 @@ export async function renderNowPlayingCard(
// ----- Backdrop ------------------------------------------------------
if (coverImg) {
- // Draw the cover full-bleed, then blur via composite layers (the
- // 2D context's filter API isn't reliable in WebKit/Edge — we fake
- // it by drawing the cover scaled up and overlaying a heavy dark
- // wash).
- ctx.drawImage(coverImg, -120, -120, SIZE + 240, SIZE + 240);
- // Heavy dark overlay so the foreground text + cover stay readable.
+ // Real CSS-style blur via the 2D context filter. Both
+ // Chromium-WebView2 (Windows) and WebKitGTK 2.40+ (Linux) support
+ // it; macOS WebKit has supported it for years. Falls back to a no-
+ // op string on the off chance an old engine doesn't recognise it,
+ // which gives a slightly less blurry but still readable backdrop.
+ ctx.save();
+ ctx.filter = "blur(60px) brightness(0.6)";
+ // Draw a bit past the edges so the blur's transparent halo doesn't
+ // bleed into the canvas border.
+ ctx.drawImage(coverImg, -80, -80, SIZE + 160, SIZE + 160);
+ ctx.restore();
+ // Tinted gradient overlay so the foreground text + cover stay
+ // readable on bright artwork.
const wash = ctx.createLinearGradient(0, 0, 0, SIZE);
- wash.addColorStop(0, `rgba(0,0,0,0.55)`);
- wash.addColorStop(1, `rgba(${darken(accent, 0.4)},0.85)`);
+ wash.addColorStop(0, `rgba(0,0,0,0.45)`);
+ wash.addColorStop(1, `rgba(${darken(accent, 0.4)},0.75)`);
ctx.fillStyle = wash;
ctx.fillRect(0, 0, SIZE, SIZE);
} else {
diff --git a/src/main.tsx b/src/main.tsx
index b489400..002bed3 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,11 +1,8 @@
import React from "react";
import ReactDOM from "react-dom/client";
-import {
- getCurrentWindow,
- Window as TauriWindow,
-} from "@tauri-apps/api/window";
import App from "./App";
import { MiniPlayerApp } from "./MiniPlayerApp";
+import { ReadySignal } from "./components/common/ReadySignal";
import "./app.css";
import { i18nReady } from "./i18n";
@@ -14,48 +11,31 @@ import { i18nReady } from "./i18n";
// a stripped-down provider tree (no LibraryContext / sidebar / etc).
const isMini = new URLSearchParams(window.location.search).get("mini") === "1";
-// The main window is created with `visible: false` in tauri.conf.json so
-// the user never sees a white WebView while Rust setup + React mount run.
-// A `splashscreen` window is created in its place (small, transparent,
-// always-on-top) to give visual feedback during the cold-start delay
-// — especially on the very first launch after install, when Windows
-// SmartScreen / Defender scans every freshly-extracted DLL.
-//
-// We reveal the main window after the first frame is painted, then
-// close the splash. Order matters: show main BEFORE closing splash so
-// there's never a moment where the desktop is visible between the two.
-// The mini-player is its own window opened explicitly with visible:
-// true, so skip the dance there.
-function revealMainWindow() {
- if (isMini) return;
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- void (async () => {
- try {
- await getCurrentWindow().show();
- } catch (err) {
- console.error("[main] window.show failed", err);
- }
- try {
- const splash = await TauriWindow.getByLabel("splashscreen");
- if (splash) await splash.close();
- } catch (err) {
- console.error("[main] splash close failed", err);
- }
- })();
- });
- });
-}
+// The main window is created with `visible: false` in tauri.conf.json
+// so the user never sees a white WebView while Rust setup + React mount
+// run. A `splashscreen` window is shown in its place. The backend
+// listens for `app://ready` and atomically reveals the main window +
+// closes the splash from native code (see `reveal_main_close_splash`
+// in src-tauri/src/lib.rs). The actual event emission lives in
+// `ReadySignal` so this entry-point file can stay HMR-friendly.
i18nReady
.catch((err) => {
console.error("[i18n] initialization failed", err);
})
.finally(() => {
- ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+ const root = ReactDOM.createRoot(
+ document.getElementById("root") as HTMLElement,
+ );
+ root.render(
- {isMini ? : }
+ {isMini ? (
+
+ ) : (
+
+
+
+ )}
,
);
- revealMainWindow();
});