feat(miner-ui): add live refresh to run-history and portfolio views - #5570
Conversation
Both views fetched their data once on mount with no refresh mechanism -- an operator had to manually reload the page to see updated state. Add a shared usePolledFetch hook (fetch once on mount, then re-fetch on a fixed interval, skipping overlapping in-flight ticks) and wire both RunHistoryPage and PortfolioPage to it via an injectable pollIntervalMs prop, so new activity appears without a full reload. Fixes #4856
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-13 06:18:29 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
RunHistoryPageandPortfolioPage(apps/gittensory-miner-ui/src/routes/{run-history,portfolio}.tsx)fetched their data exactly once on mount via a bare
useEffect, with no refresh mechanism of any kind — anoperator had to manually reload the page to see updated
run_state/portfolio_queueactivity.apps/gittensory-miner-ui/src/lib/use-polled-fetch.tshook:usePolledFetch(loadFn, intervalMs)fetches once immediately on mount, then re-invokesloadFnon a fixed interval so new activityappears without a full page reload. It skips an overlapping tick if the previous fetch is still in flight
(never stacks concurrent requests) and stops polling cleanly on unmount.
pollIntervalMsprop (defaulting to the exportedDEFAULT_POLL_INTERVAL_MS, 10 seconds — frequent enough to feel live for a cheap local SQLite read served bythe dev server, without polling so tightly it's wasteful), replacing the old fetch-once
useEffectin each.local API calls happen, not what they call or what they read.
Fixes #4856
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally —apps/gittensory-miner-ui/**sits outside vitest's rootcoverage.includeglob (only rootsrc/**is Codecov-measured), socodecov/patchcannot see this diff. Test thoroughness is not lowered for that reason (see below).npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateRan the full local gate:
npm run test:ci(810 test files, 0 failures) andnpm audit --audit-level=moderate(0 vulnerabilities), both clean on the final rebased commit.New
apps/gittensory-miner-ui/src/use-polled-fetch.test.ts(6 tests, driven withvitest's fake timers via@testing-library/react'srenderHook): fetches once immediately on mount; re-fetches and updates the returned result on every interval tick; stops polling entirely after unmount (no further calls even after advancing time well past several intervals); skips an overlapping tick when the previous fetch is still in flight instead of stacking concurrent requests, then resumes fetching on the next free tick; never applies a late-resolving fetch's result after unmount; and a sanity check on the exported default interval. Added one wiring-level test each to the existingrun-history.test.tsx/portfolio-queue.test.tsxpage suites asserting the injected loader is actually invoked again after the configured interval elapses, proving the pages are correctly wired to the hook (not just the hook working in isolation).Safety
UI Evidencesection below.DEFAULT_POLL_INTERVAL_MS).UI Evidence
This is a local, loopback-only dev-server dashboard with no hosted/public deployment to screenshot from this
environment (no browser screenshot tooling available here). Verified functionally instead: the visible
rendered output (table rows, cards) is unchanged between polls — only the underlying fetch cadence changed —
and the fake-timer-driven test suite above proves the actual re-fetch behavior end-to-end at the React
component level (mount → initial render → interval tick → loader invoked again → unmount → polling stops),
which is a stronger, more precise proof than a static screenshot could offer for this specific behavior change.
Notes
("New activity appears in the UI without a full page reload") is fully satisfied by polling alone, and it
requires no new UI chrome. A manual refresh button would be a reasonable follow-up if wanted, and could reuse
the same
usePolledFetchreturn value's implicit "last successful fetch" state.identical fetch-once
useEffectbodies before this change — factoring it out also means only one place needsupdating if the polling strategy changes later (e.g. exponential backoff on repeated failures).