diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1055e1c..d4e4bb3 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -55,6 +55,7 @@ let socket let pollTimer let statusTimer let graceTimer +let listTimer async function refreshAll() { const [p, o, e, q] = await Promise.all([ @@ -183,11 +184,15 @@ onMounted(() => { if (!connected.value) pollingFallback.value = true }, 6000) - // Initial loads are best-effort; the intervals below keep retrying. + // Initial loads are best-effort. api.getStatus().then((s) => (status.value = s)).catch(() => {}) refreshAll().catch(() => {}) - loadStrategies().catch(() => {}) - loadPresets().catch(() => {}) + + // The strategy list and presets rarely change, so load them once but keep + // retrying until they arrive: a single failed first request used to leave the + // strategy dropdown empty forever. + loadStrategies() + loadPresets() // Poll fast enough that the UI feels live even when it is the only channel. pollTimer = setInterval(() => refreshAll().catch(() => {}), 3000) @@ -195,12 +200,20 @@ onMounted(() => { () => api.getStatus().then((s) => (status.value = s)).catch(() => {}), 3000, ) + // Retry the static lists until non-empty (covers a failed first load and a + // backend that was still starting up). + listTimer = setInterval(() => { + if (!strategies.value.length) loadStrategies() + if (!presets.value.length) loadPresets() + if (strategies.value.length && presets.value.length) clearInterval(listTimer) + }, 3000) }) onUnmounted(() => { socket?.close() clearInterval(pollTimer) clearInterval(statusTimer) + clearInterval(listTimer) clearTimeout(graceTimer) }) diff --git a/frontend/src/components/BacktestPanel.vue b/frontend/src/components/BacktestPanel.vue index 6a2f365..4ab0a9d 100644 --- a/frontend/src/components/BacktestPanel.vue +++ b/frontend/src/components/BacktestPanel.vue @@ -37,7 +37,7 @@ async function run(payload) {