From b9c69c546cbaff53777d11dbcd26648919a2755d Mon Sep 17 00:00:00 2001 From: matitos Date: Mon, 21 Sep 2026 17:11:18 +0000 Subject: [PATCH] Retry the strategy list and make collapse obvious - The strategy/preset lists were loaded once with failures swallowed, so a single failed first request left the strategy dropdown empty forever. They are now retried every 3s until populated. - Clearer collapse affordance: hover highlight, a hide/show hint, and a tooltip; Backtest and Parameter sweep start collapsed so the strategy selector and status are immediately visible. - Adds an integration test that the strategy dropdown is populated and the panel actually collapses. --- frontend/src/App.vue | 19 ++++++-- frontend/src/components/BacktestPanel.vue | 2 +- frontend/src/components/CollapsiblePanel.vue | 4 +- frontend/src/components/SweepPanel.vue | 2 +- frontend/src/strategy-panel.test.js | 48 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 frontend/src/strategy-panel.test.js 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) {