From e97a864db381315ac265af6ae2b7495b42f9f5f5 Mon Sep 17 00:00:00 2001 From: Ng Guoyou Date: Fri, 26 Jun 2026 00:35:12 +0800 Subject: [PATCH 1/2] fix(nowplaying): cover leaves with the track, not lingering/expanding on pause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a session lost its current track (media gone while the session stayed selected — e.g. a player that tears down/recreates its SMTC session on pause), the title cleared instantly (it is prop-derived) while the album cover stayed up on the crossfade's no-art grace (NO_ART_GRACE_MS), growing to fill the box the title/artist had vacated before finally fading — the reported "cover expands for a short while then disappears". Tie the cover's lifecycle to whether there is a current track: drop the layers at once when there is no media (not only when there is no session), and gate the cover stack on `hasMedia` so it leaves in the same commit as the title. The no-art grace now only bridges a genuine art-lag (media present, art not yet arrived); the idle `label` and track-to-track crossfade are unchanged. Adds a failing-first test (a present session with null media) that reproduced the lingering cover (was: 1 layer held; now: 0). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../lib/widgets/meters/NowPlaying.test.tsx | 34 ++++++++++++++++++ client/src/lib/widgets/meters/NowPlaying.tsx | Bin 15316 -> 16606 bytes 2 files changed, 34 insertions(+) diff --git a/client/src/lib/widgets/meters/NowPlaying.test.tsx b/client/src/lib/widgets/meters/NowPlaying.test.tsx index 79fb009..26b3c73 100644 --- a/client/src/lib/widgets/meters/NowPlaying.test.tsx +++ b/client/src/lib/widgets/meters/NowPlaying.test.tsx @@ -74,6 +74,26 @@ const session = (title: string, art: string | null): SessionRecord => ({ last_model_update: { Model: { playback: null, timeline: null, media: null, source: '' } } }); +// A live session with NO current track: present (so hasSession is true) but its media update is gone, +// so there's no title and no album art. Models a player that tears down / recreates its SMTC session on +// pause — the prior track can't be carried forward (mergeMediaForward has nothing for the new id), so +// the meter sees a session with nothing to show. The cover must leave WITH the (now-empty) title. +const tracklessSession = (): SessionRecord => ({ + session_id: 2, + source: 'spotify.exe', + timestamp_created: null, + timestamp_updated: null, + last_media_update: null, + last_model_update: { + Model: { + playback: { auto_repeat: 'None', rate: 1, shuffle: false, status: 'Paused', type: 'Music' }, + timeline: null, + media: null, + source: 'spotify.exe' + } + } +}); + // Render NowPlaying (props-driven — the NowPlayingHost container does the store wiring in the app) // showing a track whose cover has loaded (the visible, full-colour layer). async function renderWithLoadedCover() { @@ -500,6 +520,20 @@ describe('NowPlaying — art clearing', () => { expect(container.querySelector('[data-part="title"]')).toBeNull(); }); + it('drops the cover at once when the session loses its track — not held (expanding) on the no-art grace', async () => { + const { container, rerender } = await renderWithLoadedCover(); + expect(container.querySelectorAll('.np-thumb').length).toBe(1); + // The active session now carries no track (no media → no title, no art). The title disappears at + // once (it's read straight from props), so the cover must too — otherwise it lingers on the no-art + // grace, expanding to fill the box where the title/artist were before finally fading (the reported + // "cover expands for a short while then disappears" on pause). This differs from a genuine art-lag + // (a track IS present, its cover just hasn't arrived) below, where keeping the cover is correct. + rerender(); + expect(container.querySelectorAll('.np-thumb').length).toBe(0); + // The artist row is gone with the track — the cover did not outlive it. + expect(container.querySelector('[data-part="artist"]')).toBeNull(); + }); + it('keeps the stale cover during the grace window, then fades it out for a genuinely art-less track', async () => { const { container, rerender } = await renderWithLoadedCover(); vi.useFakeTimers(); diff --git a/client/src/lib/widgets/meters/NowPlaying.tsx b/client/src/lib/widgets/meters/NowPlaying.tsx index 127558c13ebb249c6c38aa72e3252775683bb94f..3a29f3fe865136de72e6cbb2a46d59dc37562917 100644 GIT binary patch delta 1495 zcmZWp!HOhB5JeEN8o>cYg+Yl~b+=n*yV`?@%(Nh@Fax_HGcD`ILv&SCSJKs)MP_!_ zHoHqJf_E*!Pw=YDVf_NXz_Z|ffCsN$jL57SXSEJZbymcSi1%LPZ_V#tJ@wPx-Y!ky zZb`l*3-UuZ93b5K?Ck90PiEjPW=rq}Txpsy%u0gHY&dj?ItS$)WY#!WU{0wvH_>`n za0-pU=xPfqT#_3kT*8rd1Wl?mBh7uiU11riE!s9Q$ zhq}ToS9adiHRXq3ELh4cp{LMFsB?a2cIy&?QEFUsU1tJxZ51cQ;u-229_F-z46RiR zQ5f5x%|or832l6-^g>BHR$%J)NC|T3&e`)uJHr0cxli zNSLF0HIvt&7OAQND$LD2VJ##F4#$HeGn$c^i93OXDI6SJJiGVf(*tJojF89H?WZrl zv>P_1;W2ZnSv*StL$q*eq0z7T1S_LimDn%O4!JdTh>LpmbFIvZ

`lPYXkg+2FS4 z1stxp0x?yl(ZDL<4mPNjE1AS7(iqMuc0ol!nP*RNe6_u4*8YEEolyZ(V43)3*v~pT z8YKNYpO3S(wbI!1k_`@s^q{?X^x_}S?1CeBj!XGF2lc6Oo?$f9BGB>po9`kK#Gc-= z#9}aQnjmbTM7$dr(h}2&=mHr}3VC`(NO5x~L-0Y$VklO!kyG(97CzX5NsG(Ia&HoYXcf<{_+Y#gus1l4oyOwV`~(8YZfM;Q_aN-tFsy2OxigQ?Dp? z@#p@xFYHcKo|EP*et;j5NwK%_39s>mAiBV?-}_)#iH#D36?6aHG2t_a^Dlf{HD0ka zYV?I{o5aNnND!kj0q0C3WQ=TG=qh-?@h!vG3bzQA$(GJjJ%+c^hq1!_eoVN29fpIX z{W|@q{WX1cKRH&bMU0U_m$aSUuf{6py!P+(&GwgfpKITqJm0=Qf%fys8?PnKW)i^^ wchHiCm7iX_#wkBc4%$bPWr6|8ZA+;>Es8S&(u`;ztAuqpNPr*^4 zAhEPKHAO!&B_~y(IJLMqGe1uuJ2kbSSRu2dSRpwds6(N&K#z-)Gc!#=Lop+~Y6XD6n?f52hnDS3PWzWrCl2MwQq{KB@ z+f;pWu&MIoa#IoZ#GDdaC7{?8Q^m>KOsyw>GBuObw*%S&0-Bn&5SER8N@kfI*W|Tk KlABY_D!Bk^B}n)H From 7f6136f4ff2f90a3038937d6bcc1fddd4e0b4bcc Mon Sep 17 00:00:00 2001 From: Ng Guoyou Date: Fri, 10 Jul 2026 04:18:33 +0800 Subject: [PATCH 2/2] chore: bump version to 0.0.51 Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 +- widgetsack/Cargo.toml | 2 +- widgetsack/tauri.conf.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f93baa4..d730ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5038,7 +5038,7 @@ dependencies = [ [[package]] name = "widgetsack" -version = "0.0.50" +version = "0.0.51" dependencies = [ "fontdb", "futures-util", diff --git a/widgetsack/Cargo.toml b/widgetsack/Cargo.toml index 30571dd..978e0c8 100644 --- a/widgetsack/Cargo.toml +++ b/widgetsack/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "widgetsack" # Keep in lockstep with tauri.conf.json's version (the bundle/updater source of truth). -version = "0.0.50" +version = "0.0.51" description = "widgetsack desktop widget platform" authors = ["you"] license = "MIT OR Apache-2.0" diff --git a/widgetsack/tauri.conf.json b/widgetsack/tauri.conf.json index 1577a10..5690e9e 100644 --- a/widgetsack/tauri.conf.json +++ b/widgetsack/tauri.conf.json @@ -50,7 +50,7 @@ "createUpdaterArtifacts": false }, "productName": "widgetsack", - "version": "0.0.50", + "version": "0.0.51", "identifier": "io.github.gyng", "plugins": {}, "app": {