Skip to content

Client-side table pagination logic is independently duplicated 3 ways across 5 miner-ui routes / 6+ call sites #8306

Description

@JSONbored

Context

apps/loopover-miner-ui/src/routes/ has five routes that each client-side paginate a table at PAGE_SIZE = 20, and the { page, pageCount, isPaginated, safePage, visible } computation plus the Pagination/PaginationContent/PaginationItem/PaginationPrevious/PaginationLink/PaginationNext wiring is duplicated three different ways across them instead of sharing one implementation:

  • run-history.tsx (lines ~118-123) and ranked-candidates.tsx (lines ~130-135) each inline the exact same 5-line useState/pageCount/isPaginated/safePage/visibleRows computation, then hand-roll the full Pagination/PaginationContent/... JSX directly inside the component (no local wrapper at all).
  • ledgers.tsx and portfolio.tsx each define their own local TablePagination({ page, pageCount, onPageChange }) component (identical in both files) and duplicate the same 5-line page-state computation twice per file (once per table on that route).
  • attempts.tsx goes one step further and already generalizes the page-state computation into a reusable usePagedRows<T>(rows) hook (lines ~105-118) plus its own local TablePagination component — the closest thing to a canonical, generic version of this pattern already in the codebase — but it's still local to attempts.tsx alone, not shared with the other four routes.

In total this is the identical ~30 lines of logic (a generic page-state hook + a generic pager component) reimplemented independently at 6+ call sites across 5 files, with attempts.tsx's usePagedRows/TablePagination already being the most reusable shape.

Requirements

  • Extract usePagedRows<T>(rows: T[], pageSize?: number) and TablePagination into a single shared module under apps/loopover-miner-ui/src/lib/ (e.g. lib/paged-rows.ts for the hook, reusing/relocating attempts.tsx's existing implementation as the starting point — do not invent a differently-shaped API) and a shared presentational component (e.g. components/table-pagination.tsx built on the existing @loopover/ui-kit/components/pagination primitives, matching attempts.tsx's TablePagination JSX).
  • Update all five routes (attempts.tsx, run-history.tsx, ranked-candidates.tsx, ledgers.tsx, portfolio.tsx) to import and use the shared hook + component instead of their own local copy or inline JSX. Every call site that currently duplicates this logic must be converted — a PR that extracts the shared module but leaves any of the five routes on its own local/inline copy does not resolve this issue.
  • Preserve the current PAGE_SIZE = 20 default and existing pagination behavior exactly (page clamping via Math.min(page, pageCount - 1), aria-disabled on the boundary Previous/Next controls) — this is a refactor, not a behavior change.
  • Do not change the Pagination primitive itself in @loopover/ui-kit as part of this issue (see the separate aria-disabled visual-styling issue for that).

Deliverables

  • usePagedRows<T> extracted to a shared apps/loopover-miner-ui/src/lib/ module
  • TablePagination extracted to a shared apps/loopover-miner-ui/src/components/ module
  • attempts.tsx, run-history.tsx, ranked-candidates.tsx, ledgers.tsx, portfolio.tsx all updated to use the shared hook + component, with their local duplicate implementations removed
  • Unit tests for the extracted usePagedRows hook covering: empty rows, rows under one page, rows spanning multiple pages, and the page-clamping behavior when rows shrinks below the current page's start index

Test Coverage Requirements

apps/loopover-miner-ui is excluded from the root Codecov config (codecov.yml's ignore: - "apps/**") but has its own local vitest coverage floor enforced via apps/loopover-miner-ui/vitest.config.ts's coverage.thresholds (85% statements / 85% branches / 75% functions / 85% lines) — this refactor must not regress that floor, and the new shared hook/component should carry direct unit tests rather than relying solely on the five routes' existing integration-style tests.

Expected Outcome

Client-side table pagination logic exists in exactly one place in apps/loopover-miner-ui, and all five paginated routes consume it — a future pagination bug fix or behavior change (e.g. adjusting PAGE_SIZE, or fixing an edge case) only needs to happen once instead of being replicated across three divergent implementations.

Links & Resources

  • apps/loopover-miner-ui/src/routes/attempts.tsx:104-118 (the usePagedRows shape to extract, as the closest-to-canonical existing implementation)
  • apps/loopover-miner-ui/src/routes/ledgers.tsx, portfolio.tsx (the local TablePagination duplicate)
  • apps/loopover-miner-ui/src/routes/run-history.tsx, ranked-candidates.tsx (the fully-inlined duplicate, no local wrapper at all)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions