From 19c68d73a74e0e97d8f0f584d829df3cc7f35098 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 19 Aug 2026 00:38:05 -0400 Subject: [PATCH] fix(ui): keep the count in a partial badge that can act MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A control is named after what activating it does, so an actionable badge said "Request album X" — and `partial` is actionable, because an album you hold nine of twelve tracks of has three left to ask for. That made the one state the ring exists for the one state whose name did not mention it. The argument the `partial` branch already carries does not stop applying because the badge became clickable: a ring says "some" to a sighted user and nothing to anyone else. The name is now the action and the count. --- .../library-status-indicator.ts | 20 +++++++++++--- .../library-status-indicator.test.ts | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/library-status-indicator/library-status-indicator.ts b/frontend/src/components/library-status-indicator/library-status-indicator.ts index 1186c16..5fb8fbe 100644 --- a/frontend/src/components/library-status-indicator/library-status-indicator.ts +++ b/frontend/src/components/library-status-indicator/library-status-indicator.ts @@ -297,9 +297,23 @@ export class LibraryStatusIndicator extends LitElement { // row to one and nothing to the other, and "Add … to library" // was the old button's promise written into the copy. if (this.actionable) { - return this.status === 'queued' - ? `Cancel the request for ${kind}${name}` - : `Request ${kind}${name}`; + if (this.status === 'queued') { + return `Cancel the request for ${kind}${name}`; + } + + // A partly-held album is actionable *and* has a count, and + // the count does not survive being named after the action + // alone. The `partial` case below says why it matters — a + // ring says "some" to a sighted user and nothing to anyone + // else — and that argument does not stop applying because + // the badge became clickable. This branch used to say only + // "Request album X", so the one state the ring exists for + // was the one state whose name did not mention it. + if (this.status === 'partial') { + return `Request the rest of ${kind}${name} — ${this.owned} of ${this.expected} tracks are in your library`; + } + + return `Request ${kind}${name}`; } switch (this.status) { diff --git a/frontend/test/components/library-status-indicator.test.ts b/frontend/test/components/library-status-indicator.test.ts index fca6361..64aae0b 100644 --- a/frontend/test/components/library-status-indicator.test.ts +++ b/frontend/test/components/library-status-indicator.test.ts @@ -77,3 +77,30 @@ describe('the library status badge', () => { expect(name).toContain('Glass Harbour'); }); }); + +/** + * A partly-held album is the one state that is *actionable and + * counted*: there are tracks left to ask for, so the badge is a button + * — and a control is named after what activating it does, which is how + * the count came to be dropped from exactly the state the ring exists + * for. Both, or the ring says "some" to an eye and nothing to anyone + * else. + */ +describe('a partial badge that can act', () => { + it('names the action and keeps the count', async () => { + const el = await fixture('library-status-indicator', { + status: 'partial', + owned: 9, + expected: 12, + entityType: 'album', + label: 'Glass Harbour', + requestMbid: 'rg-1', + }); + + const name = shadow(el, '.badge')?.getAttribute('aria-label') ?? ''; + + expect(shadow(el, 'button.badge')).not.toBeNull(); + expect(name).toContain('Request the rest of'); + expect(name).toContain('9 of 12'); + }); +});