You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Read this before starting. This is about the poll timer's schedule, not about whether a fetch
happens at all — both fetches already happen correctly today. Verify the fix with fake timers asserting when the next automatic poll fires relative to the original schedule, per Requirements below.
#7082 ("Ledgers page and portfolio's queue-actions table never join the shared live-refresh poll
cadence," closed 2026-07-18) added polling to routes/portfolio.tsx's queue-actions items and
re-confirmed the existing requirement for its summary poll. Its own Requirements section states, verbatim:
The existing manual refresh after a successful release/requeue/pause/resume action must be preserved
(an operator's own action should still reflect immediately, not wait for the next tick) — this is
additive to the timer-based refresh, not a replacement for it.
The shipped implementation in PortfolioPage does not satisfy that: both the summary and queue-actions
polls are wired through useCallbacks that depend on refreshKey, a counter bumped after every
successful release/requeue action:
usePolledFetch (src/lib/use-polled-fetch.ts) keys its useEffect on [loadFn, intervalMs]. Because refreshKey changing gives loadSummary/loadItems a new function identity every time, the effect's
cleanup fires (window.clearInterval(id)), then the effect body re-runs: it calls run() immediately and starts a brand-new window.setInterval(run, intervalMs). That is a replacement of the poll
timer — the 10-second countdown restarts from the moment of the action — not an addition to an
undisturbed periodic schedule. Concretely: if the shared cadence is every 10s and an operator releases an
item at t=23s (mid-cycle), the next scheduled poll should still land at t=30s per the "additive"
requirement; instead, because the effect restarts, it now lands at t=33s. In the pathological case of an
operator (or another automated caller acting through the same page) performing several actions within one
poll interval of each other, the periodic tick can be repeatedly deferred and never actually fire on its
own, since each action keeps re-arming a fresh intervalMs-long wait before the previous one completes.
This is a real, if usually minor, drift from what #7082 shipped as its explicit fix acceptance criterion
— the poll cadence an operator was told to expect ("every 10s, plus an immediate extra refresh on my own
action") is not what actually runs ("every 10s after my last action, whichever is later").
Requirements
After a successful release/requeue action bumps refreshKey, the immediate extra fetch must still
happen (this already works and must not regress).
The existing periodic schedule must not be reset by that action: if the last scheduled (non-action)
poll landed at some time T, the next scheduled poll must still land at T + pollIntervalMs, regardless
of any action-triggered fetch that happened in between — matching Ledgers page and portfolio's queue-actions table never join the shared live-refresh poll cadence #7082's own "additive... not a
replacement" wording.
This may require restructuring usePolledFetch (or PortfolioPage's use of it) so an imperative
"refresh now" trigger is decoupled from the hook's own setInterval lifecycle — e.g. exposing a refresh() function from usePolledFetch itself that callers can invoke without tearing down and
restarting the interval, rather than forcing a new interval via a changed loadFn identity. Changing usePolledFetch's public shape is in scope if it's the right fix; keep its existing behavior (fetch on
mount, skip overlapping ticks, cancel on unmount) intact for every other caller (routes/index.tsx, routes/run-history.tsx, routes/ledgers.tsx's two polls).
No behavior change for routes/ledgers.tsx's LedgersPage, which does not have this bug (its loadLedgers/loadGovernorPauseState references are stable; the operator's own pause/resume action
there is already reflected via a separate direct setPauseState write, not by perturbing the poll's loadFn — the correct "additive" pattern this issue is asking PortfolioPage to match).
Deliverables
PortfolioPage's summary poll no longer resets its interval schedule on a refreshKey bump
PortfolioPage's queue-actions poll no longer resets its interval schedule on a refreshKey bump
Both still perform an immediate extra fetch right after a successful action, unchanged from today
Regression test using fake timers: start a poll, let it run for part of an interval, trigger an
action-driven refresh, then advance time to the original schedule's next tick and assert a poll
fetch occurs at that original time (not pollIntervalMs after the action)
Test Coverage Requirements
apps/** is outside this repo's Codecov coverage.include scope (confirmed in PR #7082's own body), so
this is not gated by codecov/patch. The actual bar is apps/loopover-miner-ui's own Vitest suite
(npm test in that workspace) passing, plus the new regression test above actually failing against the
current refreshKey-in-useCallback-deps implementation before the fix and passing after.
Expected Outcome
PortfolioPage's summary and queue-actions polls behave exactly as #7082 specified: an operator's own
release/requeue action gets an immediate extra refresh, and the underlying DEFAULT_POLL_INTERVAL_MS
periodic schedule keeps running on its original cadence, undisturbed, exactly as it already does on every
other polled view in this app.
Links & Resources
apps/loopover-miner-ui/src/routes/portfolio.tsx (PortfolioPage), apps/loopover-miner-ui/src/lib/use-polled-fetch.ts, apps/loopover-miner-ui/src/routes/ledgers.tsx (LedgersPage's runGovernorAction, for the working
"additive" comparison), #7082 (the issue whose own acceptance criterion this doesn't fully meet).
Context
#7082 ("Ledgers page and portfolio's queue-actions table never join the shared live-refresh poll
cadence," closed 2026-07-18) added polling to
routes/portfolio.tsx's queue-actions items andre-confirmed the existing requirement for its summary poll. Its own Requirements section states, verbatim:
The shipped implementation in
PortfolioPagedoes not satisfy that: both the summary and queue-actionspolls are wired through
useCallbacks that depend onrefreshKey, a counter bumped after everysuccessful release/requeue action:
usePolledFetch(src/lib/use-polled-fetch.ts) keys itsuseEffecton[loadFn, intervalMs]. BecauserefreshKeychanging givesloadSummary/loadItemsa new function identity every time, the effect'scleanup fires (
window.clearInterval(id)), then the effect body re-runs: it callsrun()immediatelyand starts a brand-new
window.setInterval(run, intervalMs). That is a replacement of the polltimer — the 10-second countdown restarts from the moment of the action — not an addition to an
undisturbed periodic schedule. Concretely: if the shared cadence is every 10s and an operator releases an
item at t=23s (mid-cycle), the next scheduled poll should still land at t=30s per the "additive"
requirement; instead, because the effect restarts, it now lands at t=33s. In the pathological case of an
operator (or another automated caller acting through the same page) performing several actions within one
poll interval of each other, the periodic tick can be repeatedly deferred and never actually fire on its
own, since each action keeps re-arming a fresh
intervalMs-long wait before the previous one completes.This is a real, if usually minor, drift from what #7082 shipped as its explicit fix acceptance criterion
— the poll cadence an operator was told to expect ("every 10s, plus an immediate extra refresh on my own
action") is not what actually runs ("every 10s after my last action, whichever is later").
Requirements
refreshKey, the immediate extra fetch must stillhappen (this already works and must not regress).
poll landed at some time T, the next scheduled poll must still land at
T + pollIntervalMs, regardlessof any action-triggered fetch that happened in between — matching Ledgers page and portfolio's queue-actions table never join the shared live-refresh poll cadence #7082's own "additive... not a
replacement" wording.
usePolledFetch(orPortfolioPage's use of it) so an imperative"refresh now" trigger is decoupled from the hook's own
setIntervallifecycle — e.g. exposing arefresh()function fromusePolledFetchitself that callers can invoke without tearing down andrestarting the interval, rather than forcing a new interval via a changed
loadFnidentity. ChangingusePolledFetch's public shape is in scope if it's the right fix; keep its existing behavior (fetch onmount, skip overlapping ticks, cancel on unmount) intact for every other caller (
routes/index.tsx,routes/run-history.tsx,routes/ledgers.tsx's two polls).routes/ledgers.tsx'sLedgersPage, which does not have this bug (itsloadLedgers/loadGovernorPauseStatereferences are stable; the operator's own pause/resume actionthere is already reflected via a separate direct
setPauseStatewrite, not by perturbing the poll'sloadFn— the correct "additive" pattern this issue is askingPortfolioPageto match).Deliverables
PortfolioPage's summary poll no longer resets its interval schedule on arefreshKeybumpPortfolioPage's queue-actions poll no longer resets its interval schedule on arefreshKeybumpaction-driven refresh, then advance time to the original schedule's next tick and assert a poll
fetch occurs at that original time (not
pollIntervalMsafter the action)Test Coverage Requirements
apps/**is outside this repo's Codecovcoverage.includescope (confirmed in PR #7082's own body), sothis is not gated by
codecov/patch. The actual bar isapps/loopover-miner-ui's own Vitest suite(
npm testin that workspace) passing, plus the new regression test above actually failing against thecurrent
refreshKey-in-useCallback-deps implementation before the fix and passing after.Expected Outcome
PortfolioPage's summary and queue-actions polls behave exactly as #7082 specified: an operator's ownrelease/requeue action gets an immediate extra refresh, and the underlying
DEFAULT_POLL_INTERVAL_MSperiodic schedule keeps running on its original cadence, undisturbed, exactly as it already does on every
other polled view in this app.
Links & Resources
apps/loopover-miner-ui/src/routes/portfolio.tsx(PortfolioPage),apps/loopover-miner-ui/src/lib/use-polled-fetch.ts,apps/loopover-miner-ui/src/routes/ledgers.tsx(LedgersPage'srunGovernorAction, for the working"additive" comparison), #7082 (the issue whose own acceptance criterion this doesn't fully meet).