diff --git a/frontend/src/components/cover-grid/cover-grid-styles.ts b/frontend/src/components/cover-grid/cover-grid-styles.ts
index 946283f..544c60a 100644
--- a/frontend/src/components/cover-grid/cover-grid-styles.ts
+++ b/frontend/src/components/cover-grid/cover-grid-styles.ts
@@ -111,13 +111,32 @@ const gridStyles = css`
scale: 0.95;
}
+ /* Title and year on one line, and only the title truncates.
+
+ The year used to be part of the same run of text, so it was the
+ first thing an ellipsis ate: a card wide enough for a long album
+ name never showed its year, and browsing by year showed years
+ only for the albums with short names -- the sort said one thing
+ and the cards showed another.
+
+ A flex row rather than a second line, because the card's height
+ is what the virtualizer measures rows by. */
.album-name {
font-size: var(--album-name-font, 14px);
font-weight: 400;
color: var(--yj-text-primary, #fff);
+ display: flex;
+ justify-content: center;
+ align-items: baseline;
+ gap: 0.35em;
+ min-width: 0;
+ }
+
+ .album-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ min-width: 0;
}
.artist-name {
@@ -131,6 +150,8 @@ const gridStyles = css`
.album-year {
color: var(--yj-text-tertiary, #888);
+ flex: 0 0 auto;
+ white-space: nowrap;
}
/* ========================================
diff --git a/frontend/src/components/cover-grid/cover-grid.ts b/frontend/src/components/cover-grid/cover-grid.ts
index 540f4ad..83ac1aa 100644
--- a/frontend/src/components/cover-grid/cover-grid.ts
+++ b/frontend/src/components/cover-grid/cover-grid.ts
@@ -1898,10 +1898,10 @@ export class CoverGrid
class="album-name"
title="${album.Name}"
>
- ${album.Name}${album.Year
- ? html`
-
- (${album.Year})${album.Name}${album.Year
+ ? html`(${album.Year})`
: nothing}
diff --git a/frontend/test/components/album-card-year.test.ts b/frontend/test/components/album-card-year.test.ts
new file mode 100644
index 0000000..ce57a48
--- /dev/null
+++ b/frontend/test/components/album-card-year.test.ts
@@ -0,0 +1,87 @@
+/**
+ * The year on an album card survives a long album name.
+ *
+ * The year used to be part of the same run of text as the title, inside
+ * one `text-overflow: ellipsis` box — so it was the first thing the
+ * ellipsis ate. A card wide enough for a long name never showed its
+ * year at all, which means sorting the grid *by year* showed years only
+ * for the albums with short names: the sort said one thing and the
+ * cards showed another.
+ *
+ * The fix is a flex row in which only the title truncates, rather than
+ * a second line, because the card's height is what the virtualizer
+ * measures rows by.
+ */
+import { describe, expect, it, beforeEach } from 'vitest';
+import type { LitElement } from 'lit';
+
+import '@components/cover-grid/cover-grid';
+import { emit, stub, flush, resetHarness } from '@test/support/harness';
+import { Events } from '../../src/events';
+import { fixture, shadowAll } from '@test/support/render';
+
+const LONG =
+ 'The Rise and Fall of a Midwest Princess in the Key of Everything';
+
+/** Long names throughout: the fault only shows on a card under
+ * pressure, and a grid of "Album 3" proves nothing. */
+const ALBUMS = Array.from({ length: 12 }, (_, i) => ({
+ ID: i + 1,
+ Name: `${LONG} ${i + 1}`,
+ ArtistName: 'Aurora Fields',
+ Year: 2019 + (i % 5),
+}));
+
+/** Give the virtualizer a viewport; a zero-height host renders nothing. */
+function sized(el: HTMLElement): void {
+ el.style.display = 'block';
+ el.style.height = '600px';
+ el.style.width = '900px';
+}
+
+async function settle(el: LitElement): Promise {
+ await flush();
+ await el.updateComplete;
+ await new Promise((r) => setTimeout(r, 80));
+}
+
+describe('the album card’s year', () => {
+ beforeEach(() => {
+ resetHarness();
+ stub('library.Library.GetAlbums', ALBUMS);
+ stub('library.Library.GetTracks', []);
+ emit(Events.LibraryScanComplete);
+ });
+
+ it('is rendered on every card, however long the name', async () => {
+ const el = await fixture('cover-grid');
+
+ sized(el);
+ await settle(el);
+
+ const cards = shadowAll(el, '.album-card');
+ const years = shadowAll(el, '.album-year');
+
+ expect(cards.length).toBeGreaterThan(0);
+ expect(years).toHaveLength(cards.length);
+ expect(years.every((y) => /^\(\d{4}\)$/.test(y.textContent!.trim()))).toBe(
+ true,
+ );
+ });
+
+ it('is not what the ellipsis eats', async () => {
+ const el = await fixture('cover-grid');
+
+ sized(el);
+ await settle(el);
+
+ const year = shadowAll(el, '.album-year')[0]!;
+ const title = shadowAll(el, '.album-title')[0]!;
+
+ // The title is the box that gives way...
+ expect(title.scrollWidth).toBeGreaterThan(title.clientWidth);
+ // ...and the year keeps every pixel it asked for.
+ expect(year.clientWidth).toBeGreaterThan(0);
+ expect(year.scrollWidth).toBeLessThanOrEqual(year.clientWidth + 1);
+ });
+});