diff --git a/server/src/wled_controller/api/routes/color_strip_sources.py b/server/src/wled_controller/api/routes/color_strip_sources.py index 57c3e02..3485c6b 100644 --- a/server/src/wled_controller/api/routes/color_strip_sources.py +++ b/server/src/wled_controller/api/routes/color_strip_sources.py @@ -890,6 +890,14 @@ async def test_color_strip_ws( meta["layer_infos"] = layer_infos await websocket.send_text(_json.dumps(meta)) + # For api_input: send the current buffer immediately so the client + # gets a frame right away (fallback color if inactive) rather than + # leaving the canvas blank/stale until external data arrives. + if is_api_input: + initial_colors = stream.get_latest_colors() + if initial_colors is not None: + await websocket.send_bytes(initial_colors.tobytes()) + # For picture sources, grab the live stream for frame preview _frame_live = None if is_picture and hasattr(stream, 'live_stream'): diff --git a/server/src/wled_controller/static/js/features/color-strips.ts b/server/src/wled_controller/static/js/features/color-strips.ts index 997dcb3..642c478 100644 --- a/server/src/wled_controller/static/js/features/color-strips.ts +++ b/server/src/wled_controller/static/js/features/color-strips.ts @@ -2563,10 +2563,15 @@ function _openTestModal(sourceId: string) { modal.onclick = (e) => { if (e.target === modal) closeTestCssSourceModal(); }; _cssTestSourceId = sourceId; - // Reset views + // Reset views and clear stale canvas content (document.getElementById('css-test-strip-view') as HTMLElement).style.display = 'none'; (document.getElementById('css-test-rect-view') as HTMLElement).style.display = 'none'; (document.getElementById('css-test-layers-view') as HTMLElement).style.display = 'none'; + // Clear all test canvases to prevent stale frames from previous sessions + modal.querySelectorAll('canvas').forEach(c => { + const ctx = c.getContext('2d'); + if (ctx) ctx.clearRect(0, 0, c.width, c.height); + }); (document.getElementById('css-test-led-group') as HTMLElement).style.display = ''; // Input source selector: shown for both CSS test and CSPT test, hidden for api_input const csptGroup = document.getElementById('css-test-cspt-input-group') as HTMLElement | null; @@ -2660,6 +2665,10 @@ function _cssTestConnect(sourceId: string, ledCount: number, fps?: number) { const isPicture = _cssTestMeta.edges && _cssTestMeta.edges.length > 0; _cssTestIsComposite = _cssTestMeta.layers && _cssTestMeta.layers.length > 0; + // Reset FPS timestamps so the initial bootstrap frame + // (sent right after metadata for api_input) isn't counted + if (_cssTestIsApiInput) _cssTestFpsTimestamps = []; + // Show correct view (document.getElementById('css-test-strip-view') as HTMLElement).style.display = (isPicture || _cssTestIsComposite) ? 'none' : ''; (document.getElementById('css-test-rect-view') as HTMLElement).style.display = isPicture ? '' : 'none';