Push layout edits to displays, and let a layout be renamed

Editing a layout notified nothing at all — no push to the displays using it — so a zone change
waited for the next heartbeat refresh at best. Combined with the Android rebuild being keyed on the
layout ID (which does not change when you edit a layout in place), that is why adding a fourth zone
took a force-stop to appear. The player-side fix makes the rebuild happen; this makes it prompt.

Renaming: duplicating a template produces "<template> (Copy)" and there was nowhere to change it.
The server has always accepted a name on PUT /layouts/:id; no UI ever sent one. The only name field
in the editor belongs to the selected ZONE, which is easy to mistake for the layout's own — zones
could always be renamed, layouts never could. The heading is now an input and its value rides along
with the Save the user already presses.

Verified on an Android 12 emulator, app left running throughout:
  3-zone layout assigned      -> "Multi-zone layout with 3 zones (was=null)"
  4th zone added in place     -> "Multi-zone layout with 4 zones (layout=a96c39ab, was=a96c39ab)"
The ids match, so the old id-only condition would have skipped the rebuild entirely. Applied ~1s
after the PUT, with no restart and no force-stop.

Also verified the background-audio fix on the same device: 1 started audio player with the video in
the foreground, 0 once another app was brought to the front. (First attempt was invalid — HOME
re-shows this player because it is the default launcher, so it never backgrounds.)

859 server tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
This commit is contained in:
Claude 2026-07-30 19:58:21 -05:00
parent abdb3b434d
commit cad19abee1
3 changed files with 27 additions and 2 deletions

View file

@ -1499,6 +1499,7 @@ export default {
'layout.properties': 'Properties',
'layout.delete_zone': 'Delete Zone',
'layout.zone_n': 'Zone {n}',
'layout.rename': 'Layout name — click to rename',
'layout.prop.name': 'Name',
'layout.prop.x': 'X (%)',
'layout.prop.y': 'Y (%)',

View file

@ -116,7 +116,12 @@ async function renderEditor(container, layoutId) {
${t('layout.back')}
</a>
<div class="page-header">
<h1 id="layoutName">${esc(layout.name)}</h1>
<!-- Editable in place. Duplicating a template names the copy "<template> (Copy)" and there
was nowhere at all to change it the only name field in this editor belongs to the
selected ZONE, which is easy to mistake for the layout's own. Reported on #234. -->
<input id="layoutName" class="input" value="${esc(layout.name)}"
aria-label="${t('layout.rename')}" title="${t('layout.rename')}"
style="font-size:24px;font-weight:600;background:transparent;border:1px solid transparent;padding:2px 6px;max-width:420px">
<div style="display:flex;gap:8px">
<button class="btn btn-secondary btn-sm" id="addZoneBtn">${t('layout.add_zone')}</button>
<button class="btn btn-primary btn-sm" id="saveLayoutBtn">${t('common.save')}</button>
@ -297,9 +302,12 @@ async function renderEditor(container, layoutId) {
// exactly. The old per-zone delete-then-add loop could accumulate zones
// (and regenerated every zone id each save). Keep each zone's id so
// device->zone assignments survive.
const newName = (document.getElementById('layoutName')?.value || '').trim();
const updated = await API(`/layouts/${layoutId}`, {
method: 'PUT',
body: JSON.stringify({ zones }),
// Name goes with the zones so renaming is part of the Save the user already
// presses, not a second hidden action.
body: JSON.stringify(newName ? { zones, name: newName } : { zones }),
});
if (updated && updated.error) { showToast(updated.error, 'error'); return; }
layout = updated;

View file

@ -145,6 +145,22 @@ router.put('/:id', (req, res) => {
const updated = db.prepare('SELECT * FROM layouts WHERE id = ?').get(req.params.id);
updated.zones = db.prepare('SELECT * FROM layout_zones WHERE layout_id = ? ORDER BY sort_order').all(req.params.id);
// Push to the displays using this layout. Editing a layout used to notify nothing at all, so a
// zone change waited for the next heartbeat refresh at best — and on Android it did not apply
// even then, because the rebuild was keyed on the layout ID, which does not change when you edit
// a layout in place. Reported on #234 as "I added 4 zones and they dont appear on the screen".
// The player-side fix makes the rebuild happen; this makes it happen promptly.
try {
const io = req.app.get('io');
if (io) {
const { buildPlaylistPayload } = require('../ws/deviceSocket');
const commandQueue = require('../lib/command-queue');
for (const d of db.prepare('SELECT id FROM devices WHERE layout_id = ?').all(req.params.id)) {
commandQueue.queueOrEmitPlaylistUpdate(io.of('/device'), d.id, buildPlaylistPayload);
}
}
} catch (e) { /* best-effort; the heartbeat refresh still picks it up */ }
res.json(updated);
});