fix(player): stop navbar title/icons overlapping on small screens; show device names in grouping

#500: the absolutely-centered page title and the right-aligned icon bar
shared the same space in the fixed-height navbar and overlapped on phones
(portrait). On <=600px the navbar now wraps into two rows: row 1 keeps the
logo with the title beside it (the title fills the remaining width and
ellipsizes), and the icon bar drops onto its own centered, full-width row
below. CSS-only.

#498: the zone/grouping UI showed raw IP addresses instead of device names.
Root cause was a field-name casing bug: Zone.js read info.Name (uppercase),
but the device info field is info.name (lowercase) everywhere else in the UI
(app.js, DeviceList, Library, TTS, ...). So the lookup always missed and fell
back to the IP. Fixed the casing in the deviceName() helper, and made the
"Add to zone" picker show the device name with the IP as a smaller secondary
line (reusing the .picker-device-info/name/ip pattern the other pickers
already use). Member/master rows resolve names via deviceName().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-14 21:48:02 +02:00
co-authored by Claude Opus 4.8
parent d6257e6108
commit 23949189f7
2 changed files with 38 additions and 4 deletions
@@ -146,6 +146,37 @@ img { display: block; max-width: 100%; }
font-family: monospace;
}
/* Small screens (phones, portrait): the absolutely-centered page title and the
right-aligned icon bar share the same space in the fixed-height navbar and
collide (#500). Let the navbar wrap into two rows: row 1 keeps the logo with
the title beside it (the title fills the remaining width and ellipsizes), and
the icon bar drops onto its own full-width row below. */
@media (max-width: 600px) {
.navbar {
flex-wrap: wrap;
height: auto;
min-height: 52px;
row-gap: 0.25rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
.page-title {
position: static;
transform: none;
flex: 1 1 auto; /* sit next to the logo and fill the rest of row 1 */
width: auto;
min-width: 0; /* allow the title to shrink + ellipsize */
}
.nav-links {
order: 1; /* force the icon bar onto its own row below */
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
}
.nav-logo {
width: 24px;
height: 24px;
@@ -52,7 +52,7 @@ export function Zone({ deviceId, devices }) {
const zoneIps = new Set([zone.masterIp, ...(zone.members || []).map(m => m.ip)].filter(Boolean));
const available = Object.entries(devices || {}).filter(([ip]) => !zoneIps.has(ip));
const deviceName = (ip) => devices[ip]?.info?.Name ?? ip;
const deviceName = (ip) => devices[ip]?.info?.name || ip;
return html`
<div class="zone-section">
@@ -76,7 +76,7 @@ export function Zone({ deviceId, devices }) {
${(zone.members || []).map(m => html`
<div class="zone-member" key=${m.ip}>
<span class="zone-badge slave">Member</span>
<span class="zone-member-name">${m.name || m.ip}</span>
<span class="zone-member-name">${m.name || deviceName(m.ip)}</span>
<button class="btn-icon zone-remove" title="Remove from zone"
onClick=${() => removeDevice(m.ip)}>✕</button>
</div>
@@ -93,7 +93,7 @@ export function Zone({ deviceId, devices }) {
${zone.isSlave && html`
<div class="zone-row">
<span class="zone-badge slave">Member</span>
<span class="zone-member-name">Zone: ${zone.masterName || zone.masterIp}</span>
<span class="zone-member-name">Zone: ${zone.masterName || deviceName(zone.masterIp)}</span>
<button class="btn-secondary zone-btn" onClick=${leave}>Leave zone</button>
</div>
`}
@@ -105,7 +105,10 @@ export function Zone({ deviceId, devices }) {
<div class="picker-devices">
${available.map(([ip, d]) => html`
<button class="picker-device-btn" key=${ip} onClick=${() => addDevice(ip)}>
${d.info?.Name ?? ip}
<div class="picker-device-info">
<span class="picker-device-name">${d.info?.name || ip}</span>
<span class="picker-device-ip">${d.info?.ip_address || ip}</span>
</div>
</button>
`)}
</div>