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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Replace waitForLoadState('networkidle') in the post-login redirect with
waitForURL('**/app/stake**'). Persistent WebSocket connections prevent
networkidle from ever firing, mirroring the same fix applied to navigate.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Include taxDue in taxCostPercent computation so the Tax% and Net%
shown in the header P&L line are consistent with the Tax Cost card
and Total row, which already use taxDue + taxPaid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
taxPaidGes = taxDue + taxPaid, so the displayed value includes both
outstanding tax and historically paid tax. Rename the UI label from
'Tax Paid' to 'Tax Cost' to accurately reflect the combined amount.
Update the matching E2E test selector accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace waitForLoadState('networkidle') with a comment explaining that callers
must assert on a route-specific element. The SPA keeps persistent WebSocket
connections for blockchain event subscriptions, so networkidle never fires
within the 10 s window, causing spurious TimeoutErrors in 01-acquire-and-stake
and 02-max-stake-all-tax-rates.
Vue Router processes popstate synchronously inside page.evaluate(), so the
route transition has already started by the time evaluate() resolves. Callers'
toBeVisible() assertions (with their own timeouts) serve as the readiness gate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarifies that the event-driven engineering principles apply to infrastructure (Docker, scripts, startup/teardown) and test/scenario execution — NOT frontend HTTP API polling.
Frontend polling (e.g. LiveStats → Ponder GraphQL every 30s) is fine. The scalability solution is caching at the proxy layer (`Cache-Control` headers via Caddy), not WebSocket subscriptions.
Relates to #447
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/470
Replace the try/catch on observing the transient 'Submitting…' button text with a
two-phase disabled→enabled check. The button gains the `disabled` attribute the
moment `swapping=true` and loses it when the swap finishes. By placing
`toBeEnabled({ timeout: 60_000 })` unconditionally after the try block, both
paths (fast RPC where disabled state cycles in <100 ms and slow RPC where it is
clearly observable) now wait for the actual ready state rather than falling
through to only a 2-second static guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all 10 bare waitForTimeout() calls in tests/e2e/usertest/helpers.ts
with proper event-driven Playwright waits per AGENTS.md Engineering Principle #3
("replace when touched"):
connectWallet():
- Remove 500ms resize-event sleep: connectButton.isVisible({ timeout: 5_000 })
already waits for the layout to settle after the resize event
- Remove 2000ms "connector init" sleep: the subsequent isVisible check was
already event-driven
- Replace 2x 1000ms panel-animation sleeps with
injectedConnector.waitFor({ state: 'visible' }) — the .connectors-element
appearing is the exact observable DOM event
- Remove 2x 2000ms "handshake" sleeps: walletDisplay.waitFor() at the end of
the function is the correct gate for connection completion
attemptStake():
- Remove 3000ms post-goto sleep: tokenAmountSlider.waitFor() at the next line
is the correct page-load gate
- Remove 2x 500ms debounce sleeps after fill/select: stakeButton.waitFor()
downstream is the correct reactive-state gate
- Remove 3000ms post-transaction sleep: the button returning to "Stake" text
(waitFor at line 619) is already the correct transaction-completion gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add eslint-disable-next-line no-restricted-syntax comments with
justification to the 10 bare waitForTimeout() calls in
tests/e2e/usertest/helpers.ts.
All waitForTimeout calls in the spec files (01–06 and all usertest
specs) were already properly documented after PR #417. helpers.ts was
the only remaining file with bare calls:
- 6 in connectWallet(): wallet connector panel animation and
connection handshake have no observable DOM event to await
- 4 in attemptStake(): Ponder indexing lag, debounced form handlers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When profit or taxPaidGes were undefined (data still loading), the
computed returned props.amount due to the ?? 0 fallbacks. The computed
now returns undefined until both values are loaded, and the template
guard is simplified to total !== undefined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace truthiness guard with Number.isFinite() so NaN and Infinity are
explicitly rejected rather than silently masked. Zero is now handled by
toLocaleString, which returns '0' correctly. Add test cases for NaN and
Infinity.
The direct cast of Error to Record<string, unknown> is rejected by
TypeScript because the types don't sufficiently overlap. Cast via
unknown first to satisfy the compiler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viem's BaseError extends Error, so the instanceof Error branch was
firing and returning error.message before the isRecord branch could
check shortMessage. Check shortMessage first inside instanceof Error
so terse viem messages are shown to users instead of verbose full ones.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace console.warn with a thrown Error when wethReceived <= 0n so any
caller without a return-value check is protected, not just always-leave.spec.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>