From a8defd10b45e869d1c618422679f88d06943948c Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 6 Mar 2026 17:32:02 +0000 Subject: [PATCH 1/3] fix: unstakePosition() has no error handling at all (#482) Wrap the entire unstakePosition() body in try/catch/finally so that errors from exitPosition(), loadActivePositions(), and loadPositions() are caught, displayed to the user via the existing error ref, and loading state is always reset correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/collapse/CollapseActive.vue | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web-app/src/components/collapse/CollapseActive.vue b/web-app/src/components/collapse/CollapseActive.vue index c46f335..688bd23 100644 --- a/web-app/src/components/collapse/CollapseActive.vue +++ b/web-app/src/components/collapse/CollapseActive.vue @@ -214,12 +214,13 @@ async function changeTax(id: bigint, nextTaxRateIndex: number | null) { } async function unstakePosition() { - await unstake.exitPosition(props.id); - loading.value = true; - const POLL_INTERVAL_MS = 1000; - const TIMEOUT_MS = 30_000; - const deadline = Date.now() + TIMEOUT_MS; + error.value = null; try { + await unstake.exitPosition(props.id); + loading.value = true; + const POLL_INTERVAL_MS = 1000; + const TIMEOUT_MS = 30_000; + const deadline = Date.now() + TIMEOUT_MS; while (Date.now() < deadline) { const { positions } = await loadActivePositions(currentChainId.value); if (!positions.some(p => BigInt(p.id) === props.id)) break; @@ -227,6 +228,10 @@ async function unstakePosition() { await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS)); } await loadPositions(currentChainId.value); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Failed to unstake position:', err); + error.value = 'Failed to unstake position.'; } finally { loading.value = false; } From 037de2d1fd7776d7a0d124bb51694b2b26ff40a8 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 6 Mar 2026 17:44:30 +0000 Subject: [PATCH 2/3] ci: retrigger after infra failure From 98dd839bb5dfad4178470ce0594971eb4e6d5599 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 6 Mar 2026 18:15:30 +0000 Subject: [PATCH 3/3] fix: unstakePosition() has no error handling at all (#482) Address AI reviewer warnings: - Add separate unstakeError ref so unstake failures are not displayed via the load-data error UI whose Retry calls loadActivePositionData - Add exitSucceeded flag to distinguish pre-exit failures ("Failed to unstake position.") from post-exit data-refresh failures ("Position unstaked, but failed to refresh data."), preventing factually wrong messages after a successful transaction - Unstake error UI has no Retry button; the Unstake button in actions remains available for retrying a failed transaction Co-Authored-By: Claude Sonnet 4.6 --- web-app/src/components/collapse/CollapseActive.vue | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/web-app/src/components/collapse/CollapseActive.vue b/web-app/src/components/collapse/CollapseActive.vue index 688bd23..6b6f9ca 100644 --- a/web-app/src/components/collapse/CollapseActive.vue +++ b/web-app/src/components/collapse/CollapseActive.vue @@ -39,6 +39,9 @@ {{ error }} Retry +
+ {{ unstakeError }} +
Tax Cost
@@ -122,6 +125,7 @@ const taxPaidGes = ref(); const profit = ref(); const loading = ref(false); const error = ref(null); +const unstakeError = ref(null); const tag = computed(() => { // Compare by index instead of decimal to avoid floating-point issues @@ -214,9 +218,11 @@ async function changeTax(id: bigint, nextTaxRateIndex: number | null) { } async function unstakePosition() { - error.value = null; + unstakeError.value = null; + let exitSucceeded = false; try { await unstake.exitPosition(props.id); + exitSucceeded = true; loading.value = true; const POLL_INTERVAL_MS = 1000; const TIMEOUT_MS = 30_000; @@ -230,8 +236,10 @@ async function unstakePosition() { await loadPositions(currentChainId.value); } catch (err) { // eslint-disable-next-line no-console - console.error('Failed to unstake position:', err); - error.value = 'Failed to unstake position.'; + console.error(exitSucceeded ? 'Failed to refresh positions after unstake:' : 'Failed to unstake position:', err); + unstakeError.value = exitSucceeded + ? 'Position unstaked, but failed to refresh data.' + : 'Failed to unstake position.'; } finally { loading.value = false; }