fix(ui): enforce save semantics for default and preset chunks

This commit is contained in:
2026-03-22 02:53:34 +13:00
parent 5badf17719
commit 63235c7822
4 changed files with 16 additions and 14 deletions

View File

@@ -171,9 +171,13 @@ async def send_presets(request, session):
if not sender: if not sender:
return json.dumps({"error": "Transport not configured"}), 503, {'Content-Type': 'application/json'} return json.dumps({"error": "Transport not configured"}), 503, {'Content-Type': 'application/json'}
async def send_chunk(chunk_presets): async def send_chunk(chunk_presets, is_last):
# Include save flag so the led-driver can persist when desired. # Save/default should only be sent with the final presets chunk.
msg = build_message(presets=chunk_presets, save=save_flag, default=default_id) msg = build_message(
presets=chunk_presets,
save=save_flag and is_last,
default=default_id if is_last else None,
)
await sender.send(msg, addr=destination_mac) await sender.send(msg, addr=destination_mac)
MAX_BYTES = 240 MAX_BYTES = 240
@@ -195,7 +199,7 @@ async def send_presets(request, session):
last_msg = test_msg last_msg = test_msg
else: else:
try: try:
await send_chunk(batch) await send_chunk(batch, False)
except Exception: except Exception:
return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'} return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'}
await asyncio.sleep(send_delay_s) await asyncio.sleep(send_delay_s)
@@ -205,7 +209,7 @@ async def send_presets(request, session):
if batch: if batch:
try: try:
await send_chunk(batch) await send_chunk(batch, True)
except Exception: except Exception:
return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'} return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'}
await asyncio.sleep(send_delay_s) await asyncio.sleep(send_delay_s)

View File

@@ -132,7 +132,7 @@ const sendEspnowMessage = (obj) => {
// Send a select message for a preset to all device names in the current tab. // Send a select message for a preset to all device names in the current tab.
// Uses the preset ID as the select key. // Uses the preset ID as the select key.
const sendSelectForCurrentTabDevices = (presetId, sectionEl, saveToDevice = true) => { const sendSelectForCurrentTabDevices = (presetId, sectionEl) => {
const section = sectionEl || document.querySelector('.presets-section[data-tab-id]'); const section = sectionEl || document.querySelector('.presets-section[data-tab-id]');
if (!section || !presetId) { if (!section || !presetId) {
return; return;
@@ -155,9 +155,6 @@ const sendSelectForCurrentTabDevices = (presetId, sectionEl, saveToDevice = true
v: '1', v: '1',
select, select,
}; };
if (saveToDevice) {
message.save = true;
}
sendEspnowMessage(message); sendEspnowMessage(message);
}; };
@@ -1325,9 +1322,8 @@ document.addEventListener('DOMContentLoaded', () => {
// Build ESPNow messages for a single preset. // Build ESPNow messages for a single preset.
// Send order: // Send order:
// 1) preset payload (without save) // 1) preset payload (optionally with save)
// 2) optional select for device names // 2) optional select for device names (never with save)
// 3) optional save command
// saveToDevice defaults to true. // saveToDevice defaults to true.
const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice = true, setDefault = false) => { const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice = true, setDefault = false) => {
try { try {
@@ -1394,11 +1390,13 @@ const sendDefaultPreset = (presetId, deviceNames) => {
return; return;
} }
// Default should only set startup preset, not trigger live selection. // Default should only set startup preset, not trigger live selection.
// Save is attached to default messages.
// When device names are provided, scope the default update to those devices. // When device names are provided, scope the default update to those devices.
const targets = Array.isArray(deviceNames) const targets = Array.isArray(deviceNames)
? deviceNames.map((n) => (n || '').trim()).filter((n) => n.length > 0) ? deviceNames.map((n) => (n || '').trim()).filter((n) => n.length > 0)
: []; : [];
const message = { v: '1', default: presetId }; const message = { v: '1', default: presetId };
message.save = true;
if (targets.length > 0) { if (targets.length > 0) {
message.targets = targets; message.targets = targets;
} }

View File

@@ -354,7 +354,7 @@ async function loadTabContent(tabId) {
brightnessSendTimeout = setTimeout(() => { brightnessSendTimeout = setTimeout(() => {
if (typeof window.sendEspnowRaw === 'function') { if (typeof window.sendEspnowRaw === 'function') {
try { try {
window.sendEspnowRaw({ v: '1', b: val }); window.sendEspnowRaw({ v: '1', b: val, save: true });
} catch (err) { } catch (err) {
console.error('Failed to send brightness via ESPNow:', err); console.error('Failed to send brightness via ESPNow:', err);
} }