Codebase review: stability, performance, usability, and i18n fixes

Stability:
- Fix race condition: set _is_running before create_task in target processors
- Await probe task after cancel in wled_target_processor
- Replace raw fetch() with fetchWithAuth() across devices, kc-targets, pattern-templates
- Add try/catch to showTestTemplateModal in streams.js
- Wrap blocking I/O in asyncio.to_thread (picture_targets, system restore)
- Fix dashboardStopAll to filter only running targets with ok guard

Performance:
- Vectorize fire effect spark loop with numpy in effect_stream
- Vectorize FFT band binning with cumulative sum in analysis.py
- Rewrite pixel_processor with vectorized numpy (accept ndarray or list)
- Add httpx.AsyncClient connection pooling with lock in wled_provider
- Optimize _send_pixels_http to avoid np.hstack allocation in wled_client
- Mutate chart arrays in-place in dashboard, perf-charts, targets
- Merge dashboard 2-batch fetch into single Promise.all
- Hoist frame_time outside loop in mapped_stream

Usability:
- Fix health check interval load/save in device settings
- Swap confirm modal button classes (No=secondary, Yes=danger)
- Add aria-modal to audio/value source editors, fix close button aria-labels
- Add modal footer close button to settings modal
- Add dedicated calibration LED count validation error keys

i18n:
- Replace ~50 hardcoded English strings with t() calls across 12 JS files
- Add 50 new keys to en.json, ru.json, zh.json
- Localize inline toasts in index.html with window.t fallback
- Add data-i18n to command palette footer
- Add localization policy to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 12:12:37 +03:00
parent c95c6e9a44
commit bd8d7a019f
31 changed files with 460 additions and 233 deletions

View File

@@ -113,9 +113,15 @@ function _updateTargetFpsChart(targetId, fpsTarget) {
if (!chart) return;
const actualH = _targetFpsHistory[targetId] || [];
const currentH = _targetFpsCurrentHistory[targetId] || [];
chart.data.labels = actualH.map(() => '');
chart.data.datasets[0].data = [...actualH];
chart.data.datasets[1].data = [...currentH];
// Mutate in-place to avoid array copies
const ds0 = chart.data.datasets[0].data;
ds0.length = 0;
ds0.push(...actualH);
const ds1 = chart.data.datasets[1].data;
ds1.length = 0;
ds1.push(...currentH);
while (chart.data.labels.length < ds0.length) chart.data.labels.push('');
chart.data.labels.length = ds0.length;
chart.options.scales.y.max = fpsTarget * 1.15;
chart.update('none');
}
@@ -352,7 +358,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
setTimeout(() => document.getElementById('target-editor-name').focus(), 100);
} catch (error) {
console.error('Failed to open target editor:', error);
showToast('Failed to open target editor', 'error');
showToast(t('target.error.editor_open_failed'), 'error');
}
}
@@ -972,7 +978,7 @@ export async function startTargetProcessing(targetId) {
showToast(t('device.started'), 'success');
} else {
const error = await response.json();
showToast(`Failed to start: ${error.detail}`, 'error');
showToast(t('target.error.start_failed'), 'error');
}
});
}
@@ -986,7 +992,7 @@ export async function stopTargetProcessing(targetId) {
showToast(t('device.stopped'), 'success');
} else {
const error = await response.json();
showToast(`Failed to stop: ${error.detail}`, 'error');
showToast(t('target.error.stop_failed'), 'error');
}
});
}
@@ -1027,7 +1033,7 @@ export async function cloneTarget(targetId) {
showTargetEditor(null, target);
} catch (error) {
console.error('Failed to clone target:', error);
showToast('Failed to clone target', 'error');
showToast(t('target.error.clone_failed'), 'error');
}
}
@@ -1042,11 +1048,11 @@ export async function toggleTargetAutoStart(targetId, enable) {
loadTargetsTab();
} else {
const error = await response.json();
showToast(`Failed: ${error.detail}`, 'error');
showToast(t('target.error.autostart_toggle_failed'), 'error');
}
} catch (error) {
console.error('Failed to toggle auto-start:', error);
showToast('Failed to toggle auto-start', 'error');
showToast(t('target.error.autostart_toggle_failed'), 'error');
}
}
@@ -1062,7 +1068,7 @@ export async function deleteTarget(targetId) {
showToast(t('targets.deleted'), 'success');
} else {
const error = await response.json();
showToast(`Failed to delete: ${error.detail}`, 'error');
showToast(t('target.error.delete_failed'), 'error');
}
});
}