Show artist breadcrumb on sidebar Library button when on artist-detail page

Artist-detail is a "pseudo-page" reachable from Library, the unified
Search page, and the global search popover. It has no [data-page]
match in the sidebar, so navigateToPage's bulk-active-removal left
every nav button unhighlighted while the user was viewing an artist —
the sidebar offered no visual anchor for where they were.

Now:
- navigateToPage('artist-detail') falls back to highlighting the
  Library button when no [data-page] match exists, anchoring the
  sidebar to the canonical home for artist detail views.
- A new _updateSidebarLibraryBreadcrumb() helper rewrites the Library
  button label between plain "Library" and a "Library / Artist Name"
  breadcrumb based on currentPage + artistDetailPageState. Long names
  (>14 chars) truncate with an ellipsis; the full name shows on hover
  via the title attribute.
- Called from navigateToPage (entering / leaving the page) and from
  loadArtistDetailData (covers same-page artist switches in the
  similar-artist chain where currentPage stays 'artist-detail').

CSS adds .nav-text-root / .nav-text-sep / .nav-text-context selectors
so the "Library" anchor word stays visually dominant while the
separator and artist name dim to a secondary tier — readable but not
competing for attention.

Pure visual change. No backend touched. No new tests (DOM-only).
pull/464/head
Broque Thomas 3 weeks ago
parent 6db2cbe2a7
commit 0c4fad033d

@ -3441,6 +3441,11 @@ function closeHelperSearch() {
// projects that span multiple commits before shipping. Strip the flag at
// release time and add a real `date:` line at the top of the version block.
const WHATS_NEW = {
'2.4.2': [
// --- post-2.4.1 dev work — entries hidden by _getLatestWhatsNewVersion until the build version bumps ---
{ date: 'Unreleased — 2.4.2 dev cycle' },
{ title: 'Sidebar Library Button Shows Artist Breadcrumb', desc: 'when you open an artist detail page (from library, search, or the global search popover), the sidebar Library button now lights up and rewrites its label to "Library / Artist Name" — long names truncate with an ellipsis and the full name shows on hover. revertes to plain "Library" when you leave. purely visual, no functionality change.', page: 'library' },
],
'2.4.1': [
// --- May 1, 2026 — patch release ---
{ date: 'May 1, 2026 — 2.4.1 release' },

@ -2189,6 +2189,13 @@ function navigateToPage(pageId, options = {}) {
const navButton = document.querySelector(`[data-page="${pageId}"]`);
if (navButton) {
navButton.classList.add('active');
} else if (pageId === 'artist-detail') {
// Artist-detail is a "pseudo-page" reachable from Library, Search,
// or the global search popover — it has no [data-page] match. Treat
// it as a Library context so the sidebar still anchors the user
// somewhere instead of showing a blank active state.
const libraryBtn = document.querySelector('[data-page="library"]');
if (libraryBtn) libraryBtn.classList.add('active');
}
// Update pages
@ -2199,6 +2206,12 @@ function navigateToPage(pageId, options = {}) {
currentPage = pageId;
// Refresh the Library button label so artist-detail shows a breadcrumb
// ("Library / Artist Name") and other pages show plain "Library".
if (typeof _updateSidebarLibraryBreadcrumb === 'function') {
_updateSidebarLibraryBreadcrumb();
}
if (!options.skipPushState) {
const urlPath = pageId === 'dashboard' ? '/' : '/' + pageId;
if (window.location.pathname !== urlPath) {

@ -707,6 +707,63 @@ let discographyFilterState = {
ownership: 'all' // 'all', 'owned', 'missing'
};
// Maximum visible characters of an artist name in the sidebar Library
// breadcrumb. Names longer than this get truncated with an ellipsis so the
// nav button width stays consistent across the rest of the sidebar.
const _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN = 14;
function _updateSidebarLibraryBreadcrumb() {
// Rewrite the Library nav button label between plain "Library" and a
// "Library / <Artist>" breadcrumb depending on whether the user is on
// the artist-detail pseudo-page. Pure visual — touches no app state.
const btn = document.querySelector('[data-page="library"]');
if (!btn) return;
const textEl = btn.querySelector('.nav-text');
if (!textEl) return;
const onArtistDetail = (typeof currentPage === 'string' && currentPage === 'artist-detail');
const artistName = onArtistDetail ? (artistDetailPageState.currentArtistName || '') : '';
if (!onArtistDetail || !artistName) {
// Default state: plain "Library" label. Use textContent so we wipe
// any previously-injected breadcrumb spans cleanly.
textEl.textContent = 'Library';
textEl.removeAttribute('data-breadcrumb');
return;
}
// Truncate long names so the button width stays consistent.
let display = artistName;
if (display.length > _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN) {
display = display.slice(0, _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN - 1).trimEnd() + '…';
}
// Render via inline spans so CSS can style the root / separator / context
// independently. Escape via textContent on individual spans.
textEl.setAttribute('data-breadcrumb', '1');
textEl.textContent = '';
const root = document.createElement('span');
root.className = 'nav-text-root';
root.textContent = 'Library';
const sep = document.createElement('span');
sep.className = 'nav-text-sep';
sep.textContent = ' / ';
const ctx = document.createElement('span');
ctx.className = 'nav-text-context';
ctx.textContent = display;
ctx.title = artistName; // full name on hover
textEl.appendChild(root);
textEl.appendChild(sep);
textEl.appendChild(ctx);
}
// Expose so init.js navigateToPage can call it without a circular import.
if (typeof window !== 'undefined') {
window._updateSidebarLibraryBreadcrumb = _updateSidebarLibraryBreadcrumb;
}
// Friendly labels for the dynamic "← Back to X" button on the artist-detail page.
// Page id (the value of currentPage) -> button label.
const _ARTIST_DETAIL_BACK_LABELS = {
@ -895,6 +952,14 @@ function initializeArtistDetailPage() {
async function loadArtistDetailData(artistId, artistName) {
console.log(`🔄 Loading artist detail data for: ${artistName} (ID: ${artistId})`);
// Refresh the sidebar Library breadcrumb so it picks up the new artist
// name. Covers same-page navigation between artists (similar-artist
// chain) where navigateToPage doesn't fire because the page id stays
// 'artist-detail'.
if (typeof _updateSidebarLibraryBreadcrumb === 'function') {
_updateSidebarLibraryBreadcrumb();
}
// Reset discography filters to defaults
resetDiscographyFilters();

@ -491,6 +491,42 @@ body {
font-weight: 600;
}
/* Library breadcrumb when on the artist-detail pseudo-page, the Library
button label rewrites to "Library / <Artist>". The accent color stays on
the "Library" root; the separator and artist name are dimmed so the
anchor word remains scannable at a glance. JS truncates long names and
sets the full name as a `title` attribute for hover. */
.nav-text[data-breadcrumb] {
display: inline-flex;
align-items: baseline;
max-width: 100%;
min-width: 0;
}
.nav-text[data-breadcrumb] .nav-text-root {
color: inherit;
flex-shrink: 0;
}
.nav-text[data-breadcrumb] .nav-text-sep {
color: rgba(255, 255, 255, 0.4);
font-weight: 400;
flex-shrink: 0;
}
.nav-button.active .nav-text[data-breadcrumb] .nav-text-sep {
color: rgba(255, 255, 255, 0.45);
}
.nav-text[data-breadcrumb] .nav-text-context {
color: rgba(255, 255, 255, 0.75);
font-weight: 500;
font-style: italic;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.nav-button.active .nav-text[data-breadcrumb] .nav-text-context {
color: rgba(255, 255, 255, 0.85);
}
/* Sidebar Spacer */
.sidebar-spacer {
flex: 1;

Loading…
Cancel
Save