Skip to content

feat(miner-ui): add live refresh to run-history and portfolio views - #5570

Merged
JSONbored merged 1 commit into
JSONbored:mainfrom
joaovictor712:feat/miner-ui-live-refresh
Jul 13, 2026
Merged

feat(miner-ui): add live refresh to run-history and portfolio views#5570
JSONbored merged 1 commit into
JSONbored:mainfrom
joaovictor712:feat/miner-ui-live-refresh

Conversation

@joaovictor712

Copy link
Copy Markdown
Contributor

Summary

  • Both RunHistoryPage and PortfolioPage (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 — an
    operator had to manually reload the page to see updated run_state/portfolio_queue activity.
  • Added a shared apps/gittensory-miner-ui/src/lib/use-polled-fetch.ts hook: usePolledFetch(loadFn, intervalMs) fetches once immediately on mount, then re-invokes loadFn on a fixed interval so new activity
    appears 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.
  • Wired both pages to the hook via a new injectable pollIntervalMs prop (defaulting to the exported
    DEFAULT_POLL_INTERVAL_MS, 10 seconds — frequent enough to feel live for a cheap local SQLite read served by
    the dev server, without polling so tightly it's wasteful), replacing the old fetch-once useEffect in each.
  • No new network surface beyond what the pages already fetched from — this only changes when the existing
    local API calls happen, not what they call or what they read.

Fixes #4856

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally — apps/gittensory-miner-ui/** sits outside vitest's root coverage.include glob (only root src/** is Codecov-measured), so codecov/patch cannot see this diff. Test thoroughness is not lowered for that reason (see below).
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Ran the full local gate: npm run test:ci (810 test files, 0 failures) and npm 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 with vitest's fake timers via @testing-library/react's renderHook): 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 existing run-history.test.tsx/portfolio-queue.test.tsx page 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

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. — N/A, no auth/session/CORS surface touched; this only changes the cadence of an existing local, loopback-only dev-server fetch.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — no API/OpenAPI/MCP surface touched; the existing local endpoints are called more often, not differently.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. — the hook re-fetches from the real injected loader; loading/error/empty states are unchanged and still render correctly on every poll tick.
  • Visible UI changes include a UI Evidence section below.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs. — no docs described per-route refresh cadence before this change, so none needed updating; the code is the source of truth here (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

  • Chose interval-based auto-polling over a manual "Refresh" button since the issue's acceptance criteria
    ("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 usePolledFetch return value's implicit "last successful fetch" state.
  • The polling hook is intentionally shared (not duplicated per page) since both pages had byte-for-byte
    identical fetch-once useEffect bodies before this change — factoring it out also means only one place needs
    updating if the polling strategy changes later (e.g. exponential backoff on repeated failures).

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
@joaovictor712
joaovictor712 requested a review from JSONbored as a code owner July 13, 2026 06:14
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 13, 2026
@loopover-orb

loopover-orb Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Tip

🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩

✅ Gittensory review result - approve/merge recommended

Review updated: 2026-07-13 06:18:29 UTC

6 files · 2 AI reviewers · no blockers · readiness 100/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR adds a small, well-scoped usePolledFetch hook and wires it into both existing pages via an injectable pollIntervalMs prop, replacing the fetch-once useEffect with polling that skips overlapping ticks and cleans up on unmount. The logic is sound: the inFlight guard and cancelled flag correctly prevent stacked requests and post-unmount state updates, and the new tests (immediate fetch, interval re-fetch, unmount stop, overlap-skip, late-resolve-after-unmount) exercise the real hook behavior rather than fabricating unreachable states. This is a narrow, low-risk, purely additive change to a local dev-server UI with no new network surface.

Nits — 5 non-blocking
  • apps/gittensory-miner-ui/src/lib/use-polled-fetch.ts: loadFn is a useEffect dependency, so callers passing an inline arrow function as loadPortfolioQueue/loadRunStates default parameter would cause the effect to re-run every render — worth confirming loadPortfolioQueue/fetchRunStates are stable module-level references (they are, per routes/*.tsx), but this is a subtle footgun for future callers.
  • The external brief's 'magic number 4856' and 'deep nesting depth 5' flags are noise here — the '4856' is an issue reference in a comment, not a numeric literal in code, and the nesting is a normal try/then/finally shape.
  • Consider documenting in use-polled-fetch.ts that loadFn must be referentially stable (or wrap with useCallback guidance) since it's a hook dependency.
  • The 10s DEFAULT_POLL_INTERVAL_MS is reasonable for a local SQLite read; no change needed.
  • nit: apps/gittensory-miner-ui/src/lib/use-polled-fetch.ts:19 relies on every loader converting failures into typed results; add a `.catch` path or a test documenting that rejected loaders are intentionally allowed to surface as unhandled test/runtime errors.
Signal Result Evidence
Code review ✅ No blockers 2 reviewers, synthesized
Linked issue ✅ Linked #4856
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 1880 registered-repo PR(s), 1240 merged, 49 issue(s).
Contributor context ✅ Confirmed Gittensor contributor galuis116; Gittensor profile; 1880 PR(s), 49 issue(s).
Gate result ✅ Passing No configured blocker found.
Improvement ✅ Minor risk: clean · value: minor — Code changes are accompanied by test evidence. LLM value judgment: moderate — The PR closes a real operator-facing gap (manual reload required to see updated run/queue state) with a small, reusable, well-tested hook rather than duplicating polling logic per page.
Linked issue satisfaction

Addressed
The PR introduces a shared usePolledFetch hook and wires both RunHistoryPage and PortfolioPage to poll on a fixed interval (default 10s) instead of fetching once on mount, directly satisfying the requirement that new activity appear without a full page reload, and includes tests validating the polling and re-render behavior.

Review context
  • Author: galuis116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, Python, Dart, TypeScript, HTML, MDX, Rust, C++
  • Official Gittensor activity: 1880 PR(s), 49 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
[BETA] Chat with Gittensory

Ask 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.

  • @gittensory ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @gittensory chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @gittensory mention with a real question is routed to the closest matching read-only command automatically -- no exact syntax required.

Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/portfolio desktop before /portfolio after /portfolio
/portfolio mobile before /portfolio (mobile) after /portfolio (mobile)
/run-history desktop before /run-history after /run-history
/run-history mobile before /run-history (mobile) after /run-history (mobile)

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.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 13, 2026
@JSONbored
JSONbored merged commit 0e6d368 into JSONbored:main Jul 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add live refresh to run-history and portfolio views

2 participants